From a5b6556d67dc8bdeb6f01947915429cc92f32a42 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 3 Jul 2014 17:42:31 +0200 Subject: slight fixes to qt5 compatibility --- src/organizer.pro | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/organizer.pro b/src/organizer.pro index 70d8e021..723a7500 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -6,7 +6,7 @@ contains(QT_VERSION, "^5.*") { - QT += core gui widgets network xml sql xmlpatterns qml quick script webkit + QT += core gui widgets network xml sql xmlpatterns qml quick script webkit webkitwidgets } else { QT += core gui network xml declarative script sql xmlpatterns webkit } @@ -288,11 +288,23 @@ QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\tutorials) $$quote($$DSTDIR) QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n) CONFIG(debug, debug|release) { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) - QMAKE_POST_LINK += copy /y $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$quote($$DSTDIR)\\dlls\\dlls.manifest $$escape_expand(\\n) - QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$escape_expand(\\n) + contains(QT_VERSION, "^5.*") { + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += copy /y $$quote($$DSTDIR\\dlls\\dlls.manifest.debug.qt5) $$quote($$DSTDIR\\dlls\\dlls.manifest) $$escape_expand(\\n) + QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug.qt5 $$escape_expand(\\n) + } else { + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += copy /y $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$quote($$DSTDIR)\\dlls\\dlls.manifest $$escape_expand(\\n) + QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$escape_expand(\\n) + } } else { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + contains(QT_VERSION, "^5.*") { + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += copy /y $$quote($$DSTDIR\\dlls\\dlls.manifest.qt5) $$quote($$DSTDIR\\dlls\\dlls.manifest) $$escape_expand(\\n) + QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.qt5 $$escape_expand(\\n) + } else { + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + } } RESOURCES += \ -- cgit v1.3.1 -- cgit v1.3.1 From 62b0c07a93bbf244dbf8ae4bc73f008cfa1fc84d Mon Sep 17 00:00:00 2001 From: TheBloke Date: Mon, 7 Jul 2014 21:39:27 +0100 Subject: Added 'make install' steps in ModOrganizer.pro - Added config to install required libraries to output(d) directory If the user adds a Deploy step, that runs 'jom install', he will have the necessary libraries copied to output(d)\dlls These libraries are - 7z.dll Qt4 Core Declarative Gui Network OpenGL Script Sql Svg Webkit Xml XmlPatterns Different set configured for Qt5, e.g. adding Widgets, Qml, Quick In a debug build, the 'd' DLLs will be used instead. Qt4 libraries are copied from system Qt4 installation directory. This allows the Qt-Creator user to hit "Run" in Qt-C and end up with a complete install, including all DLLs; no manual Exporer copying required. This therefore removes the need for post-build steps 10 & 11 in Readme.txt. Future thought - Update all current POST_LINK (x)copy steps to make install? Fixed DESTDIR in ModOrganizer.pro It was pointing to source/output(d) before, should be ..\output(d) It was not used before so didn't matter, but is now referenced for install. --- src/ModOrganizer.pro | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 4624ffec..b345d64b 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -21,7 +21,31 @@ hookdll.depends = shared organizer.depends = shared uibase plugins loot_cli CONFIG(debug, debug|release) { - DESTDIR = outputd + DESTDIR = $$PWD/../outputd } else { - DESTDIR = output + DESTDIR = $$PWD/../output } + +STATICDATAPATH = $${DESTDIR}\\..\\tools\\static_data\\dlls +DLLSPATH = $${DESTDIR}\\dlls + +otherlibs.path = $$DLLSPATH +otherlibs.files += $${STATICDATAPATH}\\7z.dll + +qtlibs.path = $$DLLSPATH + +equals(QT_MAJOR_VERSION, 5) { + QTLIBNAMES += Core Gui Network OpenGL Script Sql Svg Qml Quick Webkit Widgets Xml XmlPatterns +} else { + QTLIBNAMES += Core Declarative Gui Network OpenGL Script Sql Svg Webkit Xml XmlPatterns +} + +QTLIBSUFFIX = $${QT_MAJOR_VERSION}.dll +CONFIG(debug, debug|release): QTLIBSUFFIX = "d$${QTLIBSUFFIX}" # Can't use Debug: .. here, it ignores the line - no idea why, as it works in BossDummy.pro + +for(QTNAME, QTLIBNAMES) { + QTFILE = Qt$${QTNAME} + qtlibs.files += $$[QT_INSTALL_BINS]\\$${QTFILE}$${QTLIBSUFFIX} +} + +INSTALLS += qtlibs otherlibs -- cgit v1.3.1 From a49ed5c33c01d4964523eafb790284fcebc0563d Mon Sep 17 00:00:00 2001 From: TheBloke Date: Mon, 7 Jul 2014 21:51:35 +0100 Subject: organizer.pro - tiny tweak to Qt5 version check Directly checking QT_MAJOR_VERSION instead of pattern matching on QT_VERSION --- src/organizer.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/organizer.pro b/src/organizer.pro index de0f70af..bed1ca47 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -5,7 +5,7 @@ #------------------------------------------------- -contains(QT_VERSION, "^5.*") { +equals(QT_MAJOR_VERSION, 5) { QT += core gui widgets network xml sql xmlpatterns qml quick script webkit } else { QT += core gui network xml declarative script sql xmlpatterns webkit -- cgit v1.3.1 From 2b5748003d2ae4de1b897041f92df3e0495b2825 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Mon, 7 Jul 2014 23:32:18 +0100 Subject: .pro files - formatting/consistency fixes Many small formatting changes to the majority of .pro files Consistent style for Qt version checking, using greaterThan(QT_MAJOR_VERSION, 4) This seems safest, as would continue to work for a future Qt 6 etc. Note that this makes redudundant the commit before this one, to organizer.pro Consistent indenting Moved some file configuration sections to be together with related sections Moved some installation-related sections (e.g. OUTDIR/DSTDIR) to be at the bottom, immediately before the related POST_LINK install lines. Similarly, in some cases separated out CONFIG(debug,.. checks into separate LIB/INCLUDE sections (high in file) and installation directory sections (low in file) --- src/organizer.pro | 100 ++++++++++++++++++++++++++------------------------ src/shared/shared.pro | 4 +- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/src/organizer.pro b/src/organizer.pro index bed1ca47..5be205b4 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -5,15 +5,15 @@ #------------------------------------------------- -equals(QT_MAJOR_VERSION, 5) { +TARGET = ModOrganizer +TEMPLATE = app + +greaterThan(QT_MAJOR_VERSION, 4) { QT += core gui widgets network xml sql xmlpatterns qml quick script webkit } else { QT += core gui network xml declarative script sql xmlpatterns webkit } -TARGET = ModOrganizer -TEMPLATE = app - SOURCES += \ transfersavesdialog.cpp \ syncoverwritedialog.cpp \ @@ -202,24 +202,60 @@ FORMS += \ browserdialog.ui \ aboutdialog.ui +RESOURCES += \ + resources.qrc \ + stylesheet_resource.qrc + +RC_FILE += \ + app_icon.rc + +OTHER_FILES += \ + version.rc \ + tutorials/firststeps.qml \ + tutorials/tutorials.js \ + tutorials/tutorial_firststeps_main.js \ + tutorials/tutorials_settingsdialog.qml \ + tutorials/tutorials_mainwindow.qml \ + tutorials/Highlight.qml \ + tutorials/TutorialDescription.qml \ + tutorials/TutorialOverlay.qml \ + tutorials/tutorials_nexusdialog.qml \ + tutorials/tutorials_modinfodialog.qml \ + tutorials/tutorial_firststeps_modinfo.js \ + tutorials/tutorial_conflictresolution_main.js \ + tutorials/tutorial_conflictresolution_modinfo.js \ + app_icon.rc \ + dark.qss \ + stylesheets/dark.qss \ + tutorials/tutorial_window_installer.js \ + tutorials/tutorials_installdialog.qml \ + tutorials/tutorial_firststeps_settings.js + + +# leak detection with vld +#INCLUDEPATH += "E:/Visual Leak Detector/include" +#LIBS += -L"E:/Visual Leak Detector/lib/Win32" +#DEFINES += LEAK_CHECK_WITH_VLD + +#SOURCES += modeltest.cpp +#HEADERS += modeltest.h +#DEFINES += TEST_MODELS + + INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$(BOOSTPATH)" LIBS += -L"$(BOOSTPATH)/stage/lib" CONFIG(debug, debug|release) { - OUTDIR = $$OUT_PWD/debug - DSTDIR = $$PWD/../../outputd LIBS += -L$$OUT_PWD/../shared/debug LIBS += -L$$OUT_PWD/../bsatk/debug LIBS += -L$$OUT_PWD/../uibase/debug LIBS += -L$$OUT_PWD/../boss_modified/debug LIBS += -lDbgHelp } else { - OUTDIR = $$OUT_PWD/release - DSTDIR = $$PWD/../../output LIBS += -L$$OUT_PWD/../shared/release LIBS += -L$$OUT_PWD/../bsatk/release - LIBS += -L$$OUT_PWD/../uibase/release + LIBS += -L$$OUT_PWD/../uibase/release LIBS += -L$$OUT_PWD/../boss_modified/release QMAKE_CXXFLAGS += /Zi /GL # QMAKE_CXXFLAGS -= -O2 @@ -275,6 +311,13 @@ DEFINES += BOOST_DISABLE_ASSERTS NDEBUG HGID = $$system(hg id -i) DEFINES += HGID=\\\"$${HGID}\\\" +CONFIG(debug, debug|release) { + OUTDIR = $$OUT_PWD/debug + DSTDIR = $$PWD/../../outputd +} else { + OUTDIR = $$OUT_PWD/release + DSTDIR = $$PWD/../../output +} SRCDIR = $$PWD SRCDIR ~= s,/,$$QMAKE_DIR_SEP,g @@ -294,42 +337,3 @@ CONFIG(debug, debug|release) { } else { QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) } - -RESOURCES += \ - resources.qrc \ - stylesheet_resource.qrc - -RC_FILE += \ - app_icon.rc - -OTHER_FILES += \ - version.rc \ - tutorials/firststeps.qml \ - tutorials/tutorials.js \ - tutorials/tutorial_firststeps_main.js \ - tutorials/tutorials_settingsdialog.qml \ - tutorials/tutorials_mainwindow.qml \ - tutorials/Highlight.qml \ - tutorials/TutorialDescription.qml \ - tutorials/TutorialOverlay.qml \ - tutorials/tutorials_nexusdialog.qml \ - tutorials/tutorials_modinfodialog.qml \ - tutorials/tutorial_firststeps_modinfo.js \ - tutorials/tutorial_conflictresolution_main.js \ - tutorials/tutorial_conflictresolution_modinfo.js \ - app_icon.rc \ - dark.qss \ - stylesheets/dark.qss \ - tutorials/tutorial_window_installer.js \ - tutorials/tutorials_installdialog.qml \ - tutorials/tutorial_firststeps_settings.js - - -# leak detection with vld -#INCLUDEPATH += "E:/Visual Leak Detector/include" -#LIBS += -L"E:/Visual Leak Detector/lib/Win32" -#DEFINES += LEAK_CHECK_WITH_VLD - -#SOURCES += modeltest.cpp -#HEADERS += modeltest.h -#DEFINES += TEST_MODELS diff --git a/src/shared/shared.pro b/src/shared/shared.pro index 992fd7f2..5f327fa3 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -21,11 +21,11 @@ INCLUDEPATH += ../bsatk "$(BOOSTPATH)" CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../bsatk/debug - LIBS += -lDbgHelp + LIBS += -lDbgHelp QMAKE_CXXFLAGS_DEBUG -= -Zi QMAKE_CXXFLAGS += -Z7 } else { - LIBS += -L$$OUT_PWD/../bsatk/release + LIBS += -L$$OUT_PWD/../bsatk/release } LIBS += -lbsatk -- cgit v1.3.1 From 0046d8710e895a158b7e49678eae1514c5b7dc88 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Tue, 8 Jul 2014 04:01:24 +0100 Subject: Compilation dependency tracking enabled for organizer, hook, bsaExtractor, shared Previously, qmake did not detect when code was changed in the static libraries So for example, changing code in shared/*.cpp and then Build, would not cause a re-link and re-deploy of ModOrganizer.exe. This has been fixed by adding PRE_TARGETDEPS references to each project that depend on static libraries provided by other sub-projects. These checks are conditional on debug|release, to point to the correct lib. List of currently configured static library dependency checks - organizer - mo_shared.lib, bsatk.lib hookdll - mo_shared.lib, bsatk.lib shared - bsatk.lib bsaExtractor - bsatk.lib I have tested to confirm that changing a shared file results in re-linking and re-deplomyent of both ModOrganizer.exe and hook.dll. Note that dependencies have not been configured for DLLs, as these are loaded dynamically and so recompilation is not required. But there could be an argument for configuring DLLs as dependencies too - as if you make a change in DLL code, you might want all clients of the DLL to be recompiled to check that they have not been broken by the changes? The downside of that would be extra re-compilation every time. --- src/organizer.pro | 4 ++++ src/shared/shared.pro | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/organizer.pro b/src/organizer.pro index 5be205b4..e013aab7 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -252,6 +252,8 @@ CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../uibase/debug LIBS += -L$$OUT_PWD/../boss_modified/debug LIBS += -lDbgHelp + PRE_TARGETDEPS += $$OUT_PWD/../shared/debug/mo_shared.lib \ + $$OUT_PWD/../bsatk/debug/bsatk.lib } else { LIBS += -L$$OUT_PWD/../shared/release LIBS += -L$$OUT_PWD/../bsatk/release @@ -260,6 +262,8 @@ CONFIG(debug, debug|release) { QMAKE_CXXFLAGS += /Zi /GL # QMAKE_CXXFLAGS -= -O2 QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF + PRE_TARGETDEPS += $$OUT_PWD/../shared/release/mo_shared.lib \ + $$OUT_PWD/../bsatk/release/bsatk.lib } #QMAKE_CXXFLAGS_WARN_ON -= -W3 diff --git a/src/shared/shared.pro b/src/shared/shared.pro index 5f327fa3..c5925c24 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -24,8 +24,10 @@ CONFIG(debug, debug|release) { LIBS += -lDbgHelp QMAKE_CXXFLAGS_DEBUG -= -Zi QMAKE_CXXFLAGS += -Z7 + PRE_TARGETDEPS += $$OUT_PWD/../bsatk/debug/bsatk.lib } else { LIBS += -L$$OUT_PWD/../bsatk/release + PRE_TARGETDEPS += $$OUT_PWD/../bsatk/release/bsatk.lib } LIBS += -lbsatk -- cgit v1.3.1 From 46a32ff3b7bcf6bd4ccc9470c7712a4bbd429a48 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Tue, 8 Jul 2014 04:05:33 +0100 Subject: Additional 'make install' step - install boost_python DLL to output(d)\DLLs Extended the new ModOrganizer.pro 'make install' steps, to now also copy $$(BOOSTPATH)\stage\lib\boost_python-vc*-mt-1*.dll This is the final install step required, so that now ModOrganizer.exe works fully when a Qt-Creator "Run" step is performed. The wildcard should pick up the right version of the library regardless of MSVC compiler version used. Updated Qt5 version check to the standardised greaterThan check. --- src/ModOrganizer.pro | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index b345d64b..859f4caf 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -30,11 +30,12 @@ STATICDATAPATH = $${DESTDIR}\\..\\tools\\static_data\\dlls DLLSPATH = $${DESTDIR}\\dlls otherlibs.path = $$DLLSPATH -otherlibs.files += $${STATICDATAPATH}\\7z.dll +otherlibs.files += $${STATICDATAPATH}\\7z.dll \ + $$(BOOSTPATH)\\stage\\lib\\boost_python-vc*-mt-1*.dll qtlibs.path = $$DLLSPATH -equals(QT_MAJOR_VERSION, 5) { +greaterThan(QT_MAJOR_VERSION, 4) { QTLIBNAMES += Core Gui Network OpenGL Script Sql Svg Qml Quick Webkit Widgets Xml XmlPatterns } else { QTLIBNAMES += Core Declarative Gui Network OpenGL Script Sql Svg Webkit Xml XmlPatterns -- cgit v1.3.1 From d93df95dcfcaf0170f7fb273e9955ef6070fbaa5 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Fri, 11 Jul 2014 01:29:33 +0100 Subject: - Save game management: re-factored getting of filenames including .skse -- New - SaveGame->saveFiles(), returns all filenames for save (eg. ess + skse) -- Moved - SaveGameGameBryo->attachedFiles() to SaveGame->attachedFiles() -- This allows the getting of save file names with just a SaveGame object, not a GameBryo object, which would do a full file read including plugins and image etc. -- Therefore SaveGame is no longer abstract. - Transfer Saves: re-factored to use new SaveGame->saveFiles() method -- Removed - TransferSavesDialog->getFilesToProcess, now redundant as information is now provided by SaveGame class itself. --- src/savegame.cpp | 148 +++++++++++++++++------------- src/savegame.h | 219 ++++++++++++++++++++++---------------------- src/savegamegamebryo.cpp | 11 +-- src/savegamegamebyro.h | 171 +++++++++++++++++----------------- src/transfersavesdialog.cpp | 15 +-- src/transfersavesdialog.h | 1 - 6 files changed, 283 insertions(+), 282 deletions(-) diff --git a/src/savegame.cpp b/src/savegame.cpp index 0ddd5fd2..d09f291c 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -17,68 +17,86 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "savegame.h" -#include -#include -#include -#include "gameinfo.h" -#include -#include -#include - - -SaveGame::SaveGame(QObject *parent) - : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(QObject *parent, const QString &filename) - : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(const SaveGame& reference) - : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel), - m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber), - m_Screenshot(reference.m_Screenshot) -{ -} - - -SaveGame& SaveGame::operator=(const SaveGame &reference) -{ - if (&reference != this) { - m_FileName = reference.m_FileName; - m_PCName = reference.m_PCName; - m_PCLevel = reference.m_PCLevel; - m_PCLocation = reference.m_PCLocation; - m_SaveNumber = reference.m_SaveNumber; - m_Screenshot = reference.m_Screenshot; - } - return *this; -} - - -SaveGame::~SaveGame() -{ -} - - -void SaveGame::setCreationTime(const QString &fileName) -{ - QFileInfo creationTime(fileName); - QDateTime modified = creationTime.lastModified(); - memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); - - m_CreationTime.wDay = static_cast(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast(modified.date().month()); - m_CreationTime.wYear =static_cast( modified.date().year()); - - m_CreationTime.wHour = static_cast(modified.time().hour()); - m_CreationTime.wMinute = static_cast(modified.time().minute()); - m_CreationTime.wSecond = static_cast(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); -} +#include "savegame.h" +#include +#include +#include +#include "gameinfo.h" +#include +#include +#include + + +SaveGame::SaveGame(QObject *parent) + : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() +{ +} + + +SaveGame::SaveGame(QObject *parent, const QString &filename) + : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() +{ +} + + +SaveGame::SaveGame(const SaveGame& reference) + : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel), + m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber), + m_Screenshot(reference.m_Screenshot) +{ +} + + +SaveGame& SaveGame::operator=(const SaveGame &reference) +{ + if (&reference != this) { + m_FileName = reference.m_FileName; + m_PCName = reference.m_PCName; + m_PCLevel = reference.m_PCLevel; + m_PCLocation = reference.m_PCLocation; + m_SaveNumber = reference.m_SaveNumber; + m_Screenshot = reference.m_Screenshot; + } + return *this; +} + + +SaveGame::~SaveGame() +{ +} + +QStringList SaveGame::attachedFiles() const +{ + QStringList result; + QString seFileFile = fileName().mid(0).replace(".ess", ".skse"); + QFileInfo seFile(seFileFile); + if (seFile.exists()) { + result.append(seFile.absoluteFilePath()); + } + return result; +} + +QStringList SaveGame::saveFiles() const +{ + QStringList result = attachedFiles(); + result.append(fileName()); + return result; +} + + +void SaveGame::setCreationTime(const QString &fileName) +{ + QFileInfo creationTime(fileName); + QDateTime modified = creationTime.lastModified(); + memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); + + m_CreationTime.wDay = static_cast(modified.date().day()); + m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); + m_CreationTime.wMonth = static_cast(modified.date().month()); + m_CreationTime.wYear =static_cast( modified.date().year()); + + m_CreationTime.wHour = static_cast(modified.time().hour()); + m_CreationTime.wMinute = static_cast(modified.time().minute()); + m_CreationTime.wSecond = static_cast(modified.time().second()); + m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); +} diff --git a/src/savegame.h b/src/savegame.h index e3eb8546..9a5f7036 100644 --- a/src/savegame.h +++ b/src/savegame.h @@ -17,110 +17,115 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef SAVEGAME_H -#define SAVEGAME_H - - -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - - -/** - * @brief represents a single save game - **/ -class SaveGame : public QObject { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGame(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGame(QObject *parent, const QString &filename); - - SaveGame(const SaveGame& reference); - - SaveGame& operator=(const SaveGame &reference); - - ~SaveGame(); - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString &fileName) = 0; - - /** - * @return filename of this savegame - */ - const QString &fileName() const { return m_FileName; } - - /** - * @return a list of additional files that belong to this savegame - */ - virtual QStringList attachedFiles() const { return QStringList(); } - - /** - * @return name of the player character - **/ - const QString &pcName() const { return m_PCName; } - - /** - * @return level of the player character - **/ - unsigned short pcLevel() const { return m_PCLevel; } - - /** - * @return location of the player character - **/ - const QString &pcLocation() const { return m_PCLocation; } - - /** - * @return index of the save game - **/ - unsigned long saveNumber() const { return m_SaveNumber; } - - /** - * @return creation time of the save game - **/ - SYSTEMTIME creationTime() const { return m_CreationTime; } - - /** - * @return screenshot in the savegame - **/ - const QImage &screenshot() const { return m_Screenshot; } - -private: - - void setCreationTime(const QString &fileName); - -protected: - - QString m_FileName; - QString m_PCName; - unsigned short m_PCLevel; - QString m_PCLocation; - unsigned long m_SaveNumber; - SYSTEMTIME m_CreationTime; - QImage m_Screenshot; - -}; - - -#endif // SAVEGAME_H +#ifndef SAVEGAME_H +#define SAVEGAME_H + + +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + + +/** + * @brief represents a single save game + **/ +class SaveGame : public QObject { + +Q_OBJECT + +public: + + /** + * @brief construct an empty object + **/ + SaveGame(QObject *parent = 0); + + /** + * @brief construct a save game and immediately read out information from the file + * + * @param filename absolute path of the save game file + **/ + SaveGame(QObject *parent, const QString &filename); + + SaveGame(const SaveGame& reference); + + SaveGame& operator=(const SaveGame &reference); + + ~SaveGame(); + + /** + * @brief read out information from a savegame + * + * @param fileName absolute path of the save game file + **/ + virtual void readFile(const QString) { } + + /** + * @return filename of this savegame + */ + const QString &fileName() const { return m_FileName; } + + /** + * @return a list of additional files that belong to this savegame + */ + virtual QStringList attachedFiles() const; + + /** + * @return a list of all files that belong to this savegame + */ + virtual QStringList saveFiles() const; + + /** + * @return name of the player character + **/ + const QString &pcName() const { return m_PCName; } + + /** + * @return level of the player character + **/ + unsigned short pcLevel() const { return m_PCLevel; } + + /** + * @return location of the player character + **/ + const QString &pcLocation() const { return m_PCLocation; } + + /** + * @return index of the save game + **/ + unsigned long saveNumber() const { return m_SaveNumber; } + + /** + * @return creation time of the save game + **/ + SYSTEMTIME creationTime() const { return m_CreationTime; } + + /** + * @return screenshot in the savegame + **/ + const QImage &screenshot() const { return m_Screenshot; } + +private: + + void setCreationTime(const QString &fileName); + +protected: + + QString m_FileName; + QString m_PCName; + unsigned short m_PCLevel; + QString m_PCLocation; + unsigned long m_SaveNumber; + SYSTEMTIME m_CreationTime; + QImage m_Screenshot; + +}; + + +#endif // SAVEGAME_H diff --git a/src/savegamegamebryo.cpp b/src/savegamegamebryo.cpp index f3794347..bf113254 100644 --- a/src/savegamegamebryo.cpp +++ b/src/savegamegamebryo.cpp @@ -117,16 +117,7 @@ SaveGameGamebryo::~SaveGameGamebryo() } -QStringList SaveGameGamebryo::attachedFiles() const -{ - QStringList result; - QString seFileFile = fileName().mid(0).replace(".ess", ".skse"); - QFileInfo seFile(seFileFile); - if (seFile.exists()) { - result.append(seFile.absoluteFilePath()); - } - return result; -} + void SaveGameGamebryo::readSkyrimFile(QFile &saveFile) diff --git a/src/savegamegamebyro.h b/src/savegamegamebyro.h index 25822b0c..70400636 100644 --- a/src/savegamegamebyro.h +++ b/src/savegamegamebyro.h @@ -17,91 +17,86 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef SAVEGAMEGAMEBRYO_H -#define SAVEGAMEGAMEBRYO_H - - -#include "savegame.h" -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - - -/** - * @brief represents a single save game - **/ -class SaveGameGamebryo : public SaveGame { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGameGamebryo(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGameGamebryo(QObject *parent, const QString &filename); - - - SaveGameGamebryo(const SaveGameGamebryo &reference); - - SaveGameGamebryo &operator=(const SaveGameGamebryo &reference); - - ~SaveGameGamebryo(); - - /** - * @return a list of additional files that belong to this savegame - */ - virtual QStringList attachedFiles() const; - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString &fileName); - - /** - * @return number of plugins that were enabled when the save game was created - **/ - int numPlugins() const { return m_Plugins.size(); } - - /** - * retrieve the name of one of the plugins that were enabled when the save game - * was created. valid indices are in the range between [0, numPlugins()[ - * @param index plugin index - * @return name of the plugin - **/ - const QString &plugin(int index) const { return m_Plugins.at(index); } - - -private: - - void readESSFile(QFile &saveFile); - void readFOSFile(QFile &saveFile, bool newVegas); - void readSkyrimFile(QFile &saveFile); - - void setCreationTime(const QString &fileName); - -private: - - std::vector m_Plugins; - -}; - -Q_DECLARE_METATYPE(SaveGameGamebryo) -Q_DECLARE_METATYPE(SaveGameGamebryo*) - -#endif // SAVEGAMEGAMEBRYO_H +#ifndef SAVEGAMEGAMEBRYO_H +#define SAVEGAMEGAMEBRYO_H + + +#include "savegame.h" +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + + +/** + * @brief represents a single save game + **/ +class SaveGameGamebryo : public SaveGame { + +Q_OBJECT + +public: + + /** + * @brief construct an empty object + **/ + SaveGameGamebryo(QObject *parent = 0); + + /** + * @brief construct a save game and immediately read out information from the file + * + * @param filename absolute path of the save game file + **/ + SaveGameGamebryo(QObject *parent, const QString &filename); + + + SaveGameGamebryo(const SaveGameGamebryo &reference); + + SaveGameGamebryo &operator=(const SaveGameGamebryo &reference); + + ~SaveGameGamebryo(); + + /** + * @brief read out information from a savegame + * + * @param fileName absolute path of the save game file + **/ + virtual void readFile(const QString &fileName); + + /** + * @return number of plugins that were enabled when the save game was created + **/ + int numPlugins() const { return m_Plugins.size(); } + + /** + * retrieve the name of one of the plugins that were enabled when the save game + * was created. valid indices are in the range between [0, numPlugins()[ + * @param index plugin index + * @return name of the plugin + **/ + const QString &plugin(int index) const { return m_Plugins.at(index); } + + +private: + + void readESSFile(QFile &saveFile); + void readFOSFile(QFile &saveFile, bool newVegas); + void readSkyrimFile(QFile &saveFile); + + void setCreationTime(const QString &fileName); + +private: + + std::vector m_Plugins; + +}; + +Q_DECLARE_METATYPE(SaveGameGamebryo) +Q_DECLARE_METATYPE(SaveGameGamebryo*) + +#endif // SAVEGAMEGAMEBRYO_H diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index e69e4ab2..8e2b9b59 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -151,13 +151,6 @@ bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QStr return res == QMessageBox::Yes; } -QStringList TransferSavesDialog::getFilesToProcess(const SaveGame *save) -{ - QStringList result = save->attachedFiles(); - result.append(save->fileName()); - return result; -} - void TransferSavesDialog::on_moveToLocalBtn_clicked() { QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); @@ -170,7 +163,7 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked() for (std::vector::const_iterator iter = m_GlobalSaves.begin(); iter != m_GlobalSaves.end(); ++iter) { if ((*iter)->pcName() == selectedCharacter) { - QStringList files = getFilesToProcess(*iter); + QStringList files = (*iter)->saveFiles(); foreach (const QString &file, files) { QFileInfo fileInfo(file); QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); @@ -207,7 +200,7 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked() for (std::vector::const_iterator iter = m_GlobalSaves.begin(); iter != m_GlobalSaves.end(); ++iter) { if ((*iter)->pcName() == selectedCharacter) { - QStringList files = getFilesToProcess(*iter); + QStringList files = (*iter)->saveFiles(); foreach (const QString &file, files) { QFileInfo fileInfo(file); QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); @@ -244,7 +237,7 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked() for (std::vector::const_iterator iter = m_LocalSaves.begin(); iter != m_LocalSaves.end(); ++iter) { if ((*iter)->pcName() == selectedCharacter) { - QStringList files = getFilesToProcess(*iter); + QStringList files = (*iter)->saveFiles(); foreach (const QString &file, files) { QFileInfo fileInfo(file); QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); @@ -283,7 +276,7 @@ void TransferSavesDialog::on_copyToGlobalBtn_clicked() for (std::vector::const_iterator iter = m_LocalSaves.begin(); iter != m_LocalSaves.end(); ++iter) { if ((*iter)->pcName() == selectedCharacter) { - QStringList files = getFilesToProcess(*iter); + QStringList files = (*iter)->saveFiles(); foreach (const QString &file, files) { QFileInfo fileInfo(file); QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index 923259f6..4306b960 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -67,7 +67,6 @@ private: void refreshGlobalSaves(); void refreshLocalSaves(); bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile); - QStringList getFilesToProcess(const SaveGame *save); private: -- cgit v1.3.1 From 1b4c07eb20a8951ac72accf8ead302d65c8a2de5 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Fri, 11 Jul 2014 01:46:42 +0100 Subject: - savegameList: Improved save game handling from MainWindow -- Save game deletion now does Recycle Bin delete (wishlist #675) -- Save game deletion now also deletes .skse file (bug #687) -- Can select and delete multiple save games (ExtendedSelection) (wishlist #675) -- Uses new SaveGame->saveFiles() method to get filenames (eg .ess & .skse) -- Context menu - "Fix Mods.." option only appears if 1 save is selected -- Context menu - delete menu option labelled "Delete save" or "Delete saves", according to 1 or >1 saves selected. -- Context menu - delete menu confirmation shows list of all selected saves --- src/mainwindow.cpp | 58 ++++++++++++++++++++++++++++++++++++++---------------- src/mainwindow.h | 2 +- src/mainwindow.ui | 6 ++++++ 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9cbe9c40..344173d4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3884,26 +3884,42 @@ void MainWindow::on_categoriesList_itemSelectionChanged() void MainWindow::deleteSavegame_clicked() { - QListWidgetItem *selectedItem = ui->savegameList->item(m_SelectedSaveGame); - if (selectedItem == NULL) { + QItemSelectionModel *selection = ui->savegameList->selectionModel(); + + if (!selection->hasSelection()) return; - } - QString fileName = selectedItem->data(Qt::UserRole).toString(); + int selectedCount = selection->selectedRows().count(); + QString savesMsgLabel; + QRegExp saveSuffix(".ess$"); + QStringList deleteFiles; + + foreach (QModelIndex idx, selection->selectedRows()) { + QString name = idx.data().toString(); + QString filename = idx.data(Qt::UserRole).toString(); + + SaveGame *save = new SaveGame(this, filename); + savesMsgLabel += "
  • " + name.replace(saveSuffix, "") + "
  • "; + deleteFiles << save->saveFiles(); + } - if (QMessageBox::question(this, tr("Confirm"), tr("Really delete \"%1\"?").arg(selectedItem->text()), + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following save%1?
      %2

    Removed saves will be sent to the Recycle Bin.") + .arg((selectedCount > 1) ? "s" : "") + .arg(savesMsgLabel), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - shellDelete(QStringList() << fileName); + shellDelete(deleteFiles, true); // recycle bin delete. } } void MainWindow::fixMods_clicked() { - QListWidgetItem *selectedItem = ui->savegameList->item(m_SelectedSaveGame); - if (selectedItem == NULL) { - return; - } + QItemSelectionModel *selection = ui->savegameList->selectionModel(); + + if (!selection->hasSelection() || selection->selectedRows().count() > 1) + return; // Count should never be > 1 because of condition on context menu; check again just for safety. + + QListWidgetItem *selectedItem = ui->savegameList->item(selection->selectedRows().first().row()); // if required, parse the save game if (selectedItem->data(Qt::UserRole).isNull()) { @@ -3994,16 +4010,24 @@ void MainWindow::fixMods_clicked() void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) { - QListWidgetItem *selectedItem = ui->savegameList->itemAt(pos); - if (selectedItem == NULL) { - return; - } + QItemSelection currentSelection = ui->savegameList->selectionModel()->selection(); + + bool enableFixMods = false; + int selectedCount = currentSelection.count(); - m_SelectedSaveGame = ui->savegameList->row(selectedItem); + if ( selectedCount == 0) { + return; + } else if (currentSelection.count() == 1) + enableFixMods = true; QMenu menu; - menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); - menu.addAction(tr("Delete"), this, SLOT(deleteSavegame_clicked())); + + if (enableFixMods) + menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); + + QString deleteMenuLabel = tr("Delete save%1").arg((selectedCount > 1) ? "s" : ""); + + menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); menu.exec(ui->savegameList->mapToGlobal(pos)); } diff --git a/src/mainwindow.h b/src/mainwindow.h index cc144287..c1cfdb3c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -335,7 +335,7 @@ private: QTreeWidgetItem *m_ContextItem; QAction *m_ContextAction; - int m_SelectedSaveGame; + //int m_SelectedSaveGame; Settings m_Settings; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 1a45e21b..4201babf 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1100,6 +1100,12 @@ p, li { white-space: pre-wrap; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + -- cgit v1.3.1 From 7944bacfd9b7d74d1d45c8bea405a9daf0564002 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Sat, 12 Jul 2014 11:15:34 +0100 Subject: savegameList: A few cleanups and minor code improvements - Slight improvements over previous commits, using more appropriate methods and removing some unnecessary code. No functional changes. --- src/mainwindow.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 344173d4..fc0f8aa1 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3889,22 +3889,23 @@ void MainWindow::deleteSavegame_clicked() if (!selection->hasSelection()) return; - int selectedCount = selection->selectedRows().count(); QString savesMsgLabel; QRegExp saveSuffix(".ess$"); QStringList deleteFiles; - foreach (QModelIndex idx, selection->selectedRows()) { + foreach (QModelIndex idx, selection->selectedIndexes()) { QString name = idx.data().toString(); - QString filename = idx.data(Qt::UserRole).toString(); + SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); - SaveGame *save = new SaveGame(this, filename); savesMsgLabel += "
  • " + name.replace(saveSuffix, "") + "
  • "; + deleteFiles << save->saveFiles(); } + bool multipleRows = (selection->selectedIndexes().count() > 1); + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following save%1?
      %2

    Removed saves will be sent to the Recycle Bin.") - .arg((selectedCount > 1) ? "s" : "") + .arg((multipleRows) ? "s" : "") .arg(savesMsgLabel), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { shellDelete(deleteFiles, true); // recycle bin delete. @@ -3914,12 +3915,10 @@ void MainWindow::deleteSavegame_clicked() void MainWindow::fixMods_clicked() { - QItemSelectionModel *selection = ui->savegameList->selectionModel(); + QListWidgetItem *selectedItem = ui->savegameList->currentItem(); - if (!selection->hasSelection() || selection->selectedRows().count() > 1) - return; // Count should never be > 1 because of condition on context menu; check again just for safety. - - QListWidgetItem *selectedItem = ui->savegameList->item(selection->selectedRows().first().row()); + if (selectedItem == NULL) + return; // if required, parse the save game if (selectedItem->data(Qt::UserRole).isNull()) { @@ -4017,7 +4016,7 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) if ( selectedCount == 0) { return; - } else if (currentSelection.count() == 1) + } else if (selectedCount == 1) enableFixMods = true; QMenu menu; -- cgit v1.3.1 From 9b67e338e10e438112ad6ed03ec39dd1438758e3 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Sun, 13 Jul 2014 15:24:46 +0100 Subject: savegameList: - Context menu bugfix, previous method of getting rows didn't work when items were selected in certain ways, e.g. Control-A - Context menu now uses selectedIndexes(), which always works with all selections. - Renamed enableFixMods to multipleSelected so same value can be used to decide whether to show single or plural version of "Delete save(s)" --- src/mainwindow.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fc0f8aa1..1baf60d4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3884,16 +3884,13 @@ void MainWindow::on_categoriesList_itemSelectionChanged() void MainWindow::deleteSavegame_clicked() { - QItemSelectionModel *selection = ui->savegameList->selectionModel(); - - if (!selection->hasSelection()) - return; + QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes(); QString savesMsgLabel; QRegExp saveSuffix(".ess$"); QStringList deleteFiles; - foreach (QModelIndex idx, selection->selectedIndexes()) { + foreach (QModelIndex idx, selectedIndexes) { QString name = idx.data().toString(); SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); @@ -3902,7 +3899,7 @@ void MainWindow::deleteSavegame_clicked() deleteFiles << save->saveFiles(); } - bool multipleRows = (selection->selectedIndexes().count() > 1); + bool multipleRows = (selectedIndexes.count() > 1); if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following save%1?
      %2

    Removed saves will be sent to the Recycle Bin.") .arg((multipleRows) ? "s" : "") @@ -4009,22 +4006,23 @@ void MainWindow::fixMods_clicked() void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) { - QItemSelection currentSelection = ui->savegameList->selectionModel()->selection(); + QItemSelectionModel *selection = ui->savegameList->selectionModel(); + QModelIndexList selectedIndexes = selection->selectedIndexes(); - bool enableFixMods = false; - int selectedCount = currentSelection.count(); + bool multipleSelected = false; + int selectedCount = selectedIndexes.count(); - if ( selectedCount == 0) { + if ( !selection->hasSelection()) { return; - } else if (selectedCount == 1) - enableFixMods = true; + } else if (selectedCount > 1) + multipleSelected = true; QMenu menu; - if (enableFixMods) + if (!multipleSelected) menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); - QString deleteMenuLabel = tr("Delete save%1").arg((selectedCount > 1) ? "s" : ""); + QString deleteMenuLabel = tr("Delete save%1").arg((multipleSelected) ? "s" : ""); menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); -- cgit v1.3.1 From f59f52501c1f4bf160075dc4439f5af883f730e1 Mon Sep 17 00:00:00 2001 From: TheBloke Date: Sun, 13 Jul 2014 15:45:19 +0100 Subject: savegameList: more minor changes - Context Menu - can do same job with fewer variables and assignments - deleteSaveGame_clicked - const reference, not new value, in foreach loop --- src/mainwindow.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1baf60d4..aed95616 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3890,7 +3890,7 @@ void MainWindow::deleteSavegame_clicked() QRegExp saveSuffix(".ess$"); QStringList deleteFiles; - foreach (QModelIndex idx, selectedIndexes) { + foreach (const QModelIndex &idx, selectedIndexes) { QString name = idx.data().toString(); SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); @@ -4007,15 +4007,11 @@ void MainWindow::fixMods_clicked() void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) { QItemSelectionModel *selection = ui->savegameList->selectionModel(); - QModelIndexList selectedIndexes = selection->selectedIndexes(); - bool multipleSelected = false; - int selectedCount = selectedIndexes.count(); - - if ( !selection->hasSelection()) { + if (!selection->hasSelection()) return; - } else if (selectedCount > 1) - multipleSelected = true; + + bool multipleSelected = (selection->selectedIndexes().count() > 1); QMenu menu; -- cgit v1.3.1 From bfb3385e4932530928cff5a94ad56d8f30e5babe Mon Sep 17 00:00:00 2001 From: TheBloke Date: Sun, 13 Jul 2014 16:11:30 +0100 Subject: deleteSavegame: - Confirmation question shows number of saves to be deleted, when more than 1 --- src/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index aed95616..d6cef2dc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3901,7 +3901,8 @@ void MainWindow::deleteSavegame_clicked() bool multipleRows = (selectedIndexes.count() > 1); - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following save%1?
      %2

    Removed saves will be sent to the Recycle Bin.") + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %1save%2?
      %3

    Removed saves will be sent to the Recycle Bin.") + .arg((multipleRows) ? QString::number(selectedIndexes.count()) + " " : "") .arg((multipleRows) ? "s" : "") .arg(savesMsgLabel), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { -- cgit v1.3.1 From 84c66727bff954fd0820fc9f33833848496e9531 Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 14 Jul 2014 21:22:44 +0200 Subject: - when highlighting a mod the overwritten and overwriting mods are now highlighted in the list - when starting an external application MO now wraps the process in a job and waits on that instead. This way MO is not unlocked early when skyrim is started through skse - mod info dialog no longer offers the esp tab for foreign mods because that caused confusion - updated translation files - download directory and mod directory are now created if necessary - bugfix: staging script created unnecessary copies of translation files - bugfix: potential invalid array access when trying to determine best mod order - bugfix: deleter file wasn't removed after esp hiding was disabled - bugfix: potential access to to un-initialized login reply - bugfix: changed the initialization order to allow more ui controls to be localized --- src/ModOrganizer.pro | 10 +- src/mainwindow.cpp | 125 +- src/mainwindow.h | 3 + src/modinfo.cpp | 41 +- src/modinfo.h | 20 + src/modinfodialog.cpp | 9 +- src/modlist.cpp | 24 +- src/modlist.h | 5 + src/modlistview.cpp | 8 + src/modlistview.h | 5 + src/nxmaccessmanager.cpp | 10 +- src/organizer.pro | 7 +- src/organizer_cs.ts | 3230 +++++++++++++++++------------- src/organizer_de.ts | 2588 ++++++++++++++----------- src/organizer_es.ts | 2496 ++++++++++++++---------- src/organizer_fr.ts | 3427 ++++++++++++++++++-------------- src/organizer_ru.ts | 2524 ++++++++++++++---------- src/organizer_tr.ts | 3773 ++++++++++++++++++++---------------- src/organizer_zh_CN.ts | 2820 ++++++++++++++++----------- src/organizer_zh_TW.ts | 3114 +++++++++++++++++------------ src/pluginlist.cpp | 39 +- src/savegameinfowidgetgamebryo.cpp | 108 +- src/settings.cpp | 31 +- src/spawn.cpp | 30 +- src/version.rc | 4 +- src/viewmarkingscrollbar.cpp | 39 + src/viewmarkingscrollbar.h | 22 + 27 files changed, 14331 insertions(+), 10181 deletions(-) create mode 100644 src/viewmarkingscrollbar.cpp create mode 100644 src/viewmarkingscrollbar.h diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 4624ffec..253fe23f 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -12,13 +12,13 @@ SUBDIRS = bsatk \ proxydll \ nxmhandler \ BossDummy \ - pythonRunner \ - esptk \ - loot_cli +# pythonRunner \ + esptk# \ +# loot_cli -plugins.depends = pythonRunner +#plugins.depends = pythonRunner hookdll.depends = shared -organizer.depends = shared uibase plugins loot_cli +organizer.depends = shared uibase plugins# loot_cli CONFIG(debug, debug|release) { DESTDIR = outputd diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9cbe9c40..ce5ae865 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -168,6 +168,9 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget { ui->setupUi(this); this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); + + languageChange(m_Settings.language()); + ui->logList->setModel(LogBuffer::instance()); ui->logList->setColumnWidth(0, 100); ui->logList->setAutoScroll(true); @@ -277,6 +280,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); + connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex))); connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); @@ -310,7 +314,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(NexusInterface::instance(), SIGNAL(needLogin()), this, SLOT(nexusLogin())); connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); - + connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); @@ -615,6 +619,10 @@ void MainWindow::createHelpWidget() { QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); QMenu *buttonMenu = toolBtn->menu(); + if (buttonMenu == NULL) { + return; + } + buttonMenu->clear(); QAction *helpAction = new QAction(tr("Help on UI"), buttonMenu); connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); @@ -1155,6 +1163,7 @@ bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) } plugin->setProperty("filename", fileName); m_Settings.registerPlugin(pluginObj); + installTranslator(QFileInfo(fileName).baseName()); } { // diagnosis plugins @@ -1380,11 +1389,35 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, QCoreApplication::processEvents(); - while ((::WaitForSingleObject(processHandle, 100) == WAIT_TIMEOUT) && - !dialog->unlockClicked()) { + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + + bool isJobHandle = true; + + DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + break; + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + // keep processing events so the app doesn't appear dead QCoreApplication::processEvents(); + + res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); } + ::CloseHandle(processHandle); this->setEnabled(true); refreshDirectoryStructure(); @@ -2061,7 +2094,6 @@ void MainWindow::readSettings() setCategoryListVisible(filtersVisible); ui->displayCategoriesBtn->setChecked(filtersVisible); - languageChange(m_Settings.language()); int selectedExecutable = settings.value("selected_executable").toInt(); setExecutableIndex(selectedExecutable); @@ -3028,6 +3060,21 @@ void MainWindow::modlistChanged(const QModelIndex&, int) m_CurrentProfile->writeModlist(); } +void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) +{ + ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + if (m_ModListSortProxy != NULL) { + m_ModListSortProxy->invalidate(); + } + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) +{ + ui->modList->verticalScrollBar()->repaint(); +} + void MainWindow::removeMod_clicked() { try { @@ -3185,6 +3232,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); } else { modInfo->saveMeta(); +qDebug("%s - %d", qPrintable(modInfo->name()), modInfo->hasFlag(ModInfo::FLAG_FOREIGN)); ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); @@ -4191,6 +4239,9 @@ void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QStrin void MainWindow::installTranslator(const QString &name) { + if (m_CurrentLanguage == "en_US") { + return; + } QTranslator *translator = new QTranslator(this); QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { @@ -4212,26 +4263,19 @@ void MainWindow::languageChange(const QString &newLanguage) m_CurrentLanguage = newLanguage; - if (newLanguage != "en_US") { - installTranslator("qt"); - installTranslator(ToQString(AppConfig::translationPrefix())); - foreach(IPlugin *plugin, m_Settings.plugins()) { - QObject *pluginObj = dynamic_cast(plugin); - if (pluginObj != NULL) { - QVariant fileNameVariant = pluginObj->property("filename"); - if (fileNameVariant.isValid()) { - QString fileName = QFileInfo(fileNameVariant.toString()).baseName(); - installTranslator(fileName); - } - } - } - } + installTranslator("qt"); + installTranslator(ToQString(AppConfig::translationPrefix())); ui->retranslateUi(this); + qDebug("loaded language %s", qPrintable(newLanguage)); + ui->profileBox->setItemText(0, QObject::tr("")); -// ui->toolBar->addWidget(createHelpWidget(ui->toolBar)); + + createHelpWidget(); updateDownloadListDelegate(); updateProblemsButton(); + + ui->listOptionsBtn->setMenu(modListContextMenu()); } @@ -5110,6 +5154,9 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) void MainWindow::on_groupCombo_currentIndexChanged(int index) { + if (m_ModListSortProxy == NULL) { + return; + } QAbstractProxyModel *newModel = NULL; switch (index) { case 1: { @@ -5322,16 +5369,46 @@ void MainWindow::on_bossButton_clicked() m_PluginList.clearAdditionalInformation(); + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + bool isJobHandle = true; + if (loot != INVALID_HANDLE_VALUE) { - while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) { - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); + DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (isJobHandle) { + if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + qDebug("no more processes in job"); + break; + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + if (dialog.wasCanceled()) { - ::TerminateProcess(loot, 1); + if (isJobHandle) { + ::TerminateJobObject(loot, 1); + } else { + ::TerminateProcess(loot, 1); + } } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); std::string lootOut = readFromPipe(stdOutRead); processLOOTOut(lootOut, reportURL, errorMessages, dialog); + + res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); } + std::string remainder = readFromPipe(stdOutRead).c_str(); if (remainder.length() > 0) { processLOOTOut(remainder, reportURL, errorMessages, dialog); @@ -5344,6 +5421,8 @@ void MainWindow::on_bossButton_clicked() } else { success = true; } + } else { + reportError(tr("failed to start loot")); } } catch (const std::exception &e) { reportError(tr("failed to run loot: %1").arg(e.what())); diff --git a/src/mainwindow.h b/src/mainwindow.h index cc144287..ef111078 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -136,6 +136,7 @@ public: void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + void waitForProcessOrJob(HANDLE processHandle); public slots: void refreshLists(); @@ -560,6 +561,8 @@ private slots: void delayedRemove(); void requestDownload(const QUrl &url, QNetworkReply *reply); + void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void modListSortIndicatorChanged(int column, Qt::SortOrder order); private slots: // ui slots void profileRefresh(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 2dadf2c6..f72b5d82 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -364,8 +364,8 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() c // this is costy so cache the result QTime now = QTime::currentTime(); if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - bool overwrite = false; - bool overwritten = false; + m_OverwriteList.clear(); + m_OverwrittenList.clear(); bool regular = false; int dataID = 0; @@ -377,26 +377,29 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() c if ((*m_DirectoryStructure)->originExists(name)) { FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); std::vector files = origin.getFiles(); - for (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) { + // for all files in this origin + for (auto iter = files.begin(); iter != files.end(); ++iter) { const std::vector &alternatives = (*iter)->getAlternatives(); - if (alternatives.size() == 0) { + if ((alternatives.size() == 0) + || (alternatives[0] == dataID)) { // no alternatives -> no conflict regular = true; } else { + if ((*iter)->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } + // for all non-providing alternative origins for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - // don't treat files overwritten in data as "conflict" - if (*altIter != dataID) { - bool ignore = false; - if ((*iter)->getOrigin(ignore) == origin.getID()) { - overwrite = true; - break; + if ((*altIter != dataID) && (*altIter != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); } else { - overwritten = true; - break; + m_OverwrittenList.insert(altIndex); } - } else if (alternatives.size() == 1) { - // only alternative is data -> no conflict - regular = true; } } } @@ -405,9 +408,11 @@ ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() c m_LastConflictCheck = QTime::currentTime(); - if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED; - else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (overwritten) { + if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) { if (!regular) { m_CurrentConflictState = CONFLICT_REDUNDANT; } else { diff --git a/src/modinfo.h b/src/modinfo.h index ef647eab..cacddadf 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -472,6 +472,18 @@ public: */ virtual void saveMeta() {} + /** + * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed + * @return + */ + virtual std::set getModOverwrite() { return std::set(); } + + /** + * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed + * @return + */ + virtual std::set getModOverwritten() { return std::set(); } + signals: /** @@ -525,6 +537,11 @@ public: * @brief clear all caches held for this mod */ virtual void clearCaches(); + + virtual std::set getModOverwrite() { return m_OverwriteList; } + + virtual std::set getModOverwritten() { return m_OverwrittenList; } + private: enum EConflictType { @@ -554,6 +571,9 @@ private: mutable EConflictType m_CurrentConflictState; mutable QTime m_LastConflictCheck; + mutable std::set m_OverwriteList; // indices of mods overritten by this mod + mutable std::set m_OverwrittenList; // indices of mods overwriting this mod + }; diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index db6c7123..02ba0d38 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -99,17 +99,20 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_NEXUS, false); ui->tabWidget->setTabEnabled(TAB_FILETREE, false); ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_ESPS, false); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); + ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { initFiletree(modInfo); initINITweaks(); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); + ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); + ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } refreshLists(); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL); if (ui->tabWidget->currentIndex() == TAB_NEXUS) { diff --git a/src/modlist.cpp b/src/modlist.cpp index 3f46b85f..7e5a5f58 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "installationtester.h" #include "qtgroupingproxy.h" +#include "viewmarkingscrollbar.h" #include #include #include @@ -291,6 +292,15 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } return QVariant(); + } else if ((role == Qt::BackgroundRole) + || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { + return QColor(0, 255, 0, 64); + } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { + return QColor(255, 0, 0, 64); + } else { + return QVariant(); + } } else if (role == Qt::ToolTipRole) { if (column == COL_FLAGS) { QString result; @@ -576,6 +586,13 @@ void ModList::changeModPriority(int sourceIndex, int newPriority) emit modorder_changed(); } +void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_Overwrite = overwrite; + m_Overwritten = overwritten; + notifyChange(0, rowCount()); +} + void ModList::modInfoAboutToChange(ModInfo::Ptr info) { m_ChangeInfo.name = info->name(); @@ -903,9 +920,10 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) } else if ((event->type() == QEvent::KeyPress) && (m_Profile != NULL)) { QAbstractItemView *itemView = qobject_cast(obj); QKeyEvent *keyEvent = static_cast(event); - if ((itemView != NULL) && - (keyEvent->modifiers() == Qt::ControlModifier) && - ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + if ((itemView != NULL) + && (keyEvent->modifiers() == Qt::ControlModifier) + && ((keyEvent->key() == Qt::Key_Up) + || (keyEvent->key() == Qt::Key_Down))) { QItemSelectionModel *selectionModel = itemView->selectionModel(); const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); const QSortFilterProxyModel *filterModel = NULL; diff --git a/src/modlist.h b/src/modlist.h index b946e009..18342aa5 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -97,6 +97,8 @@ public: void changeModPriority(int sourceIndex, int newPriority); + void setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); + void modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); @@ -277,6 +279,9 @@ private: bool m_DropOnItems; + std::set m_Overwrite; + std::set m_Overwritten; + TModInfoChange m_ChangeInfo; SignalModStateChanged m_ModStateChanged; diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 1a4f6266..2789afe1 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -36,8 +36,10 @@ void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOptio ModListView::ModListView(QWidget *parent) : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { // setStyle(new ModListViewStyle(style(), indentation())); + setVerticalScrollBar(m_Scrollbar); } void ModListView::dragEnterEvent(QDragEnterEvent *event) @@ -46,3 +48,9 @@ void ModListView::dragEnterEvent(QDragEnterEvent *event) QTreeView::dragEnterEvent(event); } + +void ModListView::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +} diff --git a/src/modlistview.h b/src/modlistview.h index e501a660..4907225f 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -3,6 +3,7 @@ #include #include +#include "viewmarkingscrollbar.h" class ModListView : public QTreeView { @@ -10,10 +11,14 @@ class ModListView : public QTreeView public: explicit ModListView(QWidget *parent = 0); virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void setModel(QAbstractItemModel *model); signals: void dropModeUpdate(bool dropOnRows); public slots: +private: + + ViewMarkingScrollBar *m_Scrollbar; }; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 08c16b62..ed872870 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -169,10 +169,14 @@ void NXMAccessManager::loginTimeout() void NXMAccessManager::loginError(QNetworkReply::NetworkError) { m_ProgressDialog.hide(); - emit loginFailed(m_LoginReply->errorString()); m_LoginTimeout.stop(); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; + if (m_LoginReply != NULL) { + emit loginFailed(m_LoginReply->errorString()); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + } else { + emit loginFailed(tr("Unknown error")); + } m_Username.clear(); m_Password.clear(); } diff --git a/src/organizer.pro b/src/organizer.pro index de0f70af..17618377 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -89,7 +89,8 @@ SOURCES += \ safewritefile.cpp \ modflagicondelegate.cpp \ pluginflagicondelegate.cpp \ - organizerproxy.cpp + organizerproxy.cpp \ + viewmarkingscrollbar.cpp HEADERS += \ @@ -168,7 +169,8 @@ HEADERS += \ pdll.h \ modflagicondelegate.h \ pluginflagicondelegate.h \ - organizerproxy.h + organizerproxy.h \ + viewmarkingscrollbar.h FORMS += \ transfersavesdialog.ui \ @@ -244,6 +246,7 @@ TRANSLATIONS = organizer_de.ts \ organizer_cs.ts \ organizer_tr.ts \ organizer_en.ts \ + organizer_ko.ts \ organizer_ru.ts #!isEmpty(TRANSLATIONS) { diff --git a/src/organizer_cs.ts b/src/organizer_cs.ts index c1a15646..73c225f0 100644 --- a/src/organizer_cs.ts +++ b/src/organizer_cs.ts @@ -1,5 +1,50 @@ + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + + + + + No license + + + ActivateModsDialog @@ -14,22 +59,22 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Thole je seznam souborů esp a esm, které byli aktivní když byla pozice uložena.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pro každé esp je v pravem sloupci mod (nebo mody), které třeba aktivat, aby byli esp/esm k dispozici</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud klikneš OK, všechny mody označené v pravem sloupci se aktivují, a chybějící esp/esm budou k dispozici.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Thole je seznam souborů esp a esm, které byli aktivní když byla pozice uložena.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pro každé esp je v pravem sloupci mod (nebo mody), které třeba aktivat, aby byli esp/esm k dispozici</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud klikneš OK, všechny mody označené v pravem sloupci se aktivují, a chybějící esp/esm budou k dispozici.</span></p></body></html> @@ -72,9 +117,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. Komponenty tohto balíku. -Pokud je k dispozici komponent s jménem "00 Core" obvykle je povinný. Možnosti jsou seřazeny dle priority navrženou autorem. +Pokud je k dispozici komponent s jménem "00 Core" obvykle je povinný. Možnosti jsou seřazeny dle priority navrženou autorem. @@ -109,6 +154,29 @@ Pokud je k dispozici komponent s jménem "00 Core" obvykle je povinný. Možnost Zrušit + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -154,20 +222,20 @@ Pokud je k dispozici komponent s jménem "00 Core" obvykle je povinný. Možnost - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Můžete přiradit jedno nebo víc Nexus ID k interní kategorii. Kdykoli stáhnete mod ze stránky Nexus, program podle ID automaticky přiradí kategorie.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Když chcete zjistit kategorii v Nexusu, navštivte stránku "category list" a podržte kurzor nad linkami.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Můžete přiradit jedno nebo víc Nexus ID k interní kategorii. Kdykoli stáhnete mod ze stránky Nexus, program podle ID automaticky přiradí kategorie.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Když chcete zjistit kategorii v Nexusu, navštivte stránku "category list" a podržte kurzor nad linkami.</span></p></body></html> @@ -199,7 +267,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus Tahle funkce nemusí fungovat pokud nejste také přihlášeni na stránke Nexus @@ -226,32 +294,37 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 - + DownloadList - + Name Jméno - + Filetime Čas stáhnutí - + Done Hotovo - - Information missing, please select "Query Info" from the context menu to re-retrieve. - Info chybí, označte "Získat Info" z kontextového menu pro pokus načtení z Nexusu. + + Information missing, please select "Query Info" from the context menu to re-retrieve. + Info chybí, označte "Získat Info" z kontextového menu pro pokus načtení z Nexusu. + + + + pending download + @@ -264,28 +337,28 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Stáhnuté - dvouklik instaluj - - + + Paused - Double Click to resume - + - - + + Installed - Double Click to re-install Instalované - dvouklik přeinstaluj - - + + Uninstalled - Double Click to re-install - + @@ -304,20 +377,30 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + Paused - + Fetching Info 1 - + Fetching Info 2 - + @@ -327,7 +410,7 @@ p, li { white-space: pre-wrap; } Uninstalled - + @@ -335,95 +418,95 @@ p, li { white-space: pre-wrap; } Hotovo - - - - + + + + Are you sure? Jsi si jistý? - + This will remove all finished downloads from this list and from disk. Tímto vymažeš všechny dokončené stáhnutí se seznamu i z disku. - + This will remove all installed downloads from this list and from disk. Tímto vymažeš jenom nainstalované stáhnutí se seznamu i z disku. - + This will permanently remove all finished downloads from this list (but NOT from disk). - + - + This will permanently remove all installed downloads from this list (but NOT from disk). - + - + Install Instaluj - + Query Info Získej Info - + Delete - + - + Un-Hide - + Odekrýt - + Remove from View - + - + Cancel Zrušit - + Pause Pauza - + Remove Odstranit - + Resume Pokračuj - + Delete Installed... - + - + Delete All... - + - + Remove Installed... Odstraň už nainstalované... - + Remove All... Odstraň všechny... @@ -431,105 +514,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + + < mod %1 file %2 > + + + + + Pending + + + + Fetching Info 1 - + - + Fetching Info 2 - + - - - - + + + + Are you sure? Jsi si jistý? - + This will remove all finished downloads from this list and from disk. Tímto vymažeš všechny dokončené stáhnutí se seznamu i z disku. - + This will remove all installed downloads from this list and from disk. Tímto vymažeš jenom nainstalované stáhnutí se seznamu i z disku. - + This will remove all finished downloads from this list (but NOT from disk). - + - + This will remove all installed downloads from this list (but NOT from disk). - + - + Install Instaluj - + Query Info Získej Info - + Delete - + - + Un-Hide - + Odekrýt - + Remove from View - + - + Cancel Zrušit - + Pause Pauza - + Remove Odstraniť - + Resume Pokračuj - + Delete Installed... - + - + Delete All... - + - + Remove Installed... Odstranit už nainstalované... - + Remove All... Odstranit všechny... @@ -537,116 +630,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - Nezdařilo se přejmenovat "%1" na "%2" + + failed to rename "%1" to "%2" + Nezdařilo se přejmenovat "%1" na "%2" - + + Memory allocation error (in refreshing directory). + + + + Download again? Stáhnout znovu? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Soubor se stejným jménem už byl stáhnutý. Chcete ho stáhnout znovu? Nový soubor bude pojmenován jinak. - + failed to download %1: could not open output file: %2 Stahování zlyhalo %1: nemožno otevřít výslední soubor: %2 - + Wrong Game - + - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + - - - - - - - - - - - + + - - + + + + + + + + + + + + + invalid index neplatný index - + failed to delete %1 odstránění zlyhalo %1 - + failed to delete meta file for %1 odstránění meta souboru zlyhalo pro %1 - - - - - - + + + + + + invalid index %1 neplatný index %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id Prosím zadej Nexus mod ID - + Mod ID: Mod ID: - + + Main + Hlavní + + + + Update + + + + + Optional + Volitelné + + + + Old + Staré + + + + Misc + Jiné + + + + Unknown + Neznámé + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated Info aktualizované - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? Žádný přislouchající soubor nenalezen na Nexusu! Možná už není k dispozici nebo byl přejmenovaný? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. Žádný soubor na Nexusu nezodpovedá přesnému jménu. Zvolte ručne ten správný. - + No download server available. Please try again later. - + - + Failed to request file info from nexus: %1 Zlyhalo získání info z Nexusu %1 - + + Download failed. Server reported: %1 + + + + Download failed: %1 (%2) Stahování zlyhalo: %1 (%2) - + failed to re-open %1 zlyhalo znovu-otevření %1 @@ -756,7 +906,7 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v - + If checked, MO will be closed once the specified executable is run. Pokud je zaškrtnuté, MO se ukončí hned po spuštění Spouštěče. @@ -773,7 +923,7 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v - + Add Přidat @@ -789,47 +939,64 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v Odstranit - + + Close + + + + Select a binary Vyber binární soubor - + Executable (%1) Spuštění (%1) - + Java (32-bit) required - + - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + - + Select a directory Vyber Zložku - + Confirm Potvrdit - - Really remove "%1" from executables? - Opravdu odstranit "%1" ze seznamu Spouštění? + + Really remove "%1" from executables? + Opravdu odstranit "%1" ze seznamu Spouštění? - + Modify Ulož - + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + MO must be kept running or this application will not work correctly. MO musí bežet, aby tahle aplikace pracovala správně. @@ -900,8 +1067,8 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v - <a href="#">Link</a> - <a href="#">Odkaz</a> + <a href="#">Link</a> + <a href="#">Odkaz</a> @@ -948,7 +1115,7 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. Zvol Jméno pro mod. Takhle se zároveň bude jmenovat adresář, takže nepoužívejte znaky, které jsou zakázany pro adresáře. @@ -963,16 +1130,16 @@ Každá hra/aplikace distribuovaná skrz Steammá unikátní ID. MO potřebuje v - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle zobrazuje obsah archivu. &lt;data&gt; představuje základní adresář, který se promítne do Data adresáře hry. Můžete sami vybrat který adresář bude považován za Data tím, že naněj kliknete pravým tlačítkem a v kontext menu ho označíte. Také můžete předtím upravovat strukturu taháním myší.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle zobrazuje obsah archivu. &lt;data&gt; představuje základní adresář, který se promítne do Data adresáře hry. Můžete sami vybrat který adresář bude považován za Data tím, že naněj kliknete pravým tlačítkem a v kontext menu ho označíte. Také můžete předtím upravovat strukturu taháním myší.</span></p></body></html> @@ -994,8 +1161,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + @@ -1010,19 +1177,19 @@ p, li { white-space: pre-wrap; } - + Extracting files Rozbalují se soubory failed to create backup - + Mod Name - + @@ -1032,65 +1199,65 @@ p, li { white-space: pre-wrap; } Invalid name - + The name you entered is invalid, please enter a different one. - + - - File format "%1" not supported - Formát souboru "%1" nepodporován + + File format "%1" not supported + Formát souboru "%1" nepodporován - + None of the available installer plugins were able to handle that archive - + - + no error žádná chyba - + 7z.dll not found 7z.dll nenalezeno - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll je neplatný - + archive not found archív nenalezen - + failed to open archive nemožno otevřít archív - + unsupported archive type nepodporovaný typ archívu - + internal library error chyba internal library - + archive invalid archív je neplatný - + unknown archive error neznáma chyba archívu @@ -1104,8 +1271,8 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - Toto okno by mělo samo zmizet až se aplikace/hra ukončí.Klikněte "Odemkni" pokud se tak nestalo. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + Toto okno by mělo samo zmizet až se aplikace/hra ukončí.Klikněte "Odemkni" pokud se tak nestalo. @@ -1113,7 +1280,7 @@ p, li { white-space: pre-wrap; } MO je uzamčený pokud aplikace/hra běží. - + Unlock Odemkni @@ -1121,7 +1288,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 nezdařil se zápis do logu %1: %2 @@ -1129,12 +1296,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 vyskytla se chyba: %1 - + an error occured vyskytla se chyba @@ -1142,409 +1309,469 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories Kategorie + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + Profile Profil - + Pick a module collection Vyber kolekci modulů - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tady vytvoř profil. Každý profil obsahuje svúj vlastní seznam aktivních modů a esp. Díky tomu můžeš rychle přepínat mezi různými konfiguracemi.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Prosím mějte na paměti, že v současnosti poradí esp se neukladá pro různé profily.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tady vytvoř profil. Každý profil obsahuje svúj vlastní seznam aktivních modů a esp. Díky tomu můžeš rychle přepínat mezi různými konfiguracemi.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Prosím mějte na paměti, že v současnosti poradí esp se neukladá pro různé profily.</span></p></body></html> - Refresh list - Znovunačíst seznam + Znovunačíst seznam - + Refresh list. This is usually not necessary unless you modified data outside the program. Obnoví seznam. Tohle obvykle není zapotřebí, jedine že by jste měnili obsah dat mimo program MO. - + List of available mods. - + - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + - + Filter Filter - + No groups - + - + Nexus IDs - + Nexus ID - - - + + + Namefilter - + - + Pick a program to run. Vyber program na spuštění. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vyber program, který se spustí. Když začneš používat ModOrganizer, pokaždé by jsi měl spouštet hru i nástroje přes toto nebo přes skratky vytvořené tímto programem, jinak mody nainstalované přes MO nebudou vidět.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Můžeš přidávat různé nástroje, ale neručím, že ty které jsem netestoval poběží správně.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vyber program, který se spustí. Když začneš používat ModOrganizer, pokaždé by jsi měl spouštet hru i nástroje přes toto nebo přes skratky vytvořené tímto programem, jinak mody nainstalované přes MO nebudou vidět.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Můžeš přidávat různé nástroje, ale neručím, že ty které jsem netestoval poběží správně.</span></p></body></html> - + Run program Spustit program - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Spusti vybraný program s nastavením ModOrganizeru.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Spusti vybraný program s nastavením ModOrganizeru.</span></p></body></html> - + Run Spustit - + Create a shortcut in your start menu or on the desktop to the specified program - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle vytvoří odkaz v menu Start, který přímo bude spouštět zvolený program přes MO.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle vytvoří odkaz v menu Start, který přímo bude spouštět zvolený program přes MO.</span></p></body></html> - + Shortcut - + - + List of available esp/esm files Seznam dostupných esp/esm souborů - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tenhle seznam obsahuje esp a esm soubory z aktivovaných modů. Tyhle mají svoje vlastní pořadí priority. Tahaním myší je možné měnit pořadí. Prosím mějte na paměti, že MO zobrazí seznam jenom z modů, které jsou aktivovány (zaškrtnuté).<br />Existuje skvělý nástroj &quot;BOSS&quot; který automaticky správně seřadí tyhle soubory.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tenhle seznam obsahuje esp a esm soubory z aktivovaných modů. Tyhle mají svoje vlastní pořadí priority. Tahaním myší je možné měnit pořadí. Prosím mějte na paměti, že MO zobrazí seznam jenom z modů, které jsou aktivovány (zaškrtnuté).<br />Existuje skvělý nástroj &quot;BOSS&quot; který automaticky správně seřadí tyhle soubory.</span></p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. Seznam BS archivů, které jsou k dispozici. Archivy, které jsou zde neni označeny nebudou načteny do hry. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by Skyrim. As such they "compete" with loose files in your data directory over which is loaded. + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by Skyrim. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - - + + File Soubor - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - - - - + Data Data - + refresh data-directory overview znovunačti data - + Refresh the overview. This may take a moment. Obnov náhled. Tohle může chvíli trvat. - - - + + + Refresh Znovunačíst - + This is an overview of your data directory as visible to the game (and tools). Tohle je náhled tvé data struktury, kterou načte hra (i nástroje). - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. Přefiltruje seznam nahoře tak, že budou zobrazeny pouze konflikty. - + Show only conflicts Ukaž jenom konflikty - + Saves Uložené pozice - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle je seznam všech uložených pozic hry. Podrž kurzor ponad pozicí pro zobrazení detailných informací vrátaně seznamu esp/esm, které byli uloženy do pozice, ale v současnosti nejsou aktivní.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud kliknete &quot;Fixni Mody...&quot; v kontext menu, MO se pokusí aktivovat všechny mody a esp soubory, které byli v pozici používány. Nic se však nevypne!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle je seznam všech uložených pozic hry. Podrž kurzor ponad pozicí pro zobrazení detailných informací vrátaně seznamu esp/esm, které byli uloženy do pozice, ale v současnosti nejsou aktivní.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud kliknete &quot;Fixni Mody...&quot; v kontext menu, MO se pokusí aktivovat všechny mody a esp soubory, které byli v pozici používány. Nic se však nevypne!</span></p></body></html> - + Downloads Stáhnuté - + This is a list of mods you downloaded from Nexus. Double click one to install it. Tohle je seznam modů, které jsi stánul z Nexusu. Dvojklik nainstaluje mod. - Compact - Kompaktní + Kompaktní - + + Open list options... + + + + + + Restore Backup... + + + + + + Create Backup + + + + + Plugins + + + + + Sort + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + Show Hidden - + - + Tool Bar Panel nástrojú - + Install Mod Instaluj mod - + Install &Mod Instaluj &Mod - + Install a new mod from an archive Instaluj nový mod z archívu - + Ctrl+M Ctrl+M - + Profiles Profily - + &Profiles &Profily - + Configure Profiles Nastav profily - + Ctrl+P Ctrl+P - + Executables Spouštění - + &Executables &Spouštění - + Configure the executables that can be started through Mod Organizer Konfigurace spouštění, které lze použít pro načtení modů z MO - + Ctrl+E Ctrl+E - - + + Tools - + - + &Tools - + - + Ctrl+I Ctrl+I - + Settings Nastavení - + &Settings &Nastavení - + Configure settings and workarounds Konfigurace a nastavení programu a různých řešení - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Prohledat mody na nexusu - + Ctrl+N Ctrl+N - - + + Update - + - + Mod Organizer is up-to-date Verze Mod Organizer u je aktuální - - + + No Problems Žádné problémy - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1555,899 +1782,1095 @@ Right now this has very limited functionality V současnosti má omezenou funkcionalitu - - + + Help Pomoc - + Ctrl+H - + - + Endorse MO - + - - + + Endorse Mod Organizer - + + + + + Copy Log to Clipboard + - + + Ctrl+C + + + + Toolbar - + - + Desktop - + - + Start Menu - + - + Problems Problémy - + There are potential problems with your setup Existují potenciální problémy s programem - + Everything seems to be in order Všechno se jeví v pořádku - + Help on UI Pomoc s programem - + Documentation Wiki Dokumentace wiki - + Report Issue Nahlásit chybu - + Tutorials - + - - failed to save archives order, do you have write access to "%1"? - zlyhal zápis pořadí archivů, máte administrátorsky povoleno zapisovat na "%1"? + failed to save archives order, do you have write access to "%1"? + zlyhal zápis pořadí archivů, máte administrátorsky povoleno zapisovat na "%1"? - + failed to save load order: %1 zlyhalo uložení pořadí načtení: %1 - + Name Jméno - + Please enter a name for the new profile Prosím zadej jméno pro nový profil - + failed to create profile: %1 Zlyhalo vytvoření profilu: %1 - + Show tutorial? - + - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + - + Downloads in progress Probíhá stahování - + There are still downloads in progress, do you really want to quit? Pořád probíhá stahování, určitě chcete skončit (zruší stahování)? - + failed to read savegame: %1 nezdařilo se načíst pozici: %1 - - Plugin "%1" failed: %2 - + + Plugin "%1" failed: %2 + - - Plugin "%1" failed - + + Plugin "%1" failed + - + failed to init plugin %1: %2 - + - + Plugin error - + - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + - - Failed to start "%1" - Zlyhal start "%1" + + Failed to start "%1" + Zlyhal start "%1" - + Waiting Čekání - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. Stiskni OK až budeš přihlášen do Steamu. - - "%1" not found - "%1" nenalezeno + "%1" not found + "%1" nenalezeno - + Start Steam? Spustit Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam by měl běžet aby se podařilo spustit hru. Má se MO pokusit spustit steam teď? - + Also in: <br> Také v: <br> - + No conflict Žádné konflikty - + <Edit...> <Edit...> - - Failed to refresh list of esps: %s - - - - + This bsa is enabled in the ini file so it may be required! Tenhle BSA soubor je aktivován v ini souboru, tak zřejmě je vyžadován! - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - Tento archív se stejně načte, protože existuje plugin se stejným jménem ale jeho soubory nebudou zodpovídat pořadí nainstalování! + Tento archív se stejně načte, protože existuje plugin se stejným jménem ale jeho soubory nebudou zodpovídat pořadí nainstalování! - + Activating Network Proxy - + - - + + Installation successful Instalace úspěšná - - + + Configure Mod Konfigurace Modu - - + + This mod contains ini tweaks. Do you want to configure them now? Tenhle mod obsahuje ini úpravy. Chcete je nastavovat teď? - - - mod "%1" not found - mod "%1" nenalezen + + + mod "%1" not found + mod "%1" nenalezen - - + + Installation cancelled Instalace zrušena - - + + The mod was not installed completely. Tento mod se nenainstaloval úplne. - + Some plugins could not be loaded - + - + Too many esps and esms enabled - + - - + + Description missing - + - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + - + Choose Mod Vyber Mod - + Mod Archive Archív Modu - + Start Tutorial? - + - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + - - + + Download started Stahování začalo - + failed to update mod list: %1 nezdařilo se aktualizovat seznam modů: %1 - + failed to spawn notepad.exe: %1 zlyhalo otevření notepad.exe: %1 - + failed to open %1 - + nepodařilo se otevřít %1 - + failed to change origin name: %1 Nezdařilo se změnit původní jméno: %1 - - Failed to move "%1" from mod "%2" to "%3": %4 - - - - + <Checked> <Aktivované> - + <Unchecked> <Neaktivované> - + <Update> <Aktualizace> - + <No category> <Bez kategorie> - + <Conflicted> - + - + <Not Endorsed> - + - + failed to rename mod: %1 Nezdařilo se přejmenovat mod: %1 - + Overwrite? - + - - This will replace the existing mod "%1". Continue? - + + This will replace the existing mod "%1". Continue? + - - failed to remove mod "%1" - + + failed to remove mod "%1" + - - - - failed to rename "%1" to "%2" - Nezdařilo se přejmenovat "%1" na "%2" + + + + failed to rename "%1" to "%2" + Nezdařilo se přejmenovat "%1" na "%2" - - Multiple esps activated, please check that they don't conflict. - + + Multiple esps activated, please check that they don't conflict. + - - - + + + + Confirm Potvrdit - + Remove the following mods?<br><ul>%1</ul> - + - + failed to remove mod: %1 Nezdařilo se odstranit mod: %1 - - + + Failed Zlyhání - + Installation file no longer exists Instalační soubor již neexistuje - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. Mody nainstalovány staršími verzemi MO nemůžou být přeinstalovány tímto spůsobem. - - + + You need to be logged in with Nexus to endorse - + - - + Extract BSA Extrakce BSA - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - Tento mod obsahuje alespoň jeden BSA soubor. Chcete rozpakovat i jeho obsah? +(This removes the BSA after completion. If you don't know about BSAs, just select no) + Tento mod obsahuje alespoň jeden BSA soubor. Chcete rozpakovat i jeho obsah? (BSA se po úspěšném rozpakování odstrání. Pokud nevíte, co je BSA, zvolte možnost Ne) - - - + + failed to read %1: %2 - + - - + This archive contains invalid hashes. Some files may be broken. Tento archiv má neplatné identifikační součty. Nekteré soubory mohou být poškozeny. - + Nexus ID for this Mod is unknown Nexus ID pro tento Mod není známo - - + + About + + + + + About Qt + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + Executable "%1" not found + + + + + Failed to refresh list of esps: %1 + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... - + - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + - + A mod with this name already exists - + - + Continue? - + - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - + + Sorry - + - - I don't know a versioning scheme where %1 is newer than %2. - + + I don't know a versioning scheme where %1 is newer than %2. + - + Really enable all visible mods? Opravdu aktivovat všechny zobrazené mody? - + Really disable all visible mods? Opravdu deaktivovat všechny zobrazené mody? - + Choose what to export - + - + Everything - + - + All installed mods are included in the list - + - + Active Mods - + - + Only active (checked) mods from your current profile are included - + - + Visible - + - + All mods visible in the mod list are included - + - + export failed: %1 - + - + Install Mod... Instaluj Mod... - + Enable all visible Aktivuj všechny v seznamu - + Disable all visible Deaktivuj všechny v seznamu - + Check all for update Skontroluj všechny pro aktualizaci - + Export to csv... - + + + + + All Mods + - + Sync to Mods... Synchronizuj s Mody... - + Restore Backup - + - + Remove Backup... - + - + Add/Remove Categories - + - + Replace Categories - + - + Primary Category - + - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... Přejmenuj Mod... - + Remove Mod... Odstranit Mod... - + Reinstall Mod Přeinstaluj Mod - + Un-Endorse - + - - + + Endorse - + - - Won't endorse - + + Won't endorse + - + Endorsement state unknown - + - + Ignore missing data - + - + Visit on Nexus Navštiv na Nexusu - + Open in explorer Otevři v prohlížeči - + Information... Informace... - - + + Exception: Výnimky: - - + + Unknown exception Neznámá výnimka - + <All> <Všechny> - + <Multiple> - + - + + Really delete "%1"? + + + + Fix Mods... Oprav Mody... - - + + Delete + + + + + failed to remove %1 - + Nepodařilo se odstranit %1 - - + + failed to create %1 - + Nepodařilo se vytvořit %1 - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! Není možné změnit cíl pro stahování když probíhá stahování! - + Download failed Stahování zlyhalo - + failed to write to file %1 Nezdařil se zápis do souboru %1 - + %1 written %1 zapsáno - + Select binary Vyber binární soubor - + Binary Soubor - + Enter Name Zadej jméno - + Please enter a name for the executable Prosím zadej jméno pro spouštění - + Not an executable Není spustitelný - + This is not a recognized executable. Tenhle soubor není rozpoznán jako spustitelný. - - + + Replace file? Nahradit soubor? - + There already is a hidden version of this file. Replace it? Už existuje skrytá verze tohto souboru. Nahradit? - - + + File operation failed Operace se souborem zlyhala - - - Failed to remove "%1". Maybe you lack the required file permissions? - Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? - + There already is a visible version of this file. Replace it? Už existuje viditelná verze tohto souboru. Nahradit? - + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + Update available Aktualizace k dispozici - + Open/Execute Otevřít/Spustit - + Add as Executable Přidat Spouštení - + + Preview + + + + Un-Hide Odekrýt - + Hide Skrýt - + Write To File... Zápis do souboru... - + Do you want to endorse Mod Organizer on %1 now? - + - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 - + - - + + login successful - + - + login failed: %1. Trying to download anyway přihlášení zlyhalo: %1. Pokouším se beztak stahovat - + login failed: %1 - + - + login failed: %1. You need to log-in with Nexus to update MO. přihlášení zlyhalo: %1. Na aktualizaci MO je potřebné přihlášení k Nexusu. - + Error Chyba - + failed to extract %1 (errorcode %2) zlyhala extrakce %1 (errorcode %2) - + Extract... Extrakce... - + Edit Categories... Editovat Kategorie... - + + Deselect filter + + + + Remove - + - + Enable all - + - + Disable all - + - + Unlock load order - + - + Lock load order - + + + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + @@ -2462,8 +2885,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 neplatný index %1 @@ -2471,9 +2894,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + @@ -2484,534 +2907,567 @@ This function will guess the versioning scheme under the assumption that the ins Informace o modu - + Textfiles Textové soubory - + A list of text-files in the mod directory. Seznam textových souborů obsažených v modu. - + A list of text-files in the mod directory like readmes. Seznam textových souborů obsažených v modu, například readme. - - + + Save Uložit - + INI-Files INI soubory - + + Ini Files + + + + This is a list of .ini files in the mod. Tohle je seznam .ini souborů v modu. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Tohle je seznam .ini souborů v modu. Dají se upravovat pro zmenu konfigurace a chování modu, pokud umožňuje takové parametry. - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Uložit změny do souboru. - + Save changes to the file. This overwrites the original. There is no automatic backup! Uložit změny do souboru.Tohle přepíše původní soubor. Nevytváří se žádná automatická záloha! - + Images Obrázky - + Images located in the mod. Obrázky obsažené v modu. - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + - - + + Optional ESPs Volitelné ESP - + List of esps and esms that can not be loaded by the game. Seznam souborů .esp a .esm, které nebudou načteni do hry. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + - + Make the selected mod in the lower list unavailable. Znepřístupni označený mod v seznamu dolů. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - Označený soubor esp (v seznamu dolů) bude přemístěn do podadresáře modu a tak se stane "neviditelným" pro hru. V takovem stavu se nedá aktivovat. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + Označený soubor esp (v seznamu dolů) bude přemístěn do podadresáře modu a tak se stane "neviditelným" pro hru. V takovem stavu se nedá aktivovat. - + Move a file to the data directory. Přesuň soubor mezi Data. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Přesune soubor esp do adresáře, kde má být, aby mohl být aktivován. Prosím berte na vědomí, že tato akce jenom soubor "zpřistupní", nedělá ho automaticky aktivním. To se pak aktivuje v hlavním oknu mezi esp. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + Přesune soubor esp do adresáře, kde má být, aby mohl být aktivován. Prosím berte na vědomí, že tato akce jenom soubor "zpřistupní", nedělá ho automaticky aktivním. To se pak aktivuje v hlavním oknu mezi esp. - + ESPs in the data directory and thus visible to the game. ESP soubory mezi Data a tedy přístupné pro hru. - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. Tady jsou soubory modu, které se nacházejí ve (virtuálním) data adresáři hry a proto je bude možné aktivovat v seznamu esp v hlavním okně. - + Available ESPs ESP k dispozici - + Conflicts Konflikty - + The following conflicted files are provided by this mod Konfliktní soubory, které budou přebity tímhle modem - - + + File Soubor - + Overwritten Mods Přepsané mody - + The following conflicted files are provided by other mods Konfliktní soubory, které další mody přebijou - + Providing Mod Mod původu - + Non-Conflicted files Nekonfliktní soubory - + Categories Kategorie - + Primary Category - + - + Nexus Info Nexus Info - + Mod ID Mod ID - + Mod ID for this mod on Nexus. Mod ID tohodle modu na Nexusu. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID pro tenhle mod na Nexusu. Vyplňuje se automaticky pokud ste soubor i stáhli i nainstalovali přímo skrz MO. Jinak ho můžete zadat ručne. Správne ID naleznete u modu na Nexusu. Adresa bude vypadat takhle: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. V takovem případe 1334 je Mod ID. Mimo jiné odkaz je přímo na Mod Organizer tak proč rovnou nejít zadat Endorse?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID pro tenhle mod na Nexusu. Vyplňuje se automaticky pokud ste soubor i stáhli i nainstalovali přímo skrz MO. Jinak ho můžete zadat ručne. Správne ID naleznete u modu na Nexusu. Adresa bude vypadat takhle: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. V takovem případe 1334 je Mod ID. Mimo jiné odkaz je přímo na Mod Organizer tak proč rovnou nejít zadat Endorse?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Nainstalovaná verze modu. Bublina ukáže číslo nejaktuálnější verzi modu na Nexusu. Číslo verze se nastaví samo jenom pokud byl mod nainstalován skrz MO.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Nainstalovaná verze modu. Bublina ukáže číslo nejaktuálnější verzi modu na Nexusu. Číslo verze se nastaví samo jenom pokud byl mod nainstalován skrz MO.</span></p></body></html> - + Version Verze - + Refresh Znovunačíst - + Refresh all information from Nexus. - + - + Description Popis - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse - + - + Notes - + - + Filetree Struktura souborů - + A directory view of this mod Zložkový náhled na mod - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle je pohled na strukturu, kterou můžete ručně měnit. Můžete přesouvat soubory taháním myší a přejmenovávat je (dvojklikem).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Zmeny se okamžite dejí přimo na disku, takže</span><span style=" font-size:8pt; font-weight:600;">buďte opatrní</span><span style=" font-size:8pt;">.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Tohle je pohled na strukturu, kterou můžete ručně měnit. Můžete přesouvat soubory taháním myší a přejmenovávat je (dvojklikem).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Zmeny se okamžite dejí přimo na disku, takže</span><span style=" font-size:8pt; font-weight:600;">buďte opatrní</span><span style=" font-size:8pt;">.</span></p></body></html> - + Previous - + - + Next - + Další - + Close Zavřít - + &Delete &Smazat - + &Rename &Přejmenovat - + &Hide &Skrýt - + &Unhide &Odekrýt - + &Open &Otevřít - + &New Folder &Nová Složka - - + + Save changes? Uložit změny? - - - Save changes to "%1"? - + + + Save changes to "%1"? + - + File Exists Soubor existuje - + A file with that name exists, please enter a new one Soubor s rovnakým názvem existuje, prosím zadejte jiné jméno - + failed to move file zlyhalo přesunutí souboru - - failed to create directory "optional" - zlyhalo vytvoření zložky "optional" + + failed to create directory "optional" + zlyhalo vytvoření zložky "optional" - - + + Info requested, please wait Info vyžádáno, prosím počkejte - + Main Hlavní - + Update Update - + Optional Volitelné - + Old Staré - + Misc Jiné - + Unknown Neznámé - + Current Version: %1 Současná verze: %1 - + No update available Žádný update není k dispozici - + (description incomplete, please visit nexus) (popis chybí, prosím navštivte nexus pro kompletní zobrazení) - - <a href="%1">Visit on Nexus</a> - <a href="%1">Navštivte na Nexusu</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">Navštivte na Nexusu</a> - + Failed to delete %1 Zlyhalo vymazání %1 - - + + Confirm Potvrdit - - Are sure you want to delete "%1"? - Jsi si jistý, že chceš vymazat "%1"? + + Are sure you want to delete "%1"? + Jsi si jistý, že chceš vymazat "%1"? - + Are sure you want to delete the selected files? Jsi si jistý, že chceš vymazat označené soubory? - - + + New Folder Nová zložka - - Failed to create "%1" - Zlyhalo vytvoření "%1" + + Failed to create "%1" + Zlyhalo vytvoření "%1" - - + + Replace file? Nahradit soubor? - + There already is a hidden version of this file. Replace it? Už existuje skrytá verze tohto souboru. Nahradit? - - + + File operation failed Operace se souborem zlyhala - - - Failed to remove "%1". Maybe you lack the required file permissions? - Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? - - + + failed to rename %1 to %2 Nezdařilo se přejmenovat %1 na %2 - + There already is a visible version of this file. Replace it? Už existuje viditelná verze tohto souboru. Nahradit? - + Un-Hide Odekrýt - + Hide Skrýt - + Name - + Jméno - + Please enter a name - + - - + + Error - + Chyba - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - Tenhle kvázi mod obsahuje soubory, které byli vytvořeny nebo změněny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') + Tenhle kvázi mod obsahuje soubory, které byli vytvořeny nebo změněny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') ModInfoRegular - failed to write %1/meta.ini: %2 - zlyhal zápis %1/meta.ini: %2 + zlyhal zápis %1/meta.ini: %2 + + + + + failed to write %1/meta.ini: error %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 neobsahuje žádné esp/esm ani jiné použitelné struktury (textures, meshes, interface,...) - + Categories: <br> Kategorie: <br> @@ -3021,158 +3477,167 @@ p, li { white-space: pre-wrap; } This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - Tenhle záznam obsahuje soubory, které byli vytvořeny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') + Tenhle záznam obsahuje soubory, které byli vytvořeny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + - + + Non-MO + + + + invalid - + - installed version: %1, newest version: %2 - nainstalovaná verze: %1, nejnovjší verze: %2 + nainstalovaná verze: %1, nejnovjší verze: %2 - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + + installed version: "%1", newest version: "%2" + - + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + + + + Categories: <br> Kategorie: <br> - + Invalid name - + - + drag&drop failed: %1 - + - + Confirm Potvrdit - - Are you sure you want to remove "%1"? - Určitě chcete odstranit "%1"? + + Are you sure you want to remove "%1"? + Určitě chcete odstranit "%1"? - + Flags - + - + Mod Name - + - + Version Verze - + Priority Priorita - + Category - + - + Nexus ID - + - + Installation - + - - + + unknown - + - + Name of your mods - + - + Version of the mod (if available) Verze modu (pokud je k dispozici) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - Priorita aplikace modu. Čím větší, tím "důležitější" je mod a proto může přebít mody s nižší prioritou. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + Priorita aplikace modu. Čím větší, tím "důležitější" je mod a proto může přebít mody s nižší prioritou. - + Category of the mod. - + - + Id of the mod as used on Nexus. - + - + Emblemes to highlight things that might require attention. - + - + Time this mod was installed - + @@ -3204,17 +3669,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus - + - + timeout Překročen časový limit - + + Unknown error + + + + Please check your password Oveřte heslo @@ -3222,17 +3692,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - + + Failed to guess mod id for "%1", please pick the correct one + - + empty response prázdná odozva - + invalid response neplatná odozva @@ -3247,7 +3717,7 @@ p, li { white-space: pre-wrap; } You can use drag&drop to move files and directories to regular mods. - + @@ -3271,8 +3741,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - odstránění zlyhalo "%1" + Failed to delete "%1" + odstránění zlyhalo "%1" @@ -3282,8 +3752,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - Jsi si jistý, že chceš vymazat "%1"? + Are sure you want to delete "%1"? + Jsi si jistý, že chceš vymazat "%1"? @@ -3298,114 +3768,144 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - Zlyhalo vytvoření "%1" + Failed to create "%1" + Zlyhalo vytvoření "%1" PluginList - + Name Jméno - + Priority Priorita - + Mod Index - + - - + + Flags + + + + + unknown - + - + Name of your mods - + - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 - + - - + + Confirm - + Potvrdit - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + + + + + <b>Origin</b>: %1 + + + + + Author + Autor + + + + Description + Popis - - failed to open output file: %1 - zlyhalo otevření výstupního souboru: %1 + zlyhalo otevření výstupního souboru: %1 - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Některé vaše pluginy mají neplatné názvy! Tyhle pluginy nemůžou být načteny hrou. Prosím nahlédněte do souboru mo_interface.log pro kompletní seznam pluginů a přejmenujte je. - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) Tenhle plugin nemůže být deaktivován (vyžaduje to hra) - Origin: %1 - Původní mod: %1 + Původní mod: %1 - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - + + + + + PreviewDialog + + + Preview + + + + + Close + @@ -3413,115 +3913,110 @@ p, li { white-space: pre-wrap; } Problems - + Problémy - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + Close - + Fix - + No guided fix - + Profile - + invalid profile name %1 - + - + failed to create %1 - - - - - failed to open temporary file - - - - - failed to open "%1" for writing - + Nepodařilo se vytvořit %1 - + failed to write mod list: %1 - + - + failed to update tweaked ini file, wrong settings may be used: %1 - + - + failed to create tweaked ini: %1 - + + + + + "%1" is missing or inaccessible + - - - - - + + + + + invalid index %1 Neplatný index %1 - - Overwrite directory couldn't be parsed - + + Overwrite directory couldn't be parsed + - + invalid priority %1 neplatná priorita %1 - + failed to parse ini file (%1) - + - + failed to parse ini file (%1): %2 zlyhalo rozebrání ini souboru (%1): %2 - - - failed to modify "%1" - + + + failed to modify "%1" + - + Delete savegames? - + - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + @@ -3543,8 +4038,8 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - Pokud je zaškrtnuto, nový profil použije standartní nastavení hry namísto "globálního" nastavení. Globální nastavení jsou nastavení, které ukladá Spoušteč hry, ne Mod Organizer. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + Pokud je zaškrtnuto, nový profil použije standartní nastavení hry namísto "globálního" nastavení. Globální nastavení jsou nastavení, které ukladá Spoušteč hry, ne Mod Organizer. @@ -3566,31 +4061,31 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Tohle je seznam profilů. Každý profil obsahuje svůj seznam a poradí modů (ze společného celku modů), konfiguraci aktivovaných esps/esms, kopii vlastních .ini souborů hry a volitelný filtr uložených pozic.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> Z technických důvodů současně není možné mít oddělenou konfiguraci poradí esp/esm. To znamená, že nemůžete načíst modA.esp před modB.esp v jedném profilu a v druhém to mít naopak.</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Tohle je seznam profilů. Každý profil obsahuje svůj seznam a poradí modů (ze společného celku modů), konfiguraci aktivovaných esps/esms, kopii vlastních .ini souborů hry a volitelný filtr uložených pozic.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> Z technických důvodů současně není možné mít oddělenou konfiguraci poradí esp/esm. To znamená, že nemůžete načíst modA.esp před modB.esp v jedném profilu a v druhém to mít naopak.</p></body></html> If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + @@ -3599,22 +4094,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Hry Oblivion, Fallout 3 a Fallout NV mají chybu, která zabraňuje nahrazeným texturám a modelům (které již jsou ve hře) aby pracovali správně.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer používá řešení &quot;redirekce BSA&quot; (google is your friend) na odstranění této chyby. Prostě to aktivujte a nechte tak.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">U Skyrimu je tahle chyba částečně odstraněna, ale jestli bude mod aktivován pořád zavisí od jeho data. Proto pořád má význam mít opravu aktivovánu.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Hry Oblivion, Fallout 3 a Fallout NV mají chybu, která zabraňuje nahrazeným texturám a modelům (které již jsou ve hře) aby pracovali správně.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer používá řešení &quot;redirekce BSA&quot; (google is your friend) na odstranění této chyby. Prostě to aktivujte a nechte tak.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">U Skyrimu je tahle chyba částečně odstraněna, ale jestli bude mod aktivován pořád zavisí od jeho data. Proto pořád má význam mít opravu aktivovánu.</span></p></body></html> @@ -3661,18 +4156,18 @@ p, li { white-space: pre-wrap; } Rename - + Transfer save games to the selected profile. - + Transfer Saves - + @@ -3681,7 +4176,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. Invalidace archívu není potřebná pro tuhle hru. @@ -3708,12 +4203,12 @@ p, li { white-space: pre-wrap; } Invalid name - + Invalid profile name - + @@ -3723,27 +4218,27 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Rename Profile - + New Name - + @@ -3778,48 +4273,48 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - + invalid field name "%1" + - invalid type for "%1" (should be integer) - + invalid type for "%1" (should be integer) + - invalid type for "%1" (should be string) - + invalid type for "%1" (should be string) + - invalid type for "%1" (should be float) - + invalid type for "%1" (should be float) + no fields set up yet! - + - field not set "%1" - + field not set "%1" + - invalid character in field "%1" - + invalid character in field "%1" + empty field name - + invalid game type %1 - + @@ -3903,99 +4398,107 @@ p, li { white-space: pre-wrap; } Zlyhalo nastavení proxy-dll načítání - + Permissions required Chybí oprávnění - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + - - + + Woops Hups - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 ModOrganizer havaroval! Naneštěstí, nezdařilo se ani vytvořit diagnostický soubor: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Jedna instance Mod Organizeru už běží - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - Žádná hra nebyla nalezena v "%1". Je potřebné, aby adresář obsahoval binár hry a spouštěč. + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + Žádná hra nebyla nalezena v "%1". Je potřebné, aby adresář obsahoval binár hry a spouštěč. - - + + Please select the game to manage Prosím vyberte hru, kterou chcete spravovat - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + - - Please use "Help" from the toolbar to get usage instructions to all elements - Prosím použijte "Pomoc" z panelu nástrojú pro instrukce ke všem elementům + + failed to start application: %1 + - - + + Please use "Help" from the toolbar to get usage instructions to all elements + Prosím použijte "Pomoc" z panelu nástrojú pro instrukce ke všem elementům + + + + <Manage...> <Manage...> - + failed to parse profile %1: %2 Nezdařilo se rozebrat profil %1: %2 - - - failed to find "%1" - Nepodařilo sa najít "%1" + + failed to find "%1" + Nepodařilo sa najít "%1" - + failed to access %1 zlyhal přístup k %1 - + failed to set file time %1 nepodařilo se nastavit čas souboru %1 - + failed to create %1 Nepodařilo se vytvořit %1 - - "%1" is missing - "%1" chybí + + "%1" is missing or inaccessible + + + + "%1" is missing + "%1" chybí @@ -4021,54 +4524,59 @@ p, li { white-space: pre-wrap; } nepodařilo se otevřít %1 - + Script Extender Skript Extender - + Proxy DLL Proxy DLL - - failed to spawn "%1" - nepodařilo se vytvořit "%1" + + failed to spawn "%1" + nepodařilo se vytvořit "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - - failed to spawn "%1": %2 - nepodařilo se vytvořit "%1": %2 + + failed to spawn "%1": %2 + nepodařilo se vytvořit "%1": %2 - - "%1" doesn't exist - "%1" neexistuje + + "%1" doesn't exist + "%1" neexistuje - - failed to inject dll into "%1": %2 - nepodařilo se vsunout dll do "%1": %2 + + failed to inject dll into "%1": %2 + nepodařilo se vsunout dll do "%1": %2 - - failed to run "%1" - nepodařilo se spustit "%1" + + failed to run "%1" + nepodařilo se spustit "%1" + + + + failed to open temporary file + @@ -4076,22 +4584,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Mod Exists - + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Keep Backup - + Merge - + @@ -4101,7 +4609,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Rename - + @@ -4114,27 +4622,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - + Location - + Date - + @@ -4142,7 +4650,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Missing ESPs - + @@ -4150,37 +4658,37 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - + Výzva Copy To Clipboard - + Save As... - + Close - + Save CSV - + Text Files - + - failed to open "%1" for writing - + failed to open "%1" for writing + @@ -4205,98 +4713,98 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe SelfUpdater - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + - - - - + + + + Update Aktualizace - + An update is available (newest version: %1), do you want to install it? Je k dispozici Aktualizace (nejnovší verze: %1), chcete nainstalovat? - + Download in progress Stahování probíhá - + Download failed: %1 Stahování zlyhalo: %1 - + Failed to install update: %1 Zlyhala instalace aktualizace: %1 - - failed to open archive "%1": %2 - nepodařilo se otevřít archív "%1": %2 + + failed to open archive "%1": %2 + nepodařilo se otevřít archív "%1": %2 - + failed to move outdated files: %1. Please update manually. - + - + Update installed, Mod Organizer will now be restarted. Aktualizace nainstalována, Mod Organizer se teď restartuje. - + Error Chyba - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. Zlyhala odozva. Prosím nahlaste tuto chybu autorovi a přiložte soubor mo_interface.log. - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) Inkrementální aktualizace není k dispozici, je potřebné stáhnout celý nový balík (%1 kB) - + no file for update found. Please update manually. - + - + Failed to retrieve update information: %1 Nepodařilo se získat informace o aktualizaci: %1 - + No download server available. Please try again later. - + Settings - - - attempt to store setting for unknown plugin "%1" - + + + attempt to store setting for unknown plugin "%1" + - + Confirm Potvrdit - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Zmena adresáře modu změní všechny tvoje profily! Nenalezené mody (nebo přejmenované) v nové lokaci budou deaktivovány ve všech profilech. Není možnosť návratu pokud si nezazálohujete profily ručně. Pokračovat? @@ -4325,57 +4833,57 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Zobrazovací jazyk. Zobrazí se jenom jazyky, které máte nainstalované.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Zobrazovací jazyk. Zobrazí se jenom jazyky, které máte nainstalované.</span></p></body></html> Style - + graphical style - + graphical style of the MO user interface - + Log Level - + - Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log" + - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Debug - + Info - + @@ -4405,7 +4913,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). Adresář ve kterém jsou nainstalovány mody. Prosím berte na vědomí, že změna tohto zruší všechny asociace v starších profilech, pokud mody nebudou předem uloženy v nové lokaci (a se stejným jménem). @@ -4418,308 +4926,356 @@ p, li { white-space: pre-wrap; } Cache Directory Cache + + + User interface + + - Reset stored information from dialogs. - + If checked, the download interface will be more compact. + - This will make all dialogs show up again where you checked the "Remember selection"-box. - + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + + Reset stored information from dialogs. + + + + + This will make all dialogs show up again where you checked the "Remember selection"-box. + - + Reset Dialogs Obnovit dialogy - - + + Modify the categories available to arrange your mods. Úprava kategorií pro seřazování modů. - + Configure Mod Categories Konfigurovat Kategorie Modů - - + + Nexus Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. Povolí automatické přihlasováni na stránky Nexusu pokud je označeno. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Povolí automatické přihlasováni na stránky Nexusu pokud je označeno. Prosím berte na vědomí ,že maskování hesla v souboru modorganizer.ini není příliš silné. Pokud máte obavy, že by vám někdo mohl ukrást heslo, neukládajte ho.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Povolí automatické přihlasováni na stránky Nexusu pokud je označeno. Prosím berte na vědomí ,že maskování hesla v souboru modorganizer.ini není příliš silné. Pokud máte obavy, že by vám někdo mohl ukrást heslo, neukládajte ho.</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + - + Automatically Log-In to Nexus Automaticky přihlásit do Nexusu - + Username Přihlasovací jméno - + Password Heslo - + Disable automatic internet features - + - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + - + Offline Mode - + - + Use a proxy for network connections. - + - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + - + Use HTTP Proxy (Uses System Settings) - + - - Known Servers (Dynamically updated every download) - + + Associate with "Download with manager" links + - + + Known Servers (updated on download) + + + + Preferred Servers (Drag & Drop) - + - + Plugins - + - + Author: - + - + Version: - + - + Description: - + - + Key - + - + Value - + - + Blacklisted Plugins (use <del> to remove): - + - + Workarounds Řešení - + Steam App ID Steam App ID - + The Steam AppID for your game Steam AppID pro vaši hru - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Správné Steam App ID je potřebné pro spuštění některých her. Pro Skyrim, pokud není nastaveno správně, &quot;Mod Organizer&quot; svým mechanizmem nemusí hru úspěšně spustit.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Přednastavené je App ID &quot;regulérní&quot; verze, takže ve většine případů by jste měli být v pohode.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud si myslíte, že máte jinou verzi (GotY nebo něco), následujte tyto instrukce jak získat id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Prejděte do knižnice her na steamu</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. klikněte pravým na hru, které id chcete a vyberte </span><span style=" font-size:8pt; font-weight:600;">Vytvořit odkaz na ploše</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. klikněte pravým na novou ikonu na ploše a vyberte </span><span style=" font-size:8pt; font-weight:600;">Vlastnosti</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. v adřese URL by ste měli vidět něco takovéto: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 je id, které hledáte.</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Správné Steam App ID je potřebné pro spuštění některých her. Pro Skyrim, pokud není nastaveno správně, &quot;Mod Organizer&quot; svým mechanizmem nemusí hru úspěšně spustit.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Přednastavené je App ID &quot;regulérní&quot; verze, takže ve většine případů by jste měli být v pohode.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud si myslíte, že máte jinou verzi (GotY nebo něco), následujte tyto instrukce jak získat id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Prejděte do knižnice her na steamu</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. klikněte pravým na hru, které id chcete a vyberte </span><span style=" font-size:8pt; font-weight:600;">Vytvořit odkaz na ploše</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. klikněte pravým na novou ikonu na ploše a vyberte </span><span style=" font-size:8pt; font-weight:600;">Vlastnosti</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. v adřese URL by ste měli vidět něco takovéto: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 je id, které hledáte.</span></p></body></html> + + + Load Mechanism Mechanizmus spuštění - + Select loading mechanism. See help for details. Vyberte mechanizmus použit pro spuštění. Pro víc detailů čti Nápovědu. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + - + NMM Version - + - + The Version of Nexus Mod Manager to impersonate. - + - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. + - + Enforces that inactive ESPs and ESMs are never loaded. Zabezpečí, aby se neaktivní ESP a ESM vůbec nezobrazovali. - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Zdá se, že hry občasně načtou ESP nebo ESM soubory i když nebyli označeny ako aktivní pluginy. Nevím za jakých podmínek se to stává, ale uživatelé říkaj, že v některých případech je to neželané. Pokud je tohle označeno, ESP a ESM soubory které v seznamu nejsou označeny, nemůžou být za žádných okolností načtené ve hře. - + Hide inactive ESPs/ESMs Skrýt neaktivní ESP/ESM - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + - + Force-enable game files - + - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! Pro Skyrim, tohle je možné použít místo Invalidace Archívu. Pro všechny profily bude IA nepotřebná. Pro ostatné hry tohle není dostatečná náhrada Invalidace Archívu! - + Back-date BSAs Uprav dátumy BSA - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + - + Select download directory Vyber adresář pro stahování - + Select mod directory Vyber adresář pro mody - + Select cache directory Vyber adresář pro cache - + Confirm? Potvrdit? - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - Znovu se budou zobrazovat všechny výzvy, u kterých jste označili "Zapamatovat tuto odpověd provždy". Pokračovat? + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + Znovu se budou zobrazovat všechny výzvy, u kterých jste označili "Zapamatovat tuto odpověd provždy". Pokračovat? @@ -4765,10 +5321,14 @@ Pro ostatné hry tohle není dostatečná náhrada Invalidace Archívu! - failed to connect to running instance: %1 zlyhalo připojení k bežící instanci: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4794,8 +5354,8 @@ Pro ostatné hry tohle není dostatečná náhrada Invalidace Archívu! - <don't sync> - <don't sync> + <don't sync> + <don't sync> @@ -4813,17 +5373,17 @@ Pro ostatné hry tohle není dostatečná náhrada Invalidace Archívu! Transfer Savegames - + Global Characters - + This is a list of characters in the global location. - + @@ -4835,7 +5395,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4848,27 +5408,27 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Copy -> - + <- Move - + <- Copy - + @@ -4878,7 +5438,7 @@ On Windows XP: Profile Characters - + @@ -4887,8 +5447,8 @@ On Windows XP: - Overwrite the file "%1" - + Overwrite the file "%1" + @@ -4901,18 +5461,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - + Copy all save games of character "%1" to the profile? + - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + diff --git a/src/organizer_de.ts b/src/organizer_de.ts index adbe5580..d77f59b2 100644 --- a/src/organizer_de.ts +++ b/src/organizer_de.ts @@ -1,3 +1,4 @@ + @@ -58,22 +59,22 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Dies ist eine Liste aller ESPs und ESMs die aktiv waren als der Spielstand angelegt wurde.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Für jedes ESP enthält die rechte Spalte eine Auswahl der Mods (meistens nur eine) die das fehlende esp/esm enthalten.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn du Ok clickst werden alle mods in der rechten Spalte aktiviert und alle fehlenden esps werden aktiviert.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Dies ist eine Liste aller ESPs und ESMs die aktiv waren als der Spielstand angelegt wurde.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Für jedes ESP enthält die rechte Spalte eine Auswahl der Mods (meistens nur eine) die das fehlende esp/esm enthalten.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn du Ok clickst werden alle mods in der rechten Spalte aktiviert und alle fehlenden esps werden aktiviert.</span></p></body></html> @@ -116,9 +117,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. Komponenten dieses Pakets. -Wenn eine Komponente namens "00 Core" existiert ist diese üblicherweise zwingend erforderlich. Optionen sind nach der Priorität sortiert die der Author vorgesehen hat. +Wenn eine Komponente namens "00 Core" existiert ist diese üblicherweise zwingend erforderlich. Optionen sind nach der Priorität sortiert die der Author vorgesehen hat. @@ -153,6 +154,29 @@ Wenn eine Komponente namens "00 Core" existiert ist diese üblicherweise zwingen Abbrechen + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -198,20 +222,20 @@ Wenn eine Komponente namens "00 Core" existiert ist diese üblicherweise zwingen - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Liste von Kategorie IDs von Nexus, die dieser Kategorie zugewiesen werden sollen. Mehrere IDs werden durch Kommas getrennt.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Um die bei Nexus verwendete ID einer Kategorie herauszufinden besuche die Kategorien Seite von Nexus und fahre mit der Maus über die Links dort.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Liste von Kategorie IDs von Nexus, die dieser Kategorie zugewiesen werden sollen. Mehrere IDs werden durch Kommas getrennt.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Um die bei Nexus verwendete ID einer Kategorie herauszufinden besuche die Kategorien Seite von Nexus und fahre mit der Maus über die Links dort.</span></p></body></html> @@ -243,7 +267,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus Diese Funktion funktioniert unter Umständen nicht wenn Sie nicht eingeloggt sind @@ -270,7 +294,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 konnte bsa nicht lesen: %1 @@ -294,8 +318,8 @@ p, li { white-space: pre-wrap; } - Information missing, please select "Query Info" from the context menu to re-retrieve. - Informationen unvollständig, bitte clicke im Kontextmenü "Info abfragen" um diese erneut abzurufen. + Information missing, please select "Query Info" from the context menu to re-retrieve. + Informationen unvollständig, bitte clicke im Kontextmenü "Info abfragen" um diese erneut abzurufen. @@ -313,26 +337,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Fertig - Doppelklick zum Installieren - - + + Paused - Double Click to resume Pausiert - Doppelklick zum fortsetzen - - + + Installed - Double Click to re-install Installiert - Doppelclick um erneut zu installieren - - + + Uninstalled - Double Click to re-install Deinstalliert - Doppelklick um erneut zu installieren @@ -354,135 +378,135 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate - + < mod %1 file %2 > - + - + Pending - + - + Paused Pausiert - + Fetching Info 1 Informationen abrufen 1 - + Fetching Info 2 Informationen abrufen 2 - + Installed Installiert - + Uninstalled Deinstalliert - + Done Fertig - - - - + + + + Are you sure? Sind sie sicher? - + This will remove all finished downloads from this list and from disk. Entfernt alle abgeschlossenen Downloads aus der Liste und von der Festplatte. - + This will remove all installed downloads from this list and from disk. Entfernt alle installierten Downloads aus der Liste und von der Festplatte. - + This will permanently remove all finished downloads from this list (but NOT from disk). Dies entfernt alle fertigen Downloads von dieser Liste (aber NICHT von der Festplatte). - + This will permanently remove all installed downloads from this list (but NOT from disk). Dies entfernt alle bereits installierten Downloads aus der Liste (aber NICHT von der Festplatte). - + Install Installieren - + Query Info Info abfragen - + Delete Löschen - + Un-Hide Sichtbar machen - + Remove from View Von Liste entfernen - + Cancel Abbrechen - + Pause Pausieren - + Remove Entfernen - + Resume Fortsetzen - + Delete Installed... Installierte löschen... - + Delete All... Alle Löschen... - + Remove Installed... Installierte entfernen... - + Remove All... Alle löschen... @@ -490,115 +514,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + < mod %1 file %2 > - + - + Pending - + - + Fetching Info 1 Informationen abrufen 1 - + Fetching Info 2 Informationen abrufen 2 - - - - + + + + Are you sure? Sind sie sicher? - + This will remove all finished downloads from this list and from disk. Entfernt alle abgeschlossenen Downloads aus der Liste und von der Festplatte. - + This will remove all installed downloads from this list and from disk. Entfernt alle installierten Downloads aus der Liste und von der Festplatte. - + This will remove all finished downloads from this list (but NOT from disk). Dies entfernt alle abgeschlossenen Downloads von der Liste (aber NICHT von der Festplatte). - + This will remove all installed downloads from this list (but NOT from disk). Dies wird alle installierten Downloads von der Liste entfernen (aber NICHT von der Festplatte). - + Install Installieren - + Query Info Info abfragen - + Delete Löschen - + Un-Hide Sichtbar machen - + Remove from View Von Liste entfernen - + Cancel Abbrechen - + Pause Pausieren - + Remove Entfernen - + Resume Fortsetzen - + Delete Installed... Installierte löschen... - + Delete All... Alle löschen... - + Remove Installed... Installierte entfernen... - + Remove All... Alle löschen... @@ -606,122 +630,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - konnte "%1" nicht in "%2" umbenennen + + failed to rename "%1" to "%2" + konnte "%1" nicht in "%2" umbenennen + + + + Memory allocation error (in refreshing directory). + - + Download again? Erneut herunterladen? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Eine Datei mit dem gleichen Namen wurde bereits heruntergeladen. Willst du den Download wiederholen? Die neue Datei erhält einen anderen Namen. - + failed to download %1: could not open output file: %2 Download von %1 fehlgeschlagen: Konnte Ausgabedatei %2 nicht öffnen - + Wrong Game Falsches Spiel - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - Dieser Download ist für eine Mod für "%1" aber diese MO Installation wurde für "%2" konfiguriert. + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + Dieser Download ist für eine Mod für "%1" aber diese MO Installation wurde für "%2" konfiguriert. - - - - - - - - - - - + + + + + - - + + + + + + + + + invalid index ungültiger Index - + failed to delete %1 konnte %1 nicht löschen - + failed to delete meta file for %1 konnte meta-informationen für %1 nicht löschen - - - - - + + + + + invalid index %1 ungültiger index %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id Bitte gib die Nexus Mod ID ein - + Mod ID: Mod ID: - + + Main + Primär + + + + Update + Aktualisierung + + + + Optional + Optional + + + + Old + Alt + + + + Misc + Sonstiges + + + + Unknown + Unbekannt + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated Informationen aktualisiert - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? Keine passende Datei auf Nexus gefunden! Ist die Datei vielleicht nicht mehr verfügbar? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. Keine Datei mit diesem Namen auf Nexus gefunden. Bitte wähle die passende händisch aus. - + No download server available. Please try again later. Kein download server verfügbar. Bitte versuche es später noch einmal. - + Failed to request file info from nexus: %1 Konnte Datei-Informationen nicht von Nexus abrufen: %1 - + Download failed. Server reported: %1 - + - + Download failed: %1 (%2) Download fehlgeschlagen: %1 (%2) - + failed to re-open %1 Öffnen von %1 fehlgeschlagen @@ -902,8 +977,8 @@ Momentan ist der einzige bekannte Fall in dem dies benötigt wird der Skyrim Cre - Really remove "%1" from executables? - Die ausführbare Datei "%1" löschen? + Really remove "%1" from executables? + Die ausführbare Datei "%1" löschen? @@ -920,7 +995,7 @@ Momentan ist der einzige bekannte Fall in dem dies benötigt wird der Skyrim Cre You made changes to the current executable, do you want to save them? - + @@ -994,8 +1069,8 @@ Momentan ist der einzige bekannte Fall in dem dies benötigt wird der Skyrim Cre - <a href="#">Link</a> - <a href="#">Link</a> + <a href="#">Link</a> + <a href="#">Link</a> @@ -1042,7 +1117,7 @@ Momentan ist der einzige bekannte Fall in dem dies benötigt wird der Skyrim Cre - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. Wählen Sie einen Namen für den Mod. Der Name wird auch als Vrrzeichnisname verwendet, benutzen Sie also bitte keine Zichen oder Buchstaben, die für Verzeichnisse ungültig sind. @@ -1057,16 +1132,16 @@ Momentan ist der einzige bekannte Fall in dem dies benötigt wird der Skyrim Cre - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ansicht des Archivinhalts. &lt;data&gt; repräsentiert das Basisverzeichnis, welches dem Datenverzeichnis des Spiels entspricht. Sie können das Basisverzeichnis per Rechtsklicks Kontextmenü ändern und Dateien mit drag&amp;drop verschieben.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ansicht des Archivinhalts. &lt;data&gt; repräsentiert das Basisverzeichnis, welches dem Datenverzeichnis des Spiels entspricht. Sie können das Basisverzeichnis per Rechtsklicks Kontextmenü ändern und Dateien mit drag&amp;drop verschieben.</span></p></body></html> @@ -1088,8 +1163,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - archive.dll nicht geladen: "%1" + archive.dll not loaded: "%1" + archive.dll nicht geladen: "%1" @@ -1104,7 +1179,7 @@ p, li { white-space: pre-wrap; } - + Extracting files Extrahiere Dateien @@ -1134,57 +1209,57 @@ p, li { white-space: pre-wrap; } Der Name den Sie eingegeben haben ist ungültig, bitte geben sie einen neuen ein. - - File format "%1" not supported - Dateiformat "%1" wird nicht unterstützt + + File format "%1" not supported + Dateiformat "%1" wird nicht unterstützt - + None of the available installer plugins were able to handle that archive Keinem der vorhandenen Installations Plugins ist es möglich dieses Archiv zu öffnen. - + no error Kein Fehler - + 7z.dll not found 7z.dll nicht gefunden - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll ist ungültig - + archive not found Archiv nicht gefunden - + failed to open archive Öffnen des Archivs fehlgeschlagen - + unsupported archive type Archivtyp wird nicht unterstützt - + internal library error Interner Fehler in der Bibliothek - + archive invalid Ungültiges Archiv - + unknown archive error Unbekannter Fehler im Archiv @@ -1198,8 +1273,8 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - Dieser Dialog sollte automatisch verschwinden sobald die Applikation / das Spielt fertig ist. Klicken Sie "entsperren" wenn das nicht der Fall ist. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + Dieser Dialog sollte automatisch verschwinden sobald die Applikation / das Spielt fertig ist. Klicken Sie "entsperren" wenn das nicht der Fall ist. @@ -1215,7 +1290,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 konnte Protokoll nicht nach %1 schreiben: %2 @@ -1223,12 +1298,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 ein Fehler ist aufgetreten: %1 - + an error occured ein Fehler ist aufgetreten @@ -1236,424 +1311,473 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories Kategorien - + Profile Profil - + Pick a module collection Wähle eine Modul-Kollektion - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erstellen Sie ein Profil. Jedes Profil enthält eine eigene Liste mit aktiven Mods und ESPs. Auf diesem Weg können Sie schnell zwischen verschiedenen Einstellungen für ein Spiel wechseln.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Beachten Sie, dass derzeit die ESP Ladefolge nicht getrennt für verschiedene Profile gespeichert wird.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erstellen Sie ein Profil. Jedes Profil enthält eine eigene Liste mit aktiven Mods und ESPs. Auf diesem Weg können Sie schnell zwischen verschiedenen Einstellungen für ein Spiel wechseln.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Beachten Sie, dass derzeit die ESP Ladefolge nicht getrennt für verschiedene Profile gespeichert wird.</span></p></body></html> - Refresh list - Liste aktualisieren + Liste aktualisieren - + Refresh list. This is usually not necessary unless you modified data outside the program. Liste aktualisieren. Dies ist normalerweise nicht notwendig, es sei denn Sie haben Daten außerhalb von MO verändert. - + + + Restore Backup... + + + + + + Create Backup + + + + List of available mods. Liste der verfügbaren mods. - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - Dies ist eine Liste der installierten mods. Benutze die checkboxen um mods zu aktivieren oder deaktivieren und drag & drop um die "Installations"-Reihenfolge zu verändern. + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Dies ist eine Liste der installierten mods. Benutze die checkboxen um mods zu aktivieren oder deaktivieren und drag & drop um die "Installations"-Reihenfolge zu verändern. - + Filter Filter - + No groups Keine Gruppen - + Nexus IDs Nexus IDs - - - + + + Namefilter Namensfilter - + Pick a program to run. Wähle das auszuführende Programm. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wählen Sie das zu startende Programm. Sobald Sie anfangen Mod Organiser zu benutzen, sollten Sie das Spiel und alle zusätzlichen Programme immer von hier starten oder über Verknüpfungen die von Mod Organiser erstellt wurden. Sonst werden alle Mods die durch Mod Organiser installiert wurden nicht sichtbar sein.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Sie können weitere Programme dieser Liste hinzufügen, aber ich kann nicht garantieren, dass von mir nicht getestete Anwendungen vollständig funktionieren..</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wählen Sie das zu startende Programm. Sobald Sie anfangen Mod Organiser zu benutzen, sollten Sie das Spiel und alle zusätzlichen Programme immer von hier starten oder über Verknüpfungen die von Mod Organiser erstellt wurden. Sonst werden alle Mods die durch Mod Organiser installiert wurden nicht sichtbar sein.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Sie können weitere Programme dieser Liste hinzufügen, aber ich kann nicht garantieren, dass von mir nicht getestete Anwendungen vollständig funktionieren..</span></p></body></html> - + Run program Ausführen - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Das ausgewählte Programm durch Mod Organiser ausführen.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Das ausgewählte Programm durch Mod Organiser ausführen.</span></p></body></html> - + Run Starten - + Create a shortcut in your start menu or on the desktop to the specified program Erzeugt eine Verknüpfung zum gewählten Programm im Startmenü oder auf dem Desktop. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erstellt einen Eintrag im Startmenü, der direkt das gewählte Programm durch Mod Organsier ausführt.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erstellt einen Eintrag im Startmenü, der direkt das gewählte Programm durch Mod Organsier ausführt.</span></p></body></html> - + Shortcut Verknüpfung - + Plugins Plugins - + List of available esp/esm files Liste der verfügbaren ESP / ESM Dateien - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Diese Liste enthält alle ESPs und ESMs die in den aktiven Mods zur Verfügung steht. Diese Dateien benötigen ihre eigene Ladefolge - verwenden Sie Drag&amp;Drop um die Reihenfolge zu ändern. Beachten Sie, dass Mod Organiser nur die Reihenfolge von Mods speichert, die als aktiv markiert sind.<br />Es gibt ein großartiges Programm names &quot;BOSS&quot; um die Dateien automatisch zu sortieren.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Diese Liste enthält alle ESPs und ESMs die in den aktiven Mods zur Verfügung steht. Diese Dateien benötigen ihre eigene Ladefolge - verwenden Sie Drag&amp;Drop um die Reihenfolge zu ändern. Beachten Sie, dass Mod Organiser nur die Reihenfolge von Mods speichert, die als aktiv markiert sind.<br />Es gibt ein großartiges Programm names &quot;BOSS&quot; um die Dateien automatisch zu sortieren.</span></p></body></html> - + Sort - + + + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + + Open list options... + - + Archives - + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. Liste der BS Archive. Archive die hier nicht markiert sind werden nicht von MO verwaltet und beachten daher nicht die gewählte Installationsreihenfolge. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - BSA-Dateien sind Archive (vergleichbar mit .zip-Dateien) und enthalten Meshes, Texturen, usw. die vom Spiel benötigt werden. Daher stehen diese Dateien in "Konkurrenz" mit den Einzeldateien anderer Mods darüber, welche geladen werden sollen. + BSA-Dateien sind Archive (vergleichbar mit .zip-Dateien) und enthalten Meshes, Texturen, usw. die vom Spiel benötigt werden. Daher stehen diese Dateien in "Konkurrenz" mit den Einzeldateien anderer Mods darüber, welche geladen werden sollen. Das Standardverhalten des Spiels ist, alle BSAs automatisch zu laden die den gleichen Namen haben wie ein aktives ESP (z.B. plugin.esp und plugin.bsa) und deren Dateien Vorrang vor Einzeldateien zu geben. Die Installationsreihenfolge die du mit MO konfiguriert hast wird für diese Dateien ignoriert! BSAs die du hier markierst werden hingegen anders geladen so dass die Installationsreihenfolge korrekt eingehalten wird. - - + + File Datei - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - <html><head/><body><p>Markierte Archive (<img src=":/MO/gui/warning_16"/>) werden von Skyrim trotzdem geladen aber der <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">ursprüngliche Überschreibungsmechanismus</span></a> wird greifen: Loose Dateien überschreiben dann den Inhalt von BSAs, unabhängig von der eingerichteten mod/plugin priority.</p></body></html> + <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> + <html><head/><body><p>Markierte Archive (<img src=":/MO/gui/warning_16"/>) werden von Skyrim trotzdem geladen aber der <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">ursprüngliche Überschreibungsmechanismus</span></a> wird greifen: Loose Dateien überschreiben dann den Inhalt von BSAs, unabhängig von der eingerichteten mod/plugin priority.</p></body></html> - + Data Data - + refresh data-directory overview Data-Verzeichnis übersicht neu laden - + Refresh the overview. This may take a moment. Lädt die Übersicht neu. Dies kann einen Augenblick dauern. - - - + + + Refresh Neu laden - + This is an overview of your data directory as visible to the game (and tools). - Dies ist eine Übersicht des "data"-verzeichnisses so wie es das Spiel (und Tools) zu sehen bekommt. + Dies ist eine Übersicht des "data"-verzeichnisses so wie es das Spiel (und Tools) zu sehen bekommt. - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. Obere Liste filtern damit nur Konflikte angezeigt werden. - + Show only conflicts Nur Konflikte anzeigen - + Saves Spielstände - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Dies ist eine Liste aller Spielstände für dieses Spiel. Bewegen Sie den Mauszeiger über einen Eintrag, um genaue Informationen über den Spielstand zu bekommen, inklusive einer Liste aller ESPs / ESMs die im Spielstand verwendet werden, die aber derzeit nicht aktiv sind.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn Sie im Kontextmenü auf &quot;Mods reparieren...&quot; klicken, wird Mod Organiser versuchen die entsprechenden Mods und ESPs zu aktivieren. Es werden dabei kein Eintrag deaktiviert!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Dies ist eine Liste aller Spielstände für dieses Spiel. Bewegen Sie den Mauszeiger über einen Eintrag, um genaue Informationen über den Spielstand zu bekommen, inklusive einer Liste aller ESPs / ESMs die im Spielstand verwendet werden, die aber derzeit nicht aktiv sind.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn Sie im Kontextmenü auf &quot;Mods reparieren...&quot; klicken, wird Mod Organiser versuchen die entsprechenden Mods und ESPs zu aktivieren. Es werden dabei kein Eintrag deaktiviert!</span></p></body></html> - + Downloads Downloads - + This is a list of mods you downloaded from Nexus. Double click one to install it. Dies ist eine Liste der Mods die von Nexus heruntergeladen wurden. Doppelklicken Sie um zu installieren. - Compact - Kompakt + Kompakt - + Show Hidden Verborgene anzeigen - + Tool Bar Werkzeugleiste - + Install Mod Mod installieren - + Install &Mod &Mod installieren - + Install a new mod from an archive Installiert eine Mod aus einem Archiv - + Ctrl+M Ctrl+M - + Profiles Profile - + &Profiles &Profile - + Configure Profiles Profile konfigurieren - + Ctrl+P Ctrl+P - + Executables Programme - + &Executables Programm&e - + Configure the executables that can be started through Mod Organizer Konfigurieren der Programme die von Mod Organiser gestartet werden können - + Ctrl+E Ctrl+E - - + + Tools Werkzeuge - + &Tools &Werkzeuge - + Ctrl+I Ctrl+I - + Settings Einstellungen - + &Settings Ein&stellungen - + Configure settings and workarounds Einstellungen und Workarounds verwalten - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Durchsuche die passende Seite des Nexus-Netzwerks nach weiteren Mods - + Ctrl+N Ctrl+N - - + + Update Aktualisierung - + Mod Organizer is up-to-date Mod Organizer ist auf dem neuesten Stand - - + + No Problems Keine Probleme - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1664,949 +1788,1094 @@ Right now this has very limited functionality Diese Funktion ist noch sehr eingeschränkt - - + + Help Hilfe - + Ctrl+H Ctrl+H - + Endorse MO Endorsement für MO abgeben - - + + Endorse Mod Organizer Endorsement für Mod Organizer abgeben - + + Copy Log to Clipboard + + + + + Ctrl+C + + + + Toolbar Werkzeugleiste - + Desktop Desktop - + Start Menu Startmenü - + Problems Probleme - + There are potential problems with your setup Es bestehen möglicherweise Probleme mit Ihrer Konfiguration - + Everything seems to be in order Alles in bester Ordnung - + Help on UI Hilfe zur Oberfläche - + Documentation Wiki Wiki Dokumentation - + Report Issue Fehler melden - + Tutorials Tutorials - + About - + Über - + About Qt - + - + failed to save load order: %1 Reihenfolge konnt nicht gespeichert werden: %1 - + Name Name - + Please enter a name for the new profile Bitte geben Sie einen Namen für das neue Profil an - + failed to create profile: %1 Erstellen des Profils fehlgeschlagen: %1 - + Show tutorial? Tutorial anzeigen? - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - Sie starten Mod Organizer zum ersten mal. Wollen Sie ein Tutorial über die grundlegenden Funktionen sehen? Wenn Sie "nein" wählen können Sie das Tutorial aus dem "Hilfe"-Menü starten. + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + Sie starten Mod Organizer zum ersten mal. Wollen Sie ein Tutorial über die grundlegenden Funktionen sehen? Wenn Sie "nein" wählen können Sie das Tutorial aus dem "Hilfe"-Menü starten. - + Downloads in progress Download in Bearbeitung - + There are still downloads in progress, do you really want to quit? Es gibt noch unfertige Downloads, wollen Sie wirklich das Programm beenden? - + failed to read savegame: %1 Spielstand konnte nicht gelesen werden: %1 - - Plugin "%1" failed: %2 - Plugin "%1" fehlgeschlagen: %2 + + Plugin "%1" failed: %2 + Plugin "%1" fehlgeschlagen: %2 - - Plugin "%1" failed - Plugin "%1" fehlgeschlagen + + Plugin "%1" failed + Plugin "%1" fehlgeschlagen - + failed to init plugin %1: %2 Konnte das Plugin %1 nicht initialisieren: %2 - + Plugin error Plugin fehler - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - Es scheint als ob das Plugin "%1" beim letzten Programmstart nicht geladen werden konnte und einen Absturz von MO verursacht hat. Wollen sie das Plugin deaktivieren? + Es scheint als ob das Plugin "%1" beim letzten Programmstart nicht geladen werden konnte und einen Absturz von MO verursacht hat. Wollen sie das Plugin deaktivieren? (Bitte beachten: Wenn dies das erste mal ist dass sie diese Meldung für dieses Plugin sehen macht es vielleicht Sinn ihm eine zweite Chance zu geben. Das Plugin selber hat vielleicht eine Möglichkeit solche Fehler zu korrigieren) - - Failed to start "%1" - Konnte "%1" nicht starten + + Failed to start "%1" + Konnte "%1" nicht starten - + Waiting Warte - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. Bitte drücken sie OK sobald sie bei Steam angemeldet sind. - + Start Steam? Steam starten? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam muss laufen um das Spiel korrekt zu starten. Soll MO versuchen Steam zu starten? - + Also in: <br> Auch in: <br> - + No conflict Keine Konflikte - + <Edit...> <Bearbeiten...> - + This bsa is enabled in the ini file so it may be required! Dieses Archiv ist in der ini Konfiguration gelistet, daher ist es möglicherweise erforderlich! - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - Dieses Archiv wird vom Spiel geladen werden da ein Plugin mit dem selben Namen aktiv ist aber die enthaltenen Dateien werden sich nicht an Ihre Installations-Reihenfolge halten! + Dieses Archiv wird vom Spiel geladen werden da ein Plugin mit dem selben Namen aktiv ist aber die enthaltenen Dateien werden sich nicht an Ihre Installations-Reihenfolge halten! - + Activating Network Proxy Netzwerk Proxy aktivieren - - + + Installation successful Installation erfolgreich - - + + Configure Mod Mod konfigurieren - - + + This mod contains ini tweaks. Do you want to configure them now? Diese Mod enthält Anpassungen für die Ini datei. Wollen Sie diese nun konfigurieren? - - - mod "%1" not found - mod "%1" nicht gefunden + + + mod "%1" not found + mod "%1" nicht gefunden - - + + Installation cancelled Installation abgebrochen - - + + The mod was not installed completely. Die mod wurde nicht erfolgreich installiert. - + Some plugins could not be loaded Einige Plugins konnten nicht geladen werden - + Too many esps and esms enabled Zu viele esps und esms aktiv - - + + Description missing Beschreibung fehlt - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Die folgenden Plugins konnten nicht geladen werden. Der Grund könnte eine fehlende Abhängigkeit sein (z.B. python) oder eine veraltete Version der Abhängigkeiten: - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - Das Spiel unterstützt nicht mehr als 255 aktive Plugins (inklusive der offiziellen). Sie müssen einige unbenutzte Plugins deaktivieren oder mehrere Plugins kombinieren. Hier finden sie eine Anleitung: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + Das Spiel unterstützt nicht mehr als 255 aktive Plugins (inklusive der offiziellen). Sie müssen einige unbenutzte Plugins deaktivieren oder mehrere Plugins kombinieren. Hier finden sie eine Anleitung: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Mod wählen - + Mod Archive Mod Archiv - + Start Tutorial? Tutorial starten? - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Du bist dabei ein Tutorial zu starten. Aus technischen Gründen ist es nicht möglich das Tutorial abzubrechen. Fortsetzen? - - + + Download started Download gestartet - + failed to update mod list: %1 Aktualisieren der Modliste fehlgeschlagen: %1 - + failed to spawn notepad.exe: %1 notepad.exe konnte nicht aufgerufen werden: %1 - + failed to open %1 %1 konnte nicht geöffnet werden - + failed to change origin name: %1 konnte den Namen der Dateiquelle nicht ändern: %1 - - Executable "%1" not found - + + Executable "%1" not found + - + Failed to refresh list of esps: %1 Konnte die Plugin Liste nicht aktualisieren: %s - - failed to move "%1" from mod "%2" to "%3": %4 - + + failed to move "%1" from mod "%2" to "%3": %4 + - + <Checked> <Markierte> - + <Unchecked> <Nicht markierte> - + <Update> <Update> - + <No category> <Ohne Kategorie> - + <Conflicted> <Überschneidungen> - + <Not Endorsed> <Nicht Endorsed> - + failed to rename mod: %1 konnte die Mod nicht umbenennen: %1 - + Overwrite? Überschreiben? - - This will replace the existing mod "%1". Continue? - Dies wird die existierende Mod "%1" ersetzen. Fortsetzen? + + This will replace the existing mod "%1". Continue? + Dies wird die existierende Mod "%1" ersetzen. Fortsetzen? - - failed to remove mod "%1" - konnte die mod "%1" nicht löschen + + failed to remove mod "%1" + konnte die mod "%1" nicht löschen - - - - failed to rename "%1" to "%2" - konnte "%1" nicht in "%2" umbenennen + + + + failed to rename "%1" to "%2" + konnte "%1" nicht in "%2" umbenennen - - Multiple esps activated, please check that they don't conflict. + + Multiple esps activated, please check that they don't conflict. Mehrere esps aktiv, bitte überprüfen sie, dass diese nicht miteinander kollidieren. - - - - + + + + Confirm Bestätigen - + Remove the following mods?<br><ul>%1</ul> Die folgenden Mods entfernen?<br><ul>%1</ul> - + failed to remove mod: %1 konnte die mod nicht entfernen: %1 - - + + Failed Fehlgeschlagen - + Installation file no longer exists Installationsdatei existiert nicht mehr - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. Mods die mit alten Versionen von MO installiert wurden können nicht auf diese Weise neu-installiert werden. - - + + You need to be logged in with Nexus to endorse Sie müssen bei Nexus eingeloggt sein um Endorsements zu vergeben - - + Extract BSA BSA extrahieren - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - Diese mod enthält mindestens eine BSA datei. Soll sie entpackt werden? -(Das BSA wird danach gelöscht. Wenn Sie nicht wissen was BSAs sind wählen Sie am besten "nein") +(This removes the BSA after completion. If you don't know about BSAs, just select no) + Diese mod enthält mindestens eine BSA datei. Soll sie entpackt werden? +(Das BSA wird danach gelöscht. Wenn Sie nicht wissen was BSAs sind wählen Sie am besten "nein") - - - + + failed to read %1: %2 konnte %1 nicht lesen: %2 - - + This archive contains invalid hashes. Some files may be broken. Dieses Archiv enthält ungültige Prüfsummen. Einige Dateien sind evtl. defekt. - + Nexus ID for this Mod is unknown Nexus ID für diese Mod unbekannt - - + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... Mod erstellen... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - Es werden alle Dateien von "Overwrite" in eine neue, reguläre Mod verschoben. + Es werden alle Dateien von "Overwrite" in eine neue, reguläre Mod verschoben. Bitte wählen sie dafür einen Namen: - + A mod with this name already exists Eine Mod mit diesem Name existiert bereits - + Continue? Fortfahren? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. Das Versionsschema bestimmt, welche version neuer als eine andere identifiziert wird. Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die installierte Version veraltet ist. - - + + Sorry Entschuldigung - - I don't know a versioning scheme where %1 is newer than %2. + + I don't know a versioning scheme where %1 is newer than %2. Es ist kein Versionsschema bekannt bei welchem %1 neuer ist als %2. - + Really enable all visible mods? Alle angezeigten Mods aktivieren? - + Really disable all visible mods? Alle angezeigten Mods deaktivieren? - + Choose what to export Bitte wählen Sie was sie exportieren wollen - + Everything Alles - + All installed mods are included in the list Alle installierten Modifikationen sind in dieser Liste enthalten - + Active Mods Aktive Mods - + Only active (checked) mods from your current profile are included Ausschließlich aktive Mods aus ihrem aktuellen Profil sind enthalten - + Visible Sichtbar - + All mods visible in the mod list are included Alle sichtbaren Mods in der Mod Liste sind enthalten - + export failed: %1 Exportieren fehlgeschlagen: %1 - + Install Mod... Mod installieren... - + Enable all visible Alle sichtbaren aktvieren - + Disable all visible Alle sichtbaren deaktvieren - + Check all for update Alle auf Aktualisierungen überprüfen - + Export to csv... Als CSV exportieren... - + + All Mods + + + + Sync to Mods... Mods synchronisieren... - + Restore Backup Backup wiederherstellen - + Remove Backup... Backup entfernen... - + Add/Remove Categories Kategorien hinzufügen/entfernen - + Replace Categories Kategorien ersetzen - + Primary Category Primäre Kategorie - + Change versioning scheme Versionsschema ändern - + Un-ignore update Update nicht mehr ignorieren - + Ignore update Dieses Update ignorieren - + Rename Mod... Mod umbenennen... - + Remove Mod... Mod entfernen... - + Reinstall Mod Mod neu installieren - + Un-Endorse Endorsement zurückziehen - - + + Endorse Endorsement vergeben - - Won't endorse - Niemals "Endorsement" vergeben + + Won't endorse + Niemals "Endorsement" vergeben - + Endorsement state unknown - "Endorsement"-stand unbekannt + "Endorsement"-stand unbekannt - + Ignore missing data Fehlende Daten ignorieren - + Visit on Nexus Auf Nexus besuchen - + Open in explorer In Explorer öffnen - + Information... Informationen... - - + + Exception: Ausnahme: - - + + Unknown exception Unbekannte Ausnahme - + <All> <Alle> - + <Multiple> <Mehrere> - - Really delete "%1"? - + + Really delete "%1"? + - + Fix Mods... Mods reparieren... - + Delete Löschen - - + + failed to remove %1 %1 konnte nicht entfernt werden - - + + failed to create %1 %1 konnte nicht erstellt werden - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! Das download verzeichnis kann nicht geändert werden solange Downloads laufen! - + Download failed Download fehlgeschlagen - + failed to write to file %1 - Speichern in Datei "%1" fehlgeschlagen + Speichern in Datei "%1" fehlgeschlagen - + %1 written - "%1" gespeichert + "%1" gespeichert - + Select binary Binary wählen - + Binary Ausführbare Datei - + Enter Name Namen eingeben - + Please enter a name for the executable Bitte geben Sie einen Namen für die Anwendungsdatei ein - + Not an executable Datei ist nicht ausführbar - + This is not a recognized executable. Dies Datei wird nicht als ausführbare Datei erkannt. - - + + Replace file? Datei ersetzen? - + There already is a hidden version of this file. Replace it? Es existiert bereits eine versteckte Variante von dieser Datei. Soll diese ersetzt werden? - - + + File operation failed Dateioperation fehlgeschlagen - - - Failed to remove "%1". Maybe you lack the required file permissions? - Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? - + There already is a visible version of this file. Replace it? Es existiert bereits eine sichtbare Variante dieser Datei. Soll diese ersetzt werden? - + file not found: %1 esp nicht gefunden: %1 - + failed to generate preview for %1 - + - - Sorry, can't preview anything. This function currently does not support extracting from bsas. - + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + - + Update available Aktualisierung verfügbar - + Open/Execute Öffnen/Ausführen - + Add as Executable Als Anwendung hinzufügen - + Preview - + - + Un-Hide Sichtbar machen - + Hide Verstecken - + Write To File... In Datei speichern... - + Do you want to endorse Mod Organizer on %1 now? Willst du Mod Organizer auf %1 ein Endorsement geben? - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 Anfrage an Nexus fehlgeschlagen: %1 - - + + login successful Login erfolgreich - + login failed: %1. Trying to download anyway login fehlgeschlagen: %1. Der Download scheitert vermutlich - + login failed: %1 login fehlgeschlagen: %1 - + login failed: %1. You need to log-in with Nexus to update MO. login fehlgeschlagen: %1. Sie müssen bei Nexus eingeloggt sein um das update herunterzuladen. - + Error Fehler - + failed to extract %1 (errorcode %2) - konnte "%1" nicht extrahieren (fehlercode %2) + konnte "%1" nicht extrahieren (fehlercode %2) - + Extract... Extrahieren... - + Edit Categories... Kategorien ändern... - + + Deselect filter + + + + Remove Entfernen - + Enable all Alle aktivieren - + Disable all Alle deaktivieren - + Unlock load order Sperrung der Ladereihenfolge aufheben - + Lock load order Ladereihenfolge sperren - - BOSS working - + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + - failed to run boss: %1 - konnte bsa nicht lesen: %1 + konnte bsa nicht lesen: %1 @@ -2621,8 +2890,8 @@ Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die insta ModInfo - - + + invalid index %1 ungültiger index %1 @@ -2630,7 +2899,7 @@ Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die insta ModInfoBackup - + This is the backup of a mod Dies ist das Backup einer Mod @@ -2659,7 +2928,7 @@ Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die insta - + Save Speichern @@ -2669,53 +2938,73 @@ Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die insta INI Dateien - + + Ini Files + + + + This is a list of .ini files in the mod. Liste mit .ini Dateien im Mod. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Eine Liste mit Textdateien im Modverzeichnis. Die Dateien werden üblicherweise dazu verwendet um das Verhalten und Parameter der mods zu konfigurieren. - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Änderungen an der Datei speichern. - + Save changes to the file. This overwrites the original. There is no automatic backup! Änderungen der Datei speichern. Dies überschreibt das Original - es gibt keine automatische Sicherung! - + Images Bilder - + Images located in the mod. Bilder im Modverzeichnis. - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. Dies führt alle Bilder (.jpg und .png) wie Screenshots und ähnliches im Mod Verzeichnis auf. Anklicken für eine Großansicht. - - + + Optional ESPs Optionale ESPs - + List of esps and esms that can not be loaded by the game. Liste mit ESP und ESM Dateien die vom Spiel nicht geladen werden können. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -2726,436 +3015,444 @@ Dies sind üblicherweise Plugins mit optionaler Funktionalität. Für Details, l Die meisten Mods haben keine optionalen esps, es ist also gut möglich dass sie sich gerade eine leere Liste ansehen. - + Make the selected mod in the lower list unavailable. Den unten ausgewählten Mod als nicht verfügbar markieren. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. Die in der unteren Liste ausgewählte ESP wird in ein Unterverzeichnis des Mods verschoben und so für das Spiel unsichtbar. Sie kann nicht mehr im Spiel aktiviert werden. - + Move a file to the data directory. Datei in das Datenverzeichnis verschieben. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Verschiebt ein ESP in das ESP Verzeichnis damit es im Hauptfenster aktiviert werden kann. Bitte beachten Sie, dass das ESP nur "verfügbar" wird, es wird nicht zwangsläufig geladen! Das Laden der ESP wird im Hauptfenster von MO eingestellt. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + Verschiebt ein ESP in das ESP Verzeichnis damit es im Hauptfenster aktiviert werden kann. Bitte beachten Sie, dass das ESP nur "verfügbar" wird, es wird nicht zwangsläufig geladen! Das Laden der ESP wird im Hauptfenster von MO eingestellt. - + ESPs in the data directory and thus visible to the game. ESPs im Datenverzeichnis und für das Spiel sichtbar. - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. Dies Mod Dateien sind im (virtuellen) Datenverzeichnis des Spiels und somit in der ESP Liste des Hauptfenster auswählbar. - + Available ESPs Verfügbare ESPs - + Conflicts Konflikte - + The following conflicted files are provided by this mod Die folgenden konfliktbehafteten Dateien werden von dieser Mod bereitgestellt - - + + File Datei - + Overwritten Mods Überschriebene Mods - + The following conflicted files are provided by other mods Die folgenden konfliktbehafteten Dateien werden von anderen Mods bereitgestellt - + Providing Mod Bereitstellende Mod - + Non-Conflicted files Konfliktfreie Dateien - + Categories Kategorien - + Primary Category Primäre Kategorie - + Nexus Info Nexus Info - + Mod ID Mod ID - + Mod ID for this mod on Nexus. ID dieser mod auf Nexus. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ModID für den Mod auf Nexus. Um die korrekte Id zu finden, einfach den Mod auf Nexus aufrufen. Die URL sieht so aus:<a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" color:#000000;">. In diesem Beispiel ist 1334 die gesuchte Id. Zufällig ist das genau der Link zu Mod Organiser im Nexus. Warum nicht einfach mal anklicken und ein Endorsement hinzufügen?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ModID für den Mod auf Nexus. Um die korrekte Id zu finden, einfach den Mod auf Nexus aufrufen. Die URL sieht so aus:<a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" color:#000000;">. In diesem Beispiel ist 1334 die gesuchte Id. Zufällig ist das genau der Link zu Mod Organiser im Nexus. Warum nicht einfach mal anklicken und ein Endorsement hinzufügen?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installierte Version des Mods. Der Tooltip zeigt die auf Nexus verfügbare Version. Die installierte Versionsnummer kann nur angezeigt werden wenn der Mod durch MO installiert wurde.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installierte Version des Mods. Der Tooltip zeigt die auf Nexus verfügbare Version. Die installierte Versionsnummer kann nur angezeigt werden wenn der Mod durch MO installiert wurde.</span></p></body></html> - + Version Version - + Refresh Neu laden - + Refresh all information from Nexus. Alle Informationen von Nexus nachladen. - + Description Beschreibung - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse Endorsement vergeben - + Notes Anmerkungen - + Filetree Verzeichnisbaum - + A directory view of this mod Verzeichnisansicht des Mods - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die ist eine editierbare Ansicht des Modverzeichnisses. Sie können Dateien per Drag&amp;Drop verschieben und mit Doppelklick umbenennen.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Veränderungen werden sofort auf der Platte ausgeführt, also</span><span style=" font-size:8pt; font-weight:600;"> vorsichtig sein!</span><span style=" font-size:8pt;">.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die ist eine editierbare Ansicht des Modverzeichnisses. Sie können Dateien per Drag&amp;Drop verschieben und mit Doppelklick umbenennen.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Veränderungen werden sofort auf der Platte ausgeführt, also</span><span style=" font-size:8pt; font-weight:600;"> vorsichtig sein!</span><span style=" font-size:8pt;">.</span></p></body></html> - + Previous Vorherige - + Next Weiter - + Close Schliessen - + &Delete &Löschen - + &Rename &Umbenennen - + &Hide &Verstecken - + &Unhide &Wiederherstellen - + &Open &Öffnen - + &New Folder &Neuer Ordner - - + + Save changes? Änderungen speichern? - - - Save changes to "%1"? - Änderungen an "%1" speichern? + + + Save changes to "%1"? + Änderungen an "%1" speichern? - + File Exists Datei existiert bereits - + A file with that name exists, please enter a new one Eine Datei mit diesem Namen existiert bereits, bitte wählen Sie einen anderen Namen - + failed to move file Verschieben der Datei fehlgeschlagen - - failed to create directory "optional" - Erstellen des Verzeichnis "optional" fehlgeschlagen + + failed to create directory "optional" + Erstellen des Verzeichnis "optional" fehlgeschlagen - - + + Info requested, please wait Information wird abgerufen, bitte warten - + Main Primär - + Update Aktualisierung - + Optional Optional - + Old Alt - + Misc Sonstiges - + Unknown Unbekannt - + Current Version: %1 Aktuelle Version: %1 - + No update available Keine neue Version verfügbar - + (description incomplete, please visit nexus) (Beschreibung unvollständig. Bitte besuche die Nexus-Seite) - - <a href="%1">Visit on Nexus</a> - <a href="%1">Auf Nexus öffnen</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">Auf Nexus öffnen</a> - + Failed to delete %1 - "%1" konnte nicht gelöscht werden + "%1" konnte nicht gelöscht werden - - + + Confirm Bestätigen - - Are sure you want to delete "%1"? - Sind Sie sicher, dass Sie "%1" löschen wollen? + + Are sure you want to delete "%1"? + Sind Sie sicher, dass Sie "%1" löschen wollen? - + Are sure you want to delete the selected files? Sind Sie sicher, dass Sie die ausgewählten Dateien löschen wollen? - - + + New Folder Neuer Ordner - - Failed to create "%1" - "%1" konnte nicht erstellt werden + + Failed to create "%1" + "%1" konnte nicht erstellt werden - - + + Replace file? Datei ersetzen? - + There already is a hidden version of this file. Replace it? Es existiert bereits eine versteckte Variante von dieser Datei. Soll diese ersetzt werden? - - + + File operation failed Dateioperation fehlgeschlagen - - - Failed to remove "%1". Maybe you lack the required file permissions? - Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? - - + + failed to rename %1 to %2 - "%1" konnte nicht zu "%2" umbenannt werden + "%1" konnte nicht zu "%2" umbenannt werden - + There already is a visible version of this file. Replace it? Es existiert bereits eine sichtbare Variante dieser Datei. Soll diese ersetzt werden? - + Un-Hide Sichtbar machen - + Hide Verstecken - + Name Name - + Please enter a name Bitte geben Sie einen Namen ein. - - + + Error Fehler - + Invalid name. Must be a valid file name Ungültiger Name. Dies muss ein gültiger Dateiname sein - + A tweak by that name exists - Ein "Tweak" mit diesem Namen existiert bereits + Ein "Tweak" mit diesem Namen existiert bereits - + Create Tweak - "Tweak" anlegen + "Tweak" anlegen + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Diese Pseudo-Mod enthält Dateien des virtuellen Verzeichnisses die modifiziert wurden (z.B. durch den Construction Kit) @@ -3163,17 +3460,22 @@ p, li { white-space: pre-wrap; } ModInfoRegular - failed to write %1/meta.ini: %2 - konnte %1/meta.ini nicht schreiben: %2 + konnte %1/meta.ini nicht schreiben: %2 + + + + + failed to write %1/meta.ini: error %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 enthält keine esp / esm Dateien und keine Resourcen (Texturen, Netze, Oberfläche...) - + Categories: <br> Kategorien: <br> @@ -3221,119 +3523,124 @@ p, li { white-space: pre-wrap; } Redundant - + + Non-MO + + + + invalid ungültig - - installed version: "%1", newest version: "%2" + + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 installierte Version: %1, neueste Version: %2 - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". Die neueste Version auf Nexus scheint älter zu sein als die die sie installiert haben. Dies könnte bedeuten dass die Version die sie installiert haben entfernt wurde (z.B. weigen eines Bugs) oder der Autor verwendet ein nicht-standardisiertes Versionierungssystem und ihre Version ist in Wirklichkeit doch veraltet. In beiden Fällen ist es empfehlenswert die Version von Nexus zu installieren. - + Categories: <br> Kategorien: <br> - + Invalid name Ungültiger Name - + drag&drop failed: %1 Drag&Drop fehlgeschlagen: %1 - + Confirm Bestätigen - - Are you sure you want to remove "%1"? - Sind Sie sicher dass Sie "%1" löschen wollen? + + Are you sure you want to remove "%1"? + Sind Sie sicher dass Sie "%1" löschen wollen? - + Flags Markierungen - + Mod Name Mod Name - + Version Version - + Priority Priorität - + Category Kategorie - + Nexus ID Nexus ID - + Installation Installation - - + + unknown unbekannt - + Name of your mods Name Ihrer Mods - + Version of the mod (if available) Version des Mod (wenn verfügbar) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Installations-Priorität Ihres Mods. Je höher, desto wichtiger ist dieser Mod und überschreibt damit Dateien von Mods mit niedrigerer Priorität. - + Category of the mod. Kategorie der Mod. - + Id of the mod as used on Nexus. Id der Mod von Nexus. - + Emblemes to highlight things that might require attention. Symbole um Dinge hervorzuheben die evtl. Aufmerksamkeit erfordern. - + Time this mod was installed Zeitpunkt an dem die Mod installiert wurde @@ -3367,17 +3674,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus Logge auf Nexus ein - + timeout Zeitüberschreitung - + + Unknown error + + + + Please check your password Bitte das Passwort überprüfen @@ -3385,17 +3697,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - Konnte die mod id für "%1" nicht erraten. Bitte wähle die richtige aus + + Failed to guess mod id for "%1", please pick the correct one + Konnte die mod id für "%1" nicht erraten. Bitte wähle die richtige aus - + empty response leere Antwort - + invalid response ungültige Antwort @@ -3434,8 +3746,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - "%1" konnte nicht gelöscht werden + Failed to delete "%1" + "%1" konnte nicht gelöscht werden @@ -3445,8 +3757,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - Sind Sie sicher, dass Sie "%1" löschen wollen? + Are sure you want to delete "%1"? + Sind Sie sicher, dass Sie "%1" löschen wollen? @@ -3461,116 +3773,125 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - "%1" konnte nicht erstellt werden + Failed to create "%1" + "%1" konnte nicht erstellt werden PluginList - + Name Name - + Priority Priorität - + Mod Index Mod Index - + Flags Markierungen - - + + unknown unbekannt - + Name of your mods Namen Ihrer Mods - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - Lade-Reihenfolge des Plugins.. Je höher desto "wichtiger" ist es und überschreibt damit Daten von Plugins mit niedrigerer Priorität. + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + Lade-Reihenfolge des Plugins.. Je höher desto "wichtiger" ist es und überschreibt damit Daten von Plugins mit niedrigerer Priorität. - + The modindex determins the formids of objects originating from this mods. Der ModIndex bestimmt die FormIDs von Objekten die aus dieser Mod stammen. - + failed to update esp info for file %1 (source id: %2), error: %3 konnte die esp information für %1 (quell id: %2) nicht aktualisieren. Fehler: %3 - + esp not found: %1 esp nicht gefunden: %1 - - + + Confirm Bestätigen - + Really enable all plugins? Wirklich alle Plugins aktivieren? - + Really disable all plugins? Wirklich alle Plugins deaktivieren? - + The file containing locked plugin indices is broken Die Datei welche die Indizes gesperrter Plugins enthält ist kaputt - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Einige Ihrer Plugins haben ungültige Namen! Diese Plugins können nicht vom Spiel geladen werden. Die Datei mo_interface.log enthält eine Liste der betroffenen Plugins. Bitte benennen Sie diese um. - - BOSS dll incompatible - + + <b>Origin</b>: %1 + + + + + Author + Autor + + + + Description + Beschreibung - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) Dieses Plugin kann nicht deaktiviert werden (vom Spiel benötigt) - Origin: %1 - Ursprung: %1 + Ursprung: %1 - + Missing Masters Fehlende Master - + Enabled Masters Aktivierte Master - + failed to restore load order for %1 Konnte die Ladereihenfolge für %1 nicht wiederherstellen @@ -3580,7 +3901,7 @@ p, li { white-space: pre-wrap; } Preview - + @@ -3597,12 +3918,12 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + @@ -3634,64 +3955,69 @@ p, li { white-space: pre-wrap; } %1 konnte nicht erstellt werden - + failed to write mod list: %1 Aktualisieren der Modliste fehlgeschlagen: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - Kombination der "ini tweaks" gescheitert, es werden evtl. falsche Spieleinstellungen verwendet: %1 + Kombination der "ini tweaks" gescheitert, es werden evtl. falsche Spieleinstellungen verwendet: %1 - + failed to create tweaked ini: %1 konnte keine .ini-datei aus den Mod-spezifischen Einstellungen erzeugen: %1 - - - - - + + "%1" is missing or inaccessible + "%1" fehlt + + + + + + + invalid index %1 ungültiger index %1 - - Overwrite directory couldn't be parsed - Das Verzeichnis "Overwrite" konnte nicht gelesen werden + + Overwrite directory couldn't be parsed + Das Verzeichnis "Overwrite" konnte nicht gelesen werden - + invalid priority %1 Ungültige Priorität %1 - + failed to parse ini file (%1) Konnte ini-datei (%1) nicht auslesen - + failed to parse ini file (%1): %2 Konnte ini-datei (%1) nicht auslesen: %2 - - - failed to modify "%1" - Konnte "%1" nicht verändern + + + failed to modify "%1" + Konnte "%1" nicht verändern - + Delete savegames? Spielstände löschen? - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - Wollen Sie die lokalen Spielstände löschen? (Wenn Sie "Nein" wählen werden die Spielstände wieder sichtbar sobald Sie das Feature wieder aktivieren) + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + Wollen Sie die lokalen Spielstände löschen? (Wenn Sie "Nein" wählen werden die Spielstände wieder sichtbar sobald Sie das Feature wieder aktivieren) @@ -3713,8 +4039,8 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - Wenn dies aktiv ist wird das neue Profil die Standard-Einstellungen des Spiels statt der "globalen" verwenden. Globale Einstellungen sind die die verwendet werden wenn das Spiel ohne MO gestartet wird. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + Wenn dies aktiv ist wird das neue Profil die Standard-Einstellungen des Spiels statt der "globalen" verwenden. Globale Einstellungen sind die die verwendet werden wenn das Spiel ohne MO gestartet wird. @@ -3736,20 +4062,20 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Eine Liste der Profile. Jedes Profil enthält eine eigene Liste mit Mods und ihrer Installationsreihenfolge aus einem gemeinsamen Pool, eine Konfiguration von aktivierten ESPs und ESMs, eine Kopie der INI Datei des Spiels und einen optionalen Spielstands-Filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Bemerkung</span>Aus technischen Gründen ist es derzeit nicht möglich, eine getrennte Reihenfolge für ESPs anzugeben. Das bedeutet dass die Reihenfolge, in der Mods geladen werden in jedem Profil die gleiche ist.</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Eine Liste der Profile. Jedes Profil enthält eine eigene Liste mit Mods und ihrer Installationsreihenfolge aus einem gemeinsamen Pool, eine Konfiguration von aktivierten ESPs und ESMs, eine Kopie der INI Datei des Spiels und einen optionalen Spielstands-Filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Bemerkung</span>Aus technischen Gründen ist es derzeit nicht möglich, eine getrennte Reihenfolge für ESPs anzugeben. Das bedeutet dass die Reihenfolge, in der Mods geladen werden in jedem Profil die gleiche ist.</p></body></html> @@ -3769,22 +4095,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Spiele Oblivion, Fallout 3 und Fallout New Vegas enthalten einen Bug, der verhindert dass Texturen und Netze ersetzt werden können (d.h. alle Modifikationen von Netzen und Texturen die bereits im Spiel vorhanden sind).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organiser benutzt hierzu die sogenannte "BSA Redirection" (siehe Google) - auf diese Weise wird das Problem zuverlässig und ohne dass weitere Schritte nötig werden gelöst. Aktivieren Sie diese Option.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mit Skyrim wurde der Bug in den meisten Fällen behoben, aber die Aktvierung von Mods hängt immer noch vom Änderungsdatum der Datei ab. Daher ist es sinnvoll, diese Option zu aktiveren.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Spiele Oblivion, Fallout 3 und Fallout New Vegas enthalten einen Bug, der verhindert dass Texturen und Netze ersetzt werden können (d.h. alle Modifikationen von Netzen und Texturen die bereits im Spiel vorhanden sind).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organiser benutzt hierzu die sogenannte "BSA Redirection" (siehe Google) - auf diese Weise wird das Problem zuverlässig und ohne dass weitere Schritte nötig werden gelöst. Aktivieren Sie diese Option.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mit Skyrim wurde der Bug in den meisten Fällen behoben, aber die Aktvierung von Mods hängt immer noch vom Änderungsdatum der Datei ab. Daher ist es sinnvoll, diese Option zu aktiveren.</span></p></body></html> @@ -3851,7 +4177,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. Archiv Invalidierung wird für dieses Spiel nicht benötigt. @@ -3902,8 +4228,8 @@ p, li { white-space: pre-wrap; } - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - Das Profil dass sie löschen wollen scheint defekt zu sein oder der Pfad ist ungültig. Das folgende Verzeichnis wird gelöscht: "%1". Fortfahren? + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Das Profil dass sie löschen wollen scheint defekt zu sein oder der Pfad ist ungültig. Das folgende Verzeichnis wird gelöscht: "%1". Fortfahren? @@ -3948,23 +4274,23 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - Ungültiger Feldname "%1" + invalid field name "%1" + Ungültiger Feldname "%1" - invalid type for "%1" (should be integer) - ungültiger Datentyp für "%1" (integer erwartet) + invalid type for "%1" (should be integer) + ungültiger Datentyp für "%1" (integer erwartet) - invalid type for "%1" (should be string) - ungültiger Datentyp für "%1" (string erwartet) + invalid type for "%1" (should be string) + ungültiger Datentyp für "%1" (string erwartet) - invalid type for "%1" (should be float) - ungültiger Datentyp für "%1" (float erwartet) + invalid type for "%1" (should be float) + ungültiger Datentyp für "%1" (float erwartet) @@ -3973,13 +4299,13 @@ p, li { white-space: pre-wrap; } - field not set "%1" - feld "%1" nicht gesetzt + field not set "%1" + feld "%1" nicht gesetzt - invalid character in field "%1" - ungültiges Zeichen in Feld "%1" + invalid character in field "%1" + ungültiges Zeichen in Feld "%1" @@ -4011,18 +4337,18 @@ p, li { white-space: pre-wrap; } failed to open %1: %2 - "%1" konnte nicht geöffnet werden: %2 + "%1" konnte nicht geöffnet werden: %2 %1 not found - "%1" nicht gefunden + "%1" nicht gefunden Failed to delete %1 - "%1" konnte nicht gelöscht werden + "%1" konnte nicht gelöscht werden @@ -4032,13 +4358,13 @@ p, li { white-space: pre-wrap; } Failed to remove %1: %2 - "%1" konnte nicht entfernt werden: %2 + "%1" konnte nicht entfernt werden: %2 Failed to rename %1 to %2 - "%1" konnte nicht zu "%2" umbenannt werden + "%1" konnte nicht zu "%2" umbenannt werden @@ -4050,7 +4376,7 @@ p, li { white-space: pre-wrap; } Failed to copy %1 to %2 - "%1" konnte nicht nach "%2" kopiert werden + "%1" konnte nicht nach "%2" kopiert werden @@ -4073,87 +4399,91 @@ p, li { white-space: pre-wrap; } Laden der Proxy-dll konnte nicht eingerichtet werden - + Permissions required Berechtigungen erforderlich - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - Das aktive Nutzerkonto hat nicht die nötigen Rechte Mod Organizer auszuführen. Die notwendigen Änderungen können automatisch durchgeführt werden (das MO Verzeichnis wird für den aktiven Nutzer schreibbar gemacht). Wenn sie fortfahren werden sie gefragt werden ob sie "helper.exe" als Administrator ausführen wollen. + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + Das aktive Nutzerkonto hat nicht die nötigen Rechte Mod Organizer auszuführen. Die notwendigen Änderungen können automatisch durchgeführt werden (das MO Verzeichnis wird für den aktiven Nutzer schreibbar gemacht). Wenn sie fortfahren werden sie gefragt werden ob sie "helper.exe" als Administrator ausführen wollen. - - + + Woops Oha - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened Mod Organizer ist abgestürzt! Soll eine Diagnose-Datei erstellt werden? Wenn sie mir diese Datei (%1) an sherb@gmx.net schicken wird der Fehler mit höherer Wahrscheinlichkeit behoben. Bitte fügen sie eine kurze Beschreibung bei was sie gerade getan haben als der Absturz geschah. - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 Mod Organizer ist abgestürzt! Leider konnte ich keine diagnose-datei schreiben: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Mod Organizer läuft bereits - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - Es wurde kein Spiel in "%1" gefunden. Das Verzeichnis muss das Anwendungsdatei des Spiels und des Launchers enthalten. + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + Es wurde kein Spiel in "%1" gefunden. Das Verzeichnis muss das Anwendungsdatei des Spiels und des Launchers enthalten. - - + + Please select the game to manage Bitte wählen Sie ein Spiel zum Verwalten aus - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) Bitte wählen sie die Variante des Spiels die sie installiert haben (MO kann das Spiel nur dann korrekt starten wenn dies richtig gesetzt ist!) - - Please use "Help" from the toolbar to get usage instructions to all elements - Bitte verwenden Sie die "Hilfe" Funktion um Hinweise zu Nutzung aller Elemete zu bekommen + + failed to start application: %1 + + + + + Please use "Help" from the toolbar to get usage instructions to all elements + Bitte verwenden Sie die "Hilfe" Funktion um Hinweise zu Nutzung aller Elemete zu bekommen - - + + <Manage...> <Verwalten...> - + failed to parse profile %1: %2 Konnte Profil %1 nicht verarbeiten: %2 - - - failed to find "%1" - Konnte "%1" nicht finden + + failed to find "%1" + Konnte "%1" nicht finden - + failed to access %1 Auf %1 konnte nicht zugegriffen werden - + failed to set file time %1 Konnte Dateizeit nicht setzen %1 @@ -4164,8 +4494,9 @@ p, li { white-space: pre-wrap; } - "%1" is missing - "%1" fehlt + "%1" is missing or inaccessible + "%1" is missing + "%1" fehlt @@ -4191,62 +4522,62 @@ p, li { white-space: pre-wrap; } %1 konnte nicht geöffnet werden - + Script Extender Script Extender - + Proxy DLL Proxy DLL - - failed to spawn "%1" - "%1" konnte nicht erzeugt werden + + failed to spawn "%1" + "%1" konnte nicht erzeugt werden - + Elevation required Mehr Rechte erforderlich - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) Diesen Prozess auszuführen erfordert zusätzliche Rechte. Dies ist ein mögliches Sicherheitsrisiko daher empfehle ich dringend dass sie untersuchen ob -"%1" +"%1" nicht ohne diese Rechte lauffähig ist. Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.exe erlauben wollen Systemänderungen durchzuführen) - - failed to spawn "%1": %2 - "%1" konnte nicht erzeugt werden: %2 + + failed to spawn "%1": %2 + "%1" konnte nicht erzeugt werden: %2 - - "%1" doesn't exist - "%1" existiert nicht + + "%1" doesn't exist + "%1" existiert nicht - - failed to inject dll into "%1": %2 - Konnte dll nicht in "%1" einspeisen: %2 + + failed to inject dll into "%1": %2 + Konnte dll nicht in "%1" einspeisen: %2 - - failed to run "%1" - "%1" konnte nicht ausgeführt werden + + failed to run "%1" + "%1" konnte nicht ausgeführt werden - + failed to open temporary file Öffnen der temporären Datei fehlgeschlagen @@ -4359,8 +4690,8 @@ Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.e - failed to open "%1" for writing - "%1" konnte nicht zum Schreiben geöffnet werden + failed to open "%1" for writing + "%1" konnte nicht zum Schreiben geöffnet werden @@ -4385,79 +4716,79 @@ Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.e SelfUpdater - archive.dll not loaded: "%1" - archive.dll nicht geladen: "%1" + archive.dll not loaded: "%1" + archive.dll nicht geladen: "%1" - - - - + + + + Update Aktualisierung - + An update is available (newest version: %1), do you want to install it? Eine Aktualisierung ist verfügbar (neueste Version: %1). Soll sie installiert werden? - + Download in progress Download läuft - + Download failed: %1 Download fehlgeschlagen: %1 - + Failed to install update: %1 Konnte das update nicht installieren: %1 - - failed to open archive "%1": %2 - konnte das Archiv "%1" nicht öffnen: %2 + + failed to open archive "%1": %2 + konnte das Archiv "%1" nicht öffnen: %2 - + failed to move outdated files: %1. Please update manually. konnte veraltete Dateien nicht verschieben: %1. Bitte aktualisieren sie manuell. - + Update installed, Mod Organizer will now be restarted. Aktualisierung installiert. Mod Organizer wird sich nun neu starten. - + Error Fehler - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. Konnte Antwort nicht auslesen. Bitte melden Sie dies als Bug und fügen Sie die Datei mo_interface.log bei. - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) Es existiert keine inkrementelle Aktualisierung für diese Version, Sie müssen das vollständige Archiv herunterladen (%1 kB) - + no file for update found. Please update manually. Keine Datei für das Update gefunden. Bitte aktualisieren sie manuell. - + Failed to retrieve update information: %1 Konnte update informationen nicht abrufen: %1 - + No download server available. Please try again later. Kein download server verfügbar. Bitte versuche es später noch einmal. @@ -4465,18 +4796,18 @@ Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.e Settings - - - attempt to store setting for unknown plugin "%1" - es wurde versucht, Einstellungen für das unbekanntes Plugin "%1" zu speichern + + + attempt to store setting for unknown plugin "%1" + es wurde versucht, Einstellungen für das unbekanntes Plugin "%1" zu speichern - + Confirm Bestätigen - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Das Mod Verzeichnis zu wechseln wirkt sich auf alle Profile aus! Mods die im neuen Verzeichnis nicht existieren (oder dort anders heißen) werden in allen Profilen deaktiviert. Dies kann nicht rückgängig gemacht werden außer Sie haben ihre Profile manuell gesichert. Fortfahren? @@ -4505,16 +4836,16 @@ Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.e - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Anwendungssprache. Zeigt nur Sprachen an, für die eine Sprachdatei installiert wurde.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Anwendungssprache. Zeigt nur Sprachen an, für die eine Sprachdatei installiert wurde.</span></p></body></html> @@ -4538,15 +4869,15 @@ p, li { white-space: pre-wrap; } - Decides the amount of data printed to "ModOrganizer.log" - Bestimmt die Datenmenge die in "ModOrganizer.log" ausgegeben wird. + Decides the amount of data printed to "ModOrganizer.log" + Bestimmt die Datenmenge die in "ModOrganizer.log" ausgegeben wird. - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - Bestimmt die Menge an log-meldungen die in "ModOrganizer.log" geschrieben werden. -"Debug" erzeugt viele nützliche Informationen um Fehler zu finden. Der Einfluss auf die Performance ist gering aber die Datei kann sehr groß werden. Wenn dies ein Problem ist sollten sie für den regulären Betrieb "Info" verwenden. Auf dem "Error" Level bleibt das log üblicherweise vollstänig leer. + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Bestimmt die Menge an log-meldungen die in "ModOrganizer.log" geschrieben werden. +"Debug" erzeugt viele nützliche Informationen um Fehler zu finden. Der Einfluss auf die Performance ist gering aber die Datei kann sehr groß werden. Wenn dies ein Problem ist sollten sie für den regulären Betrieb "Info" verwenden. Auf dem "Error" Level bleibt das log üblicherweise vollstänig leer. @@ -4586,7 +4917,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). Verzeichnis in dem Mods abgelegt werden. Bitte beachten Sie dass jede Änderung dieses Verzeichnisses die Assoziation von Mods zu Profilen löscht wenn die Mod im neuen Verzeichnis nicht existiert. @@ -4599,297 +4930,340 @@ p, li { white-space: pre-wrap; } Cache Directory Cache-Verzeichnis + + + User interface + + + If checked, the download interface will be more compact. + + + + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + Reset stored information from dialogs. Gespeicherte Dialoginformation zurücksetzen. - - This will make all dialogs show up again where you checked the "Remember selection"-box. - Dies führt dazu dass alle Dialoge wieder angezeigt werden bei denen sie die "Auswahl merken"-box angewählt hatten. + + This will make all dialogs show up again where you checked the "Remember selection"-box. + Dies führt dazu dass alle Dialoge wieder angezeigt werden bei denen sie die "Auswahl merken"-box angewählt hatten. - + Reset Dialogs Dialoge zurücksetzen - - + + Modify the categories available to arrange your mods. Kategorien anpassen mit denen Mods sortiert werden können. - + Configure Mod Categories Mod Kategorien anpassen - - + + Nexus Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. Erlaubt MO sich automatisch im Nexus anzumelden, wenn die Nexus Registerkarte für das Spiel ausgewählt wird. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erlaubt es MO sich automatisch am Nexus für dieses Spiel anzumelden. Bitte beachten Sie, dass die Verschlüsselung des Kennworts in MO nicht besonders stark ist. Wenn Sie Bedenken haben, dass jemand Ihr Kennwort stehlen kann, speichern Sie es nicht in MO ab.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Erlaubt es MO sich automatisch am Nexus für dieses Spiel anzumelden. Bitte beachten Sie, dass die Verschlüsselung des Kennworts in MO nicht besonders stark ist. Wenn Sie Bedenken haben, dass jemand Ihr Kennwort stehlen kann, speichern Sie es nicht in MO ab.</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. Wenn dies ausgewählt ist und gültige Login Informationen unten eingegeben werden passiert der log-in bei Nexus automatisch (sobald MO darauf zugreift). - + Automatically Log-In to Nexus Automatisch in Nexus anmelden - + Username Nutzername - + Password Kennwort - + Disable automatic internet features Automatsiche Internet-funktionen deaktivieren - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) Automatische Internetzugriffe unterbinden. Dies wirkt sich nicht auf solche Funktionen aus die explizit vom Benutzer ausgelöst werden (wie die Prüfung von Mods auf Updates) - + Offline Mode Offline Modus - + Use a proxy for network connections. Proxy für Netzwerkverbindung nutzen. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. Proyx für Netzwerkverbindung nutzen. Dies greift auf die system-weite Einstellung zurück die z.B. über den Internet Explorer konfiguriert werden kann. Bitte beachten sie dass MO mit dieser Einstellung auf einigen Systemen ein paar Sekunden länger zum starten braucht. - + Use HTTP Proxy (Uses System Settings) HTTP Proxy verwenden (Nutzt Systemeinstellung) - - Associate with "Download with manager" links - + + Associate with "Download with manager" links + - + Known Servers (updated on download) - + - + Preferred Servers (Drag & Drop) Bevorzugte Server (Drag & Drop) - + Plugins Plugins - + Author: Autor: - + Version: Version: - + Description: Beschreibung: - + Key Schlüssel - + Value Wert - + Blacklisted Plugins (use <del> to remove): Gesperrte Plugins (<entf> drücken um Plugins von dieser List zu entfernen): - + Workarounds Workarounds - + Steam App ID Steam AppID - + The Steam AppID for your game Die Steam AppID für Ihr Spiel - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Steam AppID wird benötigt um einige Spiele direkt zu starten. Wenn dies nicht oder falsch gesetzt ist, wird die Methode &quot;Mod Organiser&quot; für Skyrim unter Umständen nicht funktionieren.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Voreinstellung ist die AppID für die normale Version, was in den meisten Fällen ausreicht.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn Sie vermuten, dass Sie eine andere Version haben (z.Bz. GotYI), führen Sie folgende SChritte durch um die ID anzupassen:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigieren Sie zu Ihrer Spielbibliothek in Steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Klicken Sie rechts auf das Spiel, dessen ID Sie benötigen und wählen sie &quot;Desktop Verknüpfung erstellen&quot;.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Klicken Sie rechts auf die erstellte Verknüpfung und wählen Sie &quot;Eigenschaften&quot;.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. Im URL Feld sollten Sie etwas sehen wie: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">In diesem Fall ist 22380 die gesuchte AppID.</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Steam AppID wird benötigt um einige Spiele direkt zu starten. Wenn dies nicht oder falsch gesetzt ist, wird die Methode &quot;Mod Organiser&quot; für Skyrim unter Umständen nicht funktionieren.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Die Voreinstellung ist die AppID für die normale Version, was in den meisten Fällen ausreicht.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn Sie vermuten, dass Sie eine andere Version haben (z.Bz. GotYI), führen Sie folgende SChritte durch um die ID anzupassen:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigieren Sie zu Ihrer Spielbibliothek in Steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Klicken Sie rechts auf das Spiel, dessen ID Sie benötigen und wählen sie &quot;Desktop Verknüpfung erstellen&quot;.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Klicken Sie rechts auf die erstellte Verknüpfung und wählen Sie &quot;Eigenschaften&quot;.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. Im URL Feld sollten Sie etwas sehen wie: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">In diesem Fall ist 22380 die gesuchte AppID.</span></p></body></html> + + + Load Mechanism Lademechanismus - + Select loading mechanism. See help for details. Lade-Mechanismus auswählen. Siehe Hilfe für mehr Details. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. Mod Organizer benötigt eine dll die in das Spiel injiziert wird um installierte Mods sichtbar zu machen. Es gibt mehrere Wege dies zu tun: *Mod Organizer* (Standard) In diesem Modus kümmert sich Mod Organizer selber um die Injizierung. Der Nachteil ist, dass sie das Spiel immer über MO starten müssen. *Script Extender* In diesem Modus wird besagte dll als Script Extender (obse, fose, nvse, skse) plugin installiert. *Proxy DLL* In diesem Modus ersetzt MO eine der dlls des Spiels mit einer die die MO dll lädt (und dann natürlich auch die ursprüngliche dll). Dies funktioniert NUR mit Steam spielen und es wurde nur mit Skyrim getestet. Bitte verwenden sie diesen Mechanismus nur wenn die anderen nicht verwendet werden können. -Wenn sie die Steam version von Oblivion verwenden wird der Standardweg nicht funktionieren. In dem Fall installieren sie bitte obse und nutzen "Script Extender" als Lademechanismus. Außerdem ist es dann nicht möglich Oblivion von MO aus zu starten. Verwenden sie MO nur um Mods einzurichten, beenden es und starten Oblivion über Steam. +Wenn sie die Steam version von Oblivion verwenden wird der Standardweg nicht funktionieren. In dem Fall installieren sie bitte obse und nutzen "Script Extender" als Lademechanismus. Außerdem ist es dann nicht möglich Oblivion von MO aus zu starten. Verwenden sie MO nur um Mods einzurichten, beenden es und starten Oblivion über Steam. - + NMM Version NMM Version - + The Version of Nexus Mod Manager to impersonate. - Die Version des Nexus Mod Manager zu der MO sich als "kompatibel" melden soll. + Die Version des Nexus Mod Manager zu der MO sich als "kompatibel" melden soll. - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer verwendet eine Schnitttelle die von Nexus zur Verfügung gestellt wird um Funktionen wie update-Prüfung und Datei-downloads zu ermöglichen. Tools die diese Schnittstelle verwenden müssen melden mit welcher Version des Nexus Mod Managers sie kompatibel sind um zugriff zu erhalten. Nexus hat diese Versionsnummer in der Vergangenheit verwendet um veraltete NMM Versionen auszuschließen. Da dieser Ausschluss sich auf defekte in NMM bezieht die nichts mit MO zu tun haben haben sie hier die Möglichkeit die Nummer einer funktionierenden NMM Version anzugeben. -Bitte beachten sie dass MO sich auch als MO beim Webserver meldet, es "tarnt" sich nicht als NMM. +Bitte beachten sie dass MO sich auch als MO beim Webserver meldet, es "tarnt" sich nicht als NMM. tl;dr-version: Wenn Nexus-Funktionen nicht funktionieren kann es helfen hier die aktuelle Versionsnummer von NMM einzutragen und erneut zu probieren. - + Enforces that inactive ESPs and ESMs are never loaded. Stellt sicher, dass inaktive ESP und ESM Dateien nie geladen werden. - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Manche Spiele scheinen gelegentlich ESP oder ESM Dateien zu laden auch wenn sie nicht als Plugins aktiviert wurden. Wenn der Haken aktiviert wurde, sind ESP und ESM Dateien die nicht ausgewählt wurden für das Spiel unsichtbar und können nicht geladen werden. - + Hide inactive ESPs/ESMs Inaktive ESP / ESM ausblenden - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Wenn aktiv können Dateien (insbesondere esps, esms und bsas) die zum Kernspiel gehören nicht deaktiviert werden. (Standard: aktiv) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. Wenn aktiv können Dateien (insbesondere esps, esms und bsas) die zum Kernspiel gehören nicht deaktiviert werden. (Standard: aktiv) -Deaktivieren sie dies wenn sie Mod Organizer mit einer "total conversion" (wie Nehrim) nutzen wollen aber beachten sie dass das Speil abstürzen wird wenn benötigte Dateien nicht aktiv sind. +Deaktivieren sie dies wenn sie Mod Organizer mit einer "total conversion" (wie Nehrim) nutzen wollen aber beachten sie dass das Speil abstürzen wird wenn benötigte Dateien nicht aktiv sind. - + Force-enable game files Laden von benötigten Spieldateien erzwingen - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! Für Skyrim kann dies als Alternative zur Archiv Invalidierung verwendet werden. Damit erübrigt sich AI für alle Profile. Für die anderen Spiele ist dies KEIN hinreichender Ersatz für AI! - + Back-date BSAs BSAs zurückdatieren - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. Dies sind Workarounds für Probleme mit Mod Organizer. Bitte lesen sie unbedingt die Hilfetexte bevor sie hier etwas ändern. @@ -4915,8 +5289,8 @@ Für die anderen Spiele ist dies KEIN hinreichender Ersatz für AI! - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - Dies führt dazu dass alle Dialog wieder angezeigt werden in denen Sie "Auswahl speichern" selektiert hatten. Fortsetzen? + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + Dies führt dazu dass alle Dialog wieder angezeigt werden in denen Sie "Auswahl speichern" selektiert hatten. Fortsetzen? @@ -4962,10 +5336,14 @@ Für die anderen Spiele ist dies KEIN hinreichender Ersatz für AI! - failed to connect to running instance: %1 konnte nicht an die laufende Instanz verbinden: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4991,7 +5369,7 @@ Für die anderen Spiele ist dies KEIN hinreichender Ersatz für AI! - <don't sync> + <don't sync> <nicht synchronisieren> @@ -5099,8 +5477,8 @@ Unter Windows XP: - Overwrite the file "%1" - Datei "%1" überschreiben + Overwrite the file "%1" + Datei "%1" überschreiben @@ -5113,18 +5491,18 @@ Unter Windows XP: - Copy all save games of character "%1" to the profile? - Alle Spielstände des Charakters "%1" ins Profil kopieren? + Copy all save games of character "%1" to the profile? + Alle Spielstände des Charakters "%1" ins Profil kopieren? - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Ale Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Ale Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Alle Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Alle Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. diff --git a/src/organizer_es.ts b/src/organizer_es.ts index 861d4a8e..99071b5c 100644 --- a/src/organizer_es.ts +++ b/src/organizer_es.ts @@ -1,3 +1,4 @@ + @@ -58,14 +59,14 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> Esta es la lista de esps y esms que estaban activos cuando se guardo esta partida. Para cada esp. La lista de la derecha contiene el mod (o mods) que pueden ser activados. @@ -112,8 +113,8 @@ Si pulsas Aceptar, todos los mods seleccionados en la columna de la derecha sera Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. - Componentes de este paquete. /n Si existe un componente que es requerido llamado "00 Core" . Las opciones de orden y prioridad han sido definidas por el autor. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. + Componentes de este paquete. /n Si existe un componente que es requerido llamado "00 Core" . Las opciones de orden y prioridad han sido definidas por el autor. @@ -148,6 +149,29 @@ If there is a component called "00 Core" it is usually required. Options are ord Cancelar + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -193,13 +217,13 @@ If there is a component called "00 Core" it is usually required. Options are ord - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> Puede coincidir con una o varias categorías de una ID interna de Nexus. Cada vez que se descarga un mod de la página de Nexus, Mod Organizador tratará de resolver la categoría definida en Nexus a una disponible en MO @@ -232,7 +256,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus Esta función puede no funcionar a menos que estes conectado con Nexus @@ -259,7 +283,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 fallo al leer bsa: %1 @@ -283,8 +307,8 @@ p, li { white-space: pre-wrap; } - Information missing, please select "Query Info" from the context menu to re-retrieve. - Falta información, por favor selecciona "Consulta de Información" en el menú contextual para volver a recuperar. + Information missing, please select "Query Info" from the context menu to re-retrieve. + Falta información, por favor selecciona "Consulta de Información" en el menú contextual para volver a recuperar. @@ -302,26 +326,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Listo. Doble click para instalar - - + + Paused - Double Click to resume Pausado - Doble Click para reanudar - - + + Installed - Double Click to re-install Instalado - Hacer doble clic para reinstalar - - + + Uninstalled - Double Click to re-install Desinstalado - Hacer doble clic para reinstalar @@ -343,135 +367,135 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate - + < mod %1 file %2 > < mod %1 file %2 > - + Pending Pendiente - + Paused Pausado - + Fetching Info 1 Recibiendo información 1 - + Fetching Info 2 Recibiendo información 2 - + Installed Instalado - + Uninstalled Desinstalado - + Done Realizado - - - - + + + + Are you sure? ¿Estás seguro? - + This will remove all finished downloads from this list and from disk. Esto limpiara todas las descargas finalizadas del disco y la lista. - + This will remove all installed downloads from this list and from disk. Esto limpiara todas las descargas instaladas del disco y de la lista. - + This will permanently remove all finished downloads from this list (but NOT from disk). Esta acción eliminará todas las descargas terminadas de esta lista (pero NO del disco). - + This will permanently remove all installed downloads from this list (but NOT from disk). Esta acción eliminará permanentemente todas las descargas instaladas de esta lista (pero NO del disco) - + Install Instalar - + Query Info Información de Consulta - + Delete Eliminar - + Un-Hide Un-Hide - + Remove from View Quitar de la Vista - + Cancel Cancelar - + Pause Pausa - + Remove Eliminar - + Resume Reanudar - + Delete Installed... Eliminar Instalado... - + Delete All... Eliminar todo... - + Remove Installed... Quitar Instalado... - + Remove All... Quitar Todo... @@ -479,115 +503,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + < mod %1 file %2 > < mod %1 file %2 > - + Pending Pendiente - + Fetching Info 1 Recibiendo información 1 - + Fetching Info 2 Recibiendo información 2 - - - - + + + + Are you sure? ¿Estás seguro? - + This will remove all finished downloads from this list and from disk. Esto limpiara todas las descargas finalizadas del disco y la lista. - + This will remove all installed downloads from this list and from disk. Esto eliminará todas las descargas finalizadas de la lista y del disco. - + This will remove all finished downloads from this list (but NOT from disk). Esto eliminará todas las descargas finalizadas de la lista (pero NO del disco). - + This will remove all installed downloads from this list (but NOT from disk). Esto eliminará todas las descargas instaladas de esta lista (pero NO del disco). - + Install Instalar - + Query Info Información de Consulta - + Delete Eliminar - + Un-Hide Un-Hide - + Remove from View Quitar de la Vista - + Cancel Cancelar - + Pause Pausa - + Remove Quitar - + Resume Reanudar - + Delete Installed... Eliminar Instalado... - + Delete All... Eliminar Todo... - + Remove Installed... Eliminando Instalación... - + Remove All... Quitar todos... @@ -595,122 +619,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - Fallo al renombrar "%1" a "%2" + + failed to rename "%1" to "%2" + Fallo al renombrar "%1" a "%2" - + + Memory allocation error (in refreshing directory). + + + + Download again? ¿Descargar de nuevo? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Un archivo con el mismo nombre ya se ha descargado. ¿Quieres volver a descargarlo? El nuevo archivo recibirá un nombre diferente. - + failed to download %1: could not open output file: %2 error en la descarga %1: no se puede abrir el fichero de destino: %2 - + Wrong Game Juego Incorrecto - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - El enlace de descarga es un mod para "%1" pero en este caso MO se ha creado para "%2". + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + El enlace de descarga es un mod para "%1" pero en este caso MO se ha creado para "%2". - - - - - - - - - - - + + + + + - - + + + + + + + + + invalid index Índice no válido. - + failed to delete %1 fallo al borrar %1 - + failed to delete meta file for %1 fallo al eliminar el archivo meta de 1% - - - - - + + + + + invalid index %1 Índice no válido %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id Por favor, introduzca la ID del mod en nexus - + Mod ID: ID del MOD - + + Main + Principal + + + + Update + + + + + Optional + Opcional + + + + Old + Antiguo + + + + Misc + Misc + + + + Unknown + Desconocido + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated Información actualizada - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? ¡Ningún archivo que corresponda se encuentra en Nexus! ¿Tal vez este archivo ya no está disponible o fue renombrado? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. Ningún archivo de Nexus coincide con el archivo seleccionado por su nombre. Por favor, elegir manualmente el correcto. - + No download server available. Please try again later. No hay ningún servidor de descarga disponible. Por favor, inténtelo de nuevo más tarde. - + Failed to request file info from nexus: %1 Fallo al solicitar información de archivo desde nexus: %1 - + Download failed. Server reported: %1 Error en la descarga. Informe servidor: %1 - + Download failed: %1 (%2) Fallo en la descarga: %1 (%2) - + failed to re-open %1 error reabriendo %1 @@ -891,8 +966,8 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p - Really remove "%1" from executables? - Realmente eliminar "%1" de ejecutables? + Really remove "%1" from executables? + Realmente eliminar "%1" de ejecutables? @@ -983,8 +1058,8 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p - <a href="#">Link</a> - <a href="#">Link</a> + <a href="#">Link</a> + <a href="#">Link</a> @@ -1031,7 +1106,7 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. Selecciona un nombre para el MOD. Tambien se usara el nombre como el directorio, por favor utilice nombres sencillos. @@ -1046,11 +1121,11 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> Esto muestra los contenidos del archivo. DATA representa el directorio base que sera virtualizado al directorio DATA del juego. Puedes cambiar el directorio base utilizando el boton derecho del raton en el menu contextual, y puedes mover los ficheros utilizando el arrastrar y soltar. @@ -1073,8 +1148,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - archivo.dll no cargado: "%1" + archive.dll not loaded: "%1" + archivo.dll no cargado: "%1" @@ -1089,7 +1164,7 @@ p, li { white-space: pre-wrap; } - + Extracting files Extrayendo ficheros @@ -1119,57 +1194,57 @@ p, li { white-space: pre-wrap; } El nombre introducido no es válido, por favor introduzca uno diferente. - - File format "%1" not supported - Formato de archivo no soportado para "%1" + + File format "%1" not supported + Formato de archivo no soportado para "%1" - + None of the available installer plugins were able to handle that archive Ninguno de los plugins del instalador disponible son capaces de manejar este archivo - + no error sin error - + 7z.dll not found 7z.dll no se encuentra - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll no es valido - + archive not found archivo no encontrado - + failed to open archive Error abriendo el fichero - + unsupported archive type formato de fichero no soportado - + internal library error error interno de libreria - + archive invalid archivo invalido - + unknown archive error Error de fichero desconocido @@ -1183,7 +1258,7 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. Este dialogo deberia desaparecer automaticamente si la aplicacion/juego ha terminado. Pulsa para desbloquear si no sucede. @@ -1200,7 +1275,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 Fallo al escribir en el log en %1: %2 @@ -1208,12 +1283,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 Ocurrió un error: %1 - + an error occured Ha ocurrido un error @@ -1221,414 +1296,473 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories Categorias - + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + Profile Perfil - + Pick a module collection Selecciona un perfil para cargarlo - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Crear perfiles aquí. Cada perfil contiene su propia lista de mods activos y esps. De esta manera puedes cambiar rápidamente entre configuraciones para diferentes juegos.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ten en cuenta que en estos momentos tu carga de esp no se mantiene separado para diferentes perfiles.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Crear perfiles aquí. Cada perfil contiene su propia lista de mods activos y esps. De esta manera puedes cambiar rápidamente entre configuraciones para diferentes juegos.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ten en cuenta que en estos momentos tu carga de esp no se mantiene separado para diferentes perfiles.</span></p></body></html> - Refresh list - Recargar lista + Recargar lista - + Refresh list. This is usually not necessary unless you modified data outside the program. Recargar lista. Esto es normalmente no necesario, a no ser que hayas modificado algo desde fuera del programa. - + + + Restore Backup... + + + + + + Create Backup + + + + List of available mods. Lista de mods disponibles. - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - Esta es una lista de mods instalados. Usa las casillas de verificación para activar/desactivar los mods y arrastrar y soltar para cambiar su órden de "instalación". + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Esta es una lista de mods instalados. Usa las casillas de verificación para activar/desactivar los mods y arrastrar y soltar para cambiar su órden de "instalación". - + Filter Filtro - + No groups Sin grupos - + Nexus IDs Nexus IDs - - - + + + Namefilter Nombre del filtro - + Pick a program to run. Selecciona el programa a iniciar. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Elije el programa a ejecutar. Una vez que comiences a usar ModOrganizer, siempre debes ejecutar tu juego y herramientas desde aquí o a través de los accesos directos creados aquí, de lo contrario los mods instalados a través de MO no serán visibles.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Puedes añadir nuevas herramientas a esta lista, pero no puedo prometer que herramientas que no he probado funcionen.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Elije el programa a ejecutar. Una vez que comiences a usar ModOrganizer, siempre debes ejecutar tu juego y herramientas desde aquí o a través de los accesos directos creados aquí, de lo contrario los mods instalados a través de MO no serán visibles.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Puedes añadir nuevas herramientas a esta lista, pero no puedo prometer que herramientas que no he probado funcionen.</span></p></body></html> - + Run program Iniciar el programa - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Iniciar el programa seleccionado con ModOrganizer activado.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Iniciar el programa seleccionado con ModOrganizer activado.</span></p></body></html> - + Run Iniciar - + Create a shortcut in your start menu or on the desktop to the specified program Crear un acceso directo en el menú Inicio o en el escritorio con el programa especificado - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esto crea un acceso directo al menú de inicio que iniciara directamente el programa seleccionado con el MO activo.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esto crea un acceso directo al menú de inicio que iniciara directamente el programa seleccionado con el MO activo.</span></p></body></html> - + Shortcut Acceso rápido - + + Plugins + Plugins + + + List of available esp/esm files Listado de ficheros esp/esm disponibles - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta lista contiene los ESP y ESMS contenidos en los mods activos. Requieren su propio orden de carga. Utilice arrastrar y soltar para modificar este orden de carga. Tenga en cuenta que MO sólo salvará el orden de carga de los mods que están activos/comprobados.<br />Hay una gran herramienta llamada &quot;BOSS&quot; para ordenar automáticamente los archivos.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta lista contiene los ESP y ESMS contenidos en los mods activos. Requieren su propio orden de carga. Utilice arrastrar y soltar para modificar este orden de carga. Tenga en cuenta que MO sólo salvará el orden de carga de los mods que están activos/comprobados.<br />Hay una gran herramienta llamada &quot;BOSS&quot; para ordenar automáticamente los archivos.</span></p></body></html> - + Sort Ordenar - + + Open list options... + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. Lista de Archivos BS disponibles. Los archivos no controlados aquí no se gestionaran por el MO e ignorarán el orden de instalación. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - Ficheros BSA son archivos (comparables a. zip) que contienen los datos activos. (mallas, texturas...) para ser utilizado por el juego. Como tales, "compiten" con los archivos sueltos en su directorio de datos sobre el cual se cargan. + Ficheros BSA son archivos (comparables a. zip) que contienen los datos activos. (mallas, texturas...) para ser utilizado por el juego. Como tales, "compiten" con los archivos sueltos en su directorio de datos sobre el cual se cargan. Por defecto, BSAs que comparten su nombre de base con un ESP activado (es decir plugin.esp y plugin.bsa) se carga automáticamente y tendrán prioridad sobre todos los archivos sueltos, el orden de instalación configurado a la izquierda es entonces ignorado! BSA marcado aquí se cargan de tal manera que su orden de instalación se cumple correctamente. - - + + File Fichero - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) todavía se cargan en Skyrim, pero el <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">archivo regular anula</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> + <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> + <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) todavía se cargan en Skyrim, pero el <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">archivo regular anula</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - + Data Datos - + refresh data-directory overview refresca la vista del directorio de datos - + Refresh the overview. This may take a moment. Refresca toda la vista. Esto puede tomar un tiempo. - - - + + + Refresh Recargar - + This is an overview of your data directory as visible to the game (and tools). Esta es una visión general del directorio de datos como visible para el juego (y herramientas). - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. Se filtra la lista anterior de modo que sólo se muestren los conflictos. - + Show only conflicts Monstrar solo los conflictos - + Saves Part. Guardadas - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta es una lista de todas las partidas guardadas para este juego. Pase el ratón sobre una entrada de la lista para obtener información detallada sobre el salve, incluida una lista de esp/esm que se utilizaron en el momento que esta salvaguarda se creo, pero que no están activos actualmente.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si haces clic en &quot;Fix Mods...&quot; en el menú contextual, MO tratará de activar todos los mods y para arreglar esos esps desaparecidos. No va a desactivar ninguna otra cosa!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta es una lista de todas las partidas guardadas para este juego. Pase el ratón sobre una entrada de la lista para obtener información detallada sobre el salve, incluida una lista de esp/esm que se utilizaron en el momento que esta salvaguarda se creo, pero que no están activos actualmente.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si haces clic en &quot;Fix Mods...&quot; en el menú contextual, MO tratará de activar todos los mods y para arreglar esos esps desaparecidos. No va a desactivar ninguna otra cosa!</span></p></body></html> - + Downloads Descargas - + This is a list of mods you downloaded from Nexus. Double click one to install it. Esta es la lista de los mods que has descargado desde Nexus. Doble Click para instalar. - Compact - Compactar + Compactar - + Show Hidden Mostrar Ocultos - + Tool Bar Barra Herramientas - + Install Mod Instalar Mod - + Install &Mod Instalar &Mod - + Install a new mod from an archive Instalar un nuevo Mod desde un archivo - + Ctrl+M Ctrl+M - + Profiles Perfiles - + &Profiles &Perfiles - + Configure Profiles Configurar Perfiles - + Ctrl+P Ctrl+P - + Executables Ejecutables - + &Executables &Ejecutables - + Configure the executables that can be started through Mod Organizer Configura el ejecutable que sera iniciado desde Mod Orgenizer - + Ctrl+E Ctrl+E - - + + Tools Herramientas - + &Tools &Herramientas - + Ctrl+I Ctrl+I - + Settings Configuracion - + &Settings &Configuracion - + Configure settings and workarounds Configurar opciones y soluciones - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Buscar en la red de Nexus mas Mods - + Ctrl+N Ctrl+N - - + + Update Actualizacion - + Mod Organizer is up-to-date Mod Organizer esta actualizado - - + + No Problems Sin problemas - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1639,876 +1773,959 @@ Right now this has very limited functionality Ahora esto tiene una funcionalidad muy limitada - - + + Help Ayuda - + Ctrl+H Ctrl+H - + Endorse MO Avalar MO - - + + Endorse Mod Organizer Avalar Mod Organizer - + + Copy Log to Clipboard + + + + + Ctrl+C + + + + Toolbar Barra de herramientas - + Desktop Escritorio - + Start Menu Menú de Inicio - + Problems Problemas - + There are potential problems with your setup Hay posibles problemas con su configuración - + Everything seems to be in order Todo parece estar en orden - + Help on UI Ayuda sobre UI - + Documentation Wiki Documentación Wiki - + Report Issue Informar de un Problema - + Tutorials Tutoriales - + About Sobre - + About Qt Sobre Qt - - failed to save archives order, do you have write access to "%1"? - Fallo al salvar la orden de archivos, , ¿tiene permiso de escritura en "%1"? + failed to save archives order, do you have write access to "%1"? + Fallo al salvar la orden de archivos, , ¿tiene permiso de escritura en "%1"? - + failed to save load order: %1 Fallo guardando el orden de carga: %1 - + Name Nombre - + Please enter a name for the new profile Por favor introduzca un nombre para el nuevo perfil - + failed to create profile: %1 Fallo al crear el perfil: %1 - + Show tutorial? ¿Mostrar tutorial? - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - Están comenzando Mod Organizer por primera vez. ¿Quieres mostrar un tutorial de sus características básicas? Si decides que no siempre podras empezar el tutorial desde la "Ayuda" del menú. + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + Están comenzando Mod Organizer por primera vez. ¿Quieres mostrar un tutorial de sus características básicas? Si decides que no siempre podras empezar el tutorial desde la "Ayuda" del menú. - + Downloads in progress Descarga en progreso - + There are still downloads in progress, do you really want to quit? Aun hay descargas en progreso, estas seguro que quieres salir? - + failed to read savegame: %1 Fallo al leer la partida guardada: %1 - - Plugin "%1" failed: %2 - Plugin "%1" fallido: %2 + + Plugin "%1" failed: %2 + Plugin "%1" fallido: %2 - - Plugin "%1" failed - Plugin "%1" fallido + + Plugin "%1" failed + Plugin "%1" fallido - + failed to init plugin %1: %2 fallo al iniciar plugin %1: %2 - + Plugin error Error de plugin - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - Parece que el plugin "%1" Fallo al cargar el último inicio y causó que MO se bloqueara. ¿Quieres desactivarlo? + Parece que el plugin "%1" Fallo al cargar el último inicio y causó que MO se bloqueara. ¿Quieres desactivarlo? (Nota: Si es la primera vez que ve este mensaje en este plugin es posible que desees intentarlo otra vez. El plugin podria ser capaz de recuperarse del problema.) - - Failed to start "%1" + + Failed to start "%1" Fallo al iniciar plugin %1: %2 - + Waiting Esperando - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. Por favor, pulsa OK una vez que hayas iniciado sesión en steam. - + Start Steam? ¿Iniciar Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam es requerido para iniciar correctamente el juego. ¿Debería MO tratar de iniciar ahora steam? - + Also in: <br> También en: <br> - + No conflict Sin conflictos - + <Edit...> <Editar...> - + This bsa is enabled in the ini file so it may be required! Esta bsa está habilitada en el archivo ini, por lo que puede ser necesario - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - Este archivo se seguirá cargado, ya que hay un plugin con el mismo nombre, pero sus archivos no seguirá el orden de instalación! + Este archivo se seguirá cargado, ya que hay un plugin con el mismo nombre, pero sus archivos no seguirá el orden de instalación! - + Activating Network Proxy Activación de proxy de red - - + + Installation successful Instalacion completada - - + + Configure Mod Configurar Mod - - + + This mod contains ini tweaks. Do you want to configure them now? Este mod contiene ajustes del ini. ¿Quieres configurarlos ahora? - - - mod "%1" not found - mod "%1" no encontrado + + + mod "%1" not found + mod "%1" no encontrado - - + + Installation cancelled Instalación cancelada - - + + The mod was not installed completely. El mod no fue instalado completamente. - + Some plugins could not be loaded Algún plugins no se pudo cargar - + Too many esps and esms enabled Demasiados esps y esms habilitado - - + + Description missing Falta la descripción - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Los siguientes plugins no se pudieron cargar. La razón puede ser dependencias faltantes (es decir python) o una versión obsoleta: - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - El juego no permite cargar más de 255 plugins activos (incluidos los oficiales). Tienes que desactivar algunos plugins no utilizados o fusionar algunos plugins en uno solo. Aquí podras encontrar una guía: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + El juego no permite cargar más de 255 plugins activos (incluidos los oficiales). Tienes que desactivar algunos plugins no utilizados o fusionar algunos plugins en uno solo. Aquí podras encontrar una guía: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Seleccione Mod - + Mod Archive Archivo Mod - + Start Tutorial? Iniciar tutorial? - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Estás a punto de iniciar un tutorial. Por razones técnicas, no es posible terminar el tutorial antes de tiempo. ¿Desea continuar? - - + + Download started Descarga iniciada - + failed to update mod list: %1 Fallo al actualizar la lista de Mods: %1 - + failed to spawn notepad.exe: %1 Fallo al cargar el Bloc de notas: %1 - + failed to open %1 Fallo al abrir %1 - + failed to change origin name: %1 fallo al cambiar el nombre original del fichero %1 - - Executable "%1" not found - Ejecutable "%1" no encontrado + + Executable "%1" not found + Ejecutable "%1" no encontrado - + Failed to refresh list of esps: %1 Fallo al actualizar la lista de esps: %1 - + <Checked> <Marcado> - + <Unchecked> <Desmarcado> - + <Update> <Actualizacion> - + <No category> <No categoría> - + <Conflicted> <En conflicto> - + <Not Endorsed> <No Avalado> - + failed to rename mod: %1 fallo al renombrar el mod: %1 - + Overwrite? ¿Sobrescribir? - - This will replace the existing mod "%1". Continue? - Esto reemplazará el vigente mod "%1". ¿Desea continuar? + + This will replace the existing mod "%1". Continue? + Esto reemplazará el vigente mod "%1". ¿Desea continuar? - - failed to remove mod "%1" - Fallo eliminando mod "%1" + + failed to remove mod "%1" + Fallo eliminando mod "%1" - - - - failed to rename "%1" to "%2" - Fallo al renombrar "%1" a "%2" + + + + failed to rename "%1" to "%2" + Fallo al renombrar "%1" a "%2" - - Multiple esps activated, please check that they don't conflict. + + Multiple esps activated, please check that they don't conflict. Múltiples esps activados, por favor verifique que no entren en conflicto. - - - - + + + + Confirm Confirmar - + Remove the following mods?<br><ul>%1</ul> ¿Quitar el siguiente mods?<br><ul>%1</ul> - + failed to remove mod: %1 fallo al eliminar mod: %1 - - + + Failed Fallo - + Installation file no longer exists El archivo de instalación ya no existe - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. Mods instalados con las viejas versiones de MO no pueden ser instalados de nuevo de este modo. - - + + You need to be logged in with Nexus to endorse Necesita estar conectado con Nexus para avalar - - + Extract BSA Extraer BSA - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - Este mod contiene al menos un BSA. ¿Quieres descomprimirlo? +(This removes the BSA after completion. If you don't know about BSAs, just select no) + Este mod contiene al menos un BSA. ¿Quieres descomprimirlo? (Esto elimina la BSA después de su finalización. Si no sabe de BSAS, solamente no lo seleccione) - - - + + failed to read %1: %2 fallo al leer %1: %2 - - + This archive contains invalid hashes. Some files may be broken. Este archivo contiene hashes no válidos. Algunos archivos pueden estar rotos. - + Nexus ID for this Mod is unknown Se desconoce la ID en Nexus para este Mod - - + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... Crear Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Esto moverá todos los archivos de sobrescritura en un nuevo mod, regular. Por favor, introduzca un nombre: - + A mod with this name already exists Ya existe un mod con este nombre - + Continue? ¿Continuar? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. El esquema de versiones decide qué versión es considerada más nueva una que otra. Esta función adivinará el esquema de versiones bajo el supuesto de que la versión instalada es obsoleta. - - + + Sorry Lo siento - - I don't know a versioning scheme where %1 is newer than %2. + + I don't know a versioning scheme where %1 is newer than %2. Se desconoce un esquema de versiones donde %1 es más reciente que %2. - + Really enable all visible mods? ¿Permitir realmente todos los mods visibles? - + Really disable all visible mods? ¿Realmente desactivar todos los mods visibles? - + Choose what to export Elija un archivo a exportar - + Everything Todo - + All installed mods are included in the list Todos los mods instalados están incluidos en la lista - + Active Mods Mods Activos - + Only active (checked) mods from your current profile are included Mods sólo activos (Marcados) es incluido de su perfil actual - + Visible Visible - + All mods visible in the mod list are included Todo mods visible en la lista de mod son incluidos - + export failed: %1 Falló al exportar: %1 - + Install Mod... Instalar Mod... - + Enable all visible Activar todos los visibles - + Disable all visible Desactivar todo lo visible - + Check all for update Comprobar todo para actualizar - + Export to csv... Exportar a CSV... - + + All Mods + + + + Sync to Mods... Sincronizar con Mods... - + Restore Backup Restaurar copia de seguridad - + Remove Backup... Eliminar copia de seguridad... - + Add/Remove Categories Añadir/Quitar Categorías - + Replace Categories Remplazar Categorías - + Primary Category Categoría Primaria - + Change versioning scheme Cambiar esquema de versiones - + Un-ignore update No ignorar actualización - + Ignore update No Ignorar actualización - + Rename Mod... Renombrar Mod... - + Remove Mod... Quitar Mod... - + Reinstall Mod Reinstalar Mod - + Un-Endorse No Avalado - - + + Endorse Avalado - - Won't endorse + + Won't endorse No avalar - + Endorsement state unknown Estado de avalado desconocido - + Ignore missing data Ignorar data desaparecido - + Visit on Nexus Visite Nexus - + Open in explorer Abrir en explorador - + Information... Informacion... - - + + Exception: Excepción: - - + + Unknown exception Excepción desconocida - + <All> <Todo> - + <Multiple> <Multiple> - - Really delete "%1"? - Realmente desea borrar "%1"? + + Really delete "%1"? + Realmente desea borrar "%1"? - + Fix Mods... Fix Mods... - + Delete Eliminar - - + + failed to remove %1 Fallo eliminando %1 - - + + failed to create %1 Fallo al crear %1 - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! No se puede cambiar el directorio de descarga, mientras que las descargas están en curso - + Download failed Descarga fallida - + failed to write to file %1 Fallo de escritura en el fichero %1 - + %1 written %1 escrito - + Select binary Selecciona el binario - + Binary Binario - + Enter Name Introducir Nombre - + Please enter a name for the executable Por favor, introduce un nombre para el ejecutable - + Not an executable No es un ejecutable - + This is not a recognized executable. Esto no es un ejecutable reconocido. - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación del archivo falló - - - Failed to remove "%1". Maybe you lack the required file permissions? - Fallo al eliminar "%1". ¿Tal vez no tengas los permisos necesarios? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Fallo al eliminar "%1". ¿Tal vez no tengas los permisos necesarios? - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + file not found: %1 archivo no encontrado: %1 - + failed to generate preview for %1 fallo al generar vista anticipada para %1 - - Sorry, can't preview anything. This function currently does not support extracting from bsas. + + Sorry, can't preview anything. This function currently does not support extracting from bsas. Lo sentimos, no se puede obtener una vista previa de nada. Esta función no admite la extracción de bsas. - + Update available Actualización disponible - + Open/Execute Abrir/Ejecutar - + Add as Executable Añadir un ejecutable - + Preview Previsualizar - + Un-Hide Desocultar - + Hide Ocultar - + Write To File... Escribir al fichero... - + Do you want to endorse Mod Organizer on %1 now? ¿Quieres avalar Mod Organizer en %1 ahora? - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 Solicitud de Nexus ha fallado: %1 - + login successful login correcto @@ -2524,64 +2741,134 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers Falló el inicio de sesión: %1 - + login failed: %1. You need to log-in with Nexus to update MO. login fallido: %1. Necesitas hacer login con Nexus para actualizar MO. - + Error Error - + failed to extract %1 (errorcode %2) fallo al extraer %1 (Código de error %2) - + Extract... Extraer... - + Edit Categories... Editar Categorías... - + + Deselect filter + + + + Remove Eliminar - + Enable all Activar todo - + Disable all Desactivar todos - + Unlock load order Desbloquear el orden de carga - + Lock load order Orden de carga bloqueado - + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + + + BOSS working - BOSS trabajando + BOSS trabajando - failed to run boss: %1 - fallo al ejecutar boss: %1 + fallo al ejecutar boss: %1 @@ -2596,8 +2883,8 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers ModInfo - - + + invalid index %1 indice invalido %1 @@ -2605,7 +2892,7 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers ModInfoBackup - + This is the backup of a mod Esta es la copia de seguridad de un mod @@ -2634,7 +2921,7 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers - + Save Guardar @@ -2644,53 +2931,73 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers Fich. INI - + + Ini Files + + + + This is a list of .ini files in the mod. Esta es la lista de ficheros INI que incluye el mod. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Esta es la lista de ficheros INI que incluye el mod. Normalmente son usados para configurar algunos aspectos o parametros del mod. - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Grabar cambios al fichero. - + Save changes to the file. This overwrites the original. There is no automatic backup! Grabar cambios al fichero. Esto sobreescribe el original. No se realizan backups! - + Images Imagenes - + Images located in the mod. Imagenes incluidas en el mod. - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. Esto muestra todas las imágenes (jpg. y .png) en el directorio mod, como capturas de pantalla. Hacer clic en uno para obtener una vista más grande. - - + + Optional ESPs ESPs Opcionales - + List of esps and esms that can not be loaded by the game. Listado de ESPs y ESMs que no se cargaran con el juego. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -2701,440 +3008,448 @@ Por lo general, contiene funciones opcionales, consulte el archivo Léame. La mayoría de los mods no tienen esps opcionales, por lo que es muy probable que está buscando en una lista vacía. - + Make the selected mod in the lower list unavailable. marca este fichero seleccionado en la lista de abajo no disponible. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. El esp seleccionado (en la lista de abajo) es el que sera colocado en el subdirectorio del mod y sera invisible para el juego. Este no podra ser activado. - + Move a file to the data directory. Error moviendo fichero al directorio de datos. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Esto mueve un esp al directorio esp, por lo que se puede habilitar en la ventana principal. Tenga en cuenta que el ESP sólo se convierte en "disponible", no necesariamente se cargará! Está configurado en la ventana principal de omo. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + Esto mueve un esp al directorio esp, por lo que se puede habilitar en la ventana principal. Tenga en cuenta que el ESP sólo se convierte en "disponible", no necesariamente se cargará! Está configurado en la ventana principal de omo. - + ESPs in the data directory and thus visible to the game. ESPs en el directorio de datos y visibles por el juego. - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. Estos son los archivos mod que se encuentran en el directorio de datos (virtual) de su juego y así será selecteable en la lista de esp en la ventana principal. - + Available ESPs ESPs Disponibles - + Conflicts Conflictos - + The following conflicted files are provided by this mod Los siguientes archivos en conflicto son proporcionados por este mod - - + + File Fichero - + Overwritten Mods Mods Sobrescritos - + The following conflicted files are provided by other mods Los siguientes archivos en conflicto son proporcionados por otros mods - + Providing Mod Proporcionar Mod - + Non-Conflicted files Archivos que no estan en conflicto - + Categories Categorías - + Primary Category Categoría Principal - + Nexus Info Inf. Nexus - + Mod ID ID del MOD - + Mod ID for this mod on Nexus. ID del Mod en Nexus. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. Esto se rellena automáticamente si has descargado e instalado el mod desde MO. De lo contrario, puedes introducirlo manualmente. Para encontrar el ID correcto, encontrar el mod en nexo. La dirección URL tendrá este aspecto: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">.En este ejemplo, 1334 es el identificador que estás buscando. Además: El anterior es el enlace para Mod Organizer en Nexus. ¿Por qué no vas ahora allí y lo avalas?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. Esto se rellena automáticamente si has descargado e instalado el mod desde MO. De lo contrario, puedes introducirlo manualmente. Para encontrar el ID correcto, encontrar el mod en nexo. La dirección URL tendrá este aspecto: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">.En este ejemplo, 1334 es el identificador que estás buscando. Además: El anterior es el enlace para Mod Organizer en Nexus. ¿Por qué no vas ahora allí y lo avalas?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Versión instalada del Mod. La descripción contendrá la versión actual disponible en nexo. La versión instalada sólo se establece si has instalado el mod través de MO.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Versión instalada del Mod. La descripción contendrá la versión actual disponible en nexo. La versión instalada sólo se establece si has instalado el mod través de MO.</span></p></body></html> - + Version Version - + Refresh Recargar - + Refresh all information from Nexus. Recargar toda la información de Nexus. - + Description Descripcion - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - + Endorse Avalar - + Notes Notas - + Filetree Contenido - + A directory view of this mod Ficheros que contiene este mod - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Los cambios pasan inmediatamente sobre el disco, también hacen</span><span style=" font-size:8pt; font-weight:600;"> ser cuidadoso</span><span style=" font-size:8pt;">.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Los cambios pasan inmediatamente sobre el disco, también hacen</span><span style=" font-size:8pt; font-weight:600;"> ser cuidadoso</span><span style=" font-size:8pt;">.</span></p></body></html> - + Previous Anterior - + Next Siguiente - + Close Cerrar - + &Delete &Delete - + &Rename &Rename - + &Hide &Ocultar - + &Unhide &Mostrar - + &Open &Abrir - + &New Folder &Nueva Carpeta - - + + Save changes? ¿Guardar cambios? - - - Save changes to "%1"? + + + Save changes to "%1"? ¿Guardar cambios a %1? - + File Exists Existe el fichero - + A file with that name exists, please enter a new one Un fichero con ese nombre ya existe, por favor selecciona otro nombre - + failed to move file Error al mover el fichero - - failed to create directory "optional" - Error al crear el directorio "optional" + + failed to create directory "optional" + Error al crear el directorio "optional" - - + + Info requested, please wait Informacion solicitada, por favor espere - + Main Principal - + Update Actualizacion - + Optional Opcional - + Old Antiguo - + Misc Misc - + Unknown Desconocido - + Current Version: %1 Version actual: %1 - + No update available Sin actualizacion - + (description incomplete, please visit nexus) (descripción incompleta, por favor visite nexus) - - <a href="%1">Visit on Nexus</a> - <a href="%1">Visite en Nexus</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">Visite en Nexus</a> - + Failed to delete %1 Error borrando %1 - - + + Confirm Confirma - - Are sure you want to delete "%1"? - Estas seguro de querer borrar "%1"? + + Are sure you want to delete "%1"? + Estas seguro de querer borrar "%1"? - + Are sure you want to delete the selected files? Etas seguro de querer borrar los ficheros seleccionados? - - + + New Folder Nueva Carpeta - - Failed to create "%1" - Fallo al crear "%1" + + Failed to create "%1" + Fallo al crear "%1" - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación de archivo falló. - - - Failed to remove "%1". Maybe you lack the required file permissions? - Fallo al eliminar "% 1". Tal vez no tienes los permisos necesarios? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Fallo al eliminar "% 1". Tal vez no tienes los permisos necesarios? - - + + failed to rename %1 to %2 Fallo al renombrar %1 a %2 - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + Un-Hide Desocultar - + Hide Ocultar - + Name Nombre - + Please enter a name Por favor, introduzca un nombre - - + + Error Error - + Invalid name. Must be a valid file name Nombre no válido. Debe ser un nombre de archivo válido - + A tweak by that name exists Existe un ajuste con ese nombre - + Create Tweak Crear Ajuste Fino + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + + + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Esta pseudo-mod contiene archivos en el árbol de datos virtual que fue modificado (es decir, mediante el kit de construcción) @@ -3142,17 +3457,22 @@ p, li { white-space: pre-wrap; } ModInfoRegular - failed to write %1/meta.ini: %2 - fallo al escribir %1/meta.ini: % 2 + fallo al escribir %1/meta.ini: % 2 + + + + + failed to write %1/meta.ini: error %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 no contiene ningún esp/esm y ningún directorio activo (textures, meshes, interface, ...) - + Categories: <br> Categorias: <br> @@ -3200,119 +3520,124 @@ p, li { white-space: pre-wrap; } Redundante - + + Non-MO + + + + invalid inválido - - installed version: "%1", newest version: "%2" + + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 - version instalada: "%1", nueva version: "%2" + version instalada: "%1", nueva version: "%2" - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - La nueva versión en Nexus parece ser más antigua que la que has instalado. Esto podría significar, que la versión ha sido retirada (es decir, debido a un error) o el autor utiliza un esquema de control de versiones no estándar y que la versión más reciente es en realidad la más reciente. De cualquier manera si lo deseas, puedes "actualizar". + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + La nueva versión en Nexus parece ser más antigua que la que has instalado. Esto podría significar, que la versión ha sido retirada (es decir, debido a un error) o el autor utiliza un esquema de control de versiones no estándar y que la versión más reciente es en realidad la más reciente. De cualquier manera si lo deseas, puedes "actualizar". - + Categories: <br> Categorias: <br> - + Invalid name Nombre no válido. - + drag&drop failed: %1 fallo al arrastrar y soltar: %1 - + Confirm Confirma - - Are you sure you want to remove "%1"? - Estas seguro de querer borrar "%1"? + + Are you sure you want to remove "%1"? + Estas seguro de querer borrar "%1"? - + Flags Banderas - + Mod Name Nombre del Mod - + Version Versión - + Priority Prioridad - + Category Categoría - + Nexus ID Nexus IDs - + Installation Instalación - - + + unknown Desconocido - + Name of your mods Nombre de tus mods - + Version of the mod (if available) Version del mod (si esta disponible) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Prioridad de instalacion de tu mod. Cuanto mas alto sea pisara los mods con menos prioridad. - + Category of the mod. Categoría del mod. - + Id of the mod as used on Nexus. Id del mod tal como se utiliza en Nexus. - + Emblemes to highlight things that might require attention. Emblemas para destacar las cosas que podrían requerir atención. - + Time this mod was installed Tiempo que fue instalado el mod @@ -3346,17 +3671,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus Inicio de sesión en Nexus - + timeout Tiempo de espera - + + Unknown error + + + + Please check your password Por favor introduzca su contraseña @@ -3364,17 +3694,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - Fallo adivinar Identificación del mod para "% 1", por favor elegir la correcta + + Failed to guess mod id for "%1", please pick the correct one + Fallo adivinar Identificación del mod para "% 1", por favor elegir la correcta - + empty response Respuesta vacía - + invalid response respuesta no válida @@ -3413,8 +3743,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - Error al borrar "%1" + Failed to delete "%1" + Error al borrar "%1" @@ -3424,8 +3754,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - Estas seguro de querer eliminar "%1"? + Are sure you want to delete "%1"? + Estas seguro de querer eliminar "%1"? @@ -3440,116 +3770,129 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - Fallo al crear "%1" + Failed to create "%1" + Fallo al crear "%1" PluginList - + Name Nombre - + Priority Prioridad - + Mod Index Índice de Mod - + Flags Banderas - - + + unknown Desconocido - + Name of your mods Nombre de tus mods - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - Prioridad de carga de tu mod. Cuanto mayor sea, más "importante" es y por lo tanto sobrescribe los datos del plugins con menor prioridad. + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + Prioridad de carga de tu mod. Cuanto mayor sea, más "importante" es y por lo tanto sobrescribe los datos del plugins con menor prioridad. - + The modindex determins the formids of objects originating from this mods. El índice de mod determins la forma ids de objetos que provienen de este mods. - + failed to update esp info for file %1 (source id: %2), error: %3 fallo al actualizar información del esp del archivo %1 (fuente id: %2), error: %3 - + esp not found: %1 ESP no encontrado: %1 - - + + Confirm Confirmar - + Really enable all plugins? ¿Realmente habilitar todos los plugins? - + Really disable all plugins? ¿Realmente deshabilitar todos los plugins? - + The file containing locked plugin indices is broken El fichero que contiene los índices del plugin están bloqueados o rotos - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Algunos de los plugins tienen nombres no válidos! Estos plugins no pueden ser cargados por el juego. Por favor, consulte mo_interface.log para ver una lista de plugins afectados y cambiarles el nombre. - + + <b>Origin</b>: %1 + + + + + Author + Autor + + + + Description + Descripcion + + BOSS dll incompatible - BOSS dll incompatible + BOSS dll incompatible - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) Este plugin no se puede desactivar (impuesto por el juego) - Origin: %1 - Origen: %1 + Origen: %1 - + Missing Masters Maestros Desaparecidos - + Enabled Masters Activar Maestros - + failed to restore load order for %1 fallo al restaurar el orden de carga 1% @@ -3576,16 +3919,16 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> @@ -3617,64 +3960,69 @@ p, li { white-space: pre-wrap; } Fallo al crear %1 - + failed to write mod list: %1 Fallo al escribir lista de mod:s: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 fallo al actualizar ajustes fichero ini, puede ser usado: %1 - + failed to create tweaked ini: %1 fallo al crear ajustes ini: %1 - - - - - + + "%1" is missing or inaccessible + "%1" no encontrado + + + + + + + invalid index %1 fallo al crear ajustes ini: %1 - - Overwrite directory couldn't be parsed + + Overwrite directory couldn't be parsed Directorio de sobrescritura no se pudo analizar - + invalid priority %1 prioridad invalida %1 - + failed to parse ini file (%1) fallo al analizar fichero ini (%1) - + failed to parse ini file (%1): %2 fallo al analizar fichero ini (%1): %2 - - - failed to modify "%1" - fallo al modificar "%1" + + + failed to modify "%1" + fallo al modificar "%1" - + Delete savegames? ¿Eliminar partidas guardadas? - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - ¿Quieres borrar partidas locales guardadas? (Si seleccionas "No", los juegos salvados aparecerán de nuevo si vuelves a habilitar las partidas locales guardadas) + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + ¿Quieres borrar partidas locales guardadas? (Si seleccionas "No", los juegos salvados aparecerán de nuevo si vuelves a habilitar las partidas locales guardadas) @@ -3696,8 +4044,8 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - Si se marca, el nuevo perfil utilizará la configuración predeterminada de juego en lugar de los ajustes "globales". La configuración global son los valores que se configuran al ejecutar el lanzador del juego directamente, sin MO. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + Si se marca, el nuevo perfil utilizará la configuración predeterminada de juego en lugar de los ajustes "globales". La configuración global son los valores que se configuran al ejecutar el lanzador del juego directamente, sin MO. @@ -3719,20 +4067,20 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Esta es la lista de perfiles. Cada perfil contiene su propia lista y el orden de instalación de mods habilitados (desde un fondo compartido), una configuración de esps/esms una copia de ficheros ini del juego y un filtro de partidas guardadas opcional.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Nota</span> Por razones técnicas, en este momento no es posible tener-órdenes de carga separadas para esps. Esto significa que no puede cargar moda.esp antes modb.esp en un perfil y al revés en otro.</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Esta es la lista de perfiles. Cada perfil contiene su propia lista y el orden de instalación de mods habilitados (desde un fondo compartido), una configuración de esps/esms una copia de ficheros ini del juego y un filtro de partidas guardadas opcional.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Nota</span> Por razones técnicas, en este momento no es posible tener-órdenes de carga separadas para esps. Esto significa que no puede cargar moda.esp antes modb.esp en un perfil y al revés en otro.</p></body></html> @@ -3752,22 +4100,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Los juegos de Oblivion, Fallout 3 y Fallout NV contienen un error que impide que la textura y los sustitutos de la malla (es decir: todas las modificaciones de las mallas y texturas que ya están en el juego) funcionen.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">El Mod Organizer utiliza una solución llamada &quot;BSA redirección&quot; (google es tu amigo) para solucionar este problema de forma fiable y sin más trabajo. Basta con activarlo y olvidarse.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Con Skyrim este error parece corregido, pero si un mod se activa todavía depende de las fechas de archivo. Por lo tanto, todavía tiene sentido activar esto.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Los juegos de Oblivion, Fallout 3 y Fallout NV contienen un error que impide que la textura y los sustitutos de la malla (es decir: todas las modificaciones de las mallas y texturas que ya están en el juego) funcionen.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">El Mod Organizer utiliza una solución llamada &quot;BSA redirección&quot; (google es tu amigo) para solucionar este problema de forma fiable y sin más trabajo. Basta con activarlo y olvidarse.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Con Skyrim este error parece corregido, pero si un mod se activa todavía depende de las fechas de archivo. Por lo tanto, todavía tiene sentido activar esto.</span></p></body></html> @@ -3834,7 +4182,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. Invalidación de archivo no es necesaria para este juego. @@ -3885,8 +4233,8 @@ p, li { white-space: pre-wrap; } - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - Este perfil que estás a punto de suprimir parece estar roto o la ruta no es válida. Estoy a punto de suprimir la carpeta siguiente: "%1". ¿Continuar? + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Este perfil que estás a punto de suprimir parece estar roto o la ruta no es válida. Estoy a punto de suprimir la carpeta siguiente: "%1". ¿Continuar? @@ -3931,23 +4279,23 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - nombre de campo no válido "%1" + invalid field name "%1" + nombre de campo no válido "%1" - invalid type for "%1" (should be integer) - tipo no válido para "%1" (debe ser un número entero) + invalid type for "%1" (should be integer) + tipo no válido para "%1" (debe ser un número entero) - invalid type for "%1" (should be string) - tipo no válido para "%1" (debe ser string) + invalid type for "%1" (should be string) + tipo no válido para "%1" (debe ser string) - invalid type for "%1" (should be float) - tipo no válido para "%1" (debe ser float) + invalid type for "%1" (should be float) + tipo no válido para "%1" (debe ser float) @@ -3956,13 +4304,13 @@ p, li { white-space: pre-wrap; } - field not set "%1" - campo no ajustado "%1" + field not set "%1" + campo no ajustado "%1" - invalid character in field "%1" - carácter inválido en el ancho del campo "%1" + invalid character in field "%1" + carácter inválido en el ancho del campo "%1" @@ -4010,7 +4358,7 @@ p, li { white-space: pre-wrap; } Failed to deactivate script extender loading - Fallo al desactivar el "Script Extender" + Fallo al desactivar el "Script Extender" @@ -4056,87 +4404,91 @@ p, li { white-space: pre-wrap; } Fallo al configurar la carga por proxy-dll - + Permissions required Se requieren permisos - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - La cuenta de usuario actual no tiene los permisos de acceso requeridos para ejecutar Mod Organizer. Los cambios necesarios se pueden hacer de forma automática (el directorio MO hará escritura para la cuenta de usuario actual). Se le pedirá ejecutar "helper.exe" con derechos administrativos. + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + La cuenta de usuario actual no tiene los permisos de acceso requeridos para ejecutar Mod Organizer. Los cambios necesarios se pueden hacer de forma automática (el directorio MO hará escritura para la cuenta de usuario actual). Se le pedirá ejecutar "helper.exe" con derechos administrativos. - - + + Woops Woops - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened ¡ModOrganizer se ha estrellado! ¿Se debe crear un archivo de diagnóstico? Si me envía el fichero (%1) a sherb@gmx.net, el error es mucho más probable que se arregle. Por favor, incluya una breve descripción de lo que estaba haciendo cuando ocurrió el accidente - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 ¡ModOrganizer se ha estrellado! Lamentablemente no fue capaz de escribir un archivo de diagnóstico: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Ya se está ejecutando una instancia de Mod Organizer - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - Juego no identificado en "%1". Se requiere que el directorio contenga el binario del juego y su lanzador. + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + Juego no identificado en "%1". Se requiere que el directorio contenga el binario del juego y su lanzador. - - + + Please select the game to manage Por favor seleccione el juego - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) Por favor selecciona la edición del juego que tienes (MO no puede iniciar el juego correctamente si esto está mal ajustado!) - - Please use "Help" from the toolbar to get usage instructions to all elements - Por favor utilice "Ayuda" en la barra superior para obtener informacion sobre todos los elementos + + failed to start application: %1 + - - + + Please use "Help" from the toolbar to get usage instructions to all elements + Por favor utilice "Ayuda" en la barra superior para obtener informacion sobre todos los elementos + + + + <Manage...> <Definir...> - + failed to parse profile %1: %2 no se pudo analizar el perfil % 1: %2 - - - failed to find "%1" + + failed to find "%1" fallo al encontrar %1 - + failed to access %1 Fallo al acceder %1 - + failed to set file time %1 Fallo al definir la hora al fihcero %1 @@ -4147,8 +4499,9 @@ p, li { white-space: pre-wrap; } - "%1" is missing - "%1" no encontrado + "%1" is missing or inaccessible + "%1" is missing + "%1" no encontrado @@ -4174,62 +4527,62 @@ p, li { white-space: pre-wrap; } Fallo al abrir %1 - + Script Extender Script Extender - + Proxy DLL Proxy DLL - - failed to spawn "%1" - Fallo al crear "%1" + + failed to spawn "%1" + Fallo al crear "%1" - + Elevation required Elevación requerida - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) Este proceso require elevación tal iniciar. Este es un riesgo potencial para la seguridad, así que aconsejo vivamente investigarlo -"%1" +"%1" puede ser instalado para trabajar sin elevación. ¿Comenzar elevación de todos modos? (se le preguntará si desea permitir a Mod Organizer.exe realizar cambios en el sistema) - - failed to spawn "%1": %2 - Fallo al crear "%1": %2 + + failed to spawn "%1": %2 + Fallo al crear "%1": %2 - - "%1" doesn't exist - "%1% no existe + + "%1" doesn't exist + "%1% no existe - - failed to inject dll into "%1": %2 - Fallo al injectar la dll en "%1": %2 + + failed to inject dll into "%1": %2 + Fallo al injectar la dll en "%1": %2 - - failed to run "%1" + + failed to run "%1" Fallo al abrir %1 - + failed to open temporary file Fallo al abrir el archivo temporal @@ -4342,8 +4695,8 @@ puede ser instalado para trabajar sin elevación. - failed to open "%1" for writing - Fallo al abrir "%1 para escritura + failed to open "%1" for writing + Fallo al abrir "%1 para escritura @@ -4368,79 +4721,79 @@ puede ser instalado para trabajar sin elevación. SelfUpdater - archive.dll not loaded: "%1" - archive.dll no cargado: "%1" + archive.dll not loaded: "%1" + archive.dll no cargado: "%1" - - - - + + + + Update Actualización - + An update is available (newest version: %1), do you want to install it? Hay disponible una actualización (versión más reciente: %1), desea instalarlo? - + Download in progress Descarga en progreso - + Download failed: %1 Fallo en la descarga: %1 - + Failed to install update: %1 No se pudo instalar la actualización: %1 - - failed to open archive "%1": %2 - error al abrir el archivo "%1": %2 + + failed to open archive "%1": %2 + error al abrir el archivo "%1": %2 - + failed to move outdated files: %1. Please update manually. Fallo al mover archivos obsoletos: %1. Por favor, actualice manualmente. - + Update installed, Mod Organizer will now be restarted. Actualización instalada, Ahora se reiniciará la Mod Organizer. - + Error Error - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. No se ha podido analizar la respuesta. Por favor, informa de este error e incluir archivo mo_interface.log. - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) Ninguna actualización incremental disponible para esta versión, el paquete completo tiene que ser descargado (%1 kB) - + no file for update found. Please update manually. ningún archivo de actualización encontrado. Por favor, actualice manualmente. - + Failed to retrieve update information: %1 Fallo al recuperar información de actualización: %1 - + No download server available. Please try again later. No hay ningún servidor de descarga disponible. Por favor, inténtelo de nuevo más tarde. @@ -4448,18 +4801,18 @@ puede ser instalado para trabajar sin elevación. Settings - - - attempt to store setting for unknown plugin "%1" - tratando de almacenar la configuración para plugin desconocido "%1" + + + attempt to store setting for unknown plugin "%1" + tratando de almacenar la configuración para plugin desconocido "%1" - + Confirm Confirmar - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? ¡Cambiar el directorio mod afecta a todos los perfiles! Mods que no están presentes (o de nombres diferentes) en la nueva ubicación se desactivarán en todos los perfiles. No hay manera de deshacer esto a menos que se realice la copia de seguridad de los perfiles manualmente. ¿Proceder? @@ -4488,16 +4841,16 @@ puede ser instalado para trabajar sin elevación. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Idioma de pantalla. Esto sólo displaya idiomas para los que tienen una traducción instalada.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Idioma de pantalla. Esto sólo displaya idiomas para los que tienen una traducción instalada.</span></p></body></html> @@ -4521,15 +4874,15 @@ p, li { white-space: pre-wrap; } - Decides the amount of data printed to "ModOrganizer.log" - Decide la cantidad de datos impresos a "ModOrganizer.log" + Decides the amount of data printed to "ModOrganizer.log" + Decide la cantidad de datos impresos a "ModOrganizer.log" - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - Decide la cantidad de datos impresos a "ModOrganizer.log". -"Depurar" produce información muy útil para encontrar problemas. Generalmente, no hay impacto notable en el rendimiento, pero el fichero puede llegar a ser bastante grande. Si esto es un problema que puedes preferir el nivel "Info" para uso regluar. En el nivel "Error" el fichero de registro por lo general permanece vacío. + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Decide la cantidad de datos impresos a "ModOrganizer.log". +"Depurar" produce información muy útil para encontrar problemas. Generalmente, no hay impacto notable en el rendimiento, pero el fichero puede llegar a ser bastante grande. Si esto es un problema que puedes preferir el nivel "Info" para uso regluar. En el nivel "Error" el fichero de registro por lo general permanece vacío. @@ -4569,7 +4922,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). Directorio donde se almacenan los mods. Tenga en cuenta que el cambio de esto romperá todas las asociaciones de perfiles con los mods que no existen en la nueva ubicación (con el mismo nombre). @@ -4582,297 +4935,340 @@ p, li { white-space: pre-wrap; } Cache Directory Directorio de Caché + + + User interface + + + If checked, the download interface will be more compact. + + + + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + Reset stored information from dialogs. Reiniciar la información almacenada de los diálogos. - - This will make all dialogs show up again where you checked the "Remember selection"-box. - Esto hará que todos los diálogos se vuelven a mostrar al marcar la opción "Recordar selección"-box. + + This will make all dialogs show up again where you checked the "Remember selection"-box. + Esto hará que todos los diálogos se vuelven a mostrar al marcar la opción "Recordar selección"-box. - + Reset Dialogs Restablecer Diálogos - - + + Modify the categories available to arrange your mods. Modificar las categorías disponibles para organizar tus mods. - + Configure Mod Categories Configurar categorías Mod - - + + Nexus Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. Permite el incio automatico en Nexus al seleccionar el juego. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Permite registro de entrada automático cuando se hace clic en la Página-Nexus para el juego. Ten en cuenta que la ofuscación con la que la contraseña se almacena en Modorganizer.ini no es muy fuerte. Si te preocupa que alguien pueda robar tu contraseña, no la guardes aquí.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Permite registro de entrada automático cuando se hace clic en la Página-Nexus para el juego. Ten en cuenta que la ofuscación con la que la contraseña se almacena en Modorganizer.ini no es muy fuerte. Si te preocupa que alguien pueda robar tu contraseña, no la guardes aquí.</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. Si está activada y si se introducen las credenciales correctas, inicia una sesión en Nexus (para navegar y descargar) es automático. - + Automatically Log-In to Nexus Inicio automatico en Nexus - + Username Usuario - + Password Contraseña - + Disable automatic internet features Deshabilitar funciones automáticas de Internet - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) Deshabilita funciones automáticas de Internet. Esto no afecta a las funciones que se invocan explícitamente por el usuario (como el control de mods para las actualizaciones, avalar, abrir el navegador web) - + Offline Mode Modo Desconectado - + Use a proxy for network connections. Utilizar un proxy para las conexiones de red. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. Utilizar un proxy para las conexiones de red. Esto utiliza la configuración de todo el sistema que puede configurarse en Internet Explorer. Ten en cuenta que MO se iniciará unos segundos más lento en algunos sistemas cuando se utiliza un proxy. - + Use HTTP Proxy (Uses System Settings) Usar HTTP Proxy (Usa configuración del sistema) - - Associate with "Download with manager" links - Asociar con "Download Manager" links + + Associate with "Download with manager" links + Asociar con "Download Manager" links - + Known Servers (updated on download) Servidores conocidos (descarga actualizada) - + Preferred Servers (Drag & Drop) Servidores preferidos (Arrastrar y soltar) - + Plugins Plugins - + Author: Autor: - + Version: Versión: - + Description: Descripción - + Key Tecla - + Value Valor - + Blacklisted Plugins (use <del> to remove): Lista negra de Plugins (usa <supr> para eliminar): - + Workarounds Soluciones - + Steam App ID Steam App ID - + The Steam AppID for your game El AppID de tu juego - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">El valor predeterminado para esto es el App ID de la &quot;regular &quot; versión por lo que en la mayoría de los casos, debe ser fijado.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si piensas que tienes una versión diferente (GotY o algo así), sigue estos pasos para llegar a la id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Vaya a la biblioteca de juegos en steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. haz clic derecho en el juego que necesita el id para elegir y en </span><span style=" font-size:8pt; font-weight:600;">Crear acceso directo al escritorio</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. haz clic en el acceso directo recién creado en el escritorio y selecciona </span><span style=" font-size:8pt; font-weight:600;">Propiedades</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. en el campo URL deberías ver algo como esto: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 es el ID que estás buscando.</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">El valor predeterminado para esto es el App ID de la &quot;regular &quot; versión por lo que en la mayoría de los casos, debe ser fijado.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si piensas que tienes una versión diferente (GotY o algo así), sigue estos pasos para llegar a la id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Vaya a la biblioteca de juegos en steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. haz clic derecho en el juego que necesita el id para elegir y en </span><span style=" font-size:8pt; font-weight:600;">Crear acceso directo al escritorio</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. haz clic en el acceso directo recién creado en el escritorio y selecciona </span><span style=" font-size:8pt; font-weight:600;">Propiedades</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. en el campo URL deberías ver algo como esto: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 es el ID que estás buscando.</span></p></body></html> + + + Load Mechanism Mecamismo de carga - + Select loading mechanism. See help for details. Selecciona la forma de cargar. Mira la ayuda para detalles. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. Mod Organizer necesita una dll que se inyecta en el juego para que todos los mods sean visibles a ella. Hay varios medios de hacer esto: *Mod Organizer* (Defecto en este modo el propio Mod Organizer inyecta la dll. La desventaja es que siempre tienes que iniciar el juego a través de MO o un enlace creado por. *Script Extender* En este modo, MO se instala como Script Extender (OBSE, FOSE, NVSE, skse) Plugin. *Proxy DLL* En este modo, MO sustituye a uno de los archivos DLL del juego con uno que carga MO (y el archivo DLL original, por supuesto). Esto sólo funcionará con los juegos de Steam y sólo se ha probado con Skyrim. Utilice esta opción sólo si los otros mecanismos no funcionan. -Si utilizas la versión Steam de Oblivion por defecto NO funcionará. En este caso, por favor, instala obse y usa "Script Extender" como mecanismo de carga. También no podras iniciar Oblivion desde MO. En su lugar, usa MO sólo para configurar los mods, a continuación, comenzar Oblivion a través de Steam. +Si utilizas la versión Steam de Oblivion por defecto NO funcionará. En este caso, por favor, instala obse y usa "Script Extender" como mecanismo de carga. También no podras iniciar Oblivion desde MO. En su lugar, usa MO sólo para configurar los mods, a continuación, comenzar Oblivion a través de Steam. - + NMM Version NMM Version - + The Version of Nexus Mod Manager to impersonate. La versión de Nexus Mod Manager para suplantar. - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer utiliza una API proporcionada por Nexus para proporcionar características como la comprobación de actualizaciones y descarga de archivos. Por desgracia, esta API no ha sido puesta a disposición oficialmente a terceros, como MO por lo que tenemos que pasar por el Nexus Mod Manager para ser permitido. En la parte superior de Nexus ha utilizado la identificación de los clientes para bloquear versiones no actualizadas del NMM y obligar a los usuarios a actualizarlo. Esto significa que la OM también tiene que hacerse pasar por la nueva versión de NMM aunque MO no necesita una actualización. Por lo tanto, puedes configurar la versión de identificar como aquí. -Ten en cuenta que MO sí identifica a sí misma como MO al servidor web, no está mintiendo acerca de lo que es. Simplemente es la adición de una versión NMM "compatible" con el agente de usuario. +Ten en cuenta que MO sí identifica a sí misma como MO al servidor web, no está mintiendo acerca de lo que es. Simplemente es la adición de una versión NMM "compatible" con el agente de usuario. tl;dr-version: Si Nexus-features no funciona, introduzca el número de la versión actual de NMM aquí y vuelva a intentarlo. - + Enforces that inactive ESPs and ESMs are never loaded. Se asegura que no se cargan nunca los ESPs y ESMs inactivos. - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Parece que los juegos ocasionalmente cargan archivos ESP o ESM, incluso si ellos todavía no se ha activado como plugins. No, pero sabe cuáles son las circunstancias, pero informes de usuario implican en algunos casos no deseados. Si esto se comprueba, ESPs ESMs no marcadas en la lista son invisibles para el juego y no se pueden cargar. - + Hide inactive ESPs/ESMs Ocultar ESPs/ESMs inactivos - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Si está marcado, los ficheros (es decir, ESP, ESM y bsas) pertenecientes al núcleo del juego no se pueden desactivar en la interfaz de usuario. (por defecto: activado) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. Si está marcado, los archivos (es decir, esps, esms y bsas) pertenecientes al núcleo del juego no se pueden desactivar en la interfaz de usuario. (por defecto: activado) Desactiva esta opción si deseas utilizar Mod Organizer con conversiones totales (como Nehrim) pero ten en cuenta que el juego se colgará si algún archivo necesario no están habilitados. - + Force-enable game files Fuerza a habilitar archivos del juego - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! Para Skyrim, esto puede ser usado en lugar del Archivo invalidación. Se debe marcar AI redundante para todos los perfiles. Para el resto de los juegos no es un sustituto suficiente para AI - + Back-date BSAs Fecha Respaldo BSAs - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. Estas son soluciones para los problemas con Mod Organizer. Por favor, asegúrese de leer el texto de ayuda antes de cambiar nada aquí. @@ -4898,8 +5294,8 @@ Para el resto de los juegos no es un sustituto suficiente para AI - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - Esto hará que todos los diálogos se vuelven a mostrar cuando marcó la opción "Recordar selección"-box. ¿Desea continuar? + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + Esto hará que todos los diálogos se vuelven a mostrar cuando marcó la opción "Recordar selección"-box. ¿Desea continuar? @@ -4945,10 +5341,14 @@ Para el resto de los juegos no es un sustituto suficiente para AI - failed to connect to running instance: %1 Error al conectarse a la instancia en ejecución: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4974,7 +5374,7 @@ Para el resto de los juegos no es un sustituto suficiente para AI - <don't sync> + <don't sync> <No sincronizar> @@ -5082,8 +5482,8 @@ C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - Overwrite the file "%1" - Sobrescribir el fichero "%1" + Overwrite the file "%1" + Sobrescribir el fichero "%1" @@ -5096,18 +5496,18 @@ C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - Copy all save games of character "%1" to the profile? - Copiar todos los caracteres del juego salvado "%1" para el perfil? + Copy all save games of character "%1" to the profile? + Copiar todos los caracteres del juego salvado "%1" para el perfil? - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. diff --git a/src/organizer_fr.ts b/src/organizer_fr.ts index 68caa4e1..4671d9e8 100644 --- a/src/organizer_fr.ts +++ b/src/organizer_fr.ts @@ -1,5 +1,50 @@ + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + Fermer + + + + No license + + + ActivateModsDialog @@ -14,22 +59,22 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une liste des fichiers ESPs et ESMs actifs lors de la création de la sauvegarde.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pour chaque esp, la colonne de droite contient le (ou les) mod(s) pouvant être activé(s) afin de rendre les esps/esms manquants disponibles.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez le bouton [OK], tous les mods sélectionnés dans les colonnes de droite et tous les esps manquants qui deviendront disponibles seront activés.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une liste des fichiers ESPs et ESMs actifs lors de la création de la sauvegarde.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pour chaque esp, la colonne de droite contient le (ou les) mod(s) pouvant être activé(s) afin de rendre les esps/esms manquants disponibles.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez le bouton [OK], tous les mods sélectionnés dans les colonnes de droite et tous les esps manquants qui deviendront disponibles seront activés.</span></p></body></html> @@ -52,7 +97,7 @@ p, li { white-space: pre-wrap; } BAIN Package Installer - Installateur de paquet BAIN ("Wrye Bash" et assimilés) + Installateur de paquet BAIN ("Wrye Bash" et assimilés) @@ -72,9 +117,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. Composants de ce paquet. -Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les options sont classées par priorités, telles que définies par l'auteur. +Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les options sont classées par priorités, telles que définies par l'auteur. @@ -109,6 +154,29 @@ Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les op Annuler + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -129,7 +197,7 @@ Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les op Internal ID for the category. The categories a mod belongs to are stored by this ID. It is recommended you use new IDs for categories you add instead of re-using existing ones. - ID interne de la catégorie. Les catégories auxquelles un mod appartient sont stockées grâce à cet ID. Il est recommandé d'utiliser de nouvelles ID pour les catégories que vous ajoutez plutôt que de réutiliser celles déjà existantes. + ID interne de la catégorie. Les catégories auxquelles un mod appartient sont stockées grâce à cet ID. Il est recommandé d'utiliser de nouvelles ID pour les catégories que vous ajoutez plutôt que de réutiliser celles déjà existantes. @@ -140,7 +208,7 @@ Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les op Name of the Categorie used for display. - Nom de la catégorie, tel qu'il sera affiché. + Nom de la catégorie, tel qu'il sera affiché. @@ -150,24 +218,24 @@ Si un composant est nommée "00 Core"; il est habituellement nécessaire. Les op Comma-Separated list of Nexus IDs to be matched to the internal ID. - Liste séparée par des virgules des IDs Nexus qui seront assignées à l'ID interne. + Liste séparée par des virgules des IDs Nexus qui seront assignées à l'ID interne. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vous pouvez assigner une ou plusieurs catégories Nexus à une ID interne. Lorsqu'un mod est téléchargé d'une page Nexus, Mod Organizer (MO) tentera de convertir la catégorie définie sur Nexus en une catégorie disponible dans celui-ci.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pour connaître les IDs de catégories utilisées sur Nexus, visitez la liste des catégories sur le site et survolez les liens qui s'y trouvent.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vous pouvez assigner une ou plusieurs catégories Nexus à une ID interne. Lorsqu'un mod est téléchargé d'une page Nexus, Mod Organizer (MO) tentera de convertir la catégorie définie sur Nexus en une catégorie disponible dans celui-ci.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pour connaître les IDs de catégories utilisées sur Nexus, visitez la liste des catégories sur le site et survolez les liens qui s'y trouvent.</span></p></body></html> @@ -177,7 +245,7 @@ p, li { white-space: pre-wrap; } If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. - Si présente, la catégorie est définie comme une sous-catégorie du parent. L'ID parent doit être une ID de catégorie valide. + Si présente, la catégorie est définie comme une sous-catégorie du parent. L'ID parent doit être une ID de catégorie valide. @@ -199,13 +267,13 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus Cette fonction pourrais être inutilisable à moins que vous soyez connecté à Nexus Username - Nom d'utilisateur + Nom d'utilisateur @@ -226,7 +294,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 Échec de lecture bsa: %1 @@ -234,24 +302,29 @@ p, li { white-space: pre-wrap; } DownloadList - + Name Nom - + Filetime Date du fichier - + Done Terminé - - Information missing, please select "Query Info" from the context menu to re-retrieve. - Information manquante; veuillez sélectionner "Demander info" dans le menu contextuel pour les récupérer. + + Information missing, please select "Query Info" from the context menu to re-retrieve. + Information manquante; veuillez sélectionner "Demander info" dans le menu contextuel pour les récupérer. + + + + pending download + @@ -264,26 +337,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Terminé - Double-cliquer pour installer - - + + Paused - Double Click to resume En pause - Double-cliquez pour reprendre - - + + Installed - Double Click to re-install Installé - Double-cliquer pour réinstaller - - + + Uninstalled - Double Click to re-install Désinstallé - Double-cliquer pour réinstaller @@ -304,6 +377,16 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + Paused @@ -335,95 +418,95 @@ p, li { white-space: pre-wrap; } Terminé - - - - + + + + Are you sure? Êtes-vous certain? - + This will remove all finished downloads from this list and from disk. Ceci supprimera tous les téléchargements complétés de la liste et du disque. - + This will remove all installed downloads from this list and from disk. Ceci supprimera tous les téléchargements installés de cette liste et du disque. - + This will permanently remove all finished downloads from this list (but NOT from disk). Ceci supprimera tous les téléchargements terminés de cette liste (mais PAS du disque). - + This will permanently remove all installed downloads from this list (but NOT from disk). Ceci supprimera tous les téléchargements installés de cette liste (mais PAS du disque). - + Install Installer - + Query Info Demander info - + Delete Supprimer - + Un-Hide - + - + Remove from View Enlever de la liste - + Cancel Annuler - + Pause Pause - + Remove Supprimer - + Resume Reprendre - + Delete Installed... Supprimer ceux installés... - + Delete All... Tout supprimer - + Remove Installed... Supprimer ceux installés... - + Remove All... Tout supprimer... @@ -431,105 +514,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + + < mod %1 file %2 > + + + + + Pending + + + + Fetching Info 1 Récupération des informations 1 - + Fetching Info 2 Récupération des informations 2 - - - - + + + + Are you sure? Êtes-vous certain? - + This will remove all finished downloads from this list and from disk. Ceci supprimera tous les téléchargements complétés de la liste et du disque. - + This will remove all installed downloads from this list and from disk. Ceci supprimera tous les téléchargements installés de la liste et du disque. - + This will remove all finished downloads from this list (but NOT from disk). Ceci supprimera tous les téléchargements complétés de cette liste (mais PAS du disque). - + This will remove all installed downloads from this list (but NOT from disk). Ceci supprimera tous les téléchargements installés de cette liste (mais PAS du disque). - + Install Installer - + Query Info Récupérer info - + Delete Supprimer - + Un-Hide - + - + Remove from View Enlever de la liste - + Cancel Annuler - + Pause Mettre en pause - + Remove Enlever - + Resume Continuer - + Delete Installed... Supprimer ceux installés... - + Delete All... Tout supprimer... - + Remove Installed... Enlever de la liste ceux installés... - + Remove All... Enlever tout de la liste... @@ -537,116 +630,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - Impossible de renommer "%1" en "%2" + + failed to rename "%1" to "%2" + Impossible de renommer "%1" en "%2" - + + Memory allocation error (in refreshing directory). + + + + Download again? Télécharger à nouveau ? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Un fichier avec le même nom a déjà été téléchargé. Voulez vous le télécharger à nouveau ? Le nouveau fichier aura un nom différent. - + failed to download %1: could not open output file: %2 - impossible de télécharger %1: impossible d'écrire le fichier: %2 + impossible de télécharger %1: impossible d'écrire le fichier: %2 - + Wrong Game - + - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + - - - - - - - - - - - + + - - + + + + + + + + + + + + + invalid index index invalide - + failed to delete %1 impossible de supprimer %1 - + failed to delete meta file for %1 impossible de supprimer le méta-fichier pour %1 - - - - - - + + + + + + invalid index %1 index invalide %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id - Veuillez entre l'ID Nexus du mod + Veuillez entre l'ID Nexus du mod - + Mod ID: ID du mod: - + + Main + Principal + + + + Update + + + + + Optional + Optionnel + + + + Old + Ancien + + + + Misc + Divers + + + + Unknown + Inconnu + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated Information mise à jour - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - Aucun fichier correspondant trouvé sur Nexus! Peut-être ce fichier n'est-il plus disponible ou a été renommé? + Aucun fichier correspondant trouvé sur Nexus! Peut-être ce fichier n'est-il plus disponible ou a été renommé? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - Aucun fichier sur Nexus ne correspond au nom du fichier sélectionné. Veuillez s'il-vous-plaît sélectionner le bon manuellement. + Aucun fichier sur Nexus ne correspond au nom du fichier sélectionné. Veuillez s'il-vous-plaît sélectionner le bon manuellement. - + No download server available. Please try again later. Aucun serveur de téléchargement disponible. Veuillez réessayer plus tard. - + Failed to request file info from nexus: %1 - Impossible de demander l'info du fichier sur Nexus: %1 + Impossible de demander l'info du fichier sur Nexus: %1 + + + + Download failed. Server reported: %1 + - + Download failed: %1 (%2) Téléchargement échoué: %1 (%2) - + failed to re-open %1 Échec lors de la tentative de réouverture %1 @@ -698,7 +848,7 @@ p, li { white-space: pre-wrap; } Browse filesystem for the executable to run. - Parcourir le système de fichiers pour l'exécutable à lancer + Parcourir le système de fichiers pour l'exécutable à lancer @@ -725,15 +875,15 @@ p, li { white-space: pre-wrap; } Allow the Steam AppID to be used for this executable to be changed. - Permet de modifier l'AppID Steam à utiliser pour cet exécutable. + Permet de modifier l'AppID Steam à utiliser pour cet exécutable. Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - Permet de modifier l'AppID Steam à utiliser pour cet exécutable. -Chaque jeu ou utilitaire distribué à travers Steam possède un identifiant (ID) unique. MO à besoin de connaitre cet ID pour démarrer ces programmes directement, sinon ceux-ci le seront par Steam et MO ne fonctionnera pas. Par défaut, MO utilisera l'AppID du jeu. + Permet de modifier l'AppID Steam à utiliser pour cet exécutable. +Chaque jeu ou utilitaire distribué à travers Steam possède un identifiant (ID) unique. MO à besoin de connaitre cet ID pour démarrer ces programmes directement, sinon ceux-ci le seront par Steam et MO ne fonctionnera pas. Par défaut, MO utilisera l'AppID du jeu. Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creation Kit pour Skyrim, qui possède sa propre AppID. Cette modification est déjà pré-configurée. @@ -744,21 +894,21 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Steam AppID to use for this executable that differs from the games AppID. - l'AppID Steam utilisé pour cet exécutable est différent des AppID de jeux. + l'AppID Steam utilisé pour cet exécutable est différent des AppID de jeux. Steam AppID to use for this executable that differs from the games AppID. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. - L'AppID Steam à utiliser pour cet exécutable, différente de l'AppID du jeu. -Chaque jeu ou utilitaire distribué à travers Steam possède un identifiant (ID) unique. MO à besoin de connaitre cet ID pour démarrer ces programmes directement, sinon ceux-ci le seront par Steam et MO ne fonctionnera pas. Par défaut, MO utilisera l'AppID du jeu (normalement 72850). + L'AppID Steam à utiliser pour cet exécutable, différente de l'AppID du jeu. +Chaque jeu ou utilitaire distribué à travers Steam possède un identifiant (ID) unique. MO à besoin de connaitre cet ID pour démarrer ces programmes directement, sinon ceux-ci le seront par Steam et MO ne fonctionnera pas. Par défaut, MO utilisera l'AppID du jeu (normalement 72850). Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creation Kit pour Skyrim, qui possède son propre AppID (normalement 202480). Cette modification est déjà pré-configurée. - + If checked, MO will be closed once the specified executable is run. Si cochée, MO sera fermé une fois que le programme sélectionné sera démarré. @@ -775,7 +925,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat - + Add Ajouter @@ -791,47 +941,64 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Enlever - + + Close + Fermer + + + Select a binary Choisir un exécutable - + Executable (%1) Executable (%1) - + Java (32-bit) required Java (32-bit) nécessaire - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - MO nécessite java 32-bit pour fonctionner. Si vous l'avez déjà installé, selectionnez le javaw.exe de son installation comme binaire. + MO nécessite java 32-bit pour fonctionner. Si vous l'avez déjà installé, selectionnez le javaw.exe de son installation comme binaire. - + Select a directory Sélectionnez un répertoire - + Confirm Confirmer - - Really remove "%1" from executables? - Êtes-vous sûr de vouloir retirer "%1" des programmes ? + + Really remove "%1" from executables? + Êtes-vous sûr de vouloir retirer "%1" des programmes ? - + Modify Modifier - + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + MO must be kept running or this application will not work correctly. MO doit continuer à fonctionner ou cette application ne fonctionnera pas correctement. @@ -858,7 +1025,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Find next occurence from current file position. - Rechercher le suivant à partir de l'emplacement présent. + Rechercher le suivant à partir de l'emplacement présent. @@ -878,7 +1045,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat FOMOD Installation Dialog - Boite de dialogue d'installation FOMOD + Boite de dialogue d'installation FOMOD @@ -902,8 +1069,8 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat - <a href="#">Link</a> - <a href="#">Lien</a> + <a href="#">Link</a> + <a href="#">Lien</a> @@ -950,8 +1117,8 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. - Choisissez un nom pour le mod. Ce nom sera aussi utilisé comme nom de dossier, n'utilisez donc pas de caractères invalides dans les noms de fichiers s'il-vous-plait. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Choisissez un nom pour le mod. Ce nom sera aussi utilisé comme nom de dossier, n'utilisez donc pas de caractères invalides dans les noms de fichiers s'il-vous-plait. @@ -961,20 +1128,20 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking... - Contenu de l'archive. Vous pouvez modifier la structure en utilisant le glisser-&déposer. Indice: Essayez aussi le clic-droit... + Contenu de l'archive. Vous pouvez modifier la structure en utilisant le glisser-&déposer. Indice: Essayez aussi le clic-droit... - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci affiche le contenu de l'archive. &lt;data&gt; représente le dossier de base qui sera mappé au dossier DATA du jeu. Vous pouvez modifier le dossier de base grâce au menu contextuel et vous pouvez déplacer les fichiers grâce au glisser-déposer</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci affiche le contenu de l'archive. &lt;data&gt; représente le dossier de base qui sera mappé au dossier DATA du jeu. Vous pouvez modifier le dossier de base grâce au menu contextuel et vous pouvez déplacer les fichiers grâce au glisser-déposer</span></p></body></html> @@ -996,8 +1163,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - archive.dll n'est pas chargé: "%1" + archive.dll not loaded: "%1" + archive.dll n'est pas chargé: "%1" @@ -1012,7 +1179,7 @@ p, li { white-space: pre-wrap; } - + Extracting files Extraction des fichiers @@ -1042,59 +1209,59 @@ p, li { white-space: pre-wrap; } Le nom que vous avez entré est invalide, essayez-en un autre SVP. - - File format "%1" not supported - Format de fichier "%1" non supporté + + File format "%1" not supported + Format de fichier "%1" non supporté - + None of the available installer plugins were able to handle that archive - Aucun des plugins installés n'arrive à traiter cette archive + Aucun des plugins installés n'arrive à traiter cette archive - + no error aucune erreur - + 7z.dll not found 7z.dll introuvable - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll invalide - + archive not found archive introuvable - + failed to open archive - impossible d'ouvrir l'archive + impossible d'ouvrir l'archive - + unsupported archive type - type d'archive non supporté + type d'archive non supporté - + internal library error erreur de bibliothèque interne - + archive invalid archive invalide - + unknown archive error - erreur d'archive inconnue + erreur d'archive inconnue @@ -1106,16 +1273,16 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - Cette boite de dialogue devrait disparaître automatiquement une fois le programme ou jeu terminé. Sinon, cliquer "Déverrouiller". + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + Cette boite de dialogue devrait disparaître automatiquement une fois le programme ou jeu terminé. Sinon, cliquer "Déverrouiller". MO is locked while the executable is running. - MO est verrouillé pendant que le programme s'exécute. + MO est verrouillé pendant que le programme s'exécute. - + Unlock Déverrouiller @@ -1123,20 +1290,20 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 - Impossible d'écrire le log %1: %2 + Impossible d'écrire le log %1: %2 MOApplication - + an error occured: %1 Une erreur est survenue : %1 - + an error occured une erreur est survenue @@ -1144,1305 +1311,1544 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories Catégories + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + Profile Profil - + Pick a module collection Choisissez un profil - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Créez les profils ici. Chaque profil contient sa propre liste de mods et d'ESPs activés. Vous pouvez ainsi basculer rapidement entre les configurations pour différentes parties.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Veuillez noter que pour l'instant, l'ordre de chargement des ESPs est commun à tous les profils.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Créez les profils ici. Chaque profil contient sa propre liste de mods et d'ESPs activés. Vous pouvez ainsi basculer rapidement entre les configurations pour différentes parties.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Veuillez noter que pour l'instant, l'ordre de chargement des ESPs est commun à tous les profils.</span></p></body></html> - Refresh list - Actualiser la liste + Actualiser la liste - + Refresh list. This is usually not necessary unless you modified data outside the program. - Actualiser la liste. Normalement inutile à moins que vous n'ayez modifié des données à l'extérieur du programme. + Actualiser la liste. Normalement inutile à moins que vous n'ayez modifié des données à l'extérieur du programme. - + List of available mods. Liste des mods disponibles. - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - Voici la liste des mods installés. Utilisez les cases à cocher pour activer/désactiver les mods ainsi que le glisser & déposer pour modifier leur séquence d' "installation". + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + Voici la liste des mods installés. Utilisez les cases à cocher pour activer/désactiver les mods ainsi que le glisser & déposer pour modifier leur séquence d' "installation". - + Filter Filtre - + No groups Aucun groupe - + Nexus IDs IDs Nexus - - - + + + Namefilter Filtre de nom - + Pick a program to run. Choisissez un programme à lancer. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choisissez le programme à exécuter. Une fois que vous utilisez Mod Organizer, vous devriez exécuter votre jeu et tous les outils reliés à partir d'ici ou via un raccourci créé par MO, sinon les mods installés par MO risquent de ne pas être visibles.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vous pouvez ajouter d'autres outils à la liste, mais je ne peut garantir que les outils que je n'ai pas testé fonctionneront.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choisissez le programme à exécuter. Une fois que vous utilisez Mod Organizer, vous devriez exécuter votre jeu et tous les outils reliés à partir d'ici ou via un raccourci créé par MO, sinon les mods installés par MO risquent de ne pas être visibles.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Vous pouvez ajouter d'autres outils à la liste, mais je ne peut garantir que les outils que je n'ai pas testé fonctionneront.</span></p></body></html> - + Run program Lancer le programme - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Exécuter le programme sélectionné avec ModOrganizer actif.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Exécuter le programme sélectionné avec ModOrganizer actif.</span></p></body></html> - + Run Lancer - + Create a shortcut in your start menu or on the desktop to the specified program Crée un raccourci dans votre menu démarrer, ou sur le bureau, pour le programme spécifié - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Crée un raccourci dans le menu Démarrer qui lance directement le programme sélectionné avec MO actif.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Crée un raccourci dans le menu Démarrer qui lance directement le programme sélectionné avec MO actif.</span></p></body></html> - + Shortcut Raccourci - + List of available esp/esm files Liste des fichiers ESP/ESM disponibles - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Cette liste contient tous les ESPs et ESMs contenus dans les mods actifs. Ceux-ci requièrent leur propre ordre de chargement. Utilisez le glisser-déposer pour modifier cet ordre. Veuillez noter que MO enregistre l'ordre de chargement seulement pour les mods actif/cochés.<br />Il y a un excellent outil nommé &quot;BOSS&quot; qui classe automatiquement ces fichiers.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Cette liste contient tous les ESPs et ESMs contenus dans les mods actifs. Ceux-ci requièrent leur propre ordre de chargement. Utilisez le glisser-déposer pour modifier cet ordre. Veuillez noter que MO enregistre l'ordre de chargement seulement pour les mods actif/cochés.<br />Il y a un excellent outil nommé &quot;BOSS&quot; qui classe automatiquement ces fichiers.</span></p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - + - - + + File Fichier - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - - - - + Data DATA - + refresh data-directory overview - Actualiser la vue d'ensemble du dossier DATA + Actualiser la vue d'ensemble du dossier DATA - + Refresh the overview. This may take a moment. - Actualiser la vue d'ensemble. Ceci peut demander un moment. + Actualiser la vue d'ensemble. Ceci peut demander un moment. - - - + + + Refresh Actualiser - + This is an overview of your data directory as visible to the game (and tools). - Ceci est une vue d'ensemble du dossier DATA tel qu'il apparaît pour le jeu (et les outils). + Ceci est une vue d'ensemble du dossier DATA tel qu'il apparaît pour le jeu (et les outils). - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. Filtrer la liste ci-dessus pour afficher seulement les conflits. - + Show only conflicts Afficher seulement les conflits - + Saves Sauvegardes - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une liste de toutes les parties sauvegardées pour ce jeu. Survolez une entrée de la liste pour obtenir des informations détaillées sur la sauvegarde, incluant une liste de tous les ESPs/EMSs utilisés lors de la sauvegarde mais actuellement désactivés ou absents.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez &quot;Réparer Mods...&quot; dans le menu contextuel, MO tentera d'activer tous les mods, ESPs et ESMs nécessaires pour résoudre le problème. Rien ne sera désactivé!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une liste de toutes les parties sauvegardées pour ce jeu. Survolez une entrée de la liste pour obtenir des informations détaillées sur la sauvegarde, incluant une liste de tous les ESPs/EMSs utilisés lors de la sauvegarde mais actuellement désactivés ou absents.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez &quot;Réparer Mods...&quot; dans le menu contextuel, MO tentera d'activer tous les mods, ESPs et ESMs nécessaires pour résoudre le problème. Rien ne sera désactivé!</span></p></body></html> - + Downloads Téléchargements - + This is a list of mods you downloaded from Nexus. Double click one to install it. - Ceci est une liste de mods que vous avez téléchargé de Nexus. Double-cliquez en un pour l'installer. + Ceci est une liste de mods que vous avez téléchargé de Nexus. Double-cliquez en un pour l'installer. - - Compact - + + Open list options... + - + + + Restore Backup... + + + + + + Create Backup + + + + + Plugins + + + + + Sort + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + Show Hidden - + - + Tool Bar - Barre d'outils + Barre d'outils - + Install Mod Installer mod - + Install &Mod Installer &mod - + Install a new mod from an archive - Installer un nouveau mod à partir d'une archive + Installer un nouveau mod à partir d'une archive - + Ctrl+M Ctrl+M - + Profiles Profils - + &Profiles &Profils - + Configure Profiles Configurer les profils - + Ctrl+P Ctrl+P - + Executables Programmes - + &Executables Programm&es - + Configure the executables that can be started through Mod Organizer Configure les programmes pouvant être lancés via Mod Organizer - + Ctrl+E Ctrl+E - - + + Tools - + - + &Tools - + - + Ctrl+I Ctrl+H - + Settings Réglages - + &Settings Réglage&s - + Configure settings and workarounds Configurer les réglages et solutions de rechange - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Effectuer une recherche sur Nexus pour plus de mods - + Ctrl+N Ctrl+N - - + + Update Mise-à-jour - + Mod Organizer is up-to-date Mod Organizer est à jour - - + + No Problems - + - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! Right now this has very limited functionality - + - - + + Help Aide - + Ctrl+H Ctrl+H - + Endorse MO - + - - + + Endorse Mod Organizer - + + + + + Copy Log to Clipboard + + + + + Ctrl+C + - + Toolbar - Barre d'outils + Barre d'outils - + Desktop - + - + Start Menu - + - + Problems - + - + There are potential problems with your setup - + - + Everything seems to be in order - + - + Help on UI - + - + Documentation Wiki - + - + Report Issue - + - + Tutorials - - - - - failed to save archives order, do you have write access to "%1"? - + - + failed to save load order: %1 - impossible d'enregistrer l'ordre de chargement: %1 + impossible d'enregistrer l'ordre de chargement: %1 - + Name Nom - + Please enter a name for the new profile Veuillez inscrire un nom pour le nouveau profil - + failed to create profile: %1 impossible de créer le profil: %1 - + Show tutorial? - + - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + - + Downloads in progress Téléchargements en cours - + There are still downloads in progress, do you really want to quit? Il encore des téléchargements en cours, voulez-vous vraiment quitter? - + failed to read savegame: %1 impossible de lire la sauvegarde: %1 - - Plugin "%1" failed: %2 - + + Plugin "%1" failed: %2 + - - Plugin "%1" failed - + + Plugin "%1" failed + - + failed to init plugin %1: %2 - + - + Plugin error - + - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + - - Failed to start "%1" - impossible de lancer "%1" + + Failed to start "%1" + impossible de lancer "%1" - + Waiting Attente - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. Veuillez cliquer OK une fois connecté à steam. - - "%1" not found - "%1" introuvable + "%1" not found + "%1" introuvable - + Start Steam? - + - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + - + Also in: <br> - + - + No conflict Aucun conflit - + <Edit...> <Modifier...> - - Failed to refresh list of esps: %s - - - - + This bsa is enabled in the ini file so it may be required! - - - - - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - + - + Activating Network Proxy - + - - + + Installation successful Installation réussie - - + + Configure Mod Configurer mod - - + + This mod contains ini tweaks. Do you want to configure them now? Ce mod contient des ajustement pour les fichiers ini. Désirez-vous les configurer maintenant? - - - mod "%1" not found - "%1" introuvable + + + mod "%1" not found + "%1" introuvable - - + + Installation cancelled - + - - + + The mod was not installed completely. - + - + Some plugins could not be loaded - + - + Too many esps and esms enabled - + - - + + Description missing - + - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + - + Choose Mod Choisir mod - + Mod Archive Archive de mod - + Start Tutorial? - + - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + - - + + Download started Téléchargement commencé - + failed to update mod list: %1 impossible de mettre à jour la liste de mods: %1 - + failed to spawn notepad.exe: %1 impossible de lancer notepad.exe: %1 - + failed to open %1 - impossible d'ouvrir %1 + impossible d'ouvrir %1 - + failed to change origin name: %1 - impossible de changer le nom d'origine: %1 - - - - Failed to move "%1" from mod "%2" to "%3": %4 - + impossible de changer le nom d'origine: %1 - + <Checked> <Cochés> - + <Unchecked> <Décochés> - + <Update> <Rafraichir> - + <No category> - + - + <Conflicted> - + - + <Not Endorsed> - + - + failed to rename mod: %1 impossible de renommer le mod: %1 - + Overwrite? - + - - This will replace the existing mod "%1". Continue? - + + This will replace the existing mod "%1". Continue? + - - failed to remove mod "%1" + + failed to remove mod "%1" Impossible de supprimer %1 - - - - failed to rename "%1" to "%2" + + + + failed to rename "%1" to "%2" Impossible de renommer %1 en %2 - - Multiple esps activated, please check that they don't conflict. - + + Multiple esps activated, please check that they don't conflict. + - - - + + + + Confirm Confirmer - + Remove the following mods?<br><ul>%1</ul> - + - + failed to remove mod: %1 impossible de renommer le mod: %1 - - + + Failed - + - + Installation file no longer exists - + - - Mods installed with old versions of MO can't be reinstalled in this way. - + + Mods installed with old versions of MO can't be reinstalled in this way. + - - + + You need to be logged in with Nexus to endorse - + - - + Extract BSA - - - - - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - + - - - + + failed to read %1: %2 Échec de lecture %1: %2 - - + This archive contains invalid hashes. Some files may be broken. - + - + Nexus ID for this Mod is unknown - + + + + + About + + + + + About Qt + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + Executable "%1" not found + + + + + Failed to refresh list of esps: %1 + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + - - + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... - + - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + - + A mod with this name already exists - + - + Continue? - + - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - + + Sorry - + - - I don't know a versioning scheme where %1 is newer than %2. - + + I don't know a versioning scheme where %1 is newer than %2. + - + Really enable all visible mods? - + - + Really disable all visible mods? - + - + Choose what to export - + - + Everything - + - + All installed mods are included in the list - + - + Active Mods Activer Mods - + Only active (checked) mods from your current profile are included - + - + Visible - + - + All mods visible in the mod list are included - + - + export failed: %1 - + - + Install Mod... Installer mod... - + Enable all visible Activer tous les mods visibles - + Disable all visible Désactiver tous les mods visibles - + Check all for update Vérifier toutes les mises à jour - + Export to csv... - + + + + + All Mods + - + Sync to Mods... - + - + Restore Backup - + - + Remove Backup... - + - + Add/Remove Categories - + - + Replace Categories - + - + Primary Category - + - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... Renommer mod... - + Remove Mod... Supprimer mod... - + Reinstall Mod Installer mod - + Un-Endorse - + - - + + Endorse - + - - Won't endorse - + + Won't endorse + - + Endorsement state unknown - + - + Ignore missing data - + - + Visit on Nexus - + - + Open in explorer - + - + Information... Information... - - + + Exception: - + - - + + Unknown exception - + - + <All> <Tous> - + <Multiple> - + + + + + Really delete "%1"? + - + Fix Mods... Réparer mods... - - + + Delete + Supprimer + + + + failed to remove %1 Impossible de supprimer %1 - - + + failed to create %1 impossible de créer %1 - - Can't change download directory while downloads are in progress! - + + Can't change download directory while downloads are in progress! + - + Download failed Téléchargement commencé - + failed to write to file %1 - impossible d'écrire dans le fichier %1 + impossible d'écrire dans le fichier %1 - + %1 written %1 écrit - + Select binary Choisir un programme - + Binary Programme - + Enter Name - + - + Please enter a name for the executable Veuillez inscrire un nom pour le nouveau profil - + Not an executable Ajouter un programme - + This is not a recognized executable. - + - - + + Replace file? - + - + There already is a hidden version of this file. Replace it? - + - - + + File operation failed - + - - - Failed to remove "%1". Maybe you lack the required file permissions? - + + + Failed to remove "%1". Maybe you lack the required file permissions? + - + There already is a visible version of this file. Replace it? - + + + + + file not found: %1 + + + + + failed to generate preview for %1 + - + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + Update available Mise à jour disponible - + Open/Execute - + - + Add as Executable Ajouter un programme - + + Preview + + + + Un-Hide - + - + Hide - + - + Write To File... Écriture du fichier... - + Do you want to endorse Mod Organizer on %1 now? - + - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 - + - - + + login successful - + - + login failed: %1. Trying to download anyway - + - + login failed: %1 - + - + login failed: %1. You need to log-in with Nexus to update MO. - + - + Error Erreur - + failed to extract %1 (errorcode %2) - + - + Extract... - + - + Edit Categories... - + + + + + Deselect filter + - + Remove Supprimer - + Enable all - + - + Disable all - + - + Unlock load order - + - + Lock load order - + + + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + @@ -2457,8 +2863,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 index invalide %1 @@ -2466,9 +2872,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + @@ -2479,536 +2885,565 @@ This function will guess the versioning scheme under the assumption that the ins Info sur le mod - + Textfiles Fichiers texte - + A list of text-files in the mod directory. Une liste des fichiers texte de ce mod. - + A list of text-files in the mod directory like readmes. Une liste des fichiers texte de ce mod, comme les lisez-moi. - - + + Save Enregistrer - + INI-Files Fichiers INI - + + Ini Files + + + + This is a list of .ini files in the mod. Ceci est une liste des fichiers .ini présents dans le mod. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. - Ceci est une liste des fichiers .ini présents dans le mod. Ils sont généralement utilisés pour configurer le comportement du mod lorsqu'il y a des paramètres configurables. + Ceci est une liste des fichiers .ini présents dans le mod. Ils sont généralement utilisés pour configurer le comportement du mod lorsqu'il y a des paramètres configurables. + + + + Ini Tweaks + - + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Enregistre les changements au fichier. - + Save changes to the file. This overwrites the original. There is no automatic backup! - Enregistrer les changements au fichier. Ceci écrase l'original. Il n'y a pas de copie de sauvegarde automatique! + Enregistrer les changements au fichier. Ceci écrase l'original. Il n'y a pas de copie de sauvegarde automatique! - + Images Images - + Images located in the mod. Images présentes dans le mod. - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + - - + + Optional ESPs ESPs optionnels - + List of esps and esms that can not be loaded by the game. Liste des esps et esms ne pouvant pas être chargés par le jeu. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + - + Make the selected mod in the lower list unavailable. Rendre le mod sélectionné dans la liste du bas non-disponible. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - L'ESP sélectionné (dans la liste inférieure) sera poussé dans un sous-dossier du mod et deviendra ainsi "invisible" pour le jeu. Il ne pourra alors plus être activé. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + L'ESP sélectionné (dans la liste inférieure) sera poussé dans un sous-dossier du mod et deviendra ainsi "invisible" pour le jeu. Il ne pourra alors plus être activé. - + Move a file to the data directory. Déplacer un fichier vers le dossier DATA. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Ceci déplace un ESP vers le répertoire DATA afin qu'il puisse être activé dans la fenêtre principale. Veuillez noter que l'ESP devient simplement "disponible", il ne sera pas nécessairement chargé. Celà doit être configuré dans la fenêtre principale de MO. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + Ceci déplace un ESP vers le répertoire DATA afin qu'il puisse être activé dans la fenêtre principale. Veuillez noter que l'ESP devient simplement "disponible", il ne sera pas nécessairement chargé. Celà doit être configuré dans la fenêtre principale de MO. - + ESPs in the data directory and thus visible to the game. ESPs dans le dossier DATA et donc visibles par le jeu. - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. - Ce sont les mods qui se trouvent dans le répertoire DATA (virtuel) de votre jeu et qu'il sera donc possible de sélectionner dans la fenêtre principale. + Ce sont les mods qui se trouvent dans le répertoire DATA (virtuel) de votre jeu et qu'il sera donc possible de sélectionner dans la fenêtre principale. - + Available ESPs ESPs disponibles - + Conflicts - + - + The following conflicted files are provided by this mod - + - - + + File Fichier - + Overwritten Mods - + - + The following conflicted files are provided by other mods - + - + Providing Mod - + - + Non-Conflicted files - + - + Categories Catégories - + Primary Category - + - + Nexus Info Info de Nexus - + Mod ID ID du mod - + Mod ID for this mod on Nexus. ID de ce mod sur Nexus. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ID de ce mod sur Nexus. Rempli automatiquement si vous téléchargez et installez un mod à partir de MO. Sinon, vous pouvez l'inscrire manuellement. Pour connaître le bon ID, trouvez le mod sur Nexus. L'URL ressemblera à ceci: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" color:#000000;">. Dans cet example, 1334 est l'ID que vous cherchez. En passant: Ce lien est celui de Mod Organizer sur le Nexus. Pourquoi ne pas le visiter et lui donner votre aval?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ID de ce mod sur Nexus. Rempli automatiquement si vous téléchargez et installez un mod à partir de MO. Sinon, vous pouvez l'inscrire manuellement. Pour connaître le bon ID, trouvez le mod sur Nexus. L'URL ressemblera à ceci: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" color:#000000;">. Dans cet example, 1334 est l'ID que vous cherchez. En passant: Ce lien est celui de Mod Organizer sur le Nexus. Pourquoi ne pas le visiter et lui donner votre aval?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version du mod installée La bulle contient la version courrante disponible sur Nexus. La version installé n'est réglée que si vous installez le mod avec MO.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Version du mod installée La bulle contient la version courrante disponible sur Nexus. La version installé n'est réglée que si vous installez le mod avec MO.</span></p></body></html> - + Version Version - + Refresh Actualiser - + Refresh all information from Nexus. - + - + Description Description - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse - + - + Notes - + - + Filetree Arborescence - + A directory view of this mod Une vue du dossier de ce mod - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une vue modifiable du dossier du mod. Vous pouvez réorganiser les fichiers par glisser-déposer et les renommer en double-cliquant.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les changements sont immédiatement appliqués sur le disque, alors</span><span style=" font-size:8pt; font-weight:600;"> soyez prudent</span><span style=" font-size:8pt;">.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une vue modifiable du dossier du mod. Vous pouvez réorganiser les fichiers par glisser-déposer et les renommer en double-cliquant.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les changements sont immédiatement appliqués sur le disque, alors</span><span style=" font-size:8pt; font-weight:600;"> soyez prudent</span><span style=" font-size:8pt;">.</span></p></body></html> - + Previous - + - + Next Suivant - + Close Fermer - + &Delete Supprimer - + &Rename &Renommer - + &Hide - + - + &Unhide - + - + &Open &Ouvrir - + &New Folder &Nouveau dossier - - + + Save changes? Enregistrer les changements? - - - Save changes to "%1"? - + + + Save changes to "%1"? + - + File Exists Un fichier du même nom existe - + A file with that name exists, please enter a new one Un fichier ainsi nommé existe déjà, veuillez entrer un nouveau nom - + failed to move file impossible de déplacer le fchier - - failed to create directory "optional" - Impossible de créer le dossier "optional" + + failed to create directory "optional" + Impossible de créer le dossier "optional" - - + + Info requested, please wait Info demandée, veuillez patienter - + Main Principal - + Update Mise-à-jour - + Optional Optionnel - + Old Ancien - + Misc Divers - + Unknown Inconnu - + Current Version: %1 Version courante: %1 - + No update available Aucune mise-à-jour disponible - + (description incomplete, please visit nexus) - + - - <a href="%1">Visit on Nexus</a> - <a href="%1">Visiter sur Nexus</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">Visiter sur Nexus</a> - + Failed to delete %1 - impossible d'effacer %1 + impossible d'effacer %1 - - + + Confirm Confirmer - - Are sure you want to delete "%1"? - Voulez-vous vraiment supprimer "%1"? + + Are sure you want to delete "%1"? + Voulez-vous vraiment supprimer "%1"? - + Are sure you want to delete the selected files? Voulez-vous vraiment supprimer les fichiers sélectionnés? - - + + New Folder Nouveau dossier - - Failed to create "%1" - Impossible de créer "%1" + + Failed to create "%1" + Impossible de créer "%1" - - + + Replace file? - + - + There already is a hidden version of this file. Replace it? - + - - + + File operation failed - + - - - Failed to remove "%1". Maybe you lack the required file permissions? - + + + Failed to remove "%1". Maybe you lack the required file permissions? + - - + + failed to rename %1 to %2 Impossible de renommer %1 en %2 - + There already is a visible version of this file. Replace it? - + - + Un-Hide - + - + Hide - + - + Name Nom - + Please enter a name - + - - + + Error Erreur - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - + ModInfoRegular - - failed to write %1/meta.ini: %2 - + + + failed to write %1/meta.ini: error %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - %1 ne contient ni esp/esm, ni dossier d'éléments de jeu (textures, meshes, interface, ...) + %1 ne contient ni esp/esm, ni dossier d'éléments de jeu (textures, meshes, interface, ...) - + Categories: <br> - + @@ -3016,158 +3451,167 @@ p, li { white-space: pre-wrap; } This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + + + + + Non-MO + - + invalid - + - installed version: %1, newest version: %2 - Version installée: %1, dernière version: %2 + Version installée: %1, dernière version: %2 + + + + installed version: "%1", newest version: "%2" + - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + - + Categories: <br> - + - + Invalid name Nom incorrect - + drag&drop failed: %1 - + - + Confirm Confirmer - - Are you sure you want to remove "%1"? - Voulez-vous vraiment supprimer "%1"? + + Are you sure you want to remove "%1"? + Voulez-vous vraiment supprimer "%1"? - + Flags - + - + Mod Name Nom du mod - + Version Version - + Priority Priorité - + Category - + - + Nexus ID IDs Nexus - + Installation - + - - + + unknown Inconnu - + Name of your mods - + - + Version of the mod (if available) Version du mod (si disponible) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - Priorité d'installation de vos mods. Plus elle est ellevée, plus le mod est "important" et écrasera les fichiers des mods de priorité inférieure. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + Priorité d'installation de vos mods. Plus elle est ellevée, plus le mod est "important" et écrasera les fichiers des mods de priorité inférieure. - + Category of the mod. - + - + Id of the mod as used on Nexus. - + - + Emblemes to highlight things that might require attention. - + - + Time this mod was installed - + @@ -3175,7 +3619,7 @@ p, li { white-space: pre-wrap; } Message of the Day - + @@ -3188,48 +3632,53 @@ p, li { white-space: pre-wrap; } Overwrites - + not implemented - + NXMAccessManager - + Logging into Nexus - + - + timeout - + - + + Unknown error + + + + Please check your password - + NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - + + Failed to guess mod id for "%1", please pick the correct one + - + empty response - + - + invalid response - + @@ -3237,12 +3686,12 @@ p, li { white-space: pre-wrap; } Overwrite - + You can use drag&drop to move files and directories to regular mods. - + @@ -3266,8 +3715,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - impossible d'effacer %1 + Failed to delete "%1" + impossible d'effacer %1 @@ -3277,8 +3726,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - Voulez-vous vraiment supprimer "%1"? + Are sure you want to delete "%1"? + Voulez-vous vraiment supprimer "%1"? @@ -3293,114 +3742,136 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - Impossible de créer "%1" + Failed to create "%1" + Impossible de créer "%1" PluginList - + Name Nom - + Priority Priorité - + Mod Index - + - - + + Flags + + + + + unknown Inconnu - + Name of your mods - + - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 ESP introuvable: %1 - - + + Confirm Confirmer - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + - - - failed to open output file: %1 - + + <b>Origin</b>: %1 + - - Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. - + + Author + Auteur - - This plugin can't be disabled (enforced by the game) - + + Description + Description - - Origin: %1 - + + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. + - + + This plugin can't be disabled (enforced by the game) + + + + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - + + + + + PreviewDialog + + + Preview + + + + + Close + Fermer @@ -3408,16 +3879,16 @@ p, li { white-space: pre-wrap; } Problems - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + @@ -3428,95 +3899,90 @@ p, li { white-space: pre-wrap; } Fix - + No guided fix - + Profile - + invalid profile name %1 - + - + failed to create %1 impossible de créer %1 - - failed to open temporary file - - - - - failed to open "%1" for writing - - - - + failed to write mod list: %1 impossible de mettre à jour la liste de mods: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 - + - + failed to create tweaked ini: %1 - + + + + + "%1" is missing or inaccessible + - - - - - + + + + + invalid index %1 index invalide %1 - - Overwrite directory couldn't be parsed - + + Overwrite directory couldn't be parsed + - + invalid priority %1 priorité invalide %1 - + failed to parse ini file (%1) impossible de traiter le fichier ini (%1) - + failed to parse ini file (%1): %2 - impossible d'analyser le profil %1: %2 + impossible d'analyser le profil %1: %2 - - - failed to modify "%1" - impossible de trouver "%1" + + + failed to modify "%1" + impossible de trouver "%1" - + Delete savegames? - + - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + @@ -3524,7 +3990,7 @@ p, li { white-space: pre-wrap; } Dialog - + @@ -3534,17 +4000,17 @@ p, li { white-space: pre-wrap; } If checked, the new profile will use the default game settings. - + - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + Default Game Settings - + @@ -3561,55 +4027,55 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ceci est la liste des profils. Chaque profil contient sa propre liste de mods, leur activation et leur ordre d'installation(à partir de tous les mods installés dans MO), la liste des ESPs et ESMs activés, une copie des fichiers ini et un filtre de sauvegarde optionel.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Notez</span> Pour des raisons techniques, il est impossible de modifier l'ordre de chargement des ESPs/ESMs entre les profils. Ainsi, si moda.esp vient avant modb.esp dans un profil, il n'est pas possible de faire l'inverse dans un autre profil.</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Ceci est la liste des profils. Chaque profil contient sa propre liste de mods, leur activation et leur ordre d'installation(à partir de tous les mods installés dans MO), la liste des ESPs et ESMs activés, une copie des fichiers ini et un filtre de sauvegarde optionel.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Notez</span> Pour des raisons techniques, il est impossible de modifier l'ordre de chargement des ESPs/ESMs entre les profils. Ainsi, si moda.esp vient avant modb.esp dans un profil, il n'est pas possible de faire l'inverse dans un autre profil.</p></body></html> If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. - Ceci garantie que les fichiers des mods sont vraiment utilisés. Vous voulez activer ceci à moins que vous n'utilisiez un autre outil pour l'invalidation des archives. + Ceci garantie que les fichiers des mods sont vraiment utilisés. Vous voulez activer ceci à moins que vous n'utilisiez un autre outil pour l'invalidation des archives. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les jeux Oblivion, Fallout 3 et Fallout NV contiennent un bug empêchant le fonctionnement des textures et modèles remplaceant ceux déjà existants dans le jeu.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer utilise une méthode nommée &quot;BSA redirection&quot; (google est votre ami) pour circonvenir le problème une fois pour toute. Activez simplement et n'y pensez plus.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Avec Skyrim, le problème semble partiellement résolu, mais l'activation d'un mod dans le jeu dépends toujours des dates des fichiers. Il est donc encore préférable de l'activer..</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les jeux Oblivion, Fallout 3 et Fallout NV contiennent un bug empêchant le fonctionnement des textures et modèles remplaceant ceux déjà existants dans le jeu.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer utilise une méthode nommée &quot;BSA redirection&quot; (google est votre ami) pour circonvenir le problème une fois pour toute. Activez simplement et n'y pensez plus.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Avec Skyrim, le problème semble partiellement résolu, mais l'activation d'un mod dans le jeu dépends toujours des dates des fichiers. Il est donc encore préférable de l'activer..</span></p></body></html> @@ -3662,12 +4128,12 @@ p, li { white-space: pre-wrap; } Transfer save games to the selected profile. - + Transfer Saves - + @@ -3676,8 +4142,8 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. - L'invalidation des archives n'est pas nécessaire pour ce jeu. + Archive invalidation isn't required for this game. + L'invalidation des archives n'est pas nécessaire pour ce jeu. @@ -3708,7 +4174,7 @@ p, li { white-space: pre-wrap; } Invalid profile name - + @@ -3718,37 +4184,37 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Rename Profile - + New Name - + failed to change archive invalidation state: %1 - impossible de changer l'état d'invalidation des archives: %1 + impossible de changer l'état d'invalidation des archives: %1 failed to determine if invalidation is active: %1 - impossible de déterminer si l'invalidation des archives est activée: %1 + impossible de déterminer si l'invalidation des archives est activée: %1 @@ -3756,7 +4222,7 @@ p, li { white-space: pre-wrap; } Failed to save custom categories - Impossible d'enregistrer les catégories personalisées + Impossible d'enregistrer les catégories personalisées @@ -3769,63 +4235,63 @@ p, li { white-space: pre-wrap; } invalid category id %1 - + - invalid field name "%1" - + invalid field name "%1" + - invalid type for "%1" (should be integer) - + invalid type for "%1" (should be integer) + - invalid type for "%1" (should be string) - + invalid type for "%1" (should be string) + - invalid type for "%1" (should be float) - + invalid type for "%1" (should be float) + no fields set up yet! - + - field not set "%1" - + field not set "%1" + - invalid character in field "%1" - + invalid character in field "%1" + empty field name - + invalid game type %1 - + helper failed - Échec de l'assistant + Échec de l'assistant failed to determine account name - impossible de détermine le nom d'usager + impossible de détermine le nom d'usager @@ -3836,7 +4302,7 @@ p, li { white-space: pre-wrap; } failed to open %1: %2 - impossible d'ouvrir %1: %2 + impossible d'ouvrir %1: %2 @@ -3852,7 +4318,7 @@ p, li { white-space: pre-wrap; } Failed to deactivate script extender loading - Impossible de désactiver le chargement via l'extenseur de script + Impossible de désactiver le chargement via l'extenseur de script @@ -3880,17 +4346,17 @@ p, li { white-space: pre-wrap; } Failed to set up script extender loading - Impossible de mettre en place le chargement via l'extenseur de script + Impossible de mettre en place le chargement via l'extenseur de script Failed to delete old proxy-dll %1 - Impossible de supprimer l'ancien DLL par procuration %1 + Impossible de supprimer l'ancien DLL par procuration %1 Failed to overwrite %1 - Impossible d'écraser %1 + Impossible d'écraser %1 @@ -3898,104 +4364,112 @@ p, li { white-space: pre-wrap; } Impossible de mettre en place le chargement via DLL par procuration - + Permissions required Permissions requises - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + - - + + Woops - + - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 - + - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Une copie du Mod Organizer tourne déjà - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + - - + + Please select the game to manage Veuillez choisir le jeu à gérer - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + - - Please use "Help" from the toolbar to get usage instructions to all elements - Veuillez utiliser l'aide dans la barre d'outil pour obtenir des instructions à propos de tous les éléments + + failed to start application: %1 + - - + + Please use "Help" from the toolbar to get usage instructions to all elements + Veuillez utiliser l'aide dans la barre d'outil pour obtenir des instructions à propos de tous les éléments + + + + <Manage...> <Gérer...> - + failed to parse profile %1: %2 - impossible d'analyser le profil %1: %2 + impossible d'analyser le profil %1: %2 - - - failed to find "%1" - impossible de trouver "%1" + + failed to find "%1" + impossible de trouver "%1" - + failed to access %1 - impossible d'accéder à %1 + impossible d'accéder à %1 - + failed to set file time %1 impossible de changer la date du fichier %1 - + failed to create %1 impossible de créer %1 - - "%1" is missing - "%1" manquant + + "%1" is missing or inaccessible + + + + "%1" is missing + "%1" manquant Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - + @@ -4013,57 +4487,62 @@ p, li { white-space: pre-wrap; } failed to open %1 - impossible d'ouvrir %1 + impossible d'ouvrir %1 - + Script Extender Extenseur de script - + Proxy DLL DLL par procuration - - failed to spawn "%1" - impossible de lancer "%1" + + failed to spawn "%1" + impossible de lancer "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - - failed to spawn "%1": %2 - impossible de lancer "%1": %2 + + failed to spawn "%1": %2 + impossible de lancer "%1": %2 - - "%1" doesn't exist - "%1" inexistant + + "%1" doesn't exist + "%1" inexistant - - failed to inject dll into "%1": %2 - impossible d'injecter le DLL dans "%1": %2 + + failed to inject dll into "%1": %2 + impossible d'injecter le DLL dans "%1": %2 - - failed to run "%1" - impossible de lancer "%1" + + failed to run "%1" + impossible de lancer "%1" + + + + failed to open temporary file + @@ -4071,27 +4550,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Mod Exists - + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Keep Backup - + Merge - + Replace - + @@ -4109,22 +4588,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - + Location - + @@ -4145,17 +4624,17 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - + Copy To Clipboard - + Save As... - + @@ -4165,7 +4644,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save CSV - + @@ -4174,8 +4653,8 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - failed to open "%1" for writing - + failed to open "%1" for writing + @@ -4183,7 +4662,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Select - + @@ -4200,100 +4679,100 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe SelfUpdater - archive.dll not loaded: "%1" - archive.dll n'est pas chargé: "%1" + archive.dll not loaded: "%1" + archive.dll n'est pas chargé: "%1" - - - - + + + + Update Mettre à jour - + An update is available (newest version: %1), do you want to install it? - Une mise à jour est disponible (dernière version: %1), voulez-vous l'installer? + Une mise à jour est disponible (dernière version: %1), voulez-vous l'installer? - + Download in progress Téléchargement en cours - + Download failed: %1 Téléchargement échoué: %1 - + Failed to install update: %1 - Impossible d'installer la mise à jour %1 + Impossible d'installer la mise à jour %1 - - failed to open archive "%1": %2 - impossible d'ouvrir l'archive "%1": %2 + + failed to open archive "%1": %2 + impossible d'ouvrir l'archive "%1": %2 - + failed to move outdated files: %1. Please update manually. - + - + Update installed, Mod Organizer will now be restarted. Mise à jour complétée, Mod Organizer va maintenant redémarrer. - + Error Erreur - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. - + - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) - + - + no file for update found. Please update manually. - + - + Failed to retrieve update information: %1 - + - + No download server available. Please try again later. - Aucun serveur de téléchargement disponible. Veuillez s'il-vous-plait réessayer plus tard. + Aucun serveur de téléchargement disponible. Veuillez s'il-vous-plait réessayer plus tard. Settings - - - attempt to store setting for unknown plugin "%1" - + + + attempt to store setting for unknown plugin "%1" + - + Confirm Confirmer - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - + @@ -4316,61 +4795,61 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe The display language - Langue d'affichage + Langue d'affichage - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">La langue d'affichage. Seules les langues pour lesquelles une traduction est installée seront affichées.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">La langue d'affichage. Seules les langues pour lesquelles une traduction est installée seront affichées.</span></p></body></html> Style - + graphical style - + graphical style of the MO user interface - + Log Level - + - Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log" + - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Debug - + Info - + @@ -4380,341 +4859,389 @@ p, li { white-space: pre-wrap; } Advanced - + Directory where downloads are stored. - + Mod Directory - + Directory where mods are stored. - + - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Download Directory - + Cache Directory - + + + + + User interface + - Reset stored information from dialogs. - + If checked, the download interface will be more compact. + - This will make all dialogs show up again where you checked the "Remember selection"-box. - + Compact Download Interface + - - Reset Dialogs - + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + - + Reset stored information from dialogs. + + + + + This will make all dialogs show up again where you checked the "Remember selection"-box. + + + + + Reset Dialogs + + + + + Modify the categories available to arrange your mods. Modifier les catégories disponibles pour classer vos mods. - + Configure Mod Categories Configurer les catégories de mod - - + + Nexus Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. Permettre la connexion automatique lorsque la page Nexus du jeu est ouverte. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Permet la connexion automatique au site Nexus lorsqu'une page est ouverte. Veuillez noter que l'encodage utilisé pour stocker le mot de passe dans le fichier modorganizer.ini n'est pas très robuste. Si vous craignez que quelqu'un puisse voler votre mot de passe, ne l'enregistrez pas ici.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Permet la connexion automatique au site Nexus lorsqu'une page est ouverte. Veuillez noter que l'encodage utilisé pour stocker le mot de passe dans le fichier modorganizer.ini n'est pas très robuste. Si vous craignez que quelqu'un puisse voler votre mot de passe, ne l'enregistrez pas ici.</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + - + Automatically Log-In to Nexus Connexion automatique à Nexus - + Username - Nom d'usager + Nom d'usager - + Password Mot de passe - + Disable automatic internet features - + - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + - + Offline Mode - + - + Use a proxy for network connections. - + - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + - + Use HTTP Proxy (Uses System Settings) - + - - Known Servers (Dynamically updated every download) - + + Associate with "Download with manager" links + - + + Known Servers (updated on download) + + + + Preferred Servers (Drag & Drop) - + - + Plugins - + - + Author: Auteur - + Version: Version - + Description: Description - + Key - + - + Value - + - + Blacklisted Plugins (use <del> to remove): - + - + Workarounds Solutions alternatives - + Steam App ID - ID d'application Steam + ID d'application Steam - + The Steam AppID for your game - L'AppID Steam de votre jeu + L'AppID Steam de votre jeu - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">L'App ID Steam est requise pour lancer directement certains jeux. Pour Skyrim, si l'APP ID est inconnu ou incorrect, la méthode de chagement de &quot;Mod Organizer&quot; risque de ne pas fonctionner.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Le réglage par défaut pour ceci est l'App ID de la version &quot;normale&quot;, donc dans la plupart des cas, tout devrait être fonctionnel.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous croyez avoir une version différente (GotY autre), suivez ces étapes pour obtenir l'ID:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Ouvrez la bibliothèque des jeux dans Steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Faites un clic-droit sur le jeu dont vous désirez l'ID et sélectionnez </span><span style=" font-size:8pt; font-weight:600;">Créer un raccourci sur le bureau</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Faites un clic-droit sur le nouveau raccourci créé sut le bureau et sélectionnez </span><span style=" font-size:8pt; font-weight:600;">Propriétés</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4.Dans le champs URL, vous devriez voir quelque chose comme: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 est l'App ID que vous cherchez.</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">L'App ID Steam est requise pour lancer directement certains jeux. Pour Skyrim, si l'APP ID est inconnu ou incorrect, la méthode de chagement de &quot;Mod Organizer&quot; risque de ne pas fonctionner.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Le réglage par défaut pour ceci est l'App ID de la version &quot;normale&quot;, donc dans la plupart des cas, tout devrait être fonctionnel.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous croyez avoir une version différente (GotY autre), suivez ces étapes pour obtenir l'ID:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Ouvrez la bibliothèque des jeux dans Steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Faites un clic-droit sur le jeu dont vous désirez l'ID et sélectionnez </span><span style=" font-size:8pt; font-weight:600;">Créer un raccourci sur le bureau</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Faites un clic-droit sur le nouveau raccourci créé sut le bureau et sélectionnez </span><span style=" font-size:8pt; font-weight:600;">Propriétés</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4.Dans le champs URL, vous devriez voir quelque chose comme: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 est l'App ID que vous cherchez.</span></p></body></html> + + + Load Mechanism Chargement MO - + Select loading mechanism. See help for details. - Choisir la méthode de chargement. Voir l'aide pour les détails. + Choisir la méthode de chargement. Voir l'aide pour les détails. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + - + NMM Version - + - + The Version of Nexus Mod Manager to impersonate. - + - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. + - + Enforces that inactive ESPs and ESMs are never loaded. Garantie que les ESPs et ESMs inactifs ne soient jamais chargés. - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - Il semble que les jeux chargent parfois des fichiers ESP ou ESM même s'ils ne sont pas activés. -Je n'en connais pas encore les circonstances, mais les rapports des usagers impliquent que dans certains cas, ce comportement est indésirable. Si vous cochez ceci, les ESPs et ESMs qui ne sotn pas cochés seront invisible pour le jeu et ne pourront pas être chargés. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + Il semble que les jeux chargent parfois des fichiers ESP ou ESM même s'ils ne sont pas activés. +Je n'en connais pas encore les circonstances, mais les rapports des usagers impliquent que dans certains cas, ce comportement est indésirable. Si vous cochez ceci, les ESPs et ESMs qui ne sotn pas cochés seront invisible pour le jeu et ne pourront pas être chargés. - + Hide inactive ESPs/ESMs Cacher les ESPs et ESMs inactifs - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + - + Force-enable game files - + - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - Pour Skyrim, ceci peut être utilisé au lieu de l'invalidation des archives. Ça devrait rendre l'invalidation redondante pour tous les profils. -Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archives! + Pour Skyrim, ceci peut être utilisé au lieu de l'invalidation des archives. Ça devrait rendre l'invalidation redondante pour tous les profils. +Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archives! - + Back-date BSAs Antidater les BSAs - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + - + Select download directory Sélectionnez un répertoire - + Select mod directory Sélectionnez un répertoire - + Select cache directory Sélectionnez un répertoire - + Confirm? Confirmer - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + @@ -4760,14 +5287,18 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archive - failed to connect to running instance: %1 - Échec de connection à l'instance active: %1 + Échec de connection à l'instance active: %1 + + + + failed to communicate with running instance: %1 + failed to receive data from secondary instance: %1 - Échec de réception de données de l'instance secondaire: %1 + Échec de réception de données de l'instance secondaire: %1 @@ -4775,7 +5306,7 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archive Sync Overwrite - + @@ -4785,12 +5316,12 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archive Sync To - + - <don't sync> - + <don't sync> + @@ -4808,17 +5339,17 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des archive Transfer Savegames - + Global Characters - + This is a list of characters in the global location. - + @@ -4830,7 +5361,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4843,27 +5374,27 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Copy -> - + <- Move - + <- Copy - + @@ -4873,17 +5404,17 @@ On Windows XP: Profile Characters - + Overwrite - + - Overwrite the file "%1" - + Overwrite the file "%1" + @@ -4896,18 +5427,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - + Copy all save games of character "%1" to the profile? + - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + diff --git a/src/organizer_ru.ts b/src/organizer_ru.ts index 2d46e983..f9e9b493 100644 --- a/src/organizer_ru.ts +++ b/src/organizer_ru.ts @@ -1,3 +1,4 @@ + @@ -58,22 +59,22 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Это список esp и esm, которые были активны, когда сохранение было создано.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Для каждого esp, правый столбец содержит мод (или моды), которые могут быть включены, чтобы неактивные esp/esm стали активными.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы нажмете ОК, все моды, выбранные в правой колонке будут активированы.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Это список esp и esm, которые были активны, когда сохранение было создано.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Для каждого esp, правый столбец содержит мод (или моды), которые могут быть включены, чтобы неактивные esp/esm стали активными.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы нажмете ОК, все моды, выбранные в правой колонке будут активированы.</span></p></body></html> @@ -116,9 +117,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. Компоненты этого пакета. -Если присутствует компонент с именем "00 Core", то он обычно является обязательным. Доступные варианты упорядочены по приоритету, установленному автором. +Если присутствует компонент с именем "00 Core", то он обычно является обязательным. Доступные варианты упорядочены по приоритету, установленному автором. @@ -153,6 +154,29 @@ If there is a component called "00 Core" it is usually required. Options are ord Отмена + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -198,20 +222,20 @@ If there is a component called "00 Core" it is usually required. Options are ord - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Вы можете задать одну или несколько категорий Nexus внутреннему ID. Каждый раз, когда вы загружаете мод со страницы Nexus, МО постарается автоматически задать моду категорию, присвоенную ему на Nexus.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Чтобы узнать ID категории, используемой на Nexus, перейдите в список категорий Nexus и наведите курсор мыши на нужную ссылку.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Вы можете задать одну или несколько категорий Nexus внутреннему ID. Каждый раз, когда вы загружаете мод со страницы Nexus, МО постарается автоматически задать моду категорию, присвоенную ему на Nexus.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Чтобы узнать ID категории, используемой на Nexus, перейдите в список категорий Nexus и наведите курсор мыши на нужную ссылку.</span></p></body></html> @@ -243,7 +267,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus Эта функция может не работать, если вход выполнен с Nexus @@ -270,7 +294,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 не удалось прочитать bsa: %1 @@ -294,8 +318,8 @@ p, li { white-space: pre-wrap; } - Information missing, please select "Query Info" from the context menu to re-retrieve. - Информация отсутствует, для её запроса выберите в контекстном меню пункт "Запросить информацию". + Information missing, please select "Query Info" from the context menu to re-retrieve. + Информация отсутствует, для её запроса выберите в контекстном меню пункт "Запросить информацию". @@ -313,26 +337,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Готово - двойной щелчок для установки. - - + + Paused - Double Click to resume Пауза - двойной клик для продолжения - - + + Installed - Double Click to re-install Установлено - двойной клик для переустановки - - + + Uninstalled - Double Click to re-install Удалено - двойной клик для переустановки @@ -354,135 +378,135 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate - + < mod %1 file %2 > < мод %1 файл %2 > - + Pending Ожидание - + Paused Приостановлено - + Fetching Info 1 Получение информации 1 - + Fetching Info 2 Получение информации 2 - + Installed Установлено - + Uninstalled Удалено - + Done Готово - - - - + + + + Are you sure? Вы уверены? - + This will remove all finished downloads from this list and from disk. Это удалит все готовые загрузки из этого списка и с диска. - + This will remove all installed downloads from this list and from disk. Это удалит все установленные загрузки из этого списка и с диска. - + This will permanently remove all finished downloads from this list (but NOT from disk). Это полностью удалит все завершенные загрузки из этого списка (но НЕ с диска). - + This will permanently remove all installed downloads from this list (but NOT from disk). Это полностью удалит все установленные загрузки из этого списка (но НЕ с диска). - + Install Установить - + Query Info Запросить информацию - + Delete Удалить - + Un-Hide Показать - + Remove from View Скрыть от просмотра - + Cancel Отмена - + Pause Пауза - + Remove Удаление - + Resume Возобновить - + Delete Installed... Удалить установленные... - + Delete All... Удалить все... - + Remove Installed... Очистить от установленных... - + Remove All... Очистить от всех... @@ -490,115 +514,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + < mod %1 file %2 > < мод %1 файл %2 > - + Pending Ожидание - + Fetching Info 1 Получение информации 1 - + Fetching Info 2 Получение информации 2 - - - - + + + + Are you sure? Вы уверены? - + This will remove all finished downloads from this list and from disk. Это удалит все готовые загрузки из этого списка и с диска. - + This will remove all installed downloads from this list and from disk. Это удалит все установленные загрузки из этого листа и с диска. - + This will remove all finished downloads from this list (but NOT from disk). Это удалит все готовые загрузки из этого списка (но НЕ с диска). - + This will remove all installed downloads from this list (but NOT from disk). Это удалит все установленные загрузки из этого списка (но НЕ с диска). - + Install Установить - + Query Info Запросить информацию - + Delete Удалить - + Un-Hide Показать - + Remove from View Скрыть от просмотра - + Cancel Отмена - + Pause Пауза - + Remove Удалить - + Resume Возобновить - + Delete Installed... Удалить установленные... - + Delete All... Удалить все... - + Remove Installed... Очистить от установленных... - + Remove All... Очистить от всех... @@ -606,122 +630,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - не удалось переименовать "%1" в "%2" + + failed to rename "%1" to "%2" + не удалось переименовать "%1" в "%2" + + + + Memory allocation error (in refreshing directory). + - + Download again? Загрузить заново? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Файл с таким именем уже был загружен. Вы хотите загрузить его снова? Новый файл получит другое имя. - + failed to download %1: could not open output file: %2 не удалось загрузить %1: не удалось открыть выходной файл: %2 - + Wrong Game Неверная игра - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - Ссылка для загрузки мода для игры "%1", но этот экземпляр MO был установлен для "%2". + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + Ссылка для загрузки мода для игры "%1", но этот экземпляр MO был установлен для "%2". - - - - - - - - - - - + + + + + - - + + + + + + + + + invalid index неверный индекс - + failed to delete %1 не удалось удалить %1 - + failed to delete meta file for %1 не удалось удалить мета-файл %1 - - - - - + + + + + invalid index %1 неверный индекс %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id Пожалуйста, введите ID мода на Nexus - + Mod ID: ID мода: - + + Main + Главное + + + + Update + Обновление + + + + Optional + Опционально + + + + Old + Старые + + + + Misc + Разное + + + + Unknown + Неизвестно + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated Информация обновлена - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? Нет соответствующих модов на Nexus! Возможно фал был удален или переименован? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. Нет соответствующих фалов на Nexus. Выберите файл вручную. - + No download server available. Please try again later. Нет доступных серверов для загрузки. Попробуйте позже. - + Failed to request file info from nexus: %1 Не удалось получить информацию о файле: %1 - + Download failed. Server reported: %1 Загрузка не удалась. Сервер сообщил: %1 - + Download failed: %1 (%2) Загрузка не удалась: %1 (%2) - + failed to re-open %1 не удалось повторно открыть %1 @@ -902,8 +977,8 @@ Right now the only case I know of where this needs to be overwritten is for the - Really remove "%1" from executables? - Действительно удалить "%1" исполняемых? + Really remove "%1" from executables? + Действительно удалить "%1" исполняемых? @@ -994,8 +1069,8 @@ Right now the only case I know of where this needs to be overwritten is for the - <a href="#">Link</a> - <a href="#">Ссылка</a> + <a href="#">Link</a> + <a href="#">Ссылка</a> @@ -1042,7 +1117,7 @@ Right now the only case I know of where this needs to be overwritten is for the - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. Выбери имя для мода. Это также используется в качестве имени каталога, поэтому, пожалуйста, не используйте символы, которые запрещены в именах файлов. @@ -1057,16 +1132,16 @@ Right now the only case I know of where this needs to be overwritten is for the - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Отображает содержимое архива. &lt;data&gt; представляет базовый каталог, данные в котором будут сопоставлены с данными каталога игры. Вы можете изменить базовый каталог нажатием правой кнопки мыши, через контекстное меню, а также можете перемещаться по файлам при помощи перетаскивания.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Отображает содержимое архива. &lt;data&gt; представляет базовый каталог, данные в котором будут сопоставлены с данными каталога игры. Вы можете изменить базовый каталог нажатием правой кнопки мыши, через контекстное меню, а также можете перемещаться по файлам при помощи перетаскивания.</span></p></body></html> @@ -1088,8 +1163,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - archive.dll не загружен: "%1" + archive.dll not loaded: "%1" + archive.dll не загружен: "%1" @@ -1104,7 +1179,7 @@ p, li { white-space: pre-wrap; } - + Extracting files Извлечение файлов @@ -1134,57 +1209,57 @@ p, li { white-space: pre-wrap; } Введенное вами имя недопустимо, пожалуйста введите другое. - - File format "%1" not supported - Формат файла "%1" не поддерживается + + File format "%1" not supported + Формат файла "%1" не поддерживается - + None of the available installer plugins were able to handle that archive Не один из доступных плагинов-установщиков не смог просмотреть этот архив - + no error ошибки отсутствуют - + 7z.dll not found 7z.dll не найден - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll поврежден - + archive not found архив не найден - + failed to open archive не удалось открыть архив - + unsupported archive type не поддерживаемый тип архива - + internal library error внутренняя ошибка библиотеки - + archive invalid архив поврежден - + unknown archive error неизвестная ошибка архива @@ -1198,7 +1273,7 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. Этот диалог должен закрыться автоматически если игра/приложение запущены. Нажмите разблокировать, если этого не произошло. @@ -1215,7 +1290,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 не удалось произвести запись в журнал %1: %2 @@ -1223,12 +1298,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 произошла ошибка: %1 - + an error occured произошла ошибка @@ -1236,424 +1311,473 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories Категории - + Profile Профиль - + Pick a module collection Выберете набор модулей - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Создать профили здесь. Каждый профиль включает свой собственный список активных модов и esp. Таким образом, вы можете быстро переключаться между установками для различных прохождений игры.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Обратите внимание, что порядок загрузки esp одинаков для всех профилей.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Создать профили здесь. Каждый профиль включает свой собственный список активных модов и esp. Таким образом, вы можете быстро переключаться между установками для различных прохождений игры.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Обратите внимание, что порядок загрузки esp одинаков для всех профилей.</span></p></body></html> - Refresh list - Обновить список + Обновить список - + Refresh list. This is usually not necessary unless you modified data outside the program. Обновить список. Обычно в этом нет необходимости, пока вы не измените данные вне программы. - + + + Restore Backup... + + + + + + Create Backup + + + + List of available mods. Список доступных модов. - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. Это список установленных модов. Используйте флажки, для включения/отключения модов и перетаскивание модов, для изменения их порядка установки. - + Filter Фильтр - + No groups Без группировки - + Nexus IDs Nexus IDs - - - + + + Namefilter Фильтр по имени - + Pick a program to run. Выберете программу для запуска. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Выберете программу для запуска. Как только вы начинаете использовать ModOrganizer, вы всегда должны запускать игры и инструменты из него или через созданные в нем ярлыки, в противном случае, установленные через MO моды отображаться не будут.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Вы можете добавить новые инструменты в этот список, но работоспособность их всех не гарантированна.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Выберете программу для запуска. Как только вы начинаете использовать ModOrganizer, вы всегда должны запускать игры и инструменты из него или через созданные в нем ярлыки, в противном случае, установленные через MO моды отображаться не будут.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Вы можете добавить новые инструменты в этот список, но работоспособность их всех не гарантированна.</span></p></body></html> - + Run program Запустить программу - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Запустить выбранную программу с поддержкой ModOrganizer.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Запустить выбранную программу с поддержкой ModOrganizer.</span></p></body></html> - + Run Запустить - + Create a shortcut in your start menu or on the desktop to the specified program Создать ярлык для выбранной программы, в меню Пуск или на рабочем столе. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Создает ярлык в меню Пуск, который запускает выбранную программу с активным MO.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Создает ярлык в меню Пуск, который запускает выбранную программу с активным MO.</span></p></body></html> - + Shortcut Ярлык - + Plugins Плагины - + List of available esp/esm files Список доступных esp/esm файлов - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Этот список содержит esp и esm файлы активных модов. Для них требуется определенный порядок загрузки. Используйте перетаскивание для изменения порядка загрузки. Обратите внимание, что MO сохранит порядок загрузки только для активными/проверенных модов.<br />Существует замечательная утилита, называющаяся &quot;BOSS&quot; , которая автоматически сортирует эти файлы.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Этот список содержит esp и esm файлы активных модов. Для них требуется определенный порядок загрузки. Используйте перетаскивание для изменения порядка загрузки. Обратите внимание, что MO сохранит порядок загрузки только для активными/проверенных модов.<br />Существует замечательная утилита, называющаяся &quot;BOSS&quot; , которая автоматически сортирует эти файлы.</span></p></body></html> - + Sort Сортировать - + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + + Open list options... + + + + Archives Архивы - + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. Список доступных BSA. Они не отмечены здесь, не управляются с помощью MO и игнорируют порядок установки. - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - BSA файлы - архивы (сопоставимы с файлами .zip) которые содержат используемые игрой данные (meshes, textures, ...). Как таковые они "конкурируют" с отдельными файлами в папке Data, поверх которых загружены. + BSA файлы - архивы (сопоставимы с файлами .zip) которые содержат используемые игрой данные (meshes, textures, ...). Как таковые они "конкурируют" с отдельными файлами в папке Data, поверх которых загружены. По умолчанию, BSA, у которых имя совпадает с именем ESP (т.е. plugin.esp и plugin.bsa) будут автоматически загружены и будут иметь приоритет над всеми отдельными файлами и установленный вами слева порядок установки будет проигнорирован! BSA, отмеченные здесь, загружаются так, чтобы порядок установки соблюдался должным образом. - - + + File Файл - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - <html><head/><body><p>Помеченные архивы (<img src=":/MO/gui/warning_16"/>) всё ещё загружаются в Skyrim, но был применён<a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">стандартный механизм перезаписи файлов</span></a>: отдельные файлы перезаписывают файлы в BSA, вне зависимости от приоритета мода/плагина.</p></body></html> + <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> + <html><head/><body><p>Помеченные архивы (<img src=":/MO/gui/warning_16"/>) всё ещё загружаются в Skyrim, но был применён<a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">стандартный механизм перезаписи файлов</span></a>: отдельные файлы перезаписывают файлы в BSA, вне зависимости от приоритета мода/плагина.</p></body></html> - + Data Данные - + refresh data-directory overview обновить обзор каталога Data - + Refresh the overview. This may take a moment. Обновление обзора. Это может занять некоторое время. - - - + + + Refresh Обновить - + This is an overview of your data directory as visible to the game (and tools). Это обзор вашего каталога данных так, как он будет видим игре (и инструментам). - + Mod Мод - - + + Filter the above list so that only conflicts are displayed. Отфильтровать вышеприведенный список так, чтобы отображались только конфликты. - + Show only conflicts Показать только конфликты - + Saves Сохранения - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Список всех сохранений игры. Наведите указатель мыши на элемент списка, чтобы получить подробную информацию о сохранении, включающем список esp/esm, которые использовались во время создания сохранения, но не активны в настоящее время.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы выберете в контекстном меню пункт &quot;Исправить моды...&quot;, MO попытается подключить все моды и esp, чтобы исправить эти отсутствующие esp. Это ничего не отключит!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Список всех сохранений игры. Наведите указатель мыши на элемент списка, чтобы получить подробную информацию о сохранении, включающем список esp/esm, которые использовались во время создания сохранения, но не активны в настоящее время.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы выберете в контекстном меню пункт &quot;Исправить моды...&quot;, MO попытается подключить все моды и esp, чтобы исправить эти отсутствующие esp. Это ничего не отключит!</span></p></body></html> - + Downloads Загрузки - + This is a list of mods you downloaded from Nexus. Double click one to install it. Список модов, загруженных с Nexus. Двойной клик для установки. - Compact - Компактно + Компактно - + Show Hidden Показывать скрытые - + Tool Bar Панель инструментов - + Install Mod Установить мод - + Install &Mod Установить &мод - + Install a new mod from an archive Установить новый мод из архива - + Ctrl+M Ctrl+M - + Profiles Профили - + &Profiles &Профили - + Configure Profiles Настройка профилей - + Ctrl+P Ctrl+P - + Executables Программы - + &Executables &Программы - + Configure the executables that can be started through Mod Organizer Настройка программ, которые могут быть запущены через Mod Organizer - + Ctrl+E Ctrl+E - - + + Tools Инструменты - + &Tools &Инструменты - + Ctrl+I Ctrl+I - + Settings Настройки - + &Settings &Настройки - + Configure settings and workarounds Настройка параметров и способов обхода - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Поиск дополнительных модов на Nexus - + Ctrl+N Ctrl+N - - + + Update Обновление - + Mod Organizer is up-to-date Mod Organizer обновлен - - + + No Problems Проблем не обнаружено - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1664,949 +1788,1098 @@ Right now this has very limited functionality Прямо сейчас этот функционал сильно ограничен - - + + Help Справка - + Ctrl+H Ctrl+H - + Endorse MO Одобрить MO - - + + Endorse Mod Organizer Одобрить Mod Organizer - + + Copy Log to Clipboard + + + + + Ctrl+C + + + + Toolbar Панель инструментов - + Desktop Рабочий стол - + Start Menu Меню Пуск - + Problems Проблемы - + There are potential problems with your setup Есть возможные проблемы с вашей установкой - + Everything seems to be in order Кажется всё в порядке - + Help on UI Справка по интерфейсу - + Documentation Wiki Wiki-документация - + Report Issue Сообщить о проблеме - + Tutorials Уроки - + About О программе - + About Qt О библиотеке Qt - + failed to save load order: %1 не удалось сохранить порядок загрузки: %1 - + Name Имя - + Please enter a name for the new profile Введите имя нового профиля - + failed to create profile: %1 не удалось создать профиль: %1 - + Show tutorial? Показать урок? - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - Вы запустили Mod Organizer в первый раз. Вы хотите просмотреть уроки по основным возможностям? В случае отказа вы всегда можете открыть уроки из меню "Помощь". + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + Вы запустили Mod Organizer в первый раз. Вы хотите просмотреть уроки по основным возможностям? В случае отказа вы всегда можете открыть уроки из меню "Помощь". - + Downloads in progress Загрузки в процессе - + There are still downloads in progress, do you really want to quit? Загрузки всё ещё в процессе, вы правда хотите выйти? - + failed to read savegame: %1 не удалось прочесть сохранение: %1 - - Plugin "%1" failed: %2 - Плагин "%1" не удалось: %2 + + Plugin "%1" failed: %2 + Плагин "%1" не удалось: %2 - - Plugin "%1" failed - Плагин "%1" не удалось + + Plugin "%1" failed + Плагин "%1" не удалось - + failed to init plugin %1: %2 не удалось инициализировать плагин %1: %2 - + Plugin error Ошибка плагина - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - Кажется, что при последнем запуске не удалось загрузить плагин "%1" и это привело к падению MO. Вы хотите отключить его? + Кажется, что при последнем запуске не удалось загрузить плагин "%1" и это привело к падению MO. Вы хотите отключить его? (Замечание: Если это первый раз, когда вы видите такое сообщение для этого плагина, вероятно вы захотите сделать ещё одну попытка. Плагин может восстановиться после проблемы) - - Failed to start "%1" - Не удалось запустить "%1" + + Failed to start "%1" + Не удалось запустить "%1" - + Waiting Ожидание - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. Нажмите OK как только вы войдете в Steam. - + Start Steam? Запустить Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Требуется запущенный Steam, для корректного запуска игры. Должен ли MO попытаться запустить Steam сейчас? - + Also in: <br> Также в: <br> - + No conflict Конфликтов нет - + <Edit...> <Правка...> - + This bsa is enabled in the ini file so it may be required! Этот bsa подключен через ini, так что он может быть необходим! - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - Этот архив все равно будет загружен, так как есть плагин с одноименным названием, но его файлы не будут следовать порядку установки! + Этот архив все равно будет загружен, так как есть плагин с одноименным названием, но его файлы не будут следовать порядку установки! - + Activating Network Proxy Подключение сетевого прокси - - + + Installation successful Установка завершена - - + + Configure Mod Настройка мода - - + + This mod contains ini tweaks. Do you want to configure them now? Этот мод включает настройки ini. Вы хотите настроить их сейчас? - - - mod "%1" not found - мод "%1" не найден + + + mod "%1" not found + мод "%1" не найден - - + + Installation cancelled Установка отменена - - + + The mod was not installed completely. Мод не был установлен полностью. - + Some plugins could not be loaded Некоторые плагины не могут быть загружены - + Too many esps and esms enabled Подключено слишком много esp и esm - - + + Description missing Описание отсутствует - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Следующие плагины не могут быть загружены. Причина возможно в отсутствующих зависимостях (таких как python) или в устаревшей версии: - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - Игра не позволяет загрузить больше 255 активных плагинов (включая официальные). Вам нужно отключить некоторые ненужные плагины или объединить несколько небольших плагинов в один. Инструкция может быть найдена здесь: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + Игра не позволяет загрузить больше 255 активных плагинов (включая официальные). Вам нужно отключить некоторые ненужные плагины или объединить несколько небольших плагинов в один. Инструкция может быть найдена здесь: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Выберете мод - + Mod Archive Архив мода - + Start Tutorial? Начать урок? - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Вы собираетесь открыть урок. По техническим причинам будет невозможно закончить его досрочно. Продолжить? - - + + Download started Загрузка начата - + failed to update mod list: %1 не удалось обновить список модов: %1 - + failed to spawn notepad.exe: %1 не удалось вызвать notepad.exe: %1 - + failed to open %1 не удалось открыть %1 - + failed to change origin name: %1 не удалось изменить оригинальное имя: %1 - - Executable "%1" not found - Исполняемый файл "%1" не найден + + Executable "%1" not found + Исполняемый файл "%1" не найден - + Failed to refresh list of esps: %1 Не удалось обновить список esp: %1 - - failed to move "%1" from mod "%2" to "%3": %4 - не удалось переместить "%1" из мода "%2" в "%3": %4 + + failed to move "%1" from mod "%2" to "%3": %4 + не удалось переместить "%1" из мода "%2" в "%3": %4 - + <Checked> <Подключен> - + <Unchecked> <Отключен> - + <Update> <Обновлен> - + <No category> <Без категории> - + <Conflicted> <Конфликтует> - + <Not Endorsed> <Не одобрено> - + failed to rename mod: %1 не удалось переименовать мод: %1 - + Overwrite? Перезаписать? - - This will replace the existing mod "%1". Continue? - Это заменит существующий мод "%1". Продолжить? + + This will replace the existing mod "%1". Continue? + Это заменит существующий мод "%1". Продолжить? - - failed to remove mod "%1" - не удалось удалить мод "%1" + + failed to remove mod "%1" + не удалось удалить мод "%1" - - - - failed to rename "%1" to "%2" - не удалось переименовать "%1" в "%2" + + + + failed to rename "%1" to "%2" + не удалось переименовать "%1" в "%2" - - Multiple esps activated, please check that they don't conflict. + + Multiple esps activated, please check that they don't conflict. Подключено несколько esp, выберете из них не конфликтующие. - - - - + + + + Confirm Подтверждение - + Remove the following mods?<br><ul>%1</ul> Удалить следующие моды?<br><ul>%1</ul> - + failed to remove mod: %1 не удалось удалить мод: %1 - - + + Failed Неудача - + Installation file no longer exists Установочный файл больше не существует - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. Моды, установленные с использованием старых версий MO не могут быть переустановленны таким образом. - - + + You need to be logged in with Nexus to endorse Вы должны быть авторизированы на Nexus, чтобы одобрять. - - + Extract BSA Распаковать BSA - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - Этот мод включает как минимум один BSA. Вы хотите распаковать их? +(This removes the BSA after completion. If you don't know about BSAs, just select no) + Этот мод включает как минимум один BSA. Вы хотите распаковать их? (Это удалит BSA после завершения. Если вы не знаете ничего о BSAs, просто откажитесь) - - - + + failed to read %1: %2 не удалось прочесть %1: %2 - - + This archive contains invalid hashes. Some files may be broken. Архив содержит неверные хеш-суммы. Некоторые файлы могут быть испорчены. - + Nexus ID for this Mod is unknown Nexus ID для этого мода неизвестен - - + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... Создать мод... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Это переместит все файлы из перезаписи в новый, стандартный мод. Пожалуйста введите имя: - + A mod with this name already exists Мод с таким именем уже существует - + Continue? Продолжить? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. Схема управления версиями принимает решение, какая версия считается новее другой. Функция может попробовать угадать схему управления версиями, при условии, что установленная версия является устаревшей. - - + + Sorry Извините - - I don't know a versioning scheme where %1 is newer than %2. + + I don't know a versioning scheme where %1 is newer than %2. Мне неизвестна схема управления версиями, где %1 новее %2. - + Really enable all visible mods? Действительно подключить все видимые моды? - + Really disable all visible mods? Действительно отключить все видимые моды? - + Choose what to export Выберете, что экспортировать - + Everything Всё - + All installed mods are included in the list Все установленные моды, включенные в список - + Active Mods Активные моды - + Only active (checked) mods from your current profile are included Включены все активные (подключенные) моды вашего текущего профиля - + Visible Видимые - + All mods visible in the mod list are included Включены все моды, видимые в списке модов - + export failed: %1 экспорт не удался: %1 - + Install Mod... Установить мод... - + Enable all visible Включить все видимые - + Disable all visible Отключить все видимые - + Check all for update Проверить все на обновления - + Export to csv... Экспорт в csv... - + + All Mods + + + + Sync to Mods... Синхронизировать с модами... - + Restore Backup Восстановить из резервной копии - + Remove Backup... Удалить резервную копию... - + Add/Remove Categories Добавить/Удалить категории - + Replace Categories Заменить категории - + Primary Category Основная категория - + Change versioning scheme Изменить схему управления версиями - + Un-ignore update Снять игнорирование обновления - + Ignore update Игнорировать обновление - + Rename Mod... Переименовать мод... - + Remove Mod... Удалить мод... - + Reinstall Mod Переустановить мод - + Un-Endorse Отменить одобрение - - + + Endorse Одобрить - - Won't endorse + + Won't endorse Не одобрять - + Endorsement state unknown Статус одобрения неизвестен - + Ignore missing data Игнорировать отсутствующие данные - + Visit on Nexus Перейти на Nexus - + Open in explorer Открыть в проводнике - + Information... Информация... - - + + Exception: Исключение: - - + + Unknown exception Неизвестное исключение - + <All> <Все> - + <Multiple> <Несколько> - - Really delete "%1"? - Действительно удалить "%1"? + + Really delete "%1"? + Действительно удалить "%1"? - + Fix Mods... Исправить моды... - + Delete Удалить - - + + failed to remove %1 не удалось удалить %1 - - + + failed to create %1 не удалось создать %1 - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! Нельзя изменить каталог для загрузок, когда загрузки ещё не завершены! - + Download failed Загрузка не удалась - + failed to write to file %1 ошибка записи в файл %1 - + %1 written %1 записан - + Select binary Выберете исполняемый файл - + Binary Исполняемый файл - + Enter Name Введите имя - + Please enter a name for the executable Введите название для программы - + Not an executable Не является исполняемым - + This is not a recognized executable. Это неверный исполняемый файл. - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Уже существует скрытая версия этого файла. Заменить? - - + + File operation failed Операция с файлом не удалась - - - Failed to remove "%1". Maybe you lack the required file permissions? - Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + file not found: %1 файл не найден: %1 - + failed to generate preview for %1 не удалось получить предосмотр для %1 - - Sorry, can't preview anything. This function currently does not support extracting from bsas. + + Sorry, can't preview anything. This function currently does not support extracting from bsas. Невозможно получить предосмотр чего-либо. Функция на данный момент не поддерживает извлечение из bsa. - + Update available Доступно обновление - + Open/Execute Открыть/Выполнить - + Add as Executable Добавить как исполняемый - + Preview Предосмотр - + Un-Hide Показать - + Hide Скрыть - + Write To File... Записать в файл... - + Do you want to endorse Mod Organizer on %1 now? Вы хотите одобрить Mod Organizer на %1 сейчас? - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 Запрос на Nexus не удался: %1 - - + + login successful успешный вход - + login failed: %1. Trying to download anyway вход не удался: %1. Пытаюсь загрузить всё равно - + login failed: %1 войти не удалось: %1 - + login failed: %1. You need to log-in with Nexus to update MO. войти не удалось: %1. Вам нужно войти на Nexus, чтобы обновить MO. - + Error Ошибка - + failed to extract %1 (errorcode %2) не удалось распаковать %1 (код ошибки %2) - + Extract... Распаковать... - + Edit Categories... Изменить категории... - + + Deselect filter + + + + Remove Удалить - + Enable all Включить все - + Disable all Отключить все - + Unlock load order Снять фиксацию порядка загрузки - + Lock load order Зафиксировать порядок загрузки - + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + + + BOSS working - BOSS: работает + BOSS: работает - failed to run boss: %1 - не удалось запустить BOSS: %1 + не удалось запустить BOSS: %1 @@ -2621,8 +2894,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 неверный индекс %1 @@ -2630,7 +2903,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod Это резервная копия мода @@ -2659,7 +2932,7 @@ This function will guess the versioning scheme under the assumption that the ins - + Save Сохранить @@ -2669,53 +2942,73 @@ This function will guess the versioning scheme under the assumption that the ins INI-файлы - + + Ini Files + + + + This is a list of .ini files in the mod. Это список ini-файлов мода. - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Это список ini-файлов мода. Они используются для настройки работы модов, если это возможно. - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Сохранить изменения в файле. - + Save changes to the file. This overwrites the original. There is no automatic backup! Сохранить изменения в файле. Это перезапишет ранее созданный. Перед перезаписью файла сделайте копию. - + Images Изображение - + Images located in the mod. Изображения мода. - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. Это список всех изображений (.jpg и.png) в папке с модом, таких как снимки экрана и т.п. Выберете любое для увеличения. - - + + Optional ESPs Необязательные ESP - + List of esps and esms that can not be loaded by the game. Список esp и esm, которые не могут быть загружены игрой. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. @@ -2726,440 +3019,448 @@ Most mods do not have optional esps, so chances are good you are looking at an e Большинство модов не имеет дополнительных esp, так что очень вероятно, что вы видите пустой список. - + Make the selected mod in the lower list unavailable. Сделать выбранный мод недоступным. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - Выбранный ESP (в нижнем списке) будет перемещены в подкаталог мода и, таким образом, станут "невидимыми" для игры. Затем они больше не будут активированы. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + Выбранный ESP (в нижнем списке) будет перемещены в подкаталог мода и, таким образом, станут "невидимыми" для игры. Затем они больше не будут активированы. - + Move a file to the data directory. Переместить файл в каталог Data. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - Перемещает esp в каталог с другими esp так, что он может быть подключен в главном окне. Обратите внимание, что ESP просто становится "доступным", но не будет загружен! Это настраивается в главном окне MO. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + Перемещает esp в каталог с другими esp так, что он может быть подключен в главном окне. Обратите внимание, что ESP просто становится "доступным", но не будет загружен! Это настраивается в главном окне MO. - + ESPs in the data directory and thus visible to the game. ESP в каталоге Data, видны для игры. - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. Это файлы модов, которые находятся в (виртуальном)каталоге данных вашей игры и могут быть выбираемыми в списке esp, в главном окне. - + Available ESPs Доступные ESP - + Conflicts Конфликты - + The following conflicted files are provided by this mod Данные конфликты вызваны этим модом. - - + + File Файл - + Overwritten Mods Моды перезаписаны - + The following conflicted files are provided by other mods Конфликтные файлы других модов. - + Providing Mod Моды перезаписывают - + Non-Conflicted files Неконфликтные файлы - + Categories Категории - + Primary Category Основная категория - + Nexus Info Nexus - + Mod ID ID мода - + Mod ID for this mod on Nexus. ID мода на Nexus. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ID этого мода на Nexus. Заполняется автоматически, если скачали и установили мод из МО. В противном случае вы можете ввести его вручную. Чтобы найти правильный ID, нужно найти мод на Nexus. URL будет выглядеть следующим образом: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. В этом примере, 1334 - ID, который вы ищете. Кроме того: Выше есть ссылка на Mod Organizer на Nexus. Почему бы не перейти по ней и не одобрить?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ID этого мода на Nexus. Заполняется автоматически, если скачали и установили мод из МО. В противном случае вы можете ввести его вручную. Чтобы найти правильный ID, нужно найти мод на Nexus. URL будет выглядеть следующим образом: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. В этом примере, 1334 - ID, который вы ищете. Кроме того: Выше есть ссылка на Mod Organizer на Nexus. Почему бы не перейти по ней и не одобрить?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Установленная версия мода. Подсказка будет содержать текущую версию, доступную на Nexus. Установленная версия установится только если вы установили мод через МО.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Установленная версия мода. Подсказка будет содержать текущую версию, доступную на Nexus. Установленная версия установится только если вы установили мод через МО.</span></p></body></html> - + Version Версия - + Refresh Обновить информацию - + Refresh all information from Nexus. Обновить всю информацию с Nexus - + Description Описание - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - + Endorse Одобрить - + Notes Примечания - + Filetree Файлы - + A directory view of this mod Каталог этого мода - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Это обзор модифицируемого каталога папки мода. Вы можете перемещать файлы, используя перетаскивание и переименовывать их двойным кликом.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Изменения происходят непосредственно на диске, так что</span><span style=" font-size:8pt; font-weight:600;"> будьте осторожныl</span><span style=" font-size:8pt;">.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Это обзор модифицируемого каталога папки мода. Вы можете перемещать файлы, используя перетаскивание и переименовывать их двойным кликом.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Изменения происходят непосредственно на диске, так что</span><span style=" font-size:8pt; font-weight:600;"> будьте осторожныl</span><span style=" font-size:8pt;">.</span></p></body></html> - + Previous Назад - + Next Вперед - + Close Закрыть - + &Delete &Удалить - + &Rename &Переименовать - + &Hide &Скрыть - + &Unhide &Показать - + &Open &Открыть - + &New Folder &Новая папка - - + + Save changes? Сохранить изменения? - - - Save changes to "%1"? - Сохранить изменения в "%1"? + + + Save changes to "%1"? + Сохранить изменения в "%1"? - + File Exists Файл уже существует - + A file with that name exists, please enter a new one Файл с таким именем уже существует, укажите другое - + failed to move file не удалось переместить файл - - failed to create directory "optional" - не удалось создать папку "optional" + + failed to create directory "optional" + не удалось создать папку "optional" - - + + Info requested, please wait Информация запрошена, пожалуйста, подождите - + Main Главное - + Update Обновление - + Optional Опционально - + Old Старые - + Misc Разное - + Unknown Неизвестно - + Current Version: %1 Текущая версия: %1 - + No update available Нет доступных обновлений - + (description incomplete, please visit nexus) (описание не завершено, смотрите на nexus) - - <a href="%1">Visit on Nexus</a> - <a href="%1">Перейти на Nexus</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">Перейти на Nexus</a> - + Failed to delete %1 Не удалось удалить %1 - - + + Confirm Подтверждение - - Are sure you want to delete "%1"? - Вы уверены, что хотите удалить "%1"? + + Are sure you want to delete "%1"? + Вы уверены, что хотите удалить "%1"? - + Are sure you want to delete the selected files? Вы уверены, что хотите удалить выбранные файлы? - - + + New Folder Новая папка - - Failed to create "%1" - Не удалось создать "%1" + + Failed to create "%1" + Не удалось создать "%1" - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Скрытая версия этого файла уже существует. Заменить? - - + + File operation failed Не удалась операция с файлом - - - Failed to remove "%1". Maybe you lack the required file permissions? - Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? + + + Failed to remove "%1". Maybe you lack the required file permissions? + Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - - + + failed to rename %1 to %2 не удалось переименовать %1 в %2 - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + Un-Hide Показать - + Hide Скрыть - + Name Имя - + Please enter a name Пожалуйста, введите имя - - + + Error Ошибка - + Invalid name. Must be a valid file name Неверное имя. Необходимо допустимое имя файла. - + A tweak by that name exists Настройка с таким именем существует - + Create Tweak Создать настройку + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + + + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Этот псевдо-мод содержит файлы из виртуального древа данных, которые были изменены (в Construction Kit и других программах) @@ -3167,17 +3468,22 @@ p, li { white-space: pre-wrap; } ModInfoRegular - failed to write %1/meta.ini: %2 - не удалось записать %1/meta.ini: %2 + не удалось записать %1/meta.ini: %2 - + + + failed to write %1/meta.ini: error %2 + + + + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 не содержит ни esp/esm, ни папок ресурсов (textures, meshes, interface, ...) - + Categories: <br> Категории: <br> @@ -3225,119 +3531,124 @@ p, li { white-space: pre-wrap; } Избыточные - + + Non-MO + + + + invalid неверные - - installed version: "%1", newest version: "%2" + + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 установлена версия: %1, новейшая версия: %2 - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - Новейшая версия на Nexus кажется старее той, что установлена у вас. Это может означать, что ваша версия была снята (в связи с ошибкой и т.п.) или автор использует нестандартную схему версий, а новейшая версия на самом деле выше. В любом случае, вы можете "обновить". + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + Новейшая версия на Nexus кажется старее той, что установлена у вас. Это может означать, что ваша версия была снята (в связи с ошибкой и т.п.) или автор использует нестандартную схему версий, а новейшая версия на самом деле выше. В любом случае, вы можете "обновить". - + Categories: <br> Категории: <br> - + Invalid name Недопустимое имя - + drag&drop failed: %1 перетаскивание не удалось: %1 - + Confirm Подтверждение - - Are you sure you want to remove "%1"? - Вы действительно хотите удалить "%1"? + + Are you sure you want to remove "%1"? + Вы действительно хотите удалить "%1"? - + Flags Флаги - + Mod Name Имя мода - + Version Версия - + Priority Приоритет - + Category Категория - + Nexus ID Nexus ID - + Installation Установка - - + + unknown неизвестный - + Name of your mods Имя ваших модов - + Version of the mod (if available) Версия мода (если доступно) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Приоритет установки для ваших модов. Файлы модов с большим приоритетом перезапишут файлы модов с меньшим. - + Category of the mod. Категория мода. - + Id of the mod as used on Nexus. ID мода, используемый на Nexus. - + Emblemes to highlight things that might require attention. Выделяет подсветкой вещи, которые могут потребовать внимания. - + Time this mod was installed Время, когда мод был установлен. @@ -3371,17 +3682,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus Авторизация на Nexus - + timeout задержка - + + Unknown error + + + + Please check your password Проверьте ваш пароль @@ -3389,17 +3705,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - Не удалось опознать id мода "%1", пожалуйста, выберете правильный + + Failed to guess mod id for "%1", please pick the correct one + Не удалось опознать id мода "%1", пожалуйста, выберете правильный - + empty response пустой ответ - + invalid response неверный ответ @@ -3438,8 +3754,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - Не удалось удалить "%1" + Failed to delete "%1" + Не удалось удалить "%1" @@ -3449,8 +3765,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - Вы уверены, что хотите удалить "%1"? + Are sure you want to delete "%1"? + Вы уверены, что хотите удалить "%1"? @@ -3465,116 +3781,129 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - Не удалось создать "%1" + Failed to create "%1" + Не удалось создать "%1" PluginList - + Name Имя - + Priority Приоритет - + Mod Index Индекс - + Flags Флаги - - + + unknown неизвестно - + Name of your mods Имена ваших модов - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. Приоритет загрузки ваших модов. Моды с большим приоритетом перезапишут данные модов с меньшим приоритетом. - + The modindex determins the formids of objects originating from this mods. Индекс модов, определяющий formid объектов, происходящих из этих модов. - + failed to update esp info for file %1 (source id: %2), error: %3 не удалось обновить информацию о esp для файла %1 (id источника: %2), ошибка: %3 - + esp not found: %1 esp не найден: %1 - - + + Confirm Подтвердить - + Really enable all plugins? Действительно подключить все плагины? - + Really disable all plugins? Действительно отключить все плагины? - + The file containing locked plugin indices is broken Файл, содержащий индексы заблокированного плагина, не работает. - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Некоторые из ваших плагинов имеют неверные имена. Эти плагины не могут быть загружены игрой. Смотрите mo_interface.log для получения списка таких плагинов и переименуйте их. - + + <b>Origin</b>: %1 + + + + + Author + Автор + + + + Description + Описание + + BOSS dll incompatible - BOSS dll несовместим + BOSS dll несовместим - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) Этот плагин не может быть отключен (грузится игрой принудительно) - Origin: %1 - Источник: %1 + Источник: %1 - + Missing Masters Отсутствующие мастерфайлы - + Enabled Masters Подключенные мастерфайлы - + failed to restore load order for %1 не удалось восстановить порядок загрузки для %1 @@ -3601,16 +3930,16 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> @@ -3642,63 +3971,68 @@ p, li { white-space: pre-wrap; } не удалось создать %1 - + failed to write mod list: %1 не удалось записать список модов: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 не удалось обновить настроенный ini-файл, вероятно используются ошибочные настройки: %1 - + failed to create tweaked ini: %1 не удалось создать настроенный ini: %1 - - - - - + + "%1" is missing or inaccessible + "%1" отсутствует + + + + + + + invalid index %1 неверный индекс %1 - - Overwrite directory couldn't be parsed + + Overwrite directory couldn't be parsed Замена каталога не может быть обработана - + invalid priority %1 неверный приоритет %1 - + failed to parse ini file (%1) не удалось обработать ini файл (%1) - + failed to parse ini file (%1): %2 не удалось обработать ini файл (%1): %2 - - - failed to modify "%1" - не удалось изменить "%1" + + + failed to modify "%1" + не удалось изменить "%1" - + Delete savegames? Удалить сохранения? - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) Вы хотите удалить локальные сохранения? (При отрицательном выборе сохранения отобразятся снова, если повторно включить локальные сохранения) @@ -3721,7 +4055,7 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. Если отмечено, новый профиль будет использовать настройки игры по умолчанию, вместо общих настроек. Общие настройки, это настройки, которые вы установили, когда запустили лаунчер игры напрямую, без MO. @@ -3744,20 +4078,20 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Это список профилей. Каждый профиль содержит свой собственный список и порядок установки подключенных модов (из общего пула), настройки подключенных esps/esms, копии игровых ini-файлов и опциональный фильтр сохранений.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Примечание</span> По техническим причинам невозможно иметь отдельные порядки загрузки для esp. Это означает, что вы не сможете загрузить moda.esp пере modb.esp в одном профиле и иным образом в другом профиле.</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Это список профилей. Каждый профиль содержит свой собственный список и порядок установки подключенных модов (из общего пула), настройки подключенных esps/esms, копии игровых ini-файлов и опциональный фильтр сохранений.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Примечание</span> По техническим причинам невозможно иметь отдельные порядки загрузки для esp. Это означает, что вы не сможете загрузить moda.esp пере modb.esp в одном профиле и иным образом в другом профиле.</p></body></html> @@ -3777,22 +4111,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Игры Oblivion, Fallout 3 и Fallout NV содержат баг, который делает неработоспособными текстуры и модели реплейсеров (то есть: все изменения моделей и текстур в игре).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer использует способ обхода, называемый &quot;BSA redirection&quot; (google в помощь), чтобы решить эту проблему надежно и без дальнейшей возни. Просто подключить и забыть.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">В Скайрим, кажется, этот баг исправлен в известной степени, но будет ли мод активным, зависит по прежнему от даты модификации файлов. Поэтому всё ещё есть смысл включить это.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Игры Oblivion, Fallout 3 и Fallout NV содержат баг, который делает неработоспособными текстуры и модели реплейсеров (то есть: все изменения моделей и текстур в игре).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer использует способ обхода, называемый &quot;BSA redirection&quot; (google в помощь), чтобы решить эту проблему надежно и без дальнейшей возни. Просто подключить и забыть.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">В Скайрим, кажется, этот баг исправлен в известной степени, но будет ли мод активным, зависит по прежнему от даты модификации файлов. Поэтому всё ещё есть смысл включить это.</span></p></body></html> @@ -3859,7 +4193,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. Инвалидация не требуется для этой игры. @@ -3910,8 +4244,8 @@ p, li { white-space: pre-wrap; } - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - Профиль, который вы собираетесь удалить, кажется испорчен или содержит неверный путь. Речь идет о удалении следующей папки: "%1". Приступить? + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Профиль, который вы собираетесь удалить, кажется испорчен или содержит неверный путь. Речь идет о удалении следующей папки: "%1". Приступить? @@ -3956,23 +4290,23 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - неверное имя поля "%1" + invalid field name "%1" + неверное имя поля "%1" - invalid type for "%1" (should be integer) - неверный тип для "%1" (должно быть целое) + invalid type for "%1" (should be integer) + неверный тип для "%1" (должно быть целое) - invalid type for "%1" (should be string) - неверный тип для "%1" (должна быть строка) + invalid type for "%1" (should be string) + неверный тип для "%1" (должна быть строка) - invalid type for "%1" (should be float) - неверный тип для "%1" (должно быть с плавающей запятой) + invalid type for "%1" (should be float) + неверный тип для "%1" (должно быть с плавающей запятой) @@ -3981,13 +4315,13 @@ p, li { white-space: pre-wrap; } - field not set "%1" - поле не установлено "%1" + field not set "%1" + поле не установлено "%1" - invalid character in field "%1" - неверный символ в поле "%1" + invalid character in field "%1" + неверный символ в поле "%1" @@ -4081,87 +4415,91 @@ p, li { white-space: pre-wrap; } Не удалось установить загрузку proxy-dll - + Permissions required Требуются права доступа - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - Текущий аккаунт пользователя не имеет требуемых прав доступа для запуска Mod Organizer. Необходимые изменения могут быть сделаны автоматически (папка MO будет сделана записываемой для текущего аккаунта пользователя). Вы получите запрос о запуске "helper.exe" с правами администратора. + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + Текущий аккаунт пользователя не имеет требуемых прав доступа для запуска Mod Organizer. Необходимые изменения могут быть сделаны автоматически (папка MO будет сделана записываемой для текущего аккаунта пользователя). Вы получите запрос о запуске "helper.exe" с правами администратора. - - + + Woops Упс - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened Mod Organizer вышел из строя! Нужно ли создать диагностический файл? Если вы вышлите файл (%1) по адресу sherb@gmx.net, ошибка с намного большей вероятностью будет исправлена. Пожалуйста, добавьте краткое описание своих действий, перед тем, как произошла ошибка - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 ModOrganizer вышел из строя! К сожалению не удалось записать диагностический файл: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Другой экземпляр Mod Organizer уже запущен - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - Игра не обнаружена в "%1". Требуется, чтобы папка содержала исполняемые файлы игры. + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + Игра не обнаружена в "%1". Требуется, чтобы папка содержала исполняемые файлы игры. - - + + Please select the game to manage Выберете игру для управления - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) Пожалуйста, выберете редакцию игры, которую вы имеете (MO не сможет правильно запустить игру, если это будет установлено неверно!) - - Please use "Help" from the toolbar to get usage instructions to all elements - Используйте пункт "Справка" на панели инструментов, чтобы получить инструкции по использованию всех элементов. + + failed to start application: %1 + + + + + Please use "Help" from the toolbar to get usage instructions to all elements + Используйте пункт "Справка" на панели инструментов, чтобы получить инструкции по использованию всех элементов. - - + + <Manage...> <Управлять...> - + failed to parse profile %1: %2 не удалось обработать профиль %1: %2 - - - failed to find "%1" - не удалось найти "%1" + + failed to find "%1" + не удалось найти "%1" - + failed to access %1 не удалось получить доступ к %1 - + failed to set file time %1 не удалось изменить дату модификации для %1 @@ -4172,8 +4510,9 @@ p, li { white-space: pre-wrap; } - "%1" is missing - "%1" отсутствует + "%1" is missing or inaccessible + "%1" is missing + "%1" отсутствует @@ -4199,62 +4538,62 @@ p, li { white-space: pre-wrap; } не удалось открыть %1 - + Script Extender Script Extender - + Proxy DLL Proxy DLL - - failed to spawn "%1" - не удалось вызвать "%1" + + failed to spawn "%1" + не удалось вызвать "%1" - + Elevation required Требуется повышение прав - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) Этот процесс требует повышения прав на запуск. Это потенциальный риск для безопасности, поэтому настоятельно рекомендуется изучить -"%1" +"%1" на возможность установки без повышения прав. Запустить с повышенными правами в любом случае? (будет выведен запрос о разрешении ModOrganizer.exe сделать изменения в системе) - - failed to spawn "%1": %2 - не удалось вызвать "%1": %2 + + failed to spawn "%1": %2 + не удалось вызвать "%1": %2 - - "%1" doesn't exist - "%1" не существует + + "%1" doesn't exist + "%1" не существует - - failed to inject dll into "%1": %2 - не удалось подключить dll к "%1": %2 + + failed to inject dll into "%1": %2 + не удалось подключить dll к "%1": %2 - - failed to run "%1" - не удалось запустить "%1" + + failed to run "%1" + не удалось запустить "%1" - + failed to open temporary file не удалось открыть временный файл @@ -4367,8 +4706,8 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - failed to open "%1" for writing - не удалось открыть "%1" для записи + failed to open "%1" for writing + не удалось открыть "%1" для записи @@ -4393,79 +4732,79 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe SelfUpdater - archive.dll not loaded: "%1" - archive.dll не загружен: "%1" + archive.dll not loaded: "%1" + archive.dll не загружен: "%1" - - - - + + + + Update Обновление - + An update is available (newest version: %1), do you want to install it? Доступно обновление (последняя версия: %1). Вы хотите установить его? - + Download in progress Загрузка в процессе - + Download failed: %1 Загрузка не удалась: %1 - + Failed to install update: %1 Не удалось установить обновление: %1 - - failed to open archive "%1": %2 - не удалось открыть архив "%1": %2 + + failed to open archive "%1": %2 + не удалось открыть архив "%1": %2 - + failed to move outdated files: %1. Please update manually. не удалось переместить устаревшие файлы: %1. Пожалуйста, обновите вручную. - + Update installed, Mod Organizer will now be restarted. Обновление установлено, Mod Organizer будет перезапущен. - + Error Ошибка - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. Не удалось обработать запрос. Пожалуйста, сообщите об этом баге, включив в сообщение файл mo_interface.log. - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) Нет дополнительных обновлений для этой версии, необходимо загрузить полный пакет (%1 kB) - + no file for update found. Please update manually. не найдено файла для обновления. Пожалуйста, обновите вручную. - + Failed to retrieve update information: %1 Не удалось получить сведения об обновлении: %1 - + No download server available. Please try again later. Нет доступных для загрузки серверов. Пожалуйста, попробуйте позже. @@ -4473,18 +4812,18 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Settings - - - attempt to store setting for unknown plugin "%1" - попытка сохранить настройку для неизвестного плагина "%1" + + + attempt to store setting for unknown plugin "%1" + попытка сохранить настройку для неизвестного плагина "%1" - + Confirm Подтверждение - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Изменение каталога для модов отразится на всех ваших профилях. Нельзя будет отменить это, если только вы не сохранили резервные копии ваших профилей вручную. Продолжить? @@ -4513,16 +4852,16 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Используемый язык. Будут отображены языки, локализация на которые имеется.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Используемый язык. Будут отображены языки, локализация на которые имеется.</span></p></body></html> @@ -4546,15 +4885,15 @@ p, li { white-space: pre-wrap; } - Decides the amount of data printed to "ModOrganizer.log" - Определяет количество данных, выводимых в "ModOrganizer.log" + Decides the amount of data printed to "ModOrganizer.log" + Определяет количество данных, выводимых в "ModOrganizer.log" - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - Определяет количество данных, выводимых в "ModOrganizer.log". -"Отладка" позволяет получить очень полезную для поиска проблем информацию. Влияния на производительность не замечено, однако размер лога может быть довольно большим. Если это проблема, то вы можете предпочесть для обычного использования уровень "Информация". На уровне "Ошибка" лог обычно остается пустым. + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Определяет количество данных, выводимых в "ModOrganizer.log". +"Отладка" позволяет получить очень полезную для поиска проблем информацию. Влияния на производительность не замечено, однако размер лога может быть довольно большим. Если это проблема, то вы можете предпочесть для обычного использования уровень "Информация". На уровне "Ошибка" лог обычно остается пустым. @@ -4594,7 +4933,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). Каталог, в котором хранятся моды. Имейте ввиду, эти изменения нарушат все ассоциации профилей с несуществующими в новом расположении модами (с тем же именем). @@ -4607,297 +4946,340 @@ p, li { white-space: pre-wrap; } Cache Directory Каталог кэша + + + User interface + + + If checked, the download interface will be more compact. + + + + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + Reset stored information from dialogs. Сброс хранимой информации о диалогах. - - This will make all dialogs show up again where you checked the "Remember selection"-box. - Заставит снова появиться все диалоги, в которых вы ранее отмечали флажок "Запомнить выбор". + + This will make all dialogs show up again where you checked the "Remember selection"-box. + Заставит снова появиться все диалоги, в которых вы ранее отмечали флажок "Запомнить выбор". - + Reset Dialogs Сбросить диалоги - - + + Modify the categories available to arrange your mods. Изменение категорий, доступных для упорядочивания ваших модов. - + Configure Mod Categories Настроить категории модов - - + + Nexus Nexus - + Allows automatic log-in when the Nexus-Page for the game is clicked. Позволяет автоматически входить, кликнув на Nexus-страницу игры. - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Позволяет автоматически входить, кликнув на Nexus-страницу игры. Обратите внимание, что шифрование с которым пароль хранится в файле modorganizer.ini не очень сильное. Если вы беспокоитесь, что кто-нибудь может украсть ваш пароль, не храните его здесь.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Позволяет автоматически входить, кликнув на Nexus-страницу игры. Обратите внимание, что шифрование с которым пароль хранится в файле modorganizer.ini не очень сильное. Если вы беспокоитесь, что кто-нибудь может украсть ваш пароль, не храните его здесь.</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. Если отмечено и данные ниже введены правильно, входит на Nexus (для просмотра и загрузки) автоматически. - + Automatically Log-In to Nexus Автоматический вход на Nexus - + Username Имя пользователя - + Password Пароль - + Disable automatic internet features Отключить автоматические возможности интернет - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) Отключает автоматические возможности интернет. Это не подействует на функции, которые явно вызваны пользователем (проверка модов на обновления, одобрение, открытие в браузере) - + Offline Mode Автономный режим - + Use a proxy for network connections. Использовать прокси для соединения с сетью - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. Использовать прокси для соединения с сетью. Используются системные параметры, настраиваемые в Internet Explorer. Обратите внимание, что MO запустится на несколько секунд медленнее на некоторых системах, когда используется прокси. - + Use HTTP Proxy (Uses System Settings) Использовать HTTP Proxy (Используются системные настройки) - - Associate with "Download with manager" links - Ассоциировать с ссылками "Загрузить с помощью MO" + + Associate with "Download with manager" links + Ассоциировать с ссылками "Загрузить с помощью MO" - + Known Servers (updated on download) Известные серверы (обновлено при загрузке) - + Preferred Servers (Drag & Drop) Предпочитаемые серверы (Используйте перетаскивание) - + Plugins Плагины - + Author: Автор: - + Version: Версия: - + Description: Описание: - + Key Клавиша - + Value Значение - + Blacklisted Plugins (use <del> to remove): Плагины в черном списке (используйте <del> для удаления): - + Workarounds Способы обхода - + Steam App ID ID приложения Steam - + The Steam AppID for your game ID в Steam для вашей игры - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ID приложения в Steam необходим для прямого запуска некоторых игр. Для Skyrim, если он не установлен или неверен, механизм загрузки &quot;Mod Organizer&quot; может работать неправильно.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Предустановленный ID в большинстве случаев должен быть установлен.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы думаете, что у вас другая версия (GotY или другое), следуйте следующей инструкции по установке id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Перейдите в библиотеку игр Steam.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Сделайте правый клик по игре, id которой нужен и выберете </span><span style=" font-size:8pt; font-weight:600;">Создать ярлык на рабочем столе</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Сделайте правый клик на созданном на рабочем столе ярлыке и выберете </span><span style=" font-size:8pt; font-weight:600;">Свойства</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. в поле URL вы должны увидеть что-то вроде: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 это и есть id, который вам нужен.</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">ID приложения в Steam необходим для прямого запуска некоторых игр. Для Skyrim, если он не установлен или неверен, механизм загрузки &quot;Mod Organizer&quot; может работать неправильно.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Предустановленный ID в большинстве случаев должен быть установлен.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы думаете, что у вас другая версия (GotY или другое), следуйте следующей инструкции по установке id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Перейдите в библиотеку игр Steam.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. Сделайте правый клик по игре, id которой нужен и выберете </span><span style=" font-size:8pt; font-weight:600;">Создать ярлык на рабочем столе</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. Сделайте правый клик на созданном на рабочем столе ярлыке и выберете </span><span style=" font-size:8pt; font-weight:600;">Свойства</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. в поле URL вы должны увидеть что-то вроде: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 это и есть id, который вам нужен.</span></p></body></html> + + + Load Mechanism Механизм загрузки - + Select loading mechanism. See help for details. Выберете механизм загрузки. Смотрите справку для подробностей. - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. Mod Organizer необходимо подключить dll к игре, чтобы все моды были видны в ней. Есть несколько способов сделать это: *Mod Organizer* (по умолчанию) В этом режиме Mod Organizer сам подключает dll. Недостатком этого является то, что вам необходимо всегда начинать игру через MO или созданный им ярлык. *Script Extender* В этом режиме, MO установлен как плагин Script Extender (obse, fose, nvse, skse). *Proxy DLL* В этом режиме MO заменяет одну из игровых dll игры своей, которая загружает MO и оригинальную игру. Это работает только с играми Steam и тестировалось только на Skyrim. Используйте этот способ только если другие не работают. -Если вы используете Steam-версию Oblivion , способ по умолчанию не работает. В этом случае установите obse и используйте "Script Extender" как механизм загрузки. Также после этого вы не сможете запустить Oblivion из MO, вместо этого используйте MO только для настройки модов и запускайте игру через Steam. +Если вы используете Steam-версию Oblivion , способ по умолчанию не работает. В этом случае установите obse и используйте "Script Extender" как механизм загрузки. Также после этого вы не сможете запустить Oblivion из MO, вместо этого используйте MO только для настройки модов и запускайте игру через Steam. - + NMM Version Версия NMM - + The Version of Nexus Mod Manager to impersonate. Версия Nexus Mod Manager для представления. - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer использует API Nexus , для использования таких возможностей, как проверка обновлений и загрузка файлов. К сожалению этот API не был сделан официально доступным прочим утилитам, вроде MO, так что нужно представляться как Nexus Mod Manager, чтобы получить доступ. Помимо этого Nexus использует идентификатор версий, чтобы блокировать устаревшие версии NMM и принудительно заставить пользователей обновиться. Это значит, что MO также нужно представляться последней версией NMM, даже если MO не нуждается в обновлении. Поэтому вы можете настроить здесь также и версию для идентификации. -Обратите внимание, что MO идентифицирует себя веб-серверу как MO, он не подделывает данные о себе. Он всего лишь добавляется как "совместимая" с NMM версия, в поле user agent. +Обратите внимание, что MO идентифицирует себя веб-серверу как MO, он не подделывает данные о себе. Он всего лишь добавляется как "совместимая" с NMM версия, в поле user agent. tl;dr-версия: Если возможности Nexus не работают, вставьте здесь текущую версию NMM и повторите попытку. - + Enforces that inactive ESPs and ESMs are never loaded. Обеспечивает то, что неактивные ESP и ESM не будут загружены. - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Кажется, что иногда игры загружают ESP и ESM файлы, даже если они не были не подключены как плагины Обстоятельства этого пока не известны, но отчеты пользователей подразумевают, что это в ряде случаев нежелательно. Если этот флажок отмечен, не отмеченные в списке ESP и ESM не будут видимы в списке и не будут загружены. - + Hide inactive ESPs/ESMs Скрыть неактивные ESP/ESM - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Если отмечено, файлы (т.е. esp, esm и bsa) принадлежащие основной игре не смогут быть отключены из интерфейса. (по умолчанию: вкл) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. Если отмечено, файлы (т.е. esp, esm и bsa) принадлежащие основной игре не смогут быть отключены из интерфейса. (по умолчанию: вкл) Снимите флажок, если вы собираетесь использовать Mod Organizer с тотальными конверсиями (как Nehrim) , но будьте осторожны, игра может вылететь, если требуемые файлы будут отключены. - + Force-enable game files Принудительно подключить файлы игры - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! Для Скайрим это может быть использовано вместо инвалидации. Это должно сделать AI избыточным для всех профилей. Для других игр недостаточно замены для AI! - + Back-date BSAs Back-date BSAs - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. Это способы обхода проблем с Mod Organizer. Убедитесь, что вы прочли справку, перед тем, как делать какие-либо изменения здесь. @@ -4923,8 +5305,8 @@ For the other games this is not a sufficient replacement for AI! - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - Это позволить снова отобразить все диалоги, для которых вы ранее выбрали флажок "Запомнить выбор". + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + Это позволить снова отобразить все диалоги, для которых вы ранее выбрали флажок "Запомнить выбор". @@ -4970,10 +5352,14 @@ For the other games this is not a sufficient replacement for AI! - failed to connect to running instance: %1 не удалось подключиться к запущенному экземпляру: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4999,7 +5385,7 @@ For the other games this is not a sufficient replacement for AI! - <don't sync> + <don't sync> <не синхронизировать> @@ -5107,8 +5493,8 @@ On Windows XP: - Overwrite the file "%1" - Перезаписать файл "%1" + Overwrite the file "%1" + Перезаписать файл "%1" @@ -5121,18 +5507,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - Скопировать все игры с персонажем "%1" в профиль? + Copy all save games of character "%1" to the profile? + Скопировать все игры с персонажем "%1" в профиль? - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Переместить все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Переместить все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - Скопировать все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + Скопировать все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. diff --git a/src/organizer_tr.ts b/src/organizer_tr.ts index 7e6c51c3..73942e96 100644 --- a/src/organizer_tr.ts +++ b/src/organizer_tr.ts @@ -1,35 +1,80 @@ + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + Kapat + + + + No license + + + ActivateModsDialog Activate Mods - + This is a list of esps and esms that were active when the save game was created. - Bu oyun kaydı yaratıldığında aktif olan esp ve esm'lerin bir listesidir. + Bu oyun kaydı yaratıldığında aktif olan esp ve esm'lerin bir listesidir. - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bu, oyun kaydı yaratıldığında aktif olan espler ve esmlerin bir listesidir.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Her esp için, sağ kolon eksik esp/esm'leri tekrar mevcut hale getirmek için kullanılabilecek modu (yada modları) içerir.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Eğer Tamam'a basarsanız, sağ kolonda seçili olan tüm modlar ve müsait hale gelen tüm eksik espler aktif hale gelecektir.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bu, oyun kaydı yaratıldığında aktif olan espler ve esmlerin bir listesidir.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Her esp için, sağ kolon eksik esp/esm'leri tekrar mevcut hale getirmek için kullanılabilecek modu (yada modları) içerir.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Eğer Tamam'a basarsanız, sağ kolonda seçili olan tüm modlar ve müsait hale gelen tüm eksik espler aktif hale gelecektir.</span></p></body></html> @@ -72,9 +117,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. Bu paketinin bileşenleri -Eğer "00 Core" diye bir bileşen varsa, bu genellikle gereklidir. Seçenekler yaratıcı tarafından ayarlanmış önceliğe göre sıralıdır. +Eğer "00 Core" diye bir bileşen varsa, bu genellikle gereklidir. Seçenekler yaratıcı tarafından ayarlanmış önceliğe göre sıralıdır. @@ -109,6 +154,29 @@ Eğer "00 Core" diye bir bileşen varsa, bu genellikle gereklidir. Seçenekler y İptal + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -129,7 +197,7 @@ Eğer "00 Core" diye bir bileşen varsa, bu genellikle gereklidir. Seçenekler y Internal ID for the category. The categories a mod belongs to are stored by this ID. It is recommended you use new IDs for categories you add instead of re-using existing ones. - Kategori için iç kimlik. Bir mod'un ait olduğu kategoriler bu kimlikle saklanır. Eklediğiniz kategoriler için, varolanları yeniden kullanmak yerine yeni kimlikler kullanmanız önerilir. + Kategori için iç kimlik. Bir mod'un ait olduğu kategoriler bu kimlikle saklanır. Eklediğiniz kategoriler için, varolanları yeniden kullanmak yerine yeni kimlikler kullanmanız önerilir. @@ -154,20 +222,20 @@ Eğer "00 Core" diye bir bileşen varsa, bu genellikle gereklidir. Seçenekler y - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bir veya birden fazla nexus kategorisini bir iç kimliğe eşleştiler. Ne zaman bir nexus sayfasından bir mod indirirseniz, Mod Organizer Nexus'ta belirlenmiş kategoriyi MO'da mevcut olan bir kategoriye uydurmaya çalışır.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Nexus tarafından kullanılmakta olan bir kategori hakkında öğrenmek için nexus sayfasındaki kategoriler listesine ziyaret edin ve fareyi bağlantılar üzerinde tutun.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bir veya birden fazla nexus kategorisini bir iç kimliğe eşleştiler. Ne zaman bir nexus sayfasından bir mod indirirseniz, Mod Organizer Nexus'ta belirlenmiş kategoriyi MO'da mevcut olan bir kategoriye uydurmaya çalışır.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Nexus tarafından kullanılmakta olan bir kategori hakkında öğrenmek için nexus sayfasındaki kategoriler listesine ziyaret edin ve fareyi bağlantılar üzerinde tutun.</span></p></body></html> @@ -199,8 +267,8 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus - Bu özellik Nexus'a oturuma açmazsanız çalışmayabilir + This feature may not work unless you're logged in with Nexus + Bu özellik Nexus'a oturuma açmazsanız çalışmayabilir @@ -226,32 +294,37 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 - + DownloadList - + Name İsim - + Filetime Dosyazamanı - + Done Bitti - - Information missing, please select "Query Info" from the context menu to re-retrieve. - Bilgi eksik, lütfen menüden "Bilgi sorgula" yı seçiniz + + Information missing, please select "Query Info" from the context menu to re-retrieve. + Bilgi eksik, lütfen menüden "Bilgi sorgula" yı seçiniz + + + + pending download + @@ -264,28 +337,28 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install Bitti - Yüklemek için çift tıklayın - - + + Paused - Double Click to resume - + - - + + Installed - Double Click to re-install Yüklendi - Tekrar yüklemek için çift tıklayın - - + + Uninstalled - Double Click to re-install - + @@ -304,20 +377,30 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + Paused - + Fetching Info 1 - + Fetching Info 2 - + @@ -327,7 +410,7 @@ p, li { white-space: pre-wrap; } Uninstalled - + @@ -335,95 +418,95 @@ p, li { white-space: pre-wrap; } Bitti - - - - + + + + Are you sure? Emin misiniz? - + This will remove all finished downloads from this list and from disk. Bu, tüm bitmiş indirilenleri listeden ve diskten kaldıracaktır. - + This will remove all installed downloads from this list and from disk. Bu, tüm yüklenmiş indirilenleri listeden ve diskten kaldıracaktır. - + This will permanently remove all finished downloads from this list (but NOT from disk). - + - + This will permanently remove all installed downloads from this list (but NOT from disk). - + - + Install Yükle - + Query Info Sorgu Bilgisi - + Delete - + - + Un-Hide - + - + Remove from View - + - + Cancel İptal et - + Pause Duraklat - + Remove Kaldır - + Resume Devam et - + Delete Installed... - + - + Delete All... - + - + Remove Installed... Yüklenmişleri kaldır... - + Remove All... Hepsini kaldır... @@ -431,105 +514,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + + < mod %1 file %2 > + + + + + Pending + + + + Fetching Info 1 - + - + Fetching Info 2 - + - - - - + + + + Are you sure? Emin misiniz? - + This will remove all finished downloads from this list and from disk. Bu, tüm bitmiş indirilenleri listeden ve diskten kaldıracaktır. - + This will remove all installed downloads from this list and from disk. Bu, tüm bitmiş yüklenmiş listeden ve diskten kaldıracaktır. - + This will remove all finished downloads from this list (but NOT from disk). - + - + This will remove all installed downloads from this list (but NOT from disk). - + - + Install Yükle - + Query Info Bilgi sorgula - + Delete - + - + Un-Hide - + - + Remove from View - + - + Cancel İptal et - + Pause Duraklat - + Remove Kaldır - + Resume Devam et - + Delete Installed... - + - + Delete All... - + - + Remove Installed... Yüklenmişleri kaldır - + Remove All... Hepsini kaldır... @@ -537,116 +630,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - "%1"yi "%2" olarak yeniden adlandırma başarılı olamadı. + + failed to rename "%1" to "%2" + "%1"yi "%2" olarak yeniden adlandırma başarılı olamadı. - + + Memory allocation error (in refreshing directory). + + + + Download again? Tekrar indir? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. Aynı dosya isminde bir dosya zaten indirilmiş. Tekrar indirmek istiyor musunuz? Yeni dosya farklı bir isim alacak. - + failed to download %1: could not open output file: %2 %1 indirilemedi : : çıkış dosyası %2 açılamadı - + Wrong Game - + - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + - - - - - - - - - - - + + - - + + + + + + + + + + + + + invalid index geçersiz dizin - + failed to delete %1 %1 silinemedi - + failed to delete meta file for %1 - %1'in meta dosyası silinemedi + %1'in meta dosyası silinemedi - - - - - - + + + + + + invalid index %1 geçeriz dizin %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id Lütfen nexus mod kimliğini girin - + Mod ID: Mod kimliği: - + + Main + + + + + Update + + + + + Optional + + + + + Old + + + + + Misc + + + + + Unknown + + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated bilgi güncellendi - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? - Nexus'ta uyan bir dosya bulunamadı! Dosya artık mevcut olmayabilir yada yeniden adlandırılmış olabilir! + Nexus'ta uyan bir dosya bulunamadı! Dosya artık mevcut olmayabilir yada yeniden adlandırılmış olabilir! - + No file on Nexus matches the selected file by name. Please manually choose the correct one. - Nexus'ta seçili dosyaya isim olarak uyan dosya bulunamadı. Lütfen el ile doğru olanı seçin. + Nexus'ta seçili dosyaya isim olarak uyan dosya bulunamadı. Lütfen el ile doğru olanı seçin. - + No download server available. Please try again later. - + - + Failed to request file info from nexus: %1 - Nexus'tan dosya bilgisi istenilemedi: %1 + Nexus'tan dosya bilgisi istenilemedi: %1 + + + + Download failed. Server reported: %1 + - + Download failed: %1 (%2) İndirme başarızı: %1 (%2) - + failed to re-open %1 %1 tekrar açılamadı @@ -709,7 +859,7 @@ p, li { white-space: pre-wrap; } Start in - + @@ -725,21 +875,21 @@ p, li { white-space: pre-wrap; } Allow the Steam AppID to be used for this executable to be changed. - Bu yürütülebilir için kullanılan, Steam AppID (Uygulama kimliği)'nın değiştirilmesine izin ver. + Bu yürütülebilir için kullanılan, Steam AppID (Uygulama kimliği)'nın değiştirilmesine izin ver. Allow the Steam AppID to be used for this executable to be changed. Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. - Bu yürütülebilir için kullanılan, Steam AppID (Uygulama kimliği)'nın değiştirilmesine izin ver. -Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır. MO'nun bu programları direkt olarak çalıştırabilmesi için bu kimliği bilmesi gereklidir, aksi takdirde program steam tarafından başlatılır ve ardından MO çalışamaz.Önceden tanımlanmış durumda, MO oyun için olan AppID'yi kullanır. -Şu an itibariyle, bunun değiştirilmesi gerektiği tek durum kendine has AppID'si olan Skyrim Creation Kit'tir. Bu üstüne yazma zaten önceden ayarlanmıştır. + Bu yürütülebilir için kullanılan, Steam AppID (Uygulama kimliği)'nın değiştirilmesine izin ver. +Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır. MO'nun bu programları direkt olarak çalıştırabilmesi için bu kimliği bilmesi gereklidir, aksi takdirde program steam tarafından başlatılır ve ardından MO çalışamaz.Önceden tanımlanmış durumda, MO oyun için olan AppID'yi kullanır. +Şu an itibariyle, bunun değiştirilmesi gerektiği tek durum kendine has AppID'si olan Skyrim Creation Kit'tir. Bu üstüne yazma zaten önceden ayarlanmıştır. Overwrite Steam AppID - Steam AppID (Uygulama kimliği)'nın üzerine yaz + Steam AppID (Uygulama kimliği)'nın üzerine yaz @@ -752,20 +902,20 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. Bu yürütülebilir için kullanılacak olan ve oyununkinden farklı olan Steam AppID (Uygulama kimliği) -Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır. MO'nun bu programları direkt olarak çalıştırabilmesi için bu kimliği bilmesi gereklidir, aksi takdirde program steam tarafından başlatılır ve ardından MO çalışamaz.Önceden tanımlanmış durumda, MO oyun için olan AppID'yi kullanır (genelde 72850). -Şu an itibariyle, bunun değiştirilmesi gerektiği tek durum kendine has AppID'si olan Skyrim Creation Kit'tir. Bu üstüne yazma zaten önceden ayarlanmıştır. +Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır. MO'nun bu programları direkt olarak çalıştırabilmesi için bu kimliği bilmesi gereklidir, aksi takdirde program steam tarafından başlatılır ve ardından MO çalışamaz.Önceden tanımlanmış durumda, MO oyun için olan AppID'yi kullanır (genelde 72850). +Şu an itibariyle, bunun değiştirilmesi gerektiği tek durum kendine has AppID'si olan Skyrim Creation Kit'tir. Bu üstüne yazma zaten önceden ayarlanmıştır. - + If checked, MO will be closed once the specified executable is run. Eğer seçiliyse, belirlenmiş yürütülebilir çalıştırıldığında MO kapatılacaktır. Close MO when started - Başladığında MO'yu kapat. + Başladığında MO'yu kapat. @@ -775,7 +925,7 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır - + Add Ekle @@ -791,49 +941,66 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır Kaldır - + + Close + Kapat + + + Select a binary Bir ikili değer seç - + Executable (%1) Yürütülebilir (%1) - + Java (32-bit) required - + - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + - + Select a directory Bir klasör seç - + Confirm Onayla - - Really remove "%1" from executables? - "%1" gerçekten yürütülebilirlerden kaldırılsın mı? + + Really remove "%1" from executables? + "%1" gerçekten yürütülebilirlerden kaldırılsın mı? - + Modify Değiştir - + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + MO must be kept running or this application will not work correctly. - MO'nun çalışmaya devam etmesi gereklidir yoksa uygulama düzgün çalışmayacaktır + MO'nun çalışmaya devam etmesi gereklidir yoksa uygulama düzgün çalışmayacaktır @@ -902,8 +1069,8 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır - <a href="#">Link</a> - <a href="#">Bağlantı</a> + <a href="#">Link</a> + <a href="#">Bağlantı</a> @@ -950,7 +1117,7 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. Mod için bir isim seçin. Bu ayrıca klasör ismi olarak da kullanılır ve bu nedenle lütfen geçersiz dosya isimlerinde geçerli olmayan karakterler kullanmayınız. @@ -965,16 +1132,16 @@ Steam tarafından dağıtılan her oyun/aracın kendine has bir kimliği vardır - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bu arşivin içeriğini gösterir &lt;data&gt; oyunun data klasörüne eşlenecek olan temel klasörü ifade eder. Temel klasörü sağ tık içerik menüsü aracılığıyla değiştirebilirsiniz ve sürükleme ve bırakma ile dosyaları taşıyabilirsiniz.</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Bu arşivin içeriğini gösterir &lt;data&gt; oyunun data klasörüne eşlenecek olan temel klasörü ifade eder. Temel klasörü sağ tık içerik menüsü aracılığıyla değiştirebilirsiniz ve sürükleme ve bırakma ile dosyaları taşıyabilirsiniz.</span></p></body></html> @@ -984,20 +1151,20 @@ p, li { white-space: pre-wrap; } OK - + Cancel - + InstallationManager - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + @@ -1012,19 +1179,19 @@ p, li { white-space: pre-wrap; } - + Extracting files Dosyalar çıkarılıyor failed to create backup - + Mod Name - + @@ -1034,65 +1201,65 @@ p, li { white-space: pre-wrap; } Invalid name - + The name you entered is invalid, please enter a different one. - + - - File format "%1" not supported - Dosya formatı "%1" desteklenmiyor. + + File format "%1" not supported + Dosya formatı "%1" desteklenmiyor. - + None of the available installer plugins were able to handle that archive - + - + no error hata yok - + 7z.dll not found 7z.dll bulunamadı - - 7z.dll isn't valid + + 7z.dll isn't valid 7z.dll geçerli değil - + archive not found arşiv bulunamadı - + failed to open archive arşiv açılamadı - + unsupported archive type desteklenmeyen arşiv türü - + internal library error içsel kütüphane hatası - + archive invalid arşiv geçersiz - + unknown archive error bilinmeyen arşiv hatası @@ -1106,8 +1273,8 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. - Eğer uygulama/oyun tamamlandığında bu dialoğun kaybolması gerekir. Eğer yapmazsa "Kilit aç"'a tıklayın. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + Eğer uygulama/oyun tamamlandığında bu dialoğun kaybolması gerekir. Eğer yapmazsa "Kilit aç"'a tıklayın. @@ -1115,7 +1282,7 @@ p, li { white-space: pre-wrap; } Yürütülebilir çalışırken MO kilitlidir. - + Unlock Kilit aç @@ -1123,7 +1290,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 %1: %2 ya kayıt yazılamadı @@ -1131,1290 +1298,1521 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 - + - + an error occured - + MainWindow - - + + Categories Kategoriler + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + - Profile - + And + - Pick a module collection - + If checked, all mods that match at least one of the selected categories are displayed. + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - + Or + + + + + Profile + + + + + Pick a module collection + - - Refresh list - + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + - + Refresh list. This is usually not necessary unless you modified data outside the program. - + - + List of available mods. - + - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + - + Filter - + - + No groups - + - + Nexus IDs - + Nexus kimliği - - - + + + Namefilter - + - + Pick a program to run. - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + - + Run program - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + - + Run - + - + Create a shortcut in your start menu or on the desktop to the specified program - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + - + Shortcut - + - + List of available esp/esm files - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - + - - + + File - + - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - - - - + Data - + - + refresh data-directory overview - + - + Refresh the overview. This may take a moment. - + - - - + + + Refresh - + - + This is an overview of your data directory as visible to the game (and tools). - + - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. - + - + Show only conflicts - + - + Saves - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + - + Downloads - + - + This is a list of mods you downloaded from Nexus. Double click one to install it. - + - - Compact - + + Open list options... + - + + + Restore Backup... + + + + + + Create Backup + + + + + Plugins + + + + + Sort + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + Show Hidden - + - + Tool Bar - + - + Install Mod - + - + Install &Mod - + - + Install a new mod from an archive - + - + Ctrl+M - + - + Profiles - + - + &Profiles - + - + Configure Profiles - + - + Ctrl+P - + - + Executables - + - + &Executables - + - + Configure the executables that can be started through Mod Organizer - + - + Ctrl+E - + - - + + Tools - + - + &Tools - + - + Ctrl+I - + - + Settings - + - + &Settings - + - + Configure settings and workarounds - + - + Ctrl+S - + - + Nexus - + - + Search nexus network for more mods - + - + Ctrl+N - + - - + + Update - + - + Mod Organizer is up-to-date - + - - + + No Problems - + - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! Right now this has very limited functionality - + - - + + Help - + - + Ctrl+H - + - + Endorse MO - + - - + + Endorse Mod Organizer - + + + + + Copy Log to Clipboard + + + + + Ctrl+C + - + Toolbar - + - + Desktop - + - + Start Menu - + - + Problems - + - + There are potential problems with your setup - + - + Everything seems to be in order - + - + Help on UI - + - + Documentation Wiki - + - + Report Issue - + - + Tutorials - - - - - failed to save archives order, do you have write access to "%1"? - + - + failed to save load order: %1 - + - + Name İsim - + Please enter a name for the new profile - + - + failed to create profile: %1 - + - + Show tutorial? - + - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + - + Downloads in progress - + - + There are still downloads in progress, do you really want to quit? - + - + failed to read savegame: %1 - + - - Plugin "%1" failed: %2 - + + Plugin "%1" failed: %2 + - - Plugin "%1" failed - + + Plugin "%1" failed + - + failed to init plugin %1: %2 - + - + Plugin error - + - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + - - Failed to start "%1" - + + Failed to start "%1" + - + Waiting - + - - Please press OK once you're logged into steam. - + + Please press OK once you're logged into steam. + - - "%1" not found - - - - + Start Steam? - + - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + - + Also in: <br> - + - + No conflict - + - + <Edit...> - + - - Failed to refresh list of esps: %s - - - - + This bsa is enabled in the ini file so it may be required! - - - - - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - + - + Activating Network Proxy - + - - + + Installation successful - + - - + + Configure Mod - + - - + + This mod contains ini tweaks. Do you want to configure them now? - + - - - mod "%1" not found - + + + mod "%1" not found + - - + + Installation cancelled - + - - + + The mod was not installed completely. - + - + Some plugins could not be loaded - + - + Too many esps and esms enabled - + - - + + Description missing - + - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + - + Choose Mod - + - + Mod Archive - + - + Start Tutorial? - + - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + - - + + Download started - + - + failed to update mod list: %1 - + - + failed to spawn notepad.exe: %1 - + - + failed to open %1 - + - + failed to change origin name: %1 - - - - - Failed to move "%1" from mod "%2" to "%3": %4 - + - + <Checked> - + - + <Unchecked> - + - + <Update> - + - + <No category> - + - + <Conflicted> - + - + <Not Endorsed> - + - + failed to rename mod: %1 - + - + Overwrite? - + - - This will replace the existing mod "%1". Continue? - + + This will replace the existing mod "%1". Continue? + - - failed to remove mod "%1" - + + failed to remove mod "%1" + - - - - failed to rename "%1" to "%2" - "%1"yi "%2" olarak yeniden adlandırma başarılı olamadı. + + + + failed to rename "%1" to "%2" + "%1"yi "%2" olarak yeniden adlandırma başarılı olamadı. - - Multiple esps activated, please check that they don't conflict. - + + Multiple esps activated, please check that they don't conflict. + - - - + + + + Confirm Onayla - + Remove the following mods?<br><ul>%1</ul> - + - + failed to remove mod: %1 - + - - + + Failed - + - + Installation file no longer exists - + - - Mods installed with old versions of MO can't be reinstalled in this way. - + + Mods installed with old versions of MO can't be reinstalled in this way. + - - + + You need to be logged in with Nexus to endorse - + - - + Extract BSA - + - - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - - - - - - + + failed to read %1: %2 %1: %2 okunamadı - - + This archive contains invalid hashes. Some files may be broken. - + - + Nexus ID for this Mod is unknown - + + + + + About + + + + + About Qt + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + Executable "%1" not found + + + + + Failed to refresh list of esps: %1 + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + - - + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... - + - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + - + A mod with this name already exists - + - + Continue? - + - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - + + Sorry - + - - I don't know a versioning scheme where %1 is newer than %2. - + + I don't know a versioning scheme where %1 is newer than %2. + - + Really enable all visible mods? - + - + Really disable all visible mods? - + - + Choose what to export - + - + Everything - + - + All installed mods are included in the list - + - + Active Mods - + - + Only active (checked) mods from your current profile are included - + - + Visible - + - + All mods visible in the mod list are included - + - + export failed: %1 - + - + Install Mod... - + - + Enable all visible - + - + Disable all visible - + - + Check all for update - + - + Export to csv... - + - + + All Mods + + + + Sync to Mods... - + - + Restore Backup - + - + Remove Backup... - + - + Add/Remove Categories - + - + Replace Categories - + - + Primary Category - + - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... - + - + Remove Mod... - + - + Reinstall Mod - + - + Un-Endorse - + - - + + Endorse - + - - Won't endorse - + + Won't endorse + - + Endorsement state unknown - + - + Ignore missing data - + - + Visit on Nexus - + - + Open in explorer - + - + Information... - + - - + + Exception: - + - - + + Unknown exception - + - + <All> - + - + <Multiple> - + - + + Really delete "%1"? + + + + Fix Mods... - + + + + + Delete + - - + + failed to remove %1 - + - - + + failed to create %1 - + - - Can't change download directory while downloads are in progress! - + + Can't change download directory while downloads are in progress! + - + Download failed - + - + failed to write to file %1 - + - + %1 written - + - + Select binary - + - + Binary İkili değer - + Enter Name - + - + Please enter a name for the executable - + - + Not an executable - + - + This is not a recognized executable. - + - - + + Replace file? - + - + There already is a hidden version of this file. Replace it? - + - - + + File operation failed - + - - - Failed to remove "%1". Maybe you lack the required file permissions? - + + + Failed to remove "%1". Maybe you lack the required file permissions? + - + There already is a visible version of this file. Replace it? - + + + + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + - + Update available - + - + Open/Execute - + - + Add as Executable - + - + + Preview + + + + Un-Hide - + - + Hide - + - + Write To File... - + - + Do you want to endorse Mod Organizer on %1 now? - + - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 - + - - + + login successful - + - + login failed: %1. Trying to download anyway - + - + login failed: %1 - + - + login failed: %1. You need to log-in with Nexus to update MO. - + - + Error - + - + failed to extract %1 (errorcode %2) - + - + Extract... - + - + Edit Categories... - + + + + + Deselect filter + - + Remove - + - + Enable all - + - + Disable all - + - + Unlock load order - + - + Lock load order - + + + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + @@ -2429,8 +2827,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 geçersiz dizin %1 @@ -2438,9 +2836,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + @@ -2451,523 +2849,552 @@ This function will guess the versioning scheme under the assumption that the ins Mod bilgisi - + Textfiles Yazı dosyaları - + A list of text-files in the mod directory. Mod klasöründeki yazı dosyalarının bir listesi - + A list of text-files in the mod directory like readmes. Mod klasöründeki benioku dosyaları gibi yazı dosyalarının bir listesi - - + + Save Kaydet - + INI-Files INI-Dosyaları - + + Ini Files + + + + This is a list of .ini files in the mod. Mod klasöründeki .ini dosyalarının bir listesi - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Moddaki .ini dosyalarının bir listesi. Bunlar genelde, ayarlabilir parametrelerin olduğuğu durumlarda modların davranışlarını ayarlamak için kullanılır. - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. Değişiklikleri dosyaya kaydet. - + Save changes to the file. This overwrites the original. There is no automatic backup! Değişiklikleri dosyaya kaydet. Bu orijinal dosyanın üzerine yazar. Otomatik yedekleme yoktur! - + Images Resimler - + Images located in the mod. Modun içinde yer alan resimler - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + - - + + Optional ESPs - İsteğe bağlı ESP'ler + İsteğe bağlı ESP'ler - + List of esps and esms that can not be loaded by the game. Oyun tarafından yüklenemeyen espler ve esmlerin bir listesi. - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + - + Make the selected mod in the lower list unavailable. Aşağıdaki listedeki seçili modu kullanılamaz yapar. - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. - Seçili esp (aşağıdaki listede) modun bir alt klasörüne itilecek ve böylece oyuna "görünmez" olacaktır. Artık aktif hale getirilemez. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + Seçili esp (aşağıdaki listede) modun bir alt klasörüne itilecek ve böylece oyuna "görünmez" olacaktır. Artık aktif hale getirilemez. - + Move a file to the data directory. Dosyayı veri klasörüne taşı. - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + - + ESPs in the data directory and thus visible to the game. - + - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. - + - + Available ESPs - + - + Conflicts - + - + The following conflicted files are provided by this mod - + - - + + File - + - + Overwritten Mods - + - + The following conflicted files are provided by other mods - + - + Providing Mod - + - + Non-Conflicted files - + - + Categories Kategoriler - + Primary Category - + - + Nexus Info - + - + Mod ID - + - + Mod ID for this mod on Nexus. - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + - + Version Versiyon - + Refresh - + - + Refresh all information from Nexus. - + - + Description - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse - + - + Notes - + - + Filetree - + - + A directory view of this mod - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + - + Previous - + - + Next - + Sonraki - + Close Kapat - + &Delete - + - + &Rename - + - + &Hide - + - + &Unhide - + - + &Open &Aç - + &New Folder - + - - + + Save changes? - + - - - Save changes to "%1"? - + + + Save changes to "%1"? + - + File Exists - + - + A file with that name exists, please enter a new one - + - + failed to move file - + - - failed to create directory "optional" - + + failed to create directory "optional" + - - + + Info requested, please wait - + - + Main - + - + Update - + - + Optional - + - + Old - + - + Misc - + - + Unknown - + - + Current Version: %1 - + - + No update available - + - + (description incomplete, please visit nexus) - + - - <a href="%1">Visit on Nexus</a> - + + <a href="%1">Visit on Nexus</a> + - + Failed to delete %1 - + - - + + Confirm Onayla - - Are sure you want to delete "%1"? - + + Are sure you want to delete "%1"? + - + Are sure you want to delete the selected files? - + - - + + New Folder - + - - Failed to create "%1" - + + Failed to create "%1" + - - + + Replace file? - + - + There already is a hidden version of this file. Replace it? - + - - + + File operation failed - + - - - Failed to remove "%1". Maybe you lack the required file permissions? - + + + Failed to remove "%1". Maybe you lack the required file permissions? + - - + + failed to rename %1 to %2 - + - + There already is a visible version of this file. Replace it? - + - + Un-Hide - + - + Hide - + - + Name - + İsim - + Please enter a name - + - - + + Error - + - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - + ModInfoRegular - - failed to write %1/meta.ini: %2 - + + + failed to write %1/meta.ini: error %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - + - + Categories: <br> - + @@ -2975,158 +3402,163 @@ p, li { white-space: pre-wrap; } This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + - + + Non-MO + + + + invalid - + - - installed version: %1, newest version: %2 - + + installed version: "%1", newest version: "%2" + - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + - + Categories: <br> - + - + Invalid name - + - + drag&drop failed: %1 - + - + Confirm Onayla - - Are you sure you want to remove "%1"? - + + Are you sure you want to remove "%1"? + - + Flags - + - + Mod Name - + - + Version Versiyon - + Priority - + - + Category - + - + Nexus ID - + - + Installation - + - - + + unknown - + - + Name of your mods - + - + Version of the mod (if available) - + - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + - + Category of the mod. - + - + Id of the mod as used on Nexus. - + - + Emblemes to highlight things that might require attention. - + - + Time this mod was installed - + @@ -3134,12 +3566,12 @@ p, li { white-space: pre-wrap; } Message of the Day - + OK - + @@ -3147,48 +3579,53 @@ p, li { white-space: pre-wrap; } Overwrites - + not implemented - + NXMAccessManager - + Logging into Nexus - + - + timeout - + + + + + Unknown error + - + Please check your password - + NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - + + Failed to guess mod id for "%1", please pick the correct one + - + empty response - + - + invalid response - + @@ -3196,22 +3633,22 @@ p, li { white-space: pre-wrap; } Overwrite - + You can use drag&drop to move files and directories to regular mods. - + &Delete - + &Rename - + @@ -3221,12 +3658,12 @@ p, li { white-space: pre-wrap; } &New Folder - + - Failed to delete "%1" - + Failed to delete "%1" + @@ -3236,130 +3673,152 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - + Are sure you want to delete "%1"? + Are sure you want to delete the selected files? - + New Folder - + - Failed to create "%1" - + Failed to create "%1" + PluginList - + Name İsim - + Priority - + - + Mod Index - + - - + + Flags + + + + + unknown - + - + Name of your mods - + - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 - + - - + + Confirm - + Onayla - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + - - - failed to open output file: %1 - + + <b>Origin</b>: %1 + - - Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. - + + Author + Yaratıcı - - This plugin can't be disabled (enforced by the game) - + + Description + + + + + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. + - - Origin: %1 - + + This plugin can't be disabled (enforced by the game) + - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - + + + + + PreviewDialog + + + Preview + + + + + Close + Kapat @@ -3367,115 +3826,110 @@ p, li { white-space: pre-wrap; } Problems - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + Close - + Kapat Fix - + No guided fix - + Profile - + invalid profile name %1 - + - + failed to create %1 - - - - - failed to open temporary file - - - - - failed to open "%1" for writing - + - + failed to write mod list: %1 - + - + failed to update tweaked ini file, wrong settings may be used: %1 - + - + failed to create tweaked ini: %1 - + - - - - - - invalid index %1 - + + "%1" is missing or inaccessible + - - Overwrite directory couldn't be parsed - + + + + + + invalid index %1 + + Overwrite directory couldn't be parsed + + + + invalid priority %1 - + - + failed to parse ini file (%1) - + - + failed to parse ini file (%1): %2 - + - - - failed to modify "%1" - + + + failed to modify "%1" + - + Delete savegames? - + - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + @@ -3483,27 +3937,27 @@ p, li { white-space: pre-wrap; } Dialog - + Please enter a name for the new profile - + If checked, the new profile will use the default game settings. - + - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + Default Game Settings - + @@ -3511,109 +3965,109 @@ p, li { white-space: pre-wrap; } Profiles - + List of Profiles - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + Automatic Archive Invalidation - + Create a new profile from scratch - + Create - + Clone the selected profile - + This creates a new profile with the same settings and active mods as the selected one. - + Copy - + Delete the selected Profile. This can not be un-done! - + Remove - + Rename - + Transfer save games to the selected profile. - + Transfer Saves - + @@ -3622,14 +4076,14 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. - + Archive invalidation isn't required for this game. + failed to create profile: %1 - + @@ -3639,22 +4093,22 @@ p, li { white-space: pre-wrap; } Please enter a name for the new profile - + failed to copy profile: %1 - + Invalid name - + Invalid profile name - + @@ -3664,37 +4118,37 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Rename Profile - + New Name - + failed to change archive invalidation state: %1 - + failed to determine if invalidation is active: %1 - + @@ -3702,7 +4156,7 @@ p, li { white-space: pre-wrap; } Failed to save custom categories - + @@ -3710,306 +4164,315 @@ p, li { white-space: pre-wrap; } invalid index %1 - + invalid category id %1 - + - invalid field name "%1" - + invalid field name "%1" + - invalid type for "%1" (should be integer) - + invalid type for "%1" (should be integer) + - invalid type for "%1" (should be string) - + invalid type for "%1" (should be string) + - invalid type for "%1" (should be float) - + invalid type for "%1" (should be float) + no fields set up yet! - + - field not set "%1" - + field not set "%1" + - invalid character in field "%1" - + invalid character in field "%1" + empty field name - + invalid game type %1 - + helper failed - + failed to determine account name - + invalid 7-zip32.dll: %1 - + failed to open %1: %2 - + %1 not found - + Failed to delete %1 - + Failed to deactivate script extender loading - + Failed to remove %1: %2 - + Failed to rename %1 to %2 - + Failed to deactivate proxy-dll loading - + Failed to copy %1 to %2 - + Failed to set up script extender loading - + Failed to delete old proxy-dll %1 - + Failed to overwrite %1 - + Failed to set up proxy-dll loading - + - + Permissions required - + - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + - - + + Woops - + - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 - + - - + + Mod Organizer - + - + An instance of Mod Organizer is already running - + - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + - - + + Please select the game to manage - + + + + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + + failed to start application: %1 + - - Please use "Help" from the toolbar to get usage instructions to all elements - + + Please use "Help" from the toolbar to get usage instructions to all elements + - - + + <Manage...> - + - + failed to parse profile %1: %2 - + - - - failed to find "%1" - + + failed to find "%1" + - + failed to access %1 - + - + failed to set file time %1 - + - + failed to create %1 - + - - "%1" is missing - + + "%1" is missing or inaccessible + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - + Error - + wrong file format - + failed to open %1 - + - + Script Extender - + - + Proxy DLL - + - - failed to spawn "%1" - + + failed to spawn "%1" + - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - - failed to spawn "%1": %2 - + + failed to spawn "%1": %2 + - - "%1" doesn't exist - + + "%1" doesn't exist + - - failed to inject dll into "%1": %2 - + + failed to inject dll into "%1": %2 + - - failed to run "%1" - + + failed to run "%1" + + + + + failed to open temporary file + @@ -4017,22 +4480,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Mod Exists - + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Keep Backup - + Merge - + @@ -4042,12 +4505,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Rename - + Cancel - + @@ -4055,27 +4518,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - + Location - + Date - + @@ -4083,7 +4546,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Missing ESPs - + @@ -4091,17 +4554,17 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - + Copy To Clipboard - + Save As... - + @@ -4111,17 +4574,17 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save CSV - + Text Files - + - failed to open "%1" for writing - + failed to open "%1" for writing + @@ -4129,7 +4592,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Select - + @@ -4139,107 +4602,107 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Cancel - + SelfUpdater - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + - - - - + + + + Update - + - + An update is available (newest version: %1), do you want to install it? - + - + Download in progress - + - + Download failed: %1 - + - + Failed to install update: %1 - + - - failed to open archive "%1": %2 - Arşiv "%1": %2 açılamadı. + + failed to open archive "%1": %2 + Arşiv "%1": %2 açılamadı. - + failed to move outdated files: %1. Please update manually. - + - + Update installed, Mod Organizer will now be restarted. - + - + Error - + - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. - + - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) - + - + no file for update found. Please update manually. - + - + Failed to retrieve update information: %1 - + - + No download server available. Please try again later. - + Settings - - - attempt to store setting for unknown plugin "%1" - + + + attempt to store setting for unknown plugin "%1" + - + Confirm Onayla - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - + @@ -4247,399 +4710,447 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Settings - + General - + Language - + The display language - + - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + Style - + graphical style - + graphical style of the MO user interface - + Log Level - + - Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log" + - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Debug - + Info - + Error - + Advanced - + Directory where downloads are stored. - + Mod Directory - + Directory where mods are stored. - + - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Download Directory - + Cache Directory - + + + + + User interface + - Reset stored information from dialogs. - + If checked, the download interface will be more compact. + - This will make all dialogs show up again where you checked the "Remember selection"-box. - + Compact Download Interface + - - Reset Dialogs - + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + - - Modify the categories available to arrange your mods. - + Reset stored information from dialogs. + + This will make all dialogs show up again where you checked the "Remember selection"-box. + + + + + Reset Dialogs + + + + + + Modify the categories available to arrange your mods. + + + + Configure Mod Categories - + - - + + Nexus - + - + Allows automatic log-in when the Nexus-Page for the game is clicked. - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + - + Automatically Log-In to Nexus - + - + Username Kullanıcı adı - + Password Şifre - + Disable automatic internet features - + - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + - + Offline Mode - + - + Use a proxy for network connections. - + - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + - + Use HTTP Proxy (Uses System Settings) - + - - Known Servers (Dynamically updated every download) - + + Associate with "Download with manager" links + - + + Known Servers (updated on download) + + + + Preferred Servers (Drag & Drop) - + - + Plugins - + - + Author: - + - + Version: - + - + Description: - + - + Key - + - + Value - + - + Blacklisted Plugins (use <del> to remove): - + - + Workarounds - + - + Steam App ID - + - + The Steam AppID for your game - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + + + + Load Mechanism - + - + Select loading mechanism. See help for details. - + - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + - + NMM Version - + - + The Version of Nexus Mod Manager to impersonate. - + - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. + - + Enforces that inactive ESPs and ESMs are never loaded. - + - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + - + Hide inactive ESPs/ESMs - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + - + Force-enable game files - + - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! - + - + Back-date BSAs - + - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + - + Select download directory - + - + Select mod directory - + - + Select cache directory - + - + Confirm? - + - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + @@ -4647,7 +5158,7 @@ For the other games this is not a sufficient replacement for AI! Quick Install - + @@ -4668,12 +5179,12 @@ For the other games this is not a sufficient replacement for AI! OK - + Cancel - + @@ -4681,18 +5192,22 @@ For the other games this is not a sufficient replacement for AI! SHM error: %1 - + - failed to connect to running instance: %1 - + + + + + failed to communicate with running instance: %1 + failed to receive data from secondary instance: %1 - + @@ -4700,7 +5215,7 @@ For the other games this is not a sufficient replacement for AI! Sync Overwrite - + @@ -4710,22 +5225,22 @@ For the other games this is not a sufficient replacement for AI! Sync To - + - <don't sync> - + <don't sync> + failed to remove %1 - + failed to move %1 to %2 - + @@ -4733,17 +5248,17 @@ For the other games this is not a sufficient replacement for AI! Transfer Savegames - + Global Characters - + This is a list of characters in the global location. - + @@ -4755,7 +5270,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4768,27 +5283,27 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Copy -> - + <- Move - + <- Copy - + @@ -4798,17 +5313,17 @@ On Windows XP: Profile Characters - + Overwrite - + - Overwrite the file "%1" - + Overwrite the file "%1" + @@ -4821,18 +5336,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - + Copy all save games of character "%1" to the profile? + - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + diff --git a/src/organizer_zh_CN.ts b/src/organizer_zh_CN.ts index ec319f76..333d9ce7 100644 --- a/src/organizer_zh_CN.ts +++ b/src/organizer_zh_CN.ts @@ -1,5 +1,50 @@ + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + 关闭 + + + + No license + + + ActivateModsDialog @@ -14,23 +59,23 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是 esp 和 esm 文件的列表,当您的存档被创建时将会被激活。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">对于每个 esp,右列中包含了可以通过启用来使缺失的 esp 或 esm 变得可用的 Mod。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您点击确定,那么所有在右列中已选的并且可用的 Mod 和缺失的 esp 都将会被激活。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是 esp 和 esm 文件的列表,当您的存档被创建时将会被激活。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">对于每个 esp,右列中包含了可以通过启用来使缺失的 esp 或 esm 变得可用的 Mod。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您点击确定,那么所有在右列中已选的并且可用的 Mod 和缺失的 esp 都将会被激活。</span></p></body></html> @@ -73,9 +118,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. 此包中的组件。 -如果有一个组件叫作 "00 Core",那么它应该就是必需安装的,可选安装的文件一般会被作者按优先级排列。 +如果有一个组件叫作 "00 Core",那么它应该就是必需安装的,可选安装的文件一般会被作者按优先级排列。 @@ -110,6 +155,29 @@ If there is a component called "00 Core" it is usually required. Options are ord 取消 + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -155,20 +223,20 @@ If there is a component called "00 Core" it is usually required. Options are ord - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以关联单个或多个N网类别到一个内部 ID,当您在N网下载 Mod 的时候,Mod Organizer 将会尝试解析N网中的类别的定义并提供给 MO。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">要找出一个N网所使用的类别 ID,您可以访问N网页面的种类列表并将鼠标悬停在链接上面。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以关联单个或多个N网类别到一个内部 ID,当您在N网下载 Mod 的时候,Mod Organizer 将会尝试解析N网中的类别的定义并提供给 MO。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">要找出一个N网所使用的类别 ID,您可以访问N网页面的种类列表并将鼠标悬停在链接上面。</span></p></body></html> @@ -200,7 +268,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus 当您尚未登录N网时此功能可能无法正常工作 @@ -227,7 +295,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 无法读取bsa @@ -235,25 +303,30 @@ p, li { white-space: pre-wrap; } DownloadList - + Name 名称 - + Filetime 文件时间 - + Done 完成 - - Information missing, please select "Query Info" from the context menu to re-retrieve. + + Information missing, please select "Query Info" from the context menu to re-retrieve. 信息丢失,请在右键菜单里选择“查询信息”来重新检索。 + + + pending download + + DownloadListWidget @@ -265,26 +338,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install 完成 - 双击进行安装 - - + + Paused - Double Click to resume 已暂停 - 双击可继续 - - + + Installed - Double Click to re-install 已安装 - 双击重新安装 - - + + Uninstalled - Double Click to re-install 已卸载 - 双击重新安装 @@ -305,6 +378,16 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + Paused @@ -336,95 +419,95 @@ p, li { white-space: pre-wrap; } 完成 - - - - + + + + Are you sure? 确定? - + This will remove all finished downloads from this list and from disk. 这将会从列表和磁盘中移除所有已完成的下载。 - + This will remove all installed downloads from this list and from disk. 这将会从列表和磁盘中移除所有已安装的下载项目。 - + This will permanently remove all finished downloads from this list (but NOT from disk). 这将会永久清空本列表中所有已下载完成的项目(但并不会实际从硬盘删除) - + This will permanently remove all installed downloads from this list (but NOT from disk). 这将会永久清空本列表中所有已安装完成的项目(但并不会实际从硬盘删除) - + Install 安装 - + Query Info 查询信息 - + Delete &删除 - + Un-Hide 取消隐藏 - + Remove from View 从视图中移除 - + Cancel 取消 - + Pause 暂停 - + Remove 移除 - + Resume 继续 - + Delete Installed... 移除已安装的项目... - + Delete All... 全部删除... - + Remove Installed... 移除已安装的项目... - + Remove All... 移除所有... @@ -432,105 +515,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + + < mod %1 file %2 > + + + + + Pending + + + + Fetching Info 1 抓取信息 1 - + Fetching Info 2 抓取信息 2 - - - - + + + + Are you sure? 确定? - + This will remove all finished downloads from this list and from disk. 这将会从列表和磁盘中移除所有已完成的下载。 - + This will remove all installed downloads from this list and from disk. 这将会从列表和磁盘中移除所有已安装的下载项目。 - + This will remove all finished downloads from this list (but NOT from disk). 这将会从列表中移除所有已安装的下载项目。(但不会从实际磁盘中) - + This will remove all installed downloads from this list (but NOT from disk). 这将会从列表中移除所有已安装的下载项目。(但不会从实际磁盘中) - + Install 安装 - + Query Info 查询信息 - + Delete 删除 - + Un-Hide 取消隐藏 - + Remove from View 从视图中移除 - + Cancel 取消 - + Pause 暂停 - + Remove 移除 - + Resume 继续 - + Delete Installed... 移除已安装的项目... - + Delete All... 全部删除... - + Remove Installed... 移除已安装的项目... - + Remove All... 移除所有... @@ -538,116 +631,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - 重命名 "%1 "为 "%2" 时出错 + + failed to rename "%1" to "%2" + 重命名 "%1 "为 "%2" 时出错 + + + + Memory allocation error (in refreshing directory). + - + Download again? 重新下载? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. 已存在同名文件。您确定要重新下载?新文件将使用不同的文件名。 - + failed to download %1: could not open output file: %2 下载 %1 失败: 无法打开输出文件: %2 - + Wrong Game - + - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + - - - - - - - - - - - + + - - + + + + + + + + + + + + + invalid index 无效的索引 - + failed to delete %1 无法删除 %1 - + failed to delete meta file for %1 无法从 %1 中删除 mate 文件 - - - - - - + + + + + + invalid index %1 无效的索引 %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id 请输入N网 Mod ID - + Mod ID: Mod ID: - + + Main + 主要文件 + + + + Update + 更新 + + + + Optional + 可选文件 + + + + Old + 旧档 + + + + Misc + 杂项 + + + + Unknown + 未知 + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated 信息已更新 - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? 无法在N网上找到匹配的文件!也许这个文件已经不存在了或是它改名了? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. 所选的文件无法在N网上找到可匹配的项目,请手动选择正确的一个。 - + No download server available. Please try again later. 没有可用的下载服务器,请稍后再尝试下载。 - + Failed to request file info from nexus: %1 无法从N网上请求文件信息: %1 - + + Download failed. Server reported: %1 + + + + Download failed: %1 (%2) 下载失败: %1 (%2) - + failed to re-open %1 无法重新打开 %1 @@ -759,7 +909,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + If checked, MO will be closed once the specified executable is run. 如果选中,那么 MO 将在指定的程序运行后关闭。 @@ -776,7 +926,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + Add 添加 @@ -792,47 +942,64 @@ Right now the only case I know of where this needs to be overwritten is for the 移除 - + + Close + 关闭 + + + Select a binary 选择一个可执行文件 - + Executable (%1) 可执行程序 (%1) - + Java (32-bit) required 需要 Java (32-bit) 支持 - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + - + Select a directory 选择一个目录 - + Confirm 确认 - - Really remove "%1" from executables? - 真的要从程序列表中移除 "%1" 吗? + + Really remove "%1" from executables? + 真的要从程序列表中移除 "%1" 吗? - + Modify 更改 - + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + MO must be kept running or this application will not work correctly. MO 必须持续运行,否则该程序将无法正常工作。 @@ -903,8 +1070,8 @@ Right now the only case I know of where this needs to be overwritten is for the - <a href="#">Link</a> - <a href="#">链接</a> + <a href="#">Link</a> + <a href="#">链接</a> @@ -951,7 +1118,7 @@ Right now the only case I know of where this needs to be overwritten is for the - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. 输入一个 Mod 的名称,这也将作为一个目录的名称,因此请不要使用非法字符。 @@ -966,16 +1133,16 @@ Right now the only case I know of where this needs to be overwritten is for the - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这里显示了该压缩包中的内容,&lt;data&gt; 将被作为映射到游戏 Data 目录的基本目录,您可以通过右键菜单来改变基本目录并使用拖放来移动文件。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这里显示了该压缩包中的内容,&lt;data&gt; 将被作为映射到游戏 Data 目录的基本目录,您可以通过右键菜单来改变基本目录并使用拖放来移动文件。</span></p></body></html> @@ -997,8 +1164,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - archive.dll 没有加载: "%1" + archive.dll not loaded: "%1" + archive.dll 没有加载: "%1" @@ -1013,7 +1180,7 @@ p, li { white-space: pre-wrap; } - + Extracting files 正在解压文件 @@ -1043,57 +1210,57 @@ p, li { white-space: pre-wrap; } 你输入的名称无效,请重新输入。 - - File format "%1" not supported - 暂不支持文件格式: "%1" + + File format "%1" not supported + 暂不支持文件格式: "%1" - + None of the available installer plugins were able to handle that archive 没有可用的安装插件能够处理此压缩包 - + no error 没有错误 - + 7z.dll not found 未找到 7z.dll - - 7z.dll isn't valid + + 7z.dll isn't valid 无效的 7z.dll - + archive not found 未找到压缩包 - + failed to open archive 无法打开压缩包 - + unsupported archive type 不支持的压缩包类型 - + internal library error 内部库错误 - + archive invalid 无效的压缩包 - + unknown archive error 未知压缩包错误 @@ -1107,7 +1274,7 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. 当程序或游戏结束后此对话框将自动消失,如果没有请点击解锁。 @@ -1116,7 +1283,7 @@ p, li { white-space: pre-wrap; } 程序运行时 MO 将被锁定。 - + Unlock 解锁 @@ -1124,7 +1291,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 无法生成日志到 %1: %2 @@ -1132,12 +1299,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 发生错误: %1 - + an error occured 发生错误 @@ -1145,170 +1312,194 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories 种类 + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + Profile 配置文件 - + Pick a module collection 选择一个配置文件 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在这里创建配置文件,每个配置文件都包含了它们自己的 Mod 和 esp 的激活方案。这样您就可以通过快速切换设置来体验不同的游戏历程了。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">请注意: 当前您的配置文件的 esp 加载顺序并不是分开保存的。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在这里创建配置文件,每个配置文件都包含了它们自己的 Mod 和 esp 的激活方案。这样您就可以通过快速切换设置来体验不同的游戏历程了。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">请注意: 当前您的配置文件的 esp 加载顺序并不是分开保存的。</span></p></body></html> - Refresh list - 刷新列表 + 刷新列表 - + Refresh list. This is usually not necessary unless you modified data outside the program. 刷新列表,这通常不是必须的,除非您在程序之外修改了文件的数据。 - + List of available mods. 可用模组列表 - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. 已安装mod列表。请使用复选框激活/取消mod,以鼠标拖动可改变他们的“安装”顺序。 - + Filter 过滤器 - + No groups 未分组 - + Nexus IDs N网 ID - - - + + + Namefilter 名称过滤器 - + Pick a program to run. 选择要运行的程序。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">选择要运行的程序。一旦您开始使用 Mod Organizer,您应该始终从这里或通过在这里创建的快捷方式来运行您的游戏和工具,否则任何经由 MO 安装的 Mod 都会变得不可见。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保证一些我没有测试过的工具能够正常工作。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">选择要运行的程序。一旦您开始使用 Mod Organizer,您应该始终从这里或通过在这里创建的快捷方式来运行您的游戏和工具,否则任何经由 MO 安装的 Mod 都会变得不可见。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保证一些我没有测试过的工具能够正常工作。</span></p></body></html> - + Run program 运行程序 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 启用的状态下运行指定的程序。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 启用的状态下运行指定的程序。</span></p></body></html> - + Run 运行 - + Create a shortcut in your start menu or on the desktop to the specified program 在开始菜单或桌面生成你指定程序的快捷方式 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">创建一个开始菜单快捷方式,使您可以直接在 MO 激活状态下运行指定的程序。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">创建一个开始菜单快捷方式,使您可以直接在 MO 激活状态下运行指定的程序。</span></p></body></html> - + Shortcut 快捷方式 - + List of available esp/esm files 可用 esp 或 esm 文件的列表 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这个列表中包含了位于已激活 Mod 里的 esp 和 esm 文件。这些文件都需要它们自己的加载顺序,您可以使用拖放来修改加载顺序。请注意: MO 将只保存已激活或已勾选状态的 Mod 的加载顺序。<br />有个非常棒的工具叫作 &quot;BOSS&quot;,它可以自动对这些文件进行排序。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这个列表中包含了位于已激活 Mod 里的 esp 和 esm 文件。这些文件都需要它们自己的加载顺序,您可以使用拖放来修改加载顺序。请注意: MO 将只保存已激活或已勾选状态的 Mod 的加载顺序。<br />有个非常棒的工具叫作 &quot;BOSS&quot;,它可以自动对这些文件进行排序。</span></p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. 可用 BSA 文件的列表。未勾选的项目不会被 MO 管理并且会忽略安装顺序。 - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. @@ -1318,236 +1509,272 @@ BSAs checked here are loaded in such a way that your installation order is obeye 这里勾选的 BSA 将会依从您的安装顺序,并且会自行调整加载顺序。 - - + + File 文件 - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - - - - + Data Data - + refresh data-directory overview 刷新 Data 目录总览 - + Refresh the overview. This may take a moment. 刷新总览,这可能需要一些时间。 - - - + + + Refresh 刷新 - + This is an overview of your data directory as visible to the game (and tools). 这是在游戏中可见的 Data 目录 (和工具) 的总览。 - + Mod 模组 - - + + Filter the above list so that only conflicts are displayed. 过滤上面的列表,使您只能看到有冲突的文件。 - + Show only conflicts 只显示冲突 - + Saves 存档 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是此游戏所有存档的列表,将鼠标悬停在项目上来获取该存档的详细信息,里面包含了现在没有被激活但是当存档被创建时所使用的 esp 或 esm 的清单。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右键菜单中点击“修复 Mod”,那么 MO 便会尝试激活所有 Mod 和 esp 来修复那些缺失的 esp,它并不会禁用任何东西!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是此游戏所有存档的列表,将鼠标悬停在项目上来获取该存档的详细信息,里面包含了现在没有被激活但是当存档被创建时所使用的 esp 或 esm 的清单。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右键菜单中点击“修复 Mod”,那么 MO 便会尝试激活所有 Mod 和 esp 来修复那些缺失的 esp,它并不会禁用任何东西!</span></p></body></html> - + Downloads 下载 - + This is a list of mods you downloaded from Nexus. Double click one to install it. 这是从Nexus已下载的模组的列表,双击进行安装。 - Compact - 紧凑 + 紧凑 - + + Open list options... + + + + + + Restore Backup... + + + + + + Create Backup + + + + + Plugins + 插件 + + + + Sort + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + Show Hidden - + - + Tool Bar 工具栏 - + Install Mod 安装模组 - + Install &Mod 安装 &Mod - + Install a new mod from an archive 通过压缩包来安装一个新 Mod - + Ctrl+M Ctrl+M - + Profiles 配置文件 - + &Profiles &配置文件 - + Configure Profiles 设置配置文件 - + Ctrl+P Ctrl+P - + Executables 可执行程序 - + &Executables &可执行程序 - + Configure the executables that can be started through Mod Organizer 配置可通过 MO 来启动的程序 - + Ctrl+E Ctrl+E - - + + Tools 工具 - + &Tools 工具(&T) - + Ctrl+I Ctrl+I - + Settings 设置 - + &Settings &设置 - + Configure settings and workarounds 配置设定和解决方案 - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods 搜索nexus网以获取更多 Mod - + Ctrl+N Ctrl+N - - + + Update 更新 - + Mod Organizer is up-to-date Mod Organizer 现在是最新版本 - - + + No Problems 没有问题 - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1558,900 +1785,1096 @@ Right now this has very limited functionality 当前此项所能提供的功能非常有限 - - + + Help 帮助 - + Ctrl+H Ctrl+H - + Endorse MO 称赞 MO - - + + Endorse Mod Organizer 称赞 Mod Organizer - + + Copy Log to Clipboard + + + + + Ctrl+C + + + + Toolbar 工具栏 - + Desktop 桌面 - + Start Menu 开始菜单 - + Problems 问题 - + There are potential problems with your setup 您的安装中存在潜在的问题 - + Everything seems to be in order 一切井然有序 - + Help on UI 界面帮助 - + Documentation Wiki 说明文档 (维基) - + Report Issue 报告问题 - + Tutorials 教程 - - failed to save archives order, do you have write access to "%1"? - 无法保存档案顺序,您确定您有权限更改 "%1"? + failed to save archives order, do you have write access to "%1"? + 无法保存档案顺序,您确定您有权限更改 "%1"? - + failed to save load order: %1 无法保存加载顺序: %1 - + Name 名称 - + Please enter a name for the new profile 请为新配置文件输入一个名称 - + failed to create profile: %1 无法创建配置文件: %1 - + Show tutorial? 显示教程? - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. 你正在第一次使用 Mod Organizer。是否希望显示教程以了解它的基本特性?如果选择否你也可以从“帮助”菜单中开始教程。 - + Downloads in progress 正在下载 - + There are still downloads in progress, do you really want to quit? 仍有正在进行中的下载,您确定要退出吗? - + failed to read savegame: %1 无法读取存档: %1 - - Plugin "%1" failed: %2 - 插件 "%1" 失败: %2 + + Plugin "%1" failed: %2 + 插件 "%1" 失败: %2 - - Plugin "%1" failed - 插件 "%1" 失败 + + Plugin "%1" failed + 插件 "%1" 失败 - + failed to init plugin %1: %2 插件初始化失败 %1: %2 - + Plugin error - + - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + - - Failed to start "%1" - 无法启动 "%1" + + Failed to start "%1" + 无法启动 "%1" - + Waiting 稍等 - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. 当您登录 Steam 时请点击确定。 - - "%1" not found - "%1" 未找到 + "%1" not found + "%1" 未找到 - + Start Steam? 启动 Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? 想要正确地启动游戏,Steam 必须处于运行状态。需要MO尝试启动 Steam 吗? - + Also in: <br> 也在: <br> - + No conflict 没有冲突 - + <Edit...> <编辑...> - - Failed to refresh list of esps: %s - - - - + This bsa is enabled in the ini file so it may be required! 该 BSA 已在 ini 文件中启用,因此它可能是必需的。 - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - 此档案还是会被加载,因为存在同名插件。不过它所包含的的文件不会遵循安装顺序! + 此档案还是会被加载,因为存在同名插件。不过它所包含的的文件不会遵循安装顺序! - + Activating Network Proxy 激活网络代理 - - + + Installation successful 安装成功 - - + + Configure Mod 配置 Mod - - + + This mod contains ini tweaks. Do you want to configure them now? 此 Mod 中包含 ini 设定文件,您想现在就对它们进行配置吗? - - - mod "%1" not found - Mod "%1" 未找到 + + + mod "%1" not found + Mod "%1" 未找到 - - + + Installation cancelled 安装已取消 - - + + The mod was not installed completely. 该模组没有完全安装。 - + Some plugins could not be loaded 一些插件无法载入 - + Too many esps and esms enabled - + - - + + Description missing - + - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + - + Choose Mod 选择模组 - + Mod Archive Mod 压缩包 - + Start Tutorial? 开始教程? - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? 即将开始帮助教程。因为技术原因可能无法随时中断。是否继续? - - + + Download started 开始下载 - + failed to update mod list: %1 无法更新 Mod 列表: %1 - + failed to spawn notepad.exe: %1 无法生成 notepad.exe: %1 - + failed to open %1 无法打开 %1 - + failed to change origin name: %1 无法更改原始文件名: %1 - - Failed to move "%1" from mod "%2" to "%3": %4 - - - - + <Checked> <已勾选> - + <Unchecked> <未勾选> - + <Update> <有更新> - + <No category> <无类别> - + <Conflicted> <有冲突> - + <Not Endorsed> - + - + failed to rename mod: %1 无法重命名 Mod: %1 - + Overwrite? 覆盖 - - This will replace the existing mod "%1". Continue? - 这将会覆盖已存在的mod "%1"。是否继续? + + This will replace the existing mod "%1". Continue? + 这将会覆盖已存在的mod "%1"。是否继续? - - failed to remove mod "%1" + + failed to remove mod "%1" 无法移动 Mod: %1 - - - - failed to rename "%1" to "%2" - 重命名 "%1 "为 "%2" 时出错 + + + + failed to rename "%1" to "%2" + 重命名 "%1 "为 "%2" 时出错 - - Multiple esps activated, please check that they don't conflict. + + Multiple esps activated, please check that they don't conflict. 多个esp已激活,请检查以确保不冲突。 - - - + + + + Confirm 确认 - + Remove the following mods?<br><ul>%1</ul> 是否删除下列mod?<br><ul>%1</ul> - + failed to remove mod: %1 无法移动 Mod: %1 - - + + Failed 失败 - + Installation file no longer exists 安装文件不复存在 - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. 旧版 MO 安装的 Mod 无法使用此方法重新安装。 - - + + You need to be logged in with Nexus to endorse 你必须登录 Nexus 才能点“称赞” - - + Extract BSA 解压 BSA - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - 此 Mod 中至少包含一个 BSA。您确定要解压吗? +(This removes the BSA after completion. If you don't know about BSAs, just select no) + 此 Mod 中至少包含一个 BSA。您确定要解压吗? (解压完成后,BSA 文件将会被删除。如果您不了解 BSA 的话,请选择“否”) - - - + + failed to read %1: %2 无法读取 %1: %2 - - + This archive contains invalid hashes. Some files may be broken. 压缩包 Hash 值错误。部分文件可能已经损坏。 - + Nexus ID for this Mod is unknown 此模组的Nexus ID未知 - - + + About + + + + + About Qt + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + Executable "%1" not found + + + + + Failed to refresh list of esps: %1 + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... 创建Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + - + A mod with this name already exists 同名模组已存在。 - + Continue? - + - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - + + Sorry - + - - I don't know a versioning scheme where %1 is newer than %2. - + + I don't know a versioning scheme where %1 is newer than %2. + - + Really enable all visible mods? 确定要启用全部可见的模组吗? - + Really disable all visible mods? 确定要禁用全部可见的模组吗? - + Choose what to export 选择要导出的内容 - + Everything 全部 - + All installed mods are included in the list 所有包含在列表的已安装mod - + Active Mods 激活模组 - + Only active (checked) mods from your current profile are included 仅包含当前配置文件中已激活(打勾)的mod - + Visible 可见的 - + All mods visible in the mod list are included 包含列表中所有可见的mod - + export failed: %1 导出失败: %1 - + Install Mod... 安装模组... - + Enable all visible 启用所有可见项目 - + Disable all visible 禁用所有可见项目 - + Check all for update 检查所有更新 - + Export to csv... 导出为 CSV... - + + All Mods + + + + Sync to Mods... 同步到 Mod... - + Restore Backup 还原备份 - + Remove Backup... 还原备份... - + Add/Remove Categories - + - + Replace Categories - + - + Primary Category 主分类 - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... 重命名模组... - + Remove Mod... 移除模组... - + Reinstall Mod 重新安装模组 - + Un-Endorse 取消称赞 - - + + Endorse 称赞 - - Won't endorse + + Won't endorse 不想称赞 - + Endorsement state unknown 称赞状态不明 - + Ignore missing data 忽略丢失的数据 - + Visit on Nexus 在Nexus上浏览 - + Open in explorer 在资源管理器中打开 - + Information... 信息... - - + + Exception: 例外: - - + + Unknown exception 未知的例外 - + <All> <全部> - + <Multiple> XX - + + Really delete "%1"? + + + + Fix Mods... 修复模组... - - + + Delete + + + + + failed to remove %1 无法删除 %1 - - + + failed to create %1 无法创建 %1 - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! 下载文件时不能修改下载目录! - + Download failed 下载失败 - + failed to write to file %1 无法写入文件 %1 - + %1 written 已写入 %1 - + Select binary 选择可执行文件 - + Binary 程序 - + Enter Name 输入名称 - + Please enter a name for the executable 请为该可执行程序输入一个名称 - + Not an executable 不是可执行程序 - + This is not a recognized executable. 无法识别的可执行文件 - - + + Replace file? 替换文件? - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - - Failed to remove "%1". Maybe you lack the required file permissions? - 无法移除 "%1"。也许您需要足够的文件权限? + + + Failed to remove "%1". Maybe you lack the required file permissions? + 无法移除 "%1"。也许您需要足够的文件权限? - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + Update available 更新可用 - + Open/Execute 打开/执行 - + Add as Executable 添加为可执行文件 - + + Preview + + + + Un-Hide 取消隐藏 - + Hide 隐藏 - + Write To File... 写入文件... - + Do you want to endorse Mod Organizer on %1 now? 是否现在就在 %1 点赞支持 Mod Organizer? - + + Thank you! + + + + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 发往 Nexus 的请求失败: %1 - - + + login successful 登录成功 - + login failed: %1. Trying to download anyway 登录失败: %1,请尝试使用别的方法下载 - + login failed: %1 无法登录: %1 - + login failed: %1. You need to log-in with Nexus to update MO. 登录失败: %1。您需要登录到N网才能更新 MO - + Error 错误 - + failed to extract %1 (errorcode %2) 无法解压 %1 (错误代码 %2) - + Extract... 解压... - + Edit Categories... 编辑类别... - + + Deselect filter + + + + Remove 移除 - + Enable all 全部启用 - + Disable all 全部禁用 - + Unlock load order 解锁加载顺序 - + Lock load order 锁定加载顺序 + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + + MessageDialog @@ -2465,8 +2888,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 无效的索引 %1 @@ -2474,7 +2897,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod 这是模组的备份 @@ -2487,516 +2910,544 @@ This function will guess the versioning scheme under the assumption that the ins Mod 信息 - + Textfiles 文本文件 - + A list of text-files in the mod directory. Mod 目录里包含的文本文件的列表。 - + A list of text-files in the mod directory like readmes. Mod 目录里包含的文本文件 (类似于自述文件) 的列表 。 - - + + Save 保存 - + INI-Files Ini 文件 - + + Ini Files + + + + This is a list of .ini files in the mod. Mod 目录里包含的 Ini 文件的列表。 - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Mod 目录里包含的 Ini 文件的列表,这些文件通常用来配置 Mod 的行为,如果有可设置的参数的话。 - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. 保存更改到文件中。 - + Save changes to the file. This overwrites the original. There is no automatic backup! 保存更改到文件中,这将会覆盖原始文件,并且没有自动备份! - + Images 图片 - + Images located in the mod. 位于 Mod 中的图片。 - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + - - + + Optional ESPs 可选 ESP - + List of esps and esms that can not be loaded by the game. 包含了不会被游戏载入的 esp 和 esm 的列表。 - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + - + Make the selected mod in the lower list unavailable. 使下表中已选的 Mod 变得不可用。 - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. 已选的 esp (在下表中) 将会被放入 Mod 的子目录里,在游戏里将会变得“不可见”,并且之后就不能再被激活了。 - + Move a file to the data directory. 移动一个文件到 Data 目录。 - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. 移动一个 esp 文件到 esp 目录,这样它就可以在主窗口中启用了。请注意: ESP 只是变得“可用”,它并不一定会被载入!想要载入请在 MO 的主窗口中勾选。 - + ESPs in the data directory and thus visible to the game. ESP 在 Data 目录,因此它在游戏里会变得可见。 - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. 这些 Mod 文件位于您游戏的 (虚拟) Data 目录里,因此它们在主窗口的 esp 列表中会变得可选。 - + Available ESPs 可用 ESP - + Conflicts 冲突 - + The following conflicted files are provided by this mod 以下冲突文件由此 Mod 提供 - - + + File 文件 - + Overwritten Mods 覆盖的 Mod - + The following conflicted files are provided by other mods 以下冲突文件由其它 Mod 提供 - + Providing Mod 提供的 Mod - + Non-Conflicted files 非冲突文件 - + Categories 类别 - + Primary Category 主类别 - + Nexus Info N网信息 - + Mod ID Mod ID - + Mod ID for this mod on Nexus. N网上此 Mod 的 ID。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">N网上此 Mod 的 ID,如果您在 MO 中下载并安装了 Mod 它将被自动填写,否则您需要手动输入。要找到正确的 ID,在N网找到 Mod。比如,像这样的链接: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a> 在上面的例子中,1334就是您要找的 ID。此外: 上面的就是 Mod Organizer 在N网的链接,为什么不现在就到那里去赞同我呢?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">N网上此 Mod 的 ID,如果您在 MO 中下载并安装了 Mod 它将被自动填写,否则您需要手动输入。要找到正确的 ID,在N网找到 Mod。比如,像这样的链接: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a> 在上面的例子中,1334就是您要找的 ID。此外: 上面的就是 Mod Organizer 在N网的链接,为什么不现在就到那里去赞同我呢?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod 的已安装版本,鼠标提示将显示N网上的当前版本,已安装的版本号只有在您通过 MO 来安装 Mod 的时候才会自动填写。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod 的已安装版本,鼠标提示将显示N网上的当前版本,已安装的版本号只有在您通过 MO 来安装 Mod 的时候才会自动填写。</span></p></body></html> - + Version 版本 - + Refresh 刷新 - + Refresh all information from Nexus. 从Nexus刷新全部信息。 - + Description 描述 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse 称赞 - + Notes 笔记 - + Filetree 文件树 - + A directory view of this mod 这个 Mod 的目录视图 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是一个可编辑的 Mod 目录的目录视图,您可以通过拖放来移动文件或者重命名它们 (双击)。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">所做的更改将会立即作用于磁盘上的文件,所以请</span><span style=" font-size:9pt; font-weight:600;">谨慎操作</span><span style=" font-size:9pt;">。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是一个可编辑的 Mod 目录的目录视图,您可以通过拖放来移动文件或者重命名它们 (双击)。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">所做的更改将会立即作用于磁盘上的文件,所以请</span><span style=" font-size:9pt; font-weight:600;">谨慎操作</span><span style=" font-size:9pt;">。</span></p></body></html> - + Previous 上一个 - + Next 下一步 - + Close 关闭 - + &Delete &删除 - + &Rename &重命名 - + &Hide &隐藏 - + &Unhide &取消隐藏 - + &Open &打开 - + &New Folder &新建文件夹 - - + + Save changes? 保存更改吗? - - - Save changes to "%1"? + + + Save changes to "%1"? 将更改保存到“%1%”吗? - + File Exists 文件已存在 - + A file with that name exists, please enter a new one 文件名已存在,请输入其它名称 - + failed to move file 无法移动文件 - - failed to create directory "optional" - 无法创建 "optional" 目录 + + failed to create directory "optional" + 无法创建 "optional" 目录 - - + + Info requested, please wait 请求信息已发出,请稍后 - + Main 主要文件 - + Update 更新 - + Optional 可选文件 - + Old 旧档 - + Misc 杂项 - + Unknown 未知 - + Current Version: %1 当前版本: %1 - + No update available 没有可用的更新 - + (description incomplete, please visit nexus) (描述信息不完整,请访问N网) - - <a href="%1">Visit on Nexus</a> - <a href="%1">访问N网</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">访问N网</a> - + Failed to delete %1 无法删除 %1 - - + + Confirm 确认 - - Are sure you want to delete "%1"? - 确定要删除 "%1" 吗? + + Are sure you want to delete "%1"? + 确定要删除 "%1" 吗? - + Are sure you want to delete the selected files? 确定要删除所选的文件吗? - - + + New Folder 新建文件夹 - - Failed to create "%1" - 无法创建 "%1" + + Failed to create "%1" + 无法创建 "%1" - - + + Replace file? 替换文件? - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - - Failed to remove "%1". Maybe you lack the required file permissions? - 无法移除 "%1"。也许您需要足够的文件权限? + + + Failed to remove "%1". Maybe you lack the required file permissions? + 无法移除 "%1"。也许您需要足够的文件权限? - - + + failed to rename %1 to %2 无法重命名 %1 为 %2 - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + Un-Hide 取消隐藏 - + Hide 隐藏 - + Name 名称 - + Please enter a name - + - - + + Error 错误 - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) 此虚拟安装包内包含来自虚拟 Data 树的文件,但文件发生了变化 (例: 被CK修改了) @@ -3004,17 +3455,22 @@ p, li { white-space: pre-wrap; } ModInfoRegular - failed to write %1/meta.ini: %2 - 无法写入 %1/meta.ini: %2 + 无法写入 %1/meta.ini: %2 - + + + failed to write %1/meta.ini: error %2 + + + + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 中未包含 esp 或 esm 和有效的目录 (textures, meshes, interface, ...) - + Categories: <br> 种类: <br> @@ -3062,120 +3518,129 @@ p, li { white-space: pre-wrap; } 冗余 - + + Non-MO + + + + invalid 无效 - installed version: %1, newest version: %2 - 当前版本: %1,最新版本: %2 + 当前版本: %1,最新版本: %2 + + + + installed version: "%1", newest version: "%2" + - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + - + Categories: <br> 种类: <br> - + Invalid name 无效的名称 - + drag&drop failed: %1 拖拽失败: %1 - + Confirm 确认 - - Are you sure you want to remove "%1"? - 确定要移除 "%1" 吗? + + Are you sure you want to remove "%1"? + 确定要移除 "%1" 吗? - + Flags 标志 - + Mod Name Mod 名称 - + Version 版本 - + Priority 优先级 - + Category 分类 - + Nexus ID Nexus网 ID - + Installation 安装 - - + + unknown 未知 - + Name of your mods 你的mod名称 - + Version of the mod (if available) Mod 版本 (如果可用) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Mod 的安装优先级。越高就表示越“重要”,从而覆盖掉低优先级的 Mod 文件。 - + Category of the mod. mod的分类 - + Id of the mod as used on Nexus. mod在 Nexus 网上的ID编号 - + Emblemes to highlight things that might require attention. 需要注意被标记为高亮的 - + Time this mod was installed - + @@ -3207,17 +3672,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus - + - + timeout 超时 - + + Unknown error + + + + Please check your password 请检查您的密码 @@ -3225,17 +3695,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - 提取mod "%1"的ID编号失败,请自行选择正确项。 + + Failed to guess mod id for "%1", please pick the correct one + 提取mod "%1"的ID编号失败,请自行选择正确项。 - + empty response 未响应 - + invalid response 无效的响应 @@ -3274,8 +3744,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - 无法删除 "%1" + Failed to delete "%1" + 无法删除 "%1" @@ -3285,8 +3755,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - 确定要删除 "%1" 吗? + Are sure you want to delete "%1"? + 确定要删除 "%1" 吗? @@ -3301,116 +3771,146 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - 无法创建 "%1" + Failed to create "%1" + 无法创建 "%1" PluginList - + Name 名称 - + Priority 优先级 - + Mod Index Mod 索引 - - + + Flags + 标志 + + + + unknown 未知 - + Name of your mods 你的mod名称 - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 esp未找到:%1 - - + + Confirm 确认 - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + + + + + <b>Origin</b>: %1 + + + + + Author + 作者 + + + + Description + 描述 - - failed to open output file: %1 - 无法打开输出文件: %1 + 无法打开输出文件: %1 - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. 您的一些插件名称无效!这些插件无法被游戏载入。请查看 mo_interface.log 来确认那些受影响的插件并重命名它们。 - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) 这个插件不能被禁用 (由游戏执行) - Origin: %1 - 隶属于: %1 + 隶属于: %1 - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 恢复 %1 加载顺序失败 + + PreviewDialog + + + Preview + + + + + Close + 关闭 + + ProblemsDialog @@ -3420,12 +3920,12 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + @@ -3441,89 +3941,84 @@ p, li { white-space: pre-wrap; } No guided fix - + Profile - + invalid profile name %1 - + - + failed to create %1 无法创建 %1 - - failed to open temporary file - - - - - failed to open "%1" for writing - - - - + failed to write mod list: %1 无法更新 Mod 列表: %1 - + failed to update tweaked ini file, wrong settings may be used: %1 更新tweaked ini文件失败,可能会应用错误的设置: %1 - + failed to create tweaked ini: %1 创建 tweaked ini: %1 失败 - - - - - + + "%1" is missing or inaccessible + + + + + + + + invalid index %1 无效的索引 %1 - - Overwrite directory couldn't be parsed + + Overwrite directory couldn't be parsed Overwrite 目录无法解析 - + invalid priority %1 无效的优先级 %1 - + failed to parse ini file (%1) 无法解析 Ini 文件 (%1): %2 - + failed to parse ini file (%1): %2 无法解析 Ini 文件 (%1): %2 - - - failed to modify "%1" - 未能找到 "%1" + + + failed to modify "%1" + 未能找到 "%1" - + Delete savegames? 是否删除游戏存档? - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) 是否删除本地游戏存档?(如果选择“否”,当你重新恢复本地游戏时存档将再次显示) @@ -3546,7 +4041,7 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. 如果选中,那么新配置文件将会使用默认的游戏设定来替代全局设定。全局设定是您不使用 MO,直接运行游戏时所配置的设定。 @@ -3569,20 +4064,20 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">这是配置文件的列表,每个配置文件都包含了它们自己的已激活 Mod 的列表和安装顺序 (从共享区域)、一个已激活的 esp 或 esm 的配置、一个游戏 Ini 文件的拷贝和一个可选的存档过滤器。</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由于技术上的原因,目前不可能有分开保存的插件加载顺序。这意味着您不能同时在两个配置文件里使用两种不同的插件配置方案。</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">这是配置文件的列表,每个配置文件都包含了它们自己的已激活 Mod 的列表和安装顺序 (从共享区域)、一个已激活的 esp 或 esm 的配置、一个游戏 Ini 文件的拷贝和一个可选的存档过滤器。</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由于技术上的原因,目前不可能有分开保存的插件加载顺序。这意味着您不能同时在两个配置文件里使用两种不同的插件配置方案。</p></body></html> @@ -3602,22 +4097,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">湮灭、辐射3和辐射新维加斯包含了一个 Bug: 游戏阻止了用来运行游戏的 Texture 和 Mesh 被替换 (所有改动到游戏中已存在的 Meshes 和 Textures 的 Mod)。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod Organizer 使用了一种叫作“BSA 重定向”的解决方案可靠地修复了这个问题,并且无需进一步的操作,简单地激活然后忘记这件事吧。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">伴随着天际的到来,这个 Bug 似乎在一定程度上被修复了。但是一个 Mod 是否能够被正确地激活仍取决于文件的日期。因此,激活它还是有意义的。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">湮灭、辐射3和辐射新维加斯包含了一个 Bug: 游戏阻止了用来运行游戏的 Texture 和 Mesh 被替换 (所有改动到游戏中已存在的 Meshes 和 Textures 的 Mod)。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod Organizer 使用了一种叫作“BSA 重定向”的解决方案可靠地修复了这个问题,并且无需进一步的操作,简单地激活然后忘记这件事吧。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">伴随着天际的到来,这个 Bug 似乎在一定程度上被修复了。但是一个 Mod 是否能够被正确地激活仍取决于文件的日期。因此,激活它还是有意义的。</span></p></body></html> @@ -3684,7 +4179,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. 这个游戏并不需要档案无效化。 @@ -3716,7 +4211,7 @@ p, li { white-space: pre-wrap; } Invalid profile name - + @@ -3726,17 +4221,17 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + @@ -3781,48 +4276,48 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - 无效的名称 "%1" + invalid field name "%1" + 无效的名称 "%1" - invalid type for "%1" (should be integer) - 无效的类型 "%1" (应该是整数) + invalid type for "%1" (should be integer) + 无效的类型 "%1" (应该是整数) - invalid type for "%1" (should be string) - 无效的类型 "%1" (应该是字符串) + invalid type for "%1" (should be string) + 无效的类型 "%1" (应该是字符串) - invalid type for "%1" (should be float) - 无效的类型 "%1" (应该是浮点数) + invalid type for "%1" (should be float) + 无效的类型 "%1" (应该是浮点数) no fields set up yet! - + - field not set "%1" - + field not set "%1" + - invalid character in field "%1" - + invalid character in field "%1" + empty field name - + invalid game type %1 - + @@ -3906,99 +4401,107 @@ p, li { white-space: pre-wrap; } 无法设置代理DLL加载 - + Permissions required 需要权限 - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + - - + + Woops 糟糕 - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened ModOrganizer已经崩溃。诊断文件是否已经产生?如果你将文件(%1)发送至 sherb@gmx.net ,这个bug有可能会被修复。最好加入崩溃发生时情况的简短说明(用英文吧)。 - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 Mod Organizer 崩溃了!遗憾的是,我无法生成诊断文件: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Mod Organizer 的一个实例正在运行 - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - "%1" 中未检测到游戏。请确保该路径中包含游戏执行程序以及对应的 Launcher 文件。 + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + "%1" 中未检测到游戏。请确保该路径中包含游戏执行程序以及对应的 Launcher 文件。 - - + + Please select the game to manage 请选择想要管理的游戏 - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + - - Please use "Help" from the toolbar to get usage instructions to all elements + + failed to start application: %1 + + + + + Please use "Help" from the toolbar to get usage instructions to all elements 请使用工具栏上的“帮助”来获得所有元素的使用说明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 无法解析配置文件 %1: %2 - - - failed to find "%1" - 未能找到 "%1" + + failed to find "%1" + 未能找到 "%1" - + failed to access %1 无法访问 %1 - + failed to set file time %1 无法设置文件时间 %1 - + failed to create %1 无法创建 %1 - - "%1" is missing - "%1" 缺失 + + "%1" is missing or inaccessible + + + + "%1" is missing + "%1" 缺失 @@ -4024,54 +4527,59 @@ p, li { white-space: pre-wrap; } 无法打开 %1 - + Script Extender 脚本拓展 - + Proxy DLL 代理DLL - - failed to spawn "%1" - 无法生成 "%1" + + failed to spawn "%1" + 无法生成 "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + + + + + failed to spawn "%1": %2 + 无法生成 "%1": %2 - - failed to spawn "%1": %2 - 无法生成 "%1": %2 + + "%1" doesn't exist + "%1" 不存在 - - "%1" doesn't exist - "%1" 不存在 + + failed to inject dll into "%1": %2 + 无法注入 dll 到 "%1": %2 - - failed to inject dll into "%1": %2 - 无法注入 dll 到 "%1": %2 + + failed to run "%1" + 无法运行 "%1" - - failed to run "%1" - 无法运行 "%1" + + failed to open temporary file + @@ -4117,12 +4625,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + @@ -4182,8 +4690,8 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - failed to open "%1" for writing - + failed to open "%1" for writing + @@ -4208,79 +4716,79 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe SelfUpdater - archive.dll not loaded: "%1" - archive.dll 没有载入: "%1" + archive.dll not loaded: "%1" + archive.dll 没有载入: "%1" - - - - + + + + Update 更新 - + An update is available (newest version: %1), do you want to install it? 有可用的更新 (最新版本: %1),您想要安装它吗? - + Download in progress 正在下载 - + Download failed: %1 下载失败: %1 - + Failed to install update: %1 无法安装更新: %1 - - failed to open archive "%1": %2 - 无法打开压缩包 "%1": %2 + + failed to open archive "%1": %2 + 无法打开压缩包 "%1": %2 - + failed to move outdated files: %1. Please update manually. 移除过时文件失败: %1。请手动更新 - + Update installed, Mod Organizer will now be restarted. 更新完成,Mod Organizer 现在将重新启动。 - + Error 错误 - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. 解析响应时发生错误。请回报此 Bug 并附上 mo_interface.log! - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) 没有可用于此版本的更新文件,需要下载完整的安装包 (%1 KB) - + no file for update found. Please update manually. 没有发现可更新。请手动更新。 - + Failed to retrieve update information: %1 无法检索更新信息: %1 - + No download server available. Please try again later. 没有可用的下载服务器,请稍后再尝试下载。 @@ -4288,18 +4796,18 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Settings - - - attempt to store setting for unknown plugin "%1" - + + + attempt to store setting for unknown plugin "%1" + - + Confirm 确认 - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? 修改 Mod 目录将会影响您的配置!新目录中不存在 (或者名称不同) 的 Mod 将在所有配置中被禁止掉。此操作无法撤销,所以执行此操作前建议先备份下自己的配置。立即执行? @@ -4328,21 +4836,21 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">界面语言,此处仅显示您已安装的翻译语言。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">界面语言,此处仅显示您已安装的翻译语言。</span></p></body></html> Style - + @@ -4357,23 +4865,23 @@ p, li { white-space: pre-wrap; } Log Level - + - Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log" + - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Debug - + @@ -4408,7 +4916,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). 储存 Mod 的目录。请注意: 修改此目录将会破坏所有配置文件与新目录中已不存在的 Mod (相同名称) 的关联。 @@ -4421,231 +4929,265 @@ p, li { white-space: pre-wrap; } Cache Directory 缓存目录 + + + User interface + + + If checked, the download interface will be more compact. + + + + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + Reset stored information from dialogs. 重设对话框信息。 - - This will make all dialogs show up again where you checked the "Remember selection"-box. + + This will make all dialogs show up again where you checked the "Remember selection"-box. 全部对话框将全部重新显示,包括你已勾取过“记住选择”的对话框。 - + Reset Dialogs 重置对话框 - - + + Modify the categories available to arrange your mods. 修改可用的类别来整理您的 Mod。 - + Configure Mod Categories 配置 Mod 类别 - - + + Nexus N网 - + Allows automatic log-in when the Nexus-Page for the game is clicked. 当N网页面打开时将自动登录。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">允许当N网页面打开时自动登录。请注意: 密码是储存在 modorganizer.ini 里的,混淆得不是很强烈。如果您担心有人可能会窃取您的密码,那么请不要存储在这里。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">允许当N网页面打开时自动登录。请注意: 密码是储存在 modorganizer.ini 里的,混淆得不是很强烈。如果您担心有人可能会窃取您的密码,那么请不要存储在这里。</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. 勾取并在下面输入正确账户,将自动登录Nexus (浏览和下载)。 - + Automatically Log-In to Nexus 自动登录 - + Username 账号 - + Password 密码 - + Disable automatic internet features 取消自动联网功能 - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) 取消自动联网功能。这并不影响用户调用功能(如检查mod更新,点赞支持mod,打开网页浏览)。 - + Offline Mode 离线模式 - + Use a proxy for network connections. 使用代理联网 - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + - + Use HTTP Proxy (Uses System Settings) 使用 HTTP 代理(使用系统设置) - + + Associate with "Download with manager" links + + + + + Known Servers (updated on download) + + + Known Servers (Dynamically updated every download) - 已知的服务器(每次下载自动更新) + 已知的服务器(每次下载自动更新) - + Preferred Servers (Drag & Drop) 首选服务器(可拖拽顺序) - + Plugins 插件 - + Author: 作者 - + Version: 版本 - + Description: 描述 - + Key 关键 - + Value - + - + Blacklisted Plugins (use <del> to remove): - + - + Workarounds 解决方案 - + Steam App ID Steam App ID - + The Steam AppID for your game 您游戏的 Steam AppID - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必须的,它被用来直接启动一些游戏。对于天际,如果没有设置或设置错误,&quot;Mod Organizer&quot; 的加载机制可能会无法正常工作。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此预设是应用程序 ID 的“常规”版本,因此在大多数情况下,您应该要重新设置一下。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您认为您有不同的版本 (年度版或其它版本),那么请参照下列的步骤来获取 ID: </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">1. 进入 Steam 里的游戏库</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">2. 右键点击您想要获取 ID 的游戏,选择</span><span style=" font-size:9pt; font-weight:600;">创建桌面快捷方式</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">3. 右键点击您刚才在桌面上创建的快捷方式,选择</span><span style=" font-size:9pt; font-weight:600;">属性</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">4. 在链接区域您应该会看到一些像这样的: </span><span style=" font-size:9pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">其中 22380 就是您要找的 ID。</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必须的,它被用来直接启动一些游戏。对于天际,如果没有设置或设置错误,&quot;Mod Organizer&quot; 的加载机制可能会无法正常工作。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此预设是应用程序 ID 的“常规”版本,因此在大多数情况下,您应该要重新设置一下。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您认为您有不同的版本 (年度版或其它版本),那么请参照下列的步骤来获取 ID: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">1. 进入 Steam 里的游戏库</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">2. 右键点击您想要获取 ID 的游戏,选择</span><span style=" font-size:9pt; font-weight:600;">创建桌面快捷方式</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">3. 右键点击您刚才在桌面上创建的快捷方式,选择</span><span style=" font-size:9pt; font-weight:600;">属性</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">4. 在链接区域您应该会看到一些像这样的: </span><span style=" font-size:9pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">其中 22380 就是您要找的 ID。</span></p></body></html> + + + Load Mechanism 加载机制 - + Select loading mechanism. See help for details. 选择加载机制,使用帮助查看更多细节。 - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + - + NMM Version NMM 版本 - + The Version of Nexus Mod Manager to impersonate. 想要模拟的 NMM 版本号。 - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer 使用了一个N网所提供的 API 来进行类似于检查更新和下载文件这样的操作。遗憾的是这个 API 并没有给第三方工具 (比如 MO) 正式的授权,所以我们需要模拟 NMM 来进行这些操作。 在此之前,N网使用了客户端辨识系统锁定了旧版本的 NMM,强制用户更新版本。这意味着 MO 也要模拟新版本的 NMM,即便 MO 自己并不需要更新。因此您需要在这里配置版本号来进行辨识。 请注意: MO 辨识自己为 MO 到网络服务器,这并不是欺骗。它仅仅是为用户代理添加了一个“兼容”的 NMM 版本。 @@ -4653,79 +5195,97 @@ tl;dr-version: If Nexus-features don't work, insert the current version number o 变更版本号: 如果N网功能不正常了,那么请在这里输入 NMM 的当前版本号并重试一下。 - + Enforces that inactive ESPs and ESMs are never loaded. 强制执行,未激活的 ESP 和 ESM 将不会被加载。 - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. 看来,游戏偶尔会加载一些没有被激活成插件的 ESP 或 ESM 文件。 我还尚不知道它在什么情况下会这样,但是有用户报告说它在某些情况下是很不必要的。如果这个选项被选中,那么在列表中没有被勾选的 ESP 和 ESM 将不会在游戏中出现,并且也不会被载入。 - + Hide inactive ESPs/ESMs 隐藏未激活的 ESP 或 ESM - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + - + Force-enable game files - + - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! 对于天际,这个可以用来取代档案无效化,它将会使档案无效化对所有配置都变得多余。 但是对于其它游戏,这并不是一个很好的替代品! - + Back-date BSAs 重置 BSA 文件修改日期 - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + - + Select download directory 选择下载目录 - + Select mod directory 选择 Mod 目录 - + Select cache directory 选择缓存目录 - + Confirm? 确认? - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? 此操作将导致之前勾选的“记住我的选项”询问窗口再次出现,确定要重置对话框? @@ -4772,10 +5332,14 @@ For the other games this is not a sufficient replacement for AI! - failed to connect to running instance: %1 无法连接到正在运行的实例: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4801,7 +5365,7 @@ For the other games this is not a sufficient replacement for AI! - <don't sync> + <don't sync> <不要同步> @@ -4825,7 +5389,7 @@ For the other games this is not a sufficient replacement for AI! Global Characters - + @@ -4842,7 +5406,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4855,7 +5419,7 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4894,8 +5458,8 @@ On Windows XP: - Overwrite the file "%1" - 覆盖文件 "%1" + Overwrite the file "%1" + 覆盖文件 "%1" @@ -4908,18 +5472,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - 是否复制角色 "%1" 的所有游戏存档到这个配置中? + Copy all save games of character "%1" to the profile? + 是否复制角色 "%1" 的所有游戏存档到这个配置中? - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - 是否移动角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + 是否移动角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - 是否拷贝角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + 是否拷贝角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 diff --git a/src/organizer_zh_TW.ts b/src/organizer_zh_TW.ts index 11f67f8a..b7e4a346 100644 --- a/src/organizer_zh_TW.ts +++ b/src/organizer_zh_TW.ts @@ -1,5 +1,50 @@ + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + 關閉 + + + + No license + + + ActivateModsDialog @@ -14,23 +59,23 @@ - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被激活。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">對於每個 esp,右列中包含了可以通過啟用來使缺失的 esp 或 esm 變得可用的 Mod。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您點擊確定,那麼所有在右列中已選的並且可用的 Mod 和缺失的 esp 都將會被激活。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被激活。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">對於每個 esp,右列中包含了可以通過啟用來使缺失的 esp 或 esm 變得可用的 Mod。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您點擊確定,那麼所有在右列中已選的並且可用的 Mod 和缺失的 esp 都將會被激活。</span></p></body></html> @@ -73,9 +118,9 @@ p, li { white-space: pre-wrap; } Components of this package. -If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. 此包中的組件。 -如果有一個組件叫作 "00 Core",那麼它應該就是必需安裝的,可選安装的檔案一般會被作者按優先級排列。 +如果有一個組件叫作 "00 Core",那麼它應該就是必需安裝的,可選安装的檔案一般會被作者按優先級排列。 @@ -110,6 +155,29 @@ If there is a component called "00 Core" it is usually required. Options are ord 取消 + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + CategoriesDialog @@ -155,20 +223,20 @@ If there is a component called "00 Core" it is usually required. Options are ord - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以關聯單個或多個N網類別到一個內部 ID,當您在N網下載 Mod 的時候,Mod Organizer 將會嘗試解析N網中的類別的定義並提供給 MO。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">要找出一個N網所使用的類別 ID,您可以訪問N網頁面的種類列表並將鼠標懸停在連結上面。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以關聯單個或多個N網類別到一個內部 ID,當您在N網下載 Mod 的時候,Mod Organizer 將會嘗試解析N網中的類別的定義並提供給 MO。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">要找出一個N網所使用的類別 ID,您可以訪問N網頁面的種類列表並將鼠標懸停在連結上面。</span></p></body></html> @@ -200,7 +268,7 @@ p, li { white-space: pre-wrap; } - This feature may not work unless you're logged in with Nexus + This feature may not work unless you're logged in with Nexus 當您尚未登入N網時此功能可能無法正常工作 @@ -227,7 +295,7 @@ p, li { white-space: pre-wrap; } DirectoryRefresher - + failed to read bsa: %1 無法讀取 %1: %2 @@ -235,25 +303,30 @@ p, li { white-space: pre-wrap; } DownloadList - + Name 名稱 - + Filetime 檔案時間 - + Done 完成 - - Information missing, please select "Query Info" from the context menu to re-retrieve. + + Information missing, please select "Query Info" from the context menu to re-retrieve. 訊息丟失,請在右鍵菜單裡選擇“查詢訊息”來重新檢索。 + + + pending download + + DownloadListWidget @@ -265,26 +338,26 @@ p, li { white-space: pre-wrap; } - - + + Done - Double Click to install 完成 - 雙擊進行安裝 - - + + Paused - Double Click to resume - + - - + + Installed - Double Click to re-install 已安裝 - 雙擊重新安裝 - - + + Uninstalled - Double Click to re-install 已安裝 - 雙擊重新安裝 @@ -305,6 +378,16 @@ p, li { white-space: pre-wrap; } DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + Paused @@ -313,12 +396,12 @@ p, li { white-space: pre-wrap; } Fetching Info 1 - + Fetching Info 2 - + @@ -328,7 +411,7 @@ p, li { white-space: pre-wrap; } Uninstalled - + @@ -336,95 +419,95 @@ p, li { white-space: pre-wrap; } 完成 - - - - + + + + Are you sure? 確定? - + This will remove all finished downloads from this list and from disk. 這將會從列表和磁碟中移除所有已完成的下載。 - + This will remove all installed downloads from this list and from disk. 這將會從列表和磁碟中移除所有已安裝的下載項目。 - + This will permanently remove all finished downloads from this list (but NOT from disk). - + - + This will permanently remove all installed downloads from this list (but NOT from disk). - + - + Install 安裝 - + Query Info 查詢訊息 - + Delete &刪除 - + Un-Hide - + 取消隱藏 - + Remove from View - + - + Cancel 取消 - + Pause 暫停 - + Remove 移除 - + Resume 繼續 - + Delete Installed... 移除已安裝的項目... - + Delete All... - + - + Remove Installed... 移除已安裝的項目... - + Remove All... 移除所有... @@ -432,105 +515,115 @@ p, li { white-space: pre-wrap; } DownloadListWidgetDelegate - + + < mod %1 file %2 > + + + + + Pending + + + + Fetching Info 1 - + - + Fetching Info 2 - + - - - - + + + + Are you sure? 確定? - + This will remove all finished downloads from this list and from disk. 這將會從列表和磁碟中移除所有已完成的下載。 - + This will remove all installed downloads from this list and from disk. 這將會從列表和磁碟中移除所有已安裝的下載項目。 - + This will remove all finished downloads from this list (but NOT from disk). 這將會從列表和磁碟中移除所有已完成的下載。 - + This will remove all installed downloads from this list (but NOT from disk). 這將會從列表和磁碟中移除所有已安裝的下載項目。 - + Install 安裝 - + Query Info 查詢訊息 - + Delete &刪除 - + Un-Hide - + 取消隱藏 - + Remove from View - + - + Cancel 取消 - + Pause 暫停 - + Remove 移除 - + Resume 繼續 - + Delete Installed... 移除已安裝的項目... - + Delete All... - + - + Remove Installed... 移除已安裝的項目... - + Remove All... 移除所有... @@ -538,116 +631,173 @@ p, li { white-space: pre-wrap; } DownloadManager - - failed to rename "%1" to "%2" - 重新命名 "%1 "為 "%2" 時出錯 + + failed to rename "%1" to "%2" + 重新命名 "%1 "為 "%2" 時出錯 - + + Memory allocation error (in refreshing directory). + + + + Download again? 重新下載? - + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. 已存在同名檔案。您確定要重新下載?新檔案將使用不同的檔案名。 - + failed to download %1: could not open output file: %2 下載 %1 失敗: 無法開啟輸出檔案: %2 - + Wrong Game - + - - The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + - - - - - - - - - - - + + - - + + + + + + + + + + + + + invalid index 無效的索引 - + failed to delete %1 無法刪除 %1 - + failed to delete meta file for %1 無法從 %1 中刪除 mate 檔案 - - - - - - + + + + + + invalid index %1 無效的索引 %1 - + + No known download urls. Sorry, this download can't be resumed. + + + + Please enter the nexus mod id 請輸入N網 Mod ID - + Mod ID: Mod ID: - + + Main + 主要檔案 + + + + Update + 更新 + + + + Optional + 可選檔案 + + + + Old + 舊檔 + + + + Misc + 雜項 + + + + Unknown + 未知 + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + Information updated 訊息已更新 - - + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? 無法在N網上找到匹配的檔案!也許這個檔案已經不存在了或是它改名了? - + No file on Nexus matches the selected file by name. Please manually choose the correct one. 所選的檔案無法在N網上找到可匹配的項目,請手動選擇正確的一個。 - + No download server available. Please try again later. 沒有可用的下載伺服器,請稍後再嘗試下載。 - + Failed to request file info from nexus: %1 無法在N網上請求檔案訊息: %1 - + + Download failed. Server reported: %1 + + + + Download failed: %1 (%2) 下載失敗: %1 (%2) - + failed to re-open %1 無法重新開啟 %1 @@ -759,7 +909,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + If checked, MO will be closed once the specified executable is run. 如果選中,那麼 MO 將在指定的程式運行後關閉。 @@ -776,7 +926,7 @@ Right now the only case I know of where this needs to be overwritten is for the - + Add 添加 @@ -792,47 +942,64 @@ Right now the only case I know of where this needs to be overwritten is for the 移除 - + + Close + 關閉 + + + Select a binary 選擇一個可執行檔案 - + Executable (%1) 可執行程式 (%1) - + Java (32-bit) required - + - + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + - + Select a directory 選擇一個目錄 - + Confirm 確認 - - Really remove "%1" from executables? - 真的要从程式列表中移除 "%1" 吗? + + Really remove "%1" from executables? + 真的要从程式列表中移除 "%1" 吗? - + Modify 更改 - + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + MO must be kept running or this application will not work correctly. MO 必須持續運行,否則該程式將無法正常工作。 @@ -903,8 +1070,8 @@ Right now the only case I know of where this needs to be overwritten is for the - <a href="#">Link</a> - <a href="#">連結</a> + <a href="#">Link</a> + <a href="#">連結</a> @@ -951,7 +1118,7 @@ Right now the only case I know of where this needs to be overwritten is for the - Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. 輸入一個 Mod 的名稱,這也將作為一個目錄的名稱,因此請不要使用非法字符。 @@ -966,16 +1133,16 @@ Right now the only case I know of where this needs to be overwritten is for the - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這裡顯示了该壓縮包中的內容,&lt;data&gt; 將被作為映射到遊戲 Data 目錄的基本目錄,您可以通過右鍵菜單來改變基本目錄並使用拖放來移動檔案。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這裡顯示了该壓縮包中的內容,&lt;data&gt; 將被作為映射到遊戲 Data 目錄的基本目錄,您可以通過右鍵菜單來改變基本目錄並使用拖放來移動檔案。</span></p></body></html> @@ -997,8 +1164,8 @@ p, li { white-space: pre-wrap; } InstallationManager - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + @@ -1013,19 +1180,19 @@ p, li { white-space: pre-wrap; } - + Extracting files 正在解壓檔案 failed to create backup - + Mod Name - + @@ -1035,65 +1202,65 @@ p, li { white-space: pre-wrap; } Invalid name - + The name you entered is invalid, please enter a different one. - + - - File format "%1" not supported - 暫不支持檔案格式: "%1" + + File format "%1" not supported + 暫不支持檔案格式: "%1" - + None of the available installer plugins were able to handle that archive - + - + no error 沒有錯誤 - + 7z.dll not found 未找到 7z.dll - - 7z.dll isn't valid + + 7z.dll isn't valid 無效的 7z.dll - + archive not found 未找到壓縮包 - + failed to open archive 無法開啟壓縮包 - + unsupported archive type 不支持的壓縮包類型 - + internal library error 內部庫錯誤 - + archive invalid 無效的壓縮包 - + unknown archive error 未知壓縮包錯誤 @@ -1107,7 +1274,7 @@ p, li { white-space: pre-wrap; } - This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. 當程式或遊戲結束後此對話方塊將自動消失,如果沒有請點擊解鎖。 @@ -1116,7 +1283,7 @@ p, li { white-space: pre-wrap; } 程式運行時 MO 將被鎖定。 - + Unlock 解鎖 @@ -1124,7 +1291,7 @@ p, li { white-space: pre-wrap; } LogBuffer - + failed to write log to %1: %2 無法生成日誌到 %1: %2 @@ -1132,12 +1299,12 @@ p, li { white-space: pre-wrap; } MOApplication - + an error occured: %1 發生錯誤: %1 - + an error occured 發生錯誤 @@ -1145,170 +1312,194 @@ p, li { white-space: pre-wrap; } MainWindow - - + + Categories 種類 + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + Profile 配置檔案 - + Pick a module collection 選擇一個配置檔案 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在這裡建立配置檔案,每個配置檔案都包含了它們自己的 Mod 和 esp 的激活方案。這樣您就可以通過快速切換設定來體驗不同的遊戲歷程了。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">請注意: 當前您的配置檔案的 esp 加載順序並不是分開儲存的。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在這裡建立配置檔案,每個配置檔案都包含了它們自己的 Mod 和 esp 的激活方案。這樣您就可以通過快速切換設定來體驗不同的遊戲歷程了。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">請注意: 當前您的配置檔案的 esp 加載順序並不是分開儲存的。</span></p></body></html> - Refresh list - 重新整理列表 + 重新整理列表 - + Refresh list. This is usually not necessary unless you modified data outside the program. 重新整理列表,這通常不是必須的,除非您在程式之外修改了檔案的數據。 - + List of available mods. - + - - This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + - + Filter 過濾器 - + No groups - + - + Nexus IDs N網 ID - - - + + + Namefilter - + - + Pick a program to run. 選擇要運行的程式。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">選擇要運行的程式。一旦您開始使用 Mod Organizer,您應該始終從這裡或通過在這裡建立的捷徑來運行您的遊戲和工具,否則任何經由 MO 安裝的 Mod 都會變得不可見。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保證一些我沒有測試過的工具能够正常工作。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">選擇要運行的程式。一旦您開始使用 Mod Organizer,您應該始終從這裡或通過在這裡建立的捷徑來運行您的遊戲和工具,否則任何經由 MO 安裝的 Mod 都會變得不可見。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保證一些我沒有測試過的工具能够正常工作。</span></p></body></html> - + Run program 運行程式 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 啟用的狀態下運行指定的程式。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 啟用的狀態下運行指定的程式。</span></p></body></html> - + Run 運行 - + Create a shortcut in your start menu or on the desktop to the specified program - + - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">建立一個開始菜單捷徑,使您可以直接在 MO 激活狀態下運行指定的程式。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">建立一個開始菜單捷徑,使您可以直接在 MO 激活狀態下運行指定的程式。</span></p></body></html> - + Shortcut - + - + List of available esp/esm files 可用 esp 或 esm 檔案的列表 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這個列表中包含了位于已激活 Mod 裡的 esp 和 esm 檔案。這些檔案都需要它們自己的加載順序,您可以使用拖放來修改加載順序。請注意: MO 將只儲存已激活或已勾選狀態的 Mod 的加載順序。<br />有個非常棒的工具叫作 &quot;BOSS&quot;,它可以自動對這些檔案進行排序。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這個列表中包含了位于已激活 Mod 裡的 esp 和 esm 檔案。這些檔案都需要它們自己的加載順序,您可以使用拖放來修改加載順序。請注意: MO 將只儲存已激活或已勾選狀態的 Mod 的加載順序。<br />有個非常棒的工具叫作 &quot;BOSS&quot;,它可以自動對這些檔案進行排序。</span></p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. 可用 BSA 檔案的列表。未勾選的項目不會被 MO 管理並且會忽略安裝順序。 - - BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. @@ -1318,236 +1509,272 @@ BSAs checked here are loaded in such a way that your installation order is obeye 這裡勾選的 BSA 將會依從您的安裝順序,並且會自行調整加載順序。 - - + + File 檔案 - - <html><head/><body><p>Marked Archives (<img src=":/MO/gui/warning_16"/>) are still loaded on Skyrim but the <a href="http://forums.bethsoft.com/topic/1354395-update-bsas-and-you/"><span style=" text-decoration: underline; color:#0000ff;">regular file override</span></a> mechanism will apply: Loose files override BSAs, no matter the mod/plugin priority.</p></body></html> - - - - + Data Data - + refresh data-directory overview 重新整理 Data 目錄總覽 - + Refresh the overview. This may take a moment. 重新整理總覽,這可能需要一些時間。 - - - + + + Refresh 重新整理 - + This is an overview of your data directory as visible to the game (and tools). 這是在遊戲中可見的 Data 目錄 (和工具) 的總覽。 - + Mod Mod - - + + Filter the above list so that only conflicts are displayed. 過濾上面的列表,使您只能看到有衝突的檔案。 - + Show only conflicts 只顯示衝突 - + Saves 存檔 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是此遊戲所有存檔的列表,將滑鼠懸停在項目上來獲取該存檔的詳細信息,裡面包含了現在沒有被激活但是當存檔被建立時所使用的 esp 或 esm 的清單。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右鍵菜單中點擊“修復 Mod”,那麼 MO 便會嘗試激活所有 Mod 和 esp 來修復那些缺失的 esp,它並不會禁用任何東西!</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是此遊戲所有存檔的列表,將滑鼠懸停在項目上來獲取該存檔的詳細信息,裡面包含了現在沒有被激活但是當存檔被建立時所使用的 esp 或 esm 的清單。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右鍵菜單中點擊“修復 Mod”,那麼 MO 便會嘗試激活所有 Mod 和 esp 來修復那些缺失的 esp,它並不會禁用任何東西!</span></p></body></html> - + Downloads 下載 - + This is a list of mods you downloaded from Nexus. Double click one to install it. 這是當前已下載的 Mod 的列表,雙擊進行安裝。 - Compact - 緊湊 + 緊湊 - + + Open list options... + + + + + + Restore Backup... + + + + + + Create Backup + + + + + Plugins + + + + + Sort + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + Show Hidden - + - + Tool Bar 工具欄 - + Install Mod 安裝 Mod - + Install &Mod 安裝 &Mod - + Install a new mod from an archive 通過壓縮包來安裝一個新 Mod - + Ctrl+M Ctrl+M - + Profiles 配置檔案 - + &Profiles &配置檔案 - + Configure Profiles 設定配置檔案 - + Ctrl+P Ctrl+P - + Executables 可執行程式 - + &Executables &可執行程式 - + Configure the executables that can be started through Mod Organizer 配置可通過 MO 來啟動的程式 - + Ctrl+E Ctrl+E - - + + Tools - + - + &Tools - + - + Ctrl+I Ctrl+I - + Settings 設定 - + &Settings &設定 - + Configure settings and workarounds 配置設定和解決方案 - + Ctrl+S Ctrl+S - + Nexus N網 - + Search nexus network for more mods 搜尋N網以獲取更多 Mod - + Ctrl+N Ctrl+N - - + + Update 更新 - + Mod Organizer is up-to-date Mod Organizer 現在是最新版本 - - + + No Problems 沒有問題 - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1558,899 +1785,1095 @@ Right now this has very limited functionality 當前此功能所能提供的項目非常有限 - - + + Help 幫助 - + Ctrl+H Ctrl+M - + Endorse MO - + - - + + Endorse Mod Organizer - + + + + + Copy Log to Clipboard + + + + + Ctrl+C + - + Toolbar 工具欄 - + Desktop - + - + Start Menu - + - + Problems 問題 - + There are potential problems with your setup 您的安裝中存在潛在的問題 - + Everything seems to be in order 一切井然有序 - + Help on UI 介面幫助 - + Documentation Wiki 說明文檔 (維基) - + Report Issue 報告問題 - + Tutorials - + - - failed to save archives order, do you have write access to "%1"? - 無法儲存檔案順序,您確定您有權限更改 "%1"? + failed to save archives order, do you have write access to "%1"? + 無法儲存檔案順序,您確定您有權限更改 "%1"? - + failed to save load order: %1 無法儲存加載順序: %1 - + Name 名稱 - + Please enter a name for the new profile 請為新配置檔案輸入一個名稱 - + failed to create profile: %1 無法建立配置檔案: %1 - + Show tutorial? - + - - You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + - + Downloads in progress 正在下載 - + There are still downloads in progress, do you really want to quit? 仍有正在進行中的下載,您確定要退出嗎? - + failed to read savegame: %1 無法讀取存檔: %1 - - Plugin "%1" failed: %2 - + + Plugin "%1" failed: %2 + - - Plugin "%1" failed - + + Plugin "%1" failed + - + failed to init plugin %1: %2 - + - + Plugin error - + - - It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + - - Failed to start "%1" - 無法啟動 "%1" + + Failed to start "%1" + 無法啟動 "%1" - + Waiting 稍等 - - Please press OK once you're logged into steam. + + Please press OK once you're logged into steam. 當您登入 Steam 時請點擊確定。 - - "%1" not found - "%1" 未找到 + "%1" not found + "%1" 未找到 - + Start Steam? 啟動 Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? 想要正確地啟動遊戲,Steam 必須處於運行狀態,MO 要立即啟動 Steam 嗎? - + Also in: <br> 也在: <br> - + No conflict 沒有衝突 - + <Edit...> <編輯...> - - Failed to refresh list of esps: %s - - - - + This bsa is enabled in the ini file so it may be required! 該 BSA 已在 Ini 檔案中啟用,因此它可能是必需的。 - This archive will still be loaded since there is a plugin of the same name but its files will not follow installation order! - 此檔案還是會被加載,因為存在同名插件。不過它所包含的的檔案不會遵循安裝順序! + 此檔案還是會被加載,因為存在同名插件。不過它所包含的的檔案不會遵循安裝順序! - + Activating Network Proxy - + - - + + Installation successful 安裝成功 - - + + Configure Mod 配置 Mod - - + + This mod contains ini tweaks. Do you want to configure them now? 此 Mod 中包含 Ini 設定檔案,您想現在就對它們進行配置嗎? - - - mod "%1" not found - Mod "%1" 未找到 + + + mod "%1" not found + Mod "%1" 未找到 - - + + Installation cancelled 安裝已取消 - - + + The mod was not installed completely. Mod 沒有完全安裝。 - + Some plugins could not be loaded - + - + Too many esps and esms enabled - + - - + + Description missing - + - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - - The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + - + Choose Mod 選擇 Mod - + Mod Archive Mod 壓縮包 - + Start Tutorial? - + - - You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + - - + + Download started 開始下載 - + failed to update mod list: %1 無法更新 Mod 列表: %1 - + failed to spawn notepad.exe: %1 無法生成 notepad.exe: %1 - + failed to open %1 無法開啟 %1 - + failed to change origin name: %1 無法更改原始檔案名: %1 - - Failed to move "%1" from mod "%2" to "%3": %4 - - - - + <Checked> <已勾選> - + <Unchecked> <未勾選> - + <Update> <有更新> - + <No category> <無類別> - + <Conflicted> - + - + <Not Endorsed> - + - + failed to rename mod: %1 無法重新命名 Mod: %1 - + Overwrite? 覆蓋 - - This will replace the existing mod "%1". Continue? - + + This will replace the existing mod "%1". Continue? + - - failed to remove mod "%1" + + failed to remove mod "%1" 無法移動 Mod: %1 - - - - failed to rename "%1" to "%2" - 重新命名 "%1 "為 "%2" 時出錯 + + + + failed to rename "%1" to "%2" + 重新命名 "%1 "為 "%2" 時出錯 - - Multiple esps activated, please check that they don't conflict. - + + Multiple esps activated, please check that they don't conflict. + - - - + + + + Confirm 確認 - + Remove the following mods?<br><ul>%1</ul> - + - + failed to remove mod: %1 無法移動 Mod: %1 - - + + Failed 失敗 - + Installation file no longer exists 安裝檔案不複存在 - - Mods installed with old versions of MO can't be reinstalled in this way. + + Mods installed with old versions of MO can't be reinstalled in this way. 舊版 MO 安裝的 Mod 無法使用此方法重新安裝。 - - + + You need to be logged in with Nexus to endorse - + - - + Extract BSA 解壓 BSA - This mod contains at least one BSA. Do you want to unpack it? -(This removes the BSA after completion. If you don't know about BSAs, just select no) - 此 Mod 中至少包含一個 BSA。您確定要解壓嗎? +(This removes the BSA after completion. If you don't know about BSAs, just select no) + 此 Mod 中至少包含一個 BSA。您確定要解壓嗎? (解壓完成後,BSA 檔案將會被刪除。如果您不瞭解 BSA 的話,請選擇“否”) - - - + + failed to read %1: %2 無法讀取 %1: %2 - - + This archive contains invalid hashes. Some files may be broken. 壓縮包 Hash 值錯誤。部分檔案可能已經損壞。 - + Nexus ID for this Mod is unknown 此 Mod 的N網 ID 未知 - - + + About + + + + + About Qt + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + Executable "%1" not found + + + + + Failed to refresh list of esps: %1 + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + You need to be logged in with Nexus to resume a download + + + + + Create Mod... - + - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + - + A mod with this name already exists - + - + Continue? - + - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - + + Sorry - + - - I don't know a versioning scheme where %1 is newer than %2. - + + I don't know a versioning scheme where %1 is newer than %2. + - + Really enable all visible mods? 確定要啟用全部可見的 Mod 嗎? - + Really disable all visible mods? 確定要禁用全部可見的 Mod 嗎? - + Choose what to export - + - + Everything - + - + All installed mods are included in the list - + - + Active Mods 激活 Mod - + Only active (checked) mods from your current profile are included - + - + Visible - + - + All mods visible in the mod list are included - + - + export failed: %1 - + - + Install Mod... 安裝 Mod... - + Enable all visible 啟用所有可見項目 - + Disable all visible 禁用所有可見項目 - + Check all for update 檢查更新 - + Export to csv... - + + + + + All Mods + - + Sync to Mods... 同步到 Mod... - + Restore Backup - + - + Remove Backup... - + - + Add/Remove Categories - + - + Replace Categories - + - + Primary Category - + - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... 重新命名... - + Remove Mod... 移除 Mod... - + Reinstall Mod 重新安裝 Mod - + Un-Endorse - + - - + + Endorse - + - - Won't endorse - + + Won't endorse + - + Endorsement state unknown - + - + Ignore missing data - + - + Visit on Nexus 在N網上流覽 - + Open in explorer 在檔案總管中開啟 - + Information... 訊息... - - + + Exception: 例外: - - + + Unknown exception 未知的例外 - + <All> <全部> - + <Multiple> - + - + + Really delete "%1"? + + + + Fix Mods... 修復 Mod... - - + + Delete + &刪除 + + + + failed to remove %1 無法刪除 %1 - - + + failed to create %1 無法建立 %1 - - Can't change download directory while downloads are in progress! + + Can't change download directory while downloads are in progress! 下載檔案時不能修改下載目錄! - + Download failed 下載失敗 - + failed to write to file %1 無法寫入檔案 %1 - + %1 written 已寫入 %1 - + Select binary 選擇可執行檔案 - + Binary 程式 - + Enter Name 輸入名稱 - + Please enter a name for the executable 請為程式輸入一個名稱 - + Not an executable 不是可執行程式 - + This is not a recognized executable. 無法識別的可執行檔案 - - + + Replace file? 取代檔案? - + There already is a hidden version of this file. Replace it? 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed 檔案操作錯誤 - - - Failed to remove "%1". Maybe you lack the required file permissions? - 無法移除 "%1"。也許您需要足夠的檔案權限? + + + Failed to remove "%1". Maybe you lack the required file permissions? + 無法移除 "%1"。也許您需要足夠的檔案權限? - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + Update available 更新可用 - + Open/Execute 開啟/執行 - + Add as Executable 添加為可執行檔案 - + + Preview + + + + Un-Hide 取消隱藏 - + Hide 隱藏 - + Write To File... 寫入檔案... - + Do you want to endorse Mod Organizer on %1 now? - + + + + + Thank you! + - + + Thank you for your endorsement! + + + + Request to Nexus failed: %1 - + - - + + login successful 登入成功 - + login failed: %1. Trying to download anyway 登入失敗: %1,請嘗試使用別的方法下載 - + login failed: %1 無法登入: %1 - + login failed: %1. You need to log-in with Nexus to update MO. 登入失敗: %1。您需要登入到N網才能更新 MO - + Error 錯誤 - + failed to extract %1 (errorcode %2) 無法解壓 %1 (錯誤代碼 %2) - + Extract... 解壓... - + Edit Categories... 編輯類別... - + + Deselect filter + + + + Remove - + 移除 - + Enable all 全部啟用 - + Disable all 全部禁用 - + Unlock load order - + - + Lock load order - + + + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + @@ -2465,8 +2888,8 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + invalid index %1 無效的索引 %1 @@ -2474,9 +2897,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + @@ -2487,516 +2910,544 @@ This function will guess the versioning scheme under the assumption that the ins Mod 訊息 - + Textfiles 文字文件 - + A list of text-files in the mod directory. Mod 目錄裡包含的文字文件的列表。 - + A list of text-files in the mod directory like readmes. Mod 目錄裡包含的文字文件 (類似於自述文檔) 的列表。 - - + + Save 儲存 - + INI-Files Ini 檔案 - + + Ini Files + + + + This is a list of .ini files in the mod. Mod 目錄裡包含的 Ini 檔案的列表。 - + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. Mod 目錄裡包含的 Ini 檔案的列表,這些檔案通常用來配置 Mod 的行為,如果有可設定的參數的話。 - + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + Save changes to the file. 儲存更改到檔案中。 - + Save changes to the file. This overwrites the original. There is no automatic backup! 儲存更改到檔案中,這將會覆蓋原始檔案,並且沒有自動備份! - + Images 圖片 - + Images located in the mod. 位於 Mod 中的圖片。 - + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + - - + + Optional ESPs 可選 ESP - + List of esps and esms that can not be loaded by the game. 包含了不會被遊戲載入的 esp 和 esm 的列表。 - + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + - + Make the selected mod in the lower list unavailable. 使下表中已選的 Mod 變得不可用。 - - The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. 已選的 esp (在下表中) 將會被放入 Mod 的子目錄裡,在遊戲裡將會變得“不可見”,並且之後就不能再被激活了。 - + Move a file to the data directory. 移動一個檔案到 Data 目錄。 - - This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. 移動一個 esp 檔案到 esp 目錄,這樣它就可以在主窗口中啟用了。請注意: ESP 只是變得“可用”,它并不一定會被載入!想要载入请在 MO 的主窗口中勾選。 - + ESPs in the data directory and thus visible to the game. ESP 在 Data 目錄,因此它在游戲裡會變得可見。 - + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. 這些 Mod 檔案位於您游戲的 (虛擬) Data 目錄裡,因此它們在主窗口的 esp 列表中會變得可選。 - + Available ESPs 可用 ESP - + Conflicts 衝突 - + The following conflicted files are provided by this mod 以下衝突檔案由此 Mod 提供 - - + + File 檔案 - + Overwritten Mods 覆蓋的 Mod - + The following conflicted files are provided by other mods 以下衝突檔案由其它 Mod 提供 - + Providing Mod 提供的 Mod - + Non-Conflicted files 非衝突檔案 - + Categories 類別 - + Primary Category - + - + Nexus Info N網訊息 - + Mod ID Mod ID - + Mod ID for this mod on Nexus. N網上此 Mod 的 ID。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">N網上此 Mod 的 ID,如果您在 MO 中下載並安裝了 Mod 它將被自動填寫,否則您需要手動輸入。要找到正確的 ID,在N網找到 Mod。比如,像這樣的連結: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a> 在上面的例子中,1334就是您要找的 ID。此外: 上面的就是 Mod Organizer 在N網的連結,為什麼不現在就到那裡去贊同我呢?</span></a></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">N網上此 Mod 的 ID,如果您在 MO 中下載並安裝了 Mod 它將被自動填寫,否則您需要手動輸入。要找到正確的 ID,在N網找到 Mod。比如,像這樣的連結: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a> 在上面的例子中,1334就是您要找的 ID。此外: 上面的就是 Mod Organizer 在N網的連結,為什麼不現在就到那裡去贊同我呢?</span></a></p></body></html> - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod 的已安裝版本,滑鼠提示將顯示N網上的當前版本,已安裝的版本號只有在您通過 MO 來安裝 Mod 的時候才會自動填寫。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod 的已安裝版本,滑鼠提示將顯示N網上的當前版本,已安裝的版本號只有在您通過 MO 來安裝 Mod 的時候才會自動填寫。</span></p></body></html> - + Version 版本 - + Refresh 重新整理 - + Refresh all information from Nexus. - + - + Description 描述 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + - + Endorse - + - + Notes - + - + Filetree 檔案樹 - + A directory view of this mod 這個 Mod 的目錄視圖 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是一個可編輯的 Mod 目錄的目錄視圖,您可以通過拖放來移動檔案或者重新命名它們 (雙擊)。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">所做的更改將會立即作用於磁碟上的檔案,所以請</span><span style=" font-size:9pt; font-weight:600;">謹慎操作</span><span style=" font-size:9pt;">。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是一個可編輯的 Mod 目錄的目錄視圖,您可以通過拖放來移動檔案或者重新命名它們 (雙擊)。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">所做的更改將會立即作用於磁碟上的檔案,所以請</span><span style=" font-size:9pt; font-weight:600;">謹慎操作</span><span style=" font-size:9pt;">。</span></p></body></html> - + Previous - + - + Next 下一步 - + Close 關閉 - + &Delete &刪除 - + &Rename &重新命名 - + &Hide &隱藏 - + &Unhide &取消隱藏 - + &Open &開啟 - + &New Folder &新增資料夾 - - + + Save changes? 儲存更改嗎? - - - Save changes to "%1"? - + + + Save changes to "%1"? + - + File Exists 檔案已存在 - + A file with that name exists, please enter a new one 檔案名已存在,請輸入其它名稱 - + failed to move file 無法移動檔案 - - failed to create directory "optional" - 無法建立 "optional" 目錄 + + failed to create directory "optional" + 無法建立 "optional" 目錄 - - + + Info requested, please wait 請求訊息已發出,請稍後 - + Main 主要檔案 - + Update 更新 - + Optional 可選檔案 - + Old 舊檔 - + Misc 雜項 - + Unknown 未知 - + Current Version: %1 當前版本: %1 - + No update available 沒有可用的更新 - + (description incomplete, please visit nexus) (描述訊息不完整,請訪問N網) - - <a href="%1">Visit on Nexus</a> - <a href="%1">訪問N網</a> + + <a href="%1">Visit on Nexus</a> + <a href="%1">訪問N網</a> - + Failed to delete %1 無法刪除 %1 - - + + Confirm 確認 - - Are sure you want to delete "%1"? - 確定要刪除 "%1" 嗎? + + Are sure you want to delete "%1"? + 確定要刪除 "%1" 嗎? - + Are sure you want to delete the selected files? 確定要刪除所選的檔案嗎? - - + + New Folder 新增資料夾 - - Failed to create "%1" - 無法建立 "%1" + + Failed to create "%1" + 無法建立 "%1" - - + + Replace file? 取代檔案? - + There already is a hidden version of this file. Replace it? 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed 檔案操作錯誤 - - - Failed to remove "%1". Maybe you lack the required file permissions? - 無法移除 "%1"。也許您需要足夠的檔案權限? + + + Failed to remove "%1". Maybe you lack the required file permissions? + 無法移除 "%1"。也許您需要足夠的檔案權限? - - + + failed to rename %1 to %2 無法重新命名 %1 為 %2 - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + Un-Hide 取消隱藏 - + Hide 隱藏 - + Name - + 名稱 - + Please enter a name - + - - + + Error - + 錯誤 - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) 此虛擬安裝包內包含來自虛擬 Data 樹的檔案,但檔案發生了變化 (例: 被CK修改了) @@ -3004,17 +3455,22 @@ p, li { white-space: pre-wrap; } ModInfoRegular - failed to write %1/meta.ini: %2 - 無法寫入 %1/meta.ini: %2 + 無法寫入 %1/meta.ini: %2 - + + + failed to write %1/meta.ini: error %2 + + + + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 中未包含 esp 或 esm 和有效的目錄 (textures, meshes, interface, ...) - + Categories: <br> 種類: <br> @@ -3029,22 +3485,22 @@ p, li { white-space: pre-wrap; } Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + @@ -3054,128 +3510,137 @@ p, li { white-space: pre-wrap; } Overwrites & Overwritten - + Redundant - + - + + Non-MO + + + + invalid - + - installed version: %1, newest version: %2 - 當前版本: %1,最新版本: %2 + 當前版本: %1,最新版本: %2 + + + + installed version: "%1", newest version: "%2" + - - The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + - + Categories: <br> 種類: <br> - + Invalid name - + - + drag&drop failed: %1 - + - + Confirm 確認 - - Are you sure you want to remove "%1"? - 確定要移除 "%1" 吗? + + Are you sure you want to remove "%1"? + 確定要移除 "%1" 吗? - + Flags - + - + Mod Name - + - + Version 版本 - + Priority 優先級 - + Category - + - + Nexus ID N網 ID - + Installation - + - - + + unknown 未知 - + Name of your mods - + - + Version of the mod (if available) Mod 版本 (如果可用) - - Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Mod 的安裝優先級。越高就表示越“重要”,從而覆蓋掉低優先級的 Mod 檔案。 - + Category of the mod. - + - + Id of the mod as used on Nexus. - + - + Emblemes to highlight things that might require attention. - + - + Time this mod was installed - + @@ -3207,17 +3672,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus - + - + timeout 超時 - + + Unknown error + + + + Please check your password 請檢查您的密碼 @@ -3225,17 +3695,17 @@ p, li { white-space: pre-wrap; } NexusInterface - - Failed to guess mod id for "%1", please pick the correct one - + + Failed to guess mod id for "%1", please pick the correct one + - + empty response 未回應 - + invalid response 無效的回應 @@ -3250,7 +3720,7 @@ p, li { white-space: pre-wrap; } You can use drag&drop to move files and directories to regular mods. - + @@ -3274,8 +3744,8 @@ p, li { white-space: pre-wrap; } - Failed to delete "%1" - 無法刪除 "%1" + Failed to delete "%1" + 無法刪除 "%1" @@ -3285,8 +3755,8 @@ p, li { white-space: pre-wrap; } - Are sure you want to delete "%1"? - 確定要刪除 "%1" 嗎? + Are sure you want to delete "%1"? + 確定要刪除 "%1" 嗎? @@ -3301,114 +3771,144 @@ p, li { white-space: pre-wrap; } - Failed to create "%1" - 無法建立 "%1" + Failed to create "%1" + 無法建立 "%1" PluginList - + Name 名稱 - + Priority 優先級 - + Mod Index - + - - + + Flags + + + + + unknown 未知 - + Name of your mods - + - - Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 - + - - + + Confirm - + 確認 - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + + + + + <b>Origin</b>: %1 + + + + + Author + 作者 + + + + Description + 描述 - - failed to open output file: %1 - 無法開啟輸出檔案: %1 + 無法開啟輸出檔案: %1 - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. 您的一些插件名稱無效!這些插件無法被遊戲載入。請查看 mo_interface.log 來確認那些受影響的插件並重新命名它們。 - - This plugin can't be disabled (enforced by the game) + + This plugin can't be disabled (enforced by the game) 這個插件不能被禁用 (由遊戲執行) - Origin: %1 - 隸屬於: %1 + 隸屬於: %1 - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - + + + + + PreviewDialog + + + Preview + + + + + Close + 關閉 @@ -3420,111 +3920,106 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + Close - + 關閉 Fix - + No guided fix - + Profile - + invalid profile name %1 - + - + failed to create %1 無法建立 %1 - - failed to open temporary file - - - - - failed to open "%1" for writing - - - - + failed to write mod list: %1 - + - + failed to update tweaked ini file, wrong settings may be used: %1 - + - + failed to create tweaked ini: %1 - + + + + + "%1" is missing or inaccessible + - - - - - + + + + + invalid index %1 無效的索引 %1 - - Overwrite directory couldn't be parsed - + + Overwrite directory couldn't be parsed + - + invalid priority %1 無效的優先級 %1 - + failed to parse ini file (%1) 無法解析 Ini 檔案 (%1): %2 - + failed to parse ini file (%1): %2 無法解析 Ini 檔案 (%1): %2 - - - failed to modify "%1" - 未能找到 "%1" + + + failed to modify "%1" + 未能找到 "%1" - + Delete savegames? - + - - Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + @@ -3546,7 +4041,7 @@ p, li { white-space: pre-wrap; } - If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. 如果選中,那麼新配置檔案將會使用默認的遊戲設定來取代全局設定。全局設定是您不使用 MO,直接運行遊戲時所配置的設定。 @@ -3569,31 +4064,31 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">這是配置檔案的列表,每個配置檔案都包含了它們自己的已激活 Mod 的列表和安裝順序 (從共享區域)、一個已激活的 esp 或 esm 的配置、一個遊戲 Ini 檔案的拷貝和一個可選的存檔過濾器。</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由于技術上的原因,目前不可能有分開儲存的插件加載順序。這意味著您不能同时在兩個配置檔案裡使用兩種不同的插件配置方案。</p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">這是配置檔案的列表,每個配置檔案都包含了它們自己的已激活 Mod 的列表和安裝順序 (從共享區域)、一個已激活的 esp 或 esm 的配置、一個遊戲 Ini 檔案的拷貝和一個可選的存檔過濾器。</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由于技術上的原因,目前不可能有分開儲存的插件加載順序。這意味著您不能同时在兩個配置檔案裡使用兩種不同的插件配置方案。</p></body></html> If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + @@ -3602,22 +4097,22 @@ p, li { white-space: pre-wrap; } - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">湮滅、輻射3和輻射新維加斯包含了一個 Bug: 遊戲阻止了用來運行遊戲的 Texture 和 Mesh 被替換 (所有改動到遊戲中已存在的 Meshes 和 Textures 的 Mod)。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod Organizer 使用了一種叫作“BSA 重定向”的解決方案可靠地修復了這個問題,並且无需進一步的操作,簡單地激活然後忘記這件事吧。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">伴隨著天際的到來,這個 Bug 似乎在一定程度上被修復了。但是一個 Mod 是否能夠被正確地激活仍取決于檔案的日期。因此,激活它還是有意義的。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">湮滅、輻射3和輻射新維加斯包含了一個 Bug: 遊戲阻止了用來運行遊戲的 Texture 和 Mesh 被替換 (所有改動到遊戲中已存在的 Meshes 和 Textures 的 Mod)。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Mod Organizer 使用了一種叫作“BSA 重定向”的解決方案可靠地修復了這個問題,並且无需進一步的操作,簡單地激活然後忘記這件事吧。</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">伴隨著天際的到來,這個 Bug 似乎在一定程度上被修復了。但是一個 Mod 是否能夠被正確地激活仍取決于檔案的日期。因此,激活它還是有意義的。</span></p></body></html> @@ -3670,12 +4165,12 @@ p, li { white-space: pre-wrap; } Transfer save games to the selected profile. - + Transfer Saves - + @@ -3684,7 +4179,7 @@ p, li { white-space: pre-wrap; } - Archive invalidation isn't required for this game. + Archive invalidation isn't required for this game. 這個遊戲並不需要檔案無效化。 @@ -3711,12 +4206,12 @@ p, li { white-space: pre-wrap; } Invalid name - + Invalid profile name - + @@ -3726,27 +4221,27 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + - This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + Rename Profile - + New Name - + @@ -3781,48 +4276,48 @@ p, li { white-space: pre-wrap; } - invalid field name "%1" - + invalid field name "%1" + - invalid type for "%1" (should be integer) - + invalid type for "%1" (should be integer) + - invalid type for "%1" (should be string) - + invalid type for "%1" (should be string) + - invalid type for "%1" (should be float) - + invalid type for "%1" (should be float) + no fields set up yet! - + - field not set "%1" - + field not set "%1" + - invalid character in field "%1" - + invalid character in field "%1" + empty field name - + invalid game type %1 - + @@ -3906,99 +4401,107 @@ p, li { white-space: pre-wrap; } 無法設定代理DLL加載 - + Permissions required 需要權限 - - The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + - - + + Woops 糟糕 - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 Mod Organizer 崩潰了!遺憾的是,我無法生成診斷檔案: %1 - - + + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Mod Organizer 的一個實例正在運行 - - No game identified in "%1". The directory is required to contain the game binary and its launcher. - "%1" 中未檢測到遊戲。請確保該路徑中包含遊戲執行程式以及對應的 Launcher 檔案。 + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + "%1" 中未檢測到遊戲。請確保該路徑中包含遊戲執行程式以及對應的 Launcher 檔案。 - - + + Please select the game to manage 請選擇想要管理的遊戲 - - Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + - - Please use "Help" from the toolbar to get usage instructions to all elements + + failed to start application: %1 + + + + + Please use "Help" from the toolbar to get usage instructions to all elements 請使用工具列上的“幫助”來獲得所有元素的使用說明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 無法解析配置檔案 %1: %2 - - - failed to find "%1" - 未能找到 "%1" + + failed to find "%1" + 未能找到 "%1" - + failed to access %1 無法訪問 %1 - + failed to set file time %1 無法設定檔案時間 %1 - + failed to create %1 無法建立 %1 - - "%1" is missing - "%1" 缺失 + + "%1" is missing or inaccessible + + + + "%1" is missing + "%1" 缺失 @@ -4024,54 +4527,59 @@ p, li { white-space: pre-wrap; } 無法開啟 %1 - + Script Extender 腳本拓展 - + Proxy DLL 代理DLL - - failed to spawn "%1" - 無法生成 "%1" + + failed to spawn "%1" + 無法生成 "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if -"%1" +"%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + + + + + failed to spawn "%1": %2 + 無法生成 "%1": %2 - - failed to spawn "%1": %2 - 無法生成 "%1": %2 + + "%1" doesn't exist + "%1" 不存在 - - "%1" doesn't exist - "%1" 不存在 + + failed to inject dll into "%1": %2 + 無法注入 dll 到 "%1": %2 - - failed to inject dll into "%1": %2 - 無法注入 dll 到 "%1": %2 + + failed to run "%1" + 無法運行 "%1" - - failed to run "%1" - 無法運行 "%1" + + failed to open temporary file + @@ -4079,22 +4587,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Mod Exists - + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Keep Backup - + Merge - + @@ -4117,22 +4625,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - + Location - + @@ -4158,12 +4666,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Copy To Clipboard - + Save As... - + @@ -4173,7 +4681,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save CSV - + @@ -4182,8 +4690,8 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - failed to open "%1" for writing - + failed to open "%1" for writing + @@ -4208,79 +4716,79 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe SelfUpdater - archive.dll not loaded: "%1" - + archive.dll not loaded: "%1" + - - - - + + + + Update 更新 - + An update is available (newest version: %1), do you want to install it? 有可用的更新 (最新版本: %1),您想要安裝它嗎? - + Download in progress 正在下載 - + Download failed: %1 下載失敗: %1 - + Failed to install update: %1 無法安裝更新: %1 - - failed to open archive "%1": %2 - 無法開啟壓縮包 "%1": %2 + + failed to open archive "%1": %2 + 無法開啟壓縮包 "%1": %2 - + failed to move outdated files: %1. Please update manually. - + - + Update installed, Mod Organizer will now be restarted. 更新完成,Mod Organizer 現在將重新啟動。 - + Error 錯誤 - + Failed to parse response. Please report this as a bug and include the file mo_interface.log. 解析回應時發生錯誤。請回報此 Bug 並附上 mo_interface.log! - + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) 沒有可用于此版本的更新檔案,需要下載完整的安裝包 (%1 KB) - + no file for update found. Please update manually. - + - + Failed to retrieve update information: %1 無法檢索更新信息: %1 - + No download server available. Please try again later. 沒有可用的下載伺服器,請稍後再嘗試下載。 @@ -4288,18 +4796,18 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Settings - - - attempt to store setting for unknown plugin "%1" - + + + attempt to store setting for unknown plugin "%1" + - + Confirm 確認 - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? 修改 Mod 目錄將會影響您的配置!新目錄中不存在 (或者名稱不同) 的 Mod 將在所有配置中被禁止掉。此操作無法撤銷,所以執行此操作前建議先備份下自己的配置。立即執行? @@ -4328,57 +4836,57 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">介面語言,此處僅顯示您已安裝的翻譯語言。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">介面語言,此處僅顯示您已安裝的翻譯語言。</span></p></body></html> Style - + graphical style - + graphical style of the MO user interface - + Log Level - + - Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log" + - Decides the amount of data printed to "ModOrganizer.log". -"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + Debug - + Info - + @@ -4408,7 +4916,7 @@ p, li { white-space: pre-wrap; } - Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). 儲存 Mod 的目錄。請注意: 修改此目錄將會破壞所有配置檔案與新目錄中已不存在的 Mod (相同名稱) 的關聯。 @@ -4421,231 +4929,261 @@ p, li { white-space: pre-wrap; } Cache Directory 緩存目錄 + + + User interface + + - Reset stored information from dialogs. - + If checked, the download interface will be more compact. + - This will make all dialogs show up again where you checked the "Remember selection"-box. - + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + - + + Download Meta Information + + + + + Reset stored information from dialogs. + + + + + This will make all dialogs show up again where you checked the "Remember selection"-box. + + + + Reset Dialogs 重置對話方塊 - - + + Modify the categories available to arrange your mods. 修改可用的類別來整理您的 Mod。 - + Configure Mod Categories 配置 Mod 類別 - - + + Nexus N網 - + Allows automatic log-in when the Nexus-Page for the game is clicked. 當N網頁面開啟時將自動登入。 - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">允許當N網頁面開啟時自動登入。請注意: 密碼是存儲在 modorganizer.ini 裡的,混淆得不是很強烈。如果您擔心有人可能會竊取您的密碼,那麼請不要存儲在這裡。</span></p></body></html> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">允許當N網頁面開啟時自動登入。請注意: 密碼是存儲在 modorganizer.ini 裡的,混淆得不是很強烈。如果您擔心有人可能會竊取您的密碼,那麼請不要存儲在這裡。</span></p></body></html> - + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + - + Automatically Log-In to Nexus 自動登入 - + Username 帳號 - + Password 密碼 - + Disable automatic internet features - + - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + - + Offline Mode - + - + Use a proxy for network connections. - + - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + - + Use HTTP Proxy (Uses System Settings) - + - - Known Servers (Dynamically updated every download) - + + Associate with "Download with manager" links + - + + Known Servers (updated on download) + + + + Preferred Servers (Drag & Drop) - + - + Plugins - + - + Author: 作者 - + Version: 版本 - + Description: 描述 - + Key - + - + Value - + - + Blacklisted Plugins (use <del> to remove): - + - + Workarounds 解決方案 - + Steam App ID Steam App ID - + The Steam AppID for your game 您遊戲的 Steam AppID - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必須的,它被用來直接啟動一些遊戲。對於天際,如果沒有設定或設定錯誤,&quot;Mod Organizer&quot; 的加載機制可能會無法正常工作。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此預設是應用程式 ID 的“常規”版本,因此在大多數情況下,您應該要重新設定一下。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您認為您有不同的版本 (年度版或其它版本),那麼請參照下列的步驟來獲取 ID: </span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">1. 進入 Steam 裡的遊戲庫</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">2. 右鍵點擊您想要獲取 ID 的遊戲,選擇</span><span style=" font-size:9pt; font-weight:600;">建立桌面捷徑</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">3. 右鍵點擊您剛才在桌面上建立的捷徑,選擇</span><span style=" font-size:9pt; font-weight:600;">內容</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">4. 在連結區域您應該會看到一些像這樣的: </span><span style=" font-size:9pt; font-style:italic;">steam://rungameid/22380</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">其中 22380 就是您要找的 ID。</span></p></body></html> - - - +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必須的,它被用來直接啟動一些遊戲。對於天際,如果沒有設定或設定錯誤,&quot;Mod Organizer&quot; 的加載機制可能會無法正常工作。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此預設是應用程式 ID 的“常規”版本,因此在大多數情況下,您應該要重新設定一下。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您認為您有不同的版本 (年度版或其它版本),那麼請參照下列的步驟來獲取 ID: </span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">1. 進入 Steam 裡的遊戲庫</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">2. 右鍵點擊您想要獲取 ID 的遊戲,選擇</span><span style=" font-size:9pt; font-weight:600;">建立桌面捷徑</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">3. 右鍵點擊您剛才在桌面上建立的捷徑,選擇</span><span style=" font-size:9pt; font-weight:600;">內容</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">4. 在連結區域您應該會看到一些像這樣的: </span><span style=" font-size:9pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">其中 22380 就是您要找的 ID。</span></p></body></html> + + + Load Mechanism 加載機制 - + Select loading mechanism. See help for details. 選擇加載機制,使用幫助查看更多細節。 - + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. -If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + - + NMM Version NMM 版本 - + The Version of Nexus Mod Manager to impersonate. 想要模擬的 NMM 版本號。 - + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. -tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer 使用了一個N網所提供的 API 來進行類似於檢查更新和下載檔案這樣的操作。遺憾的是這個 API 並沒有給第三方工具 (比如 MO) 正式的授權,所以我們需要模擬 NMM 來進行這些操作。 在此之前,N網使用了客戶端辨識系統鎖定了舊版本的 NMM,強制用戶更新版本。這意味著 MO 也要模擬新版本的 NMM,即便 MO 自己並不需要更新。因此您需要在這裡配置版本號來進行辨識。 請注意: MO 辨識自己為 MO 到網路伺服器,這並不是欺騙。它僅僅是為用戶代理添加了一個“兼容”的 NMM 版本。 @@ -4653,79 +5191,97 @@ tl;dr-version: If Nexus-features don't work, insert the current version number o 變更版本號: 如果N網功能不正常了,那麼請在這裡輸入 NMM 的當前版本號並重試一下。 - + Enforces that inactive ESPs and ESMs are never loaded. 強制執行,未激活的 ESP 和 ESM 將不會被加載。 - - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. -I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. 看來,遊戲偶爾會加載一些沒有被激活成插件的 ESP 或 ESM 檔案。 我還尚不知道它在什麼情況下會這樣,但是有用戶報告說它在某些情況下是很不必要的。如果這個選項被選中,那麼在列表中沒有被勾選的 ESP 和 ESM 將不會在遊戲中出現,並且也不會被載入。 - + Hide inactive ESPs/ESMs 隱藏未激活的 ESP 或 ESM - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + - + Force-enable game files - + - - + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. For the other games this is not a sufficient replacement for AI! 對於天際,這個可以用來取代檔案無效化,它將會使档案无效化對所有配置都變得多余。 但是對於其它遊戲,這並不是一個很好的替代品! - + Back-date BSAs 重置 BSA 檔案修改日期 - + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + - + Select download directory 選擇下載目錄 - + Select mod directory 選擇 Mod 目錄 - + Select cache directory 選擇緩存目錄 - + Confirm? 確認? - - This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? 此操作將導致之前勾選的“記住我的選項”詢問視窗再次出現,確定要重置對話方塊? @@ -4772,10 +5328,14 @@ For the other games this is not a sufficient replacement for AI! - failed to connect to running instance: %1 無法連接到正在運行的實例: %1 + + + failed to communicate with running instance: %1 + + failed to receive data from secondary instance: %1 @@ -4801,7 +5361,7 @@ For the other games this is not a sufficient replacement for AI! - <don't sync> + <don't sync> <不要同步> @@ -4820,17 +5380,17 @@ For the other games this is not a sufficient replacement for AI! Transfer Savegames - + Global Characters - + This is a list of characters in the global location. - + @@ -4842,7 +5402,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -4855,27 +5415,27 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Copy -> - + <- Move - + <- Copy - + @@ -4885,7 +5445,7 @@ On Windows XP: Profile Characters - + @@ -4894,8 +5454,8 @@ On Windows XP: - Overwrite the file "%1" - + Overwrite the file "%1" + @@ -4908,18 +5468,18 @@ On Windows XP: - Copy all save games of character "%1" to the profile? - + Copy all save games of character "%1" to the profile? + - Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + - Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 4f363900..9f0924a2 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -469,20 +469,21 @@ void PluginList::saveTo(const QString &pluginFileName writeLockedOrder(lockedOrderFileName); if (hideUnchecked) { - QFile deleterFile(deleterFileName); - deleterFile.open(QIODevice::WriteOnly); - deleterFile.resize(0); - deleterFile.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + SafeWriteFile deleterFile(deleterFileName); + deleterFile->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); for (size_t i = 0; i < m_ESPs.size(); ++i) { int priority = m_ESPsByPriority[i]; if (!m_ESPs[priority].m_Enabled) { - deleterFile.write(m_ESPs[priority].m_Name.toUtf8()); - deleterFile.write("\r\n"); + deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); + deleterFile->write("\r\n"); } } - deleterFile.close(); - qDebug("%s saved", QDir::toNativeSeparators(deleterFileName).toUtf8().constData()); + if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); + } + } else { + shellDelete(QStringList() << deleterFileName); } m_SaveTimer.stop(); @@ -744,7 +745,8 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const { int index = modelIndex.row(); - if (role == Qt::DisplayRole) { + if ((role == Qt::DisplayRole) + || (role == Qt::EditRole)) { switch (modelIndex.column()) { case COL_NAME: { return m_ESPs[index].m_Name; @@ -860,6 +862,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) { + bool result = false; if (role == Qt::CheckStateRole) { m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; emit dataChanged(modIndex, modIndex); @@ -867,10 +870,19 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int refreshLoadOrder(); startSaveTime(); - return true; - } else { - return false; + result = true; + } else if (role == Qt::EditRole) { + if (modIndex.column() == COL_PRIORITY) { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + setPluginPriority(modIndex.row(), newPriority); + result = true; + } + refreshLoadOrder(); + } } + return result; } @@ -902,6 +914,9 @@ Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const if (!m_ESPs[index].m_ForceEnabled) { result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } + if (modelIndex.column() == COL_PRIORITY) { + result |= Qt::ItemIsEditable; + } } else { result |= Qt::ItemIsDropEnabled; } diff --git a/src/savegameinfowidgetgamebryo.cpp b/src/savegameinfowidgetgamebryo.cpp index d4f80e87..deaa3e01 100644 --- a/src/savegameinfowidgetgamebryo.cpp +++ b/src/savegameinfowidgetgamebryo.cpp @@ -17,57 +17,57 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "savegameinfowidgetgamebryo.h" -#include -#include - - -SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent) - : SaveGameInfoWidget(parent), m_PluginList(pluginList) -{ - QVBoxLayout *gameLayout = new QVBoxLayout(); - gameLayout->setMargin(0); - gameLayout->setSpacing(2); - getGameFrame()->setLayout(gameLayout); - setSave(saveGame); -} - - -void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame) -{ - SaveGameInfoWidget::setSave(saveGame); - const SaveGameGamebryo *gamebryoSave = qobject_cast(saveGame); - QLayout *layout = getGameFrame()->layout(); - QLabel *header = new QLabel(tr("Missing ESPs")); - QFont headerFont = header->font(); - QFont contentFont = headerFont; - headerFont.setItalic(true); - contentFont.setBold(true); - contentFont.setPointSize(7); - header->setFont(headerFont); - layout->addWidget(header); - int count = 0; - for (int i = 0; i < gamebryoSave->numPlugins(); ++i) { - const QString &pluginName = gamebryoSave->plugin(i); - if (m_PluginList->isEnabled(pluginName)) { - continue; - } else { - ++count; - } - - if (count > 10) { - break; - } - - QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i)); - pluginLabel->setIndent(10); - pluginLabel->setFont(contentFont); - layout->addWidget(pluginLabel); - } - if (count > 10) { - QLabel *dotDotLabel = new QLabel("..."); - dotDotLabel->setIndent(10); - dotDotLabel->setFont(contentFont); - layout->addWidget(dotDotLabel); - } -} +#include "savegameinfowidgetgamebryo.h" +#include +#include + + +SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent) + : SaveGameInfoWidget(parent), m_PluginList(pluginList) +{ + QVBoxLayout *gameLayout = new QVBoxLayout(); + gameLayout->setMargin(0); + gameLayout->setSpacing(2); + getGameFrame()->setLayout(gameLayout); + setSave(saveGame); +} + + +void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame) +{ + SaveGameInfoWidget::setSave(saveGame); + const SaveGameGamebryo *gamebryoSave = qobject_cast(saveGame); + QLayout *layout = getGameFrame()->layout(); + QLabel *header = new QLabel(tr("Missing ESPs")); + QFont headerFont = header->font(); + QFont contentFont = headerFont; + headerFont.setItalic(true); + contentFont.setBold(true); + contentFont.setPointSize(7); + header->setFont(headerFont); + layout->addWidget(header); + int count = 0; + for (int i = 0; i < gamebryoSave->numPlugins(); ++i) { + const QString &pluginName = gamebryoSave->plugin(i); + if (m_PluginList->isEnabled(pluginName)) { + continue; + } else { + ++count; + } + + if (count > 10) { + break; + } + + QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i)); + pluginLabel->setIndent(10); + pluginLabel->setFont(contentFont); + layout->addWidget(pluginLabel); + } + if (count > 10) { + QLabel *dotDotLabel = new QLabel("..."); + dotDotLabel->setIndent(10); + dotDotLabel->setFont(contentFont); + layout->addWidget(dotDotLabel); + } +} diff --git a/src/settings.cpp b/src/settings.cpp index c2855a85..3c78ed62 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -669,22 +669,33 @@ void Settings::query(QWidget *parent) m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); - if (QDir(downloadDirEdit->text()).exists()) { - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); - } - if (!QDir(cacheDirEdit->text()).exists()) { - QDir().mkpath(cacheDirEdit->text()); - } - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); - if (QDir(modDirEdit->text()).exists()) { + + + { // advanced settings if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " "Mods not present (or named differently) in the new location will be disabled in all profiles. " "There is no way to undo this unless you backed up your profiles manually. Proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + modDirEdit->setText(getModDirectory()); + } + + if (!QDir(downloadDirEdit->text()).exists()) { + QDir().mkpath(downloadDirEdit->text()); } + if (!QDir(cacheDirEdit->text()).exists()) { + QDir().mkpath(cacheDirEdit->text()); + } + if (!QDir(modDirEdit->text()).exists()) { + QDir().mkpath(modDirEdit->text()); + } + + m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); + m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); + m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); } + + QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { diff --git a/src/spawn.cpp b/src/spawn.cpp index 6adafba0..d314a326 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -88,7 +88,7 @@ bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool sus commandLine, NULL, NULL, // no special process or thread attributes inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - suspended ? CREATE_SUSPENDED : 0, // create suspended so I have time to inject the DLL + CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL NULL, // same environment as parent currentDirectory, // current directory &si, &pi // startup and process information @@ -117,12 +117,18 @@ HANDLE startBinary(const QFileInfo &binary, HANDLE stdOut, HANDLE stdErr) { + HANDLE jobObject = ::CreateJobObject(NULL, NULL); + + if (jobObject == NULL) { + qWarning("failed to create job object: %lu", ::GetLastError()); + } + HANDLE processHandle, threadHandle; std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); try { - if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked, + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), true, stdOut, stdErr, processHandle, threadHandle)) { reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); return INVALID_HANDLE_VALUE; @@ -170,12 +176,22 @@ HANDLE startBinary(const QFileInfo &binary, #ifdef _DEBUG reportError("ready?"); #endif // DEBUG - if (::ResumeThread(threadHandle) == (DWORD)-1) { - reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } } - return processHandle; + + if (::AssignProcessToJobObject(jobObject, processHandle) == 0) { + qWarning("failed to assign to job object: %lu", ::GetLastError()); + ::CloseHandle(jobObject); + jobObject = processHandle; + } else { + ::CloseHandle(processHandle); + } + + if (::ResumeThread(threadHandle) == (DWORD)-1) { + reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + ::CloseHandle(threadHandle); + return jobObject; } /* diff --git a/src/version.rc b/src/version.rc index 397fddbe..a18fd312 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 1,2,9,0 -#define VER_FILEVERSION_STR "1,2,9,0\0" +#define VER_FILEVERSION 1,2,10,0 +#define VER_FILEVERSION_STR "1,2,10,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp new file mode 100644 index 00000000..eca37562 --- /dev/null +++ b/src/viewmarkingscrollbar.cpp @@ -0,0 +1,39 @@ +#include "viewmarkingscrollbar.h" +#include +#include +#include + +ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent, int role) + : QScrollBar(parent) + , m_Model(model) + , m_Role(role) +{ + // not implemented for horizontal sliders + Q_ASSERT(this->orientation() == Qt::Vertical); +} + +void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) +{ + QScrollBar::paintEvent(event); + + QStyleOptionSlider styleOption; + initStyleOption(&styleOption); + + QPainter painter(this); + + QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarSlider, this); + QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarGroove, this); + + painter.translate(innerRect.topLeft() + QPoint(0, 3)); + qreal scale = static_cast(innerRect.height() - 3) / static_cast(m_Model->rowCount()); + + for (int i = 0; i < m_Model->rowCount(); ++i) { + QVariant data = m_Model->data(m_Model->index(i, 0), m_Role); + if (data.isValid()) { + QColor col = data.value(); + painter.setPen(col); + painter.setBrush(col); + painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); + } + } +} diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h new file mode 100644 index 00000000..12a297d1 --- /dev/null +++ b/src/viewmarkingscrollbar.h @@ -0,0 +1,22 @@ +#ifndef VIEWMARKINGSCROLLBAR_H +#define VIEWMARKINGSCROLLBAR_H + +#include +#include + + +class ViewMarkingScrollBar : public QScrollBar +{ +public: + static const int DEFAULT_ROLE = Qt::UserRole + 42; +public: + ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent = 0, int role = DEFAULT_ROLE); +protected: + virtual void paintEvent(QPaintEvent *event); +private: + QAbstractItemModel *m_Model; + int m_Role; +}; + + +#endif // VIEWMARKINGSCROLLBAR_H -- cgit v1.3.1 From d412060d59b1597ae1d11d793662610e90863fc9 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 15 Jul 2014 17:31:44 +0200 Subject: - qt 5 compatibility fixes - detection for support of optimized find no longer depends on deprecated api --- README.txt | 30 ++++++++++++++++++++++-------- src/ModOrganizer.pro | 6 +++--- src/dlls.manifest.debug.qt5 | 7 ++++--- src/dlls.manifest.qt5 | 1 + src/mainwindow.cpp | 1 - src/shared/directoryentry.cpp | 17 +++++++++++------ 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/README.txt b/README.txt index d2c3e46b..b4f37787 100644 --- a/README.txt +++ b/README.txt @@ -10,7 +10,7 @@ Please note that if you only want to work on and build a plugin you can save you Overview: --------- -As of December 2013 MO consists of the following subprojects: +As of Juli 2014 MO consists of the following subprojects: - organizer: The main userinterface. heavy usage of various libraries - hookdll: core library of the virtual file system - uibase: interop between plugins and the main application as well as some reusable functionality @@ -24,13 +24,14 @@ As of December 2013 MO consists of the following subprojects: - NCC: extension to NMM to provide a binary with command line interface for fomod installation. This is c# code and does not build with the rest of the project. Requires the rest of NMM - bossdummy: dummy dll that looks like the boss.dll. This is used instead of the real boss dlls in NCC to save some disk space - pythonrunner: library for embedding python code. Requires boost_python, python 2.7 and pyqt4 +- loot_cli: this is a command line client of loot. This can be better integrated with MO than the official loot client. And various plugins: +- bsaExtractor: offers to extract bsas after a mod has been installed - checkFNIS: Activates each time an application is started from MO and runs fnis if necessary - diagnoseBasic: Various diagnostic checks on the game - inieditor: minimalistic file editor for ini files - installerBain: handles non-scripted bain installers -- installerBCF: no functionality. Supposed to eventually allow installations using bcfs (bain conversion file). python code - installerBundle: handles installation of archives wrapped in archives - installerFomod: handles installation of xml fomods - installerManual: handles installations of archives that aren't supported by any other plugin (or if the user chooses to do the installation manually) @@ -39,7 +40,13 @@ And various plugins: - NMMImport: importer from existing nmm installation - proxyPython: integrates pythonrunner as a plugin into MO - pyniEdit: more user-friendly ini editor. python code +- previewBase: used for file previews. this plugin covers image formats supported by the qt installation (usually at least jpg, gif, png) and text files +- previewDDS: used to preview dds textures. uses code from the nif file format library +There are a few more plugins that are either broken or samples +- installerBCF: this was intended to use .bcf (bain conversion file) files as installation instructions but currently it is completely function-less +- helloWorldCpp: sample for cpp plugins. This should compile even without fulfilling most dependencies below +- pageTESAlliance: integrates the tes alliance page into MO. This integration is not nearly as tight as that of nexus. Requirements: ------------- @@ -50,13 +57,20 @@ Visual C++ compiler 2010 (VC 10.0) or up Note: If you're having trouble installing the windows sdk, you may be affected by this bug: http://support.microsoft.com/kb/2717426 Qt Libraries 4.8.x (http://qt-project.org/downloads) -- i.e. "Qt libraries 4.8.5 for Windows (VS 2010, 235 MB)" -- tested: 4.8.5 -- Qt5 is not yet supported but WIP. You will see a few conditional qt5 pathes +- i.e. "Qt libraries 4.8.6 for Windows (VS 2010, 235 MB)" +- tested: 4.8.6 - Install according to instruction +Qt 5 Compatibility: +MO compiles and mostly runs correctly built with Qt 5.3 and VC++ 2013 but +- python plugins haven't been rewritten to use qt5 yet +- pyqt5 isn't distributed as binaries for python 2.7 so this needs to be set up and built first +- tutorial doesn't work because it seems to be impossible to create a transparent Qt Quick control... +- the previewdds plugin only compiles with the opengl variant of the qt 5 distribution +- Qt5 is a bi*** to distribute + boost libraries (http://www.boost.org/) -- tested: 1.49 +- tested: 1.55 - Build according to their instructions (using vc++): http://www.boost.org/doc/libs/1_54_0/more/getting_started/windows.html - A few of the boost libraries need to be built (the rest is header-only). The only compiled libs MO needs (at the time of writing) are boost_thread (for everything that links agains bsatk) and boost_python (for pythonrunner). You can disable the others to save yourself compile time (even on a modern system compiling boost can easily take an hour) @@ -105,8 +119,8 @@ Set up (using Qt Creator): 2. Open the "Projects" tab, open the "Details" for "Build Environment" 3a. Click "Add" to add a variable called "BOOSTPATH" with the path of your boost installation as the value (i.e. C:\code\boost_1_49_0) 3b. Click "Add" to add a variable called "ZLIBPATH" with the path of your zlib installation as the value (i.e. C:\code\zlib-1.2.7) -3c. Click "Add" to add a variable called "7ZIPPATH" with the path of your zlib installation as the value (i.e. C:\code\7zip) -3d. Click "Add" to add a variable called "PYTHONPATH" with the path of your zlib installation as the value (i.e. C:\code\python) +3c. Click "Add" to add a variable called "SEVENZIPPATH" with the path of your 7zip installation as the value (i.e. C:\code\7zip) +3d. Click "Add" to add a variable called "PYTHONPATH" with the path of your python installation as the value (i.e. C:\code\python) 4. Switch the build configuration at the very top of the same page from "debug" to "release" (or vice versa) and repeat step 3 5. Compile the configuration(s) you want to use (debug and/or release) (Build All). This should compile correctly. 6. diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 4624ffec..13ec35b5 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,12 +13,12 @@ SUBDIRS = bsatk \ nxmhandler \ BossDummy \ pythonRunner \ - esptk \ - loot_cli + esptk# \ + #loot_cli plugins.depends = pythonRunner hookdll.depends = shared -organizer.depends = shared uibase plugins loot_cli +organizer.depends = shared uibase plugins# loot_cli CONFIG(debug, debug|release) { DESTDIR = outputd diff --git a/src/dlls.manifest.debug.qt5 b/src/dlls.manifest.debug.qt5 index 369539d1..6cc0a83d 100644 --- a/src/dlls.manifest.debug.qt5 +++ b/src/dlls.manifest.debug.qt5 @@ -1,11 +1,12 @@ - - - + + + + diff --git a/src/dlls.manifest.qt5 b/src/dlls.manifest.qt5 index 0a8ba7b8..5a22c2a8 100644 --- a/src/dlls.manifest.qt5 +++ b/src/dlls.manifest.qt5 @@ -7,6 +7,7 @@ + diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9cbe9c40..81ef5880 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -978,7 +978,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { hideSaveGameInfo(); } - return false; } diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 5172346b..87431ea8 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -489,13 +489,18 @@ void DirectoryEntry::propagateOrigin(int origin) static bool SupportOptimizedFind() { - OSVERSIONINFO versionInfo; - versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&versionInfo); - // large fetch and basic info for FindFirstFileEx is supported on win server 2008 r2, win 7 and newer - return (versionInfo.dwMajorVersion > 6) || - ((versionInfo.dwMajorVersion == 6) && (versionInfo.dwMinorVersion >= 1)); + + OSVERSIONINFOEX versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + versionInfo.dwMajorVersion = 6; + versionInfo.dwMinorVersion = 1; + ULONGLONG mask = ::VerSetConditionMask( + ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL); + + bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask); + return res; } -- cgit v1.3.1 From 5718af351034c1936a91a3782651733dfecdc4e5 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 15 Jul 2014 20:37:10 +0200 Subject: - Disabled compilation of loot_cli as the current loot version can't be compiled or linked against with vs 2010 - bugfix: some incompatibilities with non-skyrim games - bugfix: incorrect handling of plurals in translateable strings --- src/ModOrganizer.pro | 9 +- src/mainwindow.cpp | 22 +- src/organizer.pro | 6 +- src/organizer_cs.ts | 826 +++---- src/organizer_de.ts | 824 +++---- src/organizer_en_US.ts | 5343 ++++++++++++++++++++++++++++++++++++++++++ src/organizer_es.ts | 822 +++---- src/organizer_fr.ts | 824 +++---- src/organizer_ru.ts | 824 +++---- src/organizer_tr.ts | 822 +++---- src/organizer_zh_CN.ts | 822 +++---- src/organizer_zh_TW.ts | 822 +++---- src/savegame.cpp | 14 +- src/shared/fallout3info.cpp | 5 + src/shared/fallout3info.h | 1 + src/shared/falloutnvinfo.cpp | 5 +- src/shared/falloutnvinfo.h | 1 + src/shared/gameinfo.h | 3 + src/shared/oblivioninfo.cpp | 6 + src/shared/oblivioninfo.h | 1 + src/shared/skyriminfo.cpp | 5 + src/shared/skyriminfo.h | 2 + 22 files changed, 8748 insertions(+), 3261 deletions(-) create mode 100644 src/organizer_en_US.ts diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 0d75e883..9907d086 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -12,13 +12,12 @@ SUBDIRS = bsatk \ proxydll \ nxmhandler \ BossDummy \ -# pythonRunner \ - esptk# \ -# loot_cli + pythonRunner \ + esptk -#plugins.depends = pythonRunner +plugins.depends = pythonRunner hookdll.depends = shared -organizer.depends = shared uibase plugins# loot_cli +organizer.depends = shared uibase plugins CONFIG(debug, debug|release) { DESTDIR = $$PWD/../outputd diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f684fd82..263ddb4e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3232,7 +3232,6 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); } else { modInfo->saveMeta(); -qDebug("%s - %d", qPrintable(modInfo->name()), modInfo->hasFlag(ModInfo::FLAG_FOREIGN)); ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); @@ -3935,23 +3934,18 @@ void MainWindow::deleteSavegame_clicked() QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes(); QString savesMsgLabel; - QRegExp saveSuffix(".ess$"); QStringList deleteFiles; foreach (const QModelIndex &idx, selectedIndexes) { QString name = idx.data().toString(); SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); - savesMsgLabel += "
  • " + name.replace(saveSuffix, "") + "
  • "; + savesMsgLabel += "
  • " + QFileInfo(name).completeBaseName() + "
  • "; deleteFiles << save->saveFiles(); } - bool multipleRows = (selectedIndexes.count() > 1); - - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %1save%2?
      %3

    Removed saves will be sent to the Recycle Bin.") - .arg((multipleRows) ? QString::number(selectedIndexes.count()) + " " : "") - .arg((multipleRows) ? "s" : "") + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %n save(s)?
      %1

    Removed saves will be sent to the Recycle Bin.", "", selectedIndexes.count()) .arg(savesMsgLabel), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { shellDelete(deleteFiles, true); // recycle bin delete. @@ -4060,14 +4054,12 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) if (!selection->hasSelection()) return; - bool multipleSelected = (selection->selectedIndexes().count() > 1); - QMenu menu; - if (!multipleSelected) + if (!(selection->selectedIndexes().count() > 1)) menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); - QString deleteMenuLabel = tr("Delete save%1").arg((multipleSelected) ? "s" : ""); + QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count()); menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); @@ -4257,13 +4249,13 @@ void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QStrin void MainWindow::installTranslator(const QString &name) { - if (m_CurrentLanguage == "en_US") { +/* if (m_CurrentLanguage == "en_US") { return; - } + }*/ QTranslator *translator = new QTranslator(this); QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage != "en-US") { + if (m_CurrentLanguage != "en_US") { qWarning("localization file %s not found", qPrintable(fileName)); } // we don't actually expect localization files for english } diff --git a/src/organizer.pro b/src/organizer.pro index de7a3a35..ab87f2b3 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -261,9 +261,9 @@ CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../bsatk/release LIBS += -L$$OUT_PWD/../uibase/release LIBS += -L$$OUT_PWD/../boss_modified/release - QMAKE_CXXFLAGS += /Zi /GL + QMAKE_CXXFLAGS += /Zi# /GL # QMAKE_CXXFLAGS -= -O2 - QMAKE_LFLAGS += /DEBUG /LTCG /OPT:REF /OPT:ICF + QMAKE_LFLAGS += /DEBUG# /LTCG /OPT:REF /OPT:ICF PRE_TARGETDEPS += $$OUT_PWD/../shared/release/mo_shared.lib \ $$OUT_PWD/../bsatk/release/bsatk.lib } @@ -285,7 +285,7 @@ TRANSLATIONS = organizer_de.ts \ organizer_zh_CN.ts \ organizer_cs.ts \ organizer_tr.ts \ - organizer_en.ts \ + organizer_en_US.ts \ organizer_ko.ts \ organizer_ru.ts diff --git a/src/organizer_cs.ts b/src/organizer_cs.ts index 73c225f0..83f3c767 100644 --- a/src/organizer_cs.ts +++ b/src/organizer_cs.ts @@ -1401,7 +1401,7 @@ p, li { white-space: pre-wrap; } - + Namefilter @@ -1528,8 +1528,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh Znovunačíst @@ -1577,12 +1577,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Pokud kliknete &quot;Fixni Mody...&quot; v kontext menu, MO se pokusí aktivovat všechny mody a esp soubory, které byli v pozici používány. Nic se však nevypne!</span></p></body></html> - + Downloads Stáhnuté - + This is a list of mods you downloaded from Nexus. Double click one to install it. Tohle je seznam modů, které jsi stánul z Nexusu. Dvojklik nainstaluje mod. @@ -1633,145 +1633,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar Panel nástrojú - + Install Mod Instaluj mod - + Install &Mod Instaluj &Mod - + Install a new mod from an archive Instaluj nový mod z archívu - + Ctrl+M Ctrl+M - + Profiles Profily - + &Profiles &Profily - + Configure Profiles Nastav profily - + Ctrl+P Ctrl+P - + Executables Spouštění - + &Executables &Spouštění - + Configure the executables that can be started through Mod Organizer Konfigurace spouštění, které lze použít pro načtení modů z MO - + Ctrl+E Ctrl+E - + Tools - + &Tools - + Ctrl+I Ctrl+I - + Settings Nastavení - + &Settings &Nastavení - + Configure settings and workarounds Konfigurace a nastavení programu a různých řešení - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Prohledat mody na nexusu - + Ctrl+N Ctrl+N - - + + Update - + Mod Organizer is up-to-date Verze Mod Organizer u je aktuální - - + + No Problems Žádné problémy - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1782,84 +1782,84 @@ Right now this has very limited functionality V současnosti má omezenou funkcionalitu - - + + Help Pomoc - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar - + Desktop - + Start Menu - + Problems Problémy - + There are potential problems with your setup Existují potenciální problémy s programem - + Everything seems to be in order Všechno se jeví v pořádku - + Help on UI Pomoc s programem - + Documentation Wiki Dokumentace wiki - + Report Issue Nahlásit chybu - + Tutorials @@ -1868,88 +1868,88 @@ V současnosti má omezenou funkcionalitu zlyhal zápis pořadí archivů, máte administrátorsky povoleno zapisovat na "%1"? - + failed to save load order: %1 zlyhalo uložení pořadí načtení: %1 - + Name Jméno - + Please enter a name for the new profile Prosím zadej jméno pro nový profil - + failed to create profile: %1 Zlyhalo vytvoření profilu: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress Probíhá stahování - + There are still downloads in progress, do you really want to quit? Pořád probíhá stahování, určitě chcete skončit (zruší stahování)? - + failed to read savegame: %1 nezdařilo se načíst pozici: %1 - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + failed to init plugin %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" Zlyhal start "%1" - + Waiting Čekání - + Please press OK once you're logged into steam. Stiskni OK až budeš přihlášen do Steamu. @@ -1958,32 +1958,32 @@ V současnosti má omezenou funkcionalitu "%1" nenalezeno - + Start Steam? Spustit Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam by měl běžet aby se podařilo spustit hru. Má se MO pokusit spustit steam teď? - + Also in: <br> Také v: <br> - + No conflict Žádné konflikty - + <Edit...> <Edit...> - + This bsa is enabled in the ini file so it may be required! Tenhle BSA soubor je aktivován v ini souboru, tak zřejmě je vyžadován! @@ -1992,222 +1992,231 @@ V současnosti má omezenou funkcionalitu Tento archív se stejně načte, protože existuje plugin se stejným jménem ale jeho soubory nebudou zodpovídat pořadí nainstalování! - + Activating Network Proxy - - + + Installation successful Instalace úspěšná - - + + Configure Mod Konfigurace Modu - - + + This mod contains ini tweaks. Do you want to configure them now? Tenhle mod obsahuje ini úpravy. Chcete je nastavovat teď? - - + + mod "%1" not found mod "%1" nenalezen - - + + Installation cancelled Instalace zrušena - - + + The mod was not installed completely. Tento mod se nenainstaloval úplne. - + Some plugins could not be loaded - + Too many esps and esms enabled - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Vyber Mod - + Mod Archive Archív Modu - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - - + + Download started Stahování začalo - + failed to update mod list: %1 nezdařilo se aktualizovat seznam modů: %1 - + failed to spawn notepad.exe: %1 zlyhalo otevření notepad.exe: %1 - + failed to open %1 nepodařilo se otevřít %1 - + failed to change origin name: %1 Nezdařilo se změnit původní jméno: %1 - + <Checked> <Aktivované> - + <Unchecked> <Neaktivované> - + <Update> <Aktualizace> - + <No category> <Bez kategorie> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 Nezdařilo se přejmenovat mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" Nezdařilo se přejmenovat "%1" na "%2" - + Multiple esps activated, please check that they don't conflict. - - - - + + + + Confirm Potvrdit - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 Nezdařilo se odstranit mod: %1 - - + + Failed Zlyhání - + Installation file no longer exists Instalační soubor již neexistuje - + Mods installed with old versions of MO can't be reinstalled in this way. Mody nainstalovány staršími verzemi MO nemůžou být přeinstalovány tímto spůsobem. - - + + You need to be logged in with Nexus to endorse + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + + - + Extract BSA Extrakce BSA @@ -2218,657 +2227,662 @@ V současnosti má omezenou funkcionalitu (BSA se po úspěšném rozpakování odstrání. Pokud nevíte, co je BSA, zvolte možnost Ne) - - + + failed to read %1: %2 - + This archive contains invalid hashes. Some files may be broken. Tento archiv má neplatné identifikační součty. Nekteré soubory mohou být poškozeny. - + Nexus ID for this Mod is unknown Nexus ID pro tento Mod není známo - + About - + About Qt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - + Executable "%1" not found - + Failed to refresh list of esps: %1 - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + A mod with this name already exists - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? Opravdu aktivovat všechny zobrazené mody? - + Really disable all visible mods? Opravdu deaktivovat všechny zobrazené mody? - + Choose what to export - + Everything - + All installed mods are included in the list - + Active Mods - + Only active (checked) mods from your current profile are included - + Visible - + All mods visible in the mod list are included - + export failed: %1 - + Install Mod... Instaluj Mod... - + Enable all visible Aktivuj všechny v seznamu - + Disable all visible Deaktivuj všechny v seznamu - + Check all for update Skontroluj všechny pro aktualizaci - + Export to csv... - + All Mods - + Sync to Mods... Synchronizuj s Mody... - + Restore Backup - + Remove Backup... - + Add/Remove Categories - + Replace Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... Přejmenuj Mod... - + Remove Mod... Odstranit Mod... - + Reinstall Mod Přeinstaluj Mod - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Visit on Nexus Navštiv na Nexusu - + Open in explorer Otevři v prohlížeči - + Information... Informace... - - + + Exception: Výnimky: - - + + Unknown exception Neznámá výnimka - + <All> <Všechny> - + <Multiple> - - Really delete "%1"? - - - - + Fix Mods... Oprav Mody... - - - Delete - + + + Delete %n save(s) + Delete save(s) + + + + + - - + + failed to remove %1 Nepodařilo se odstranit %1 - - + + failed to create %1 Nepodařilo se vytvořit %1 - + Can't change download directory while downloads are in progress! Není možné změnit cíl pro stahování když probíhá stahování! - + Download failed Stahování zlyhalo - + failed to write to file %1 Nezdařil se zápis do souboru %1 - + %1 written %1 zapsáno - + Select binary Vyber binární soubor - + Binary Soubor - + Enter Name Zadej jméno - + Please enter a name for the executable Prosím zadej jméno pro spouštění - + Not an executable Není spustitelný - + This is not a recognized executable. Tenhle soubor není rozpoznán jako spustitelný. - - + + Replace file? Nahradit soubor? - + There already is a hidden version of this file. Replace it? Už existuje skrytá verze tohto souboru. Nahradit? - - + + File operation failed Operace se souborem zlyhala - - + + Failed to remove "%1". Maybe you lack the required file permissions? Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? - + There already is a visible version of this file. Replace it? Už existuje viditelná verze tohto souboru. Nahradit? - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available Aktualizace k dispozici - + Open/Execute Otevřít/Spustit - + Add as Executable Přidat Spouštení - + Preview - + Un-Hide Odekrýt - + Hide Skrýt - + Write To File... Zápis do souboru... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + login successful - + login failed: %1. Trying to download anyway přihlášení zlyhalo: %1. Pokouším se beztak stahovat - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. přihlášení zlyhalo: %1. Na aktualizaci MO je potřebné přihlášení k Nexusu. - + Error Chyba - + failed to extract %1 (errorcode %2) zlyhala extrakce %1 (errorcode %2) - + Extract... Extrakce... - + Edit Categories... Editovat Kategorie... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2894,7 +2908,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod @@ -3208,227 +3222,227 @@ p, li { white-space: pre-wrap; } Zavřít - + &Delete &Smazat - + &Rename &Přejmenovat - + &Hide &Skrýt - + &Unhide &Odekrýt - + &Open &Otevřít - + &New Folder &Nová Složka - - + + Save changes? Uložit změny? - - + + Save changes to "%1"? - + File Exists Soubor existuje - + A file with that name exists, please enter a new one Soubor s rovnakým názvem existuje, prosím zadejte jiné jméno - + failed to move file zlyhalo přesunutí souboru - + failed to create directory "optional" zlyhalo vytvoření zložky "optional" - - + + Info requested, please wait Info vyžádáno, prosím počkejte - + Main Hlavní - + Update Update - + Optional Volitelné - + Old Staré - + Misc Jiné - + Unknown Neznámé - + Current Version: %1 Současná verze: %1 - + No update available Žádný update není k dispozici - + (description incomplete, please visit nexus) (popis chybí, prosím navštivte nexus pro kompletní zobrazení) - + <a href="%1">Visit on Nexus</a> <a href="%1">Navštivte na Nexusu</a> - + Failed to delete %1 Zlyhalo vymazání %1 - - + + Confirm Potvrdit - + Are sure you want to delete "%1"? Jsi si jistý, že chceš vymazat "%1"? - + Are sure you want to delete the selected files? Jsi si jistý, že chceš vymazat označené soubory? - - + + New Folder Nová zložka - + Failed to create "%1" Zlyhalo vytvoření "%1" - - + + Replace file? Nahradit soubor? - + There already is a hidden version of this file. Replace it? Už existuje skrytá verze tohto souboru. Nahradit? - - + + File operation failed Operace se souborem zlyhala - - + + Failed to remove "%1". Maybe you lack the required file permissions? Nepodařilo se odstranit "%1". Možná nejsou k dispozici požadována práva? - - + + failed to rename %1 to %2 Nezdařilo se přejmenovat %1 na %2 - + There already is a visible version of this file. Replace it? Už existuje viditelná verze tohto souboru. Nahradit? - + Un-Hide Odekrýt - + Hide Skrýt - + Name Jméno - + Please enter a name - + Error Chyba - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3436,7 +3450,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3444,7 +3458,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Tenhle kvázi mod obsahuje soubory, které byli vytvořeny nebo změněny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') @@ -3456,18 +3470,18 @@ p, li { white-space: pre-wrap; } zlyhal zápis %1/meta.ini: %2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 neobsahuje žádné esp/esm ani jiné použitelné struktury (textures, meshes, interface,...) - + Categories: <br> Kategorie: <br> @@ -3475,52 +3489,52 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) Tenhle záznam obsahuje soubory, které byli vytvořeny během spuštení, tzn. ve virtuálním Data stromě (ku příkladu Construction Kit je sem vytváří') - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + Non-MO - + invalid @@ -3529,113 +3543,113 @@ p, li { white-space: pre-wrap; } nainstalovaná verze: %1, nejnovjší verze: %2 - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> Kategorie: <br> - + Invalid name - + drag&drop failed: %1 - + Confirm Potvrdit - + Are you sure you want to remove "%1"? Určitě chcete odstranit "%1"? - + Flags - + Mod Name - + Version Verze - + Priority Priorita - + Category - + Nexus ID - + Installation - - + + unknown - + Name of your mods - + Version of the mod (if available) Verze modu (pokud je k dispozici) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Priorita aplikace modu. Čím větší, tím "důležitější" je mod a proto může přebít mody s nižší prioritou. - + Category of the mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Time this mod was installed @@ -3847,17 +3861,17 @@ p, li { white-space: pre-wrap; } - + <b>Origin</b>: %1 - + Author Autor - + Description Popis @@ -3871,7 +3885,7 @@ p, li { white-space: pre-wrap; } Některé vaše pluginy mají neplatné názvy! Tyhle pluginy nemůžou být načteny hrou. Prosím nahlédněte do souboru mo_interface.log pro kompletní seznam pluginů a přejmenujte je. - + This plugin can't be disabled (enforced by the game) Tenhle plugin nemůže být deaktivován (vyžaduje to hra) @@ -3880,17 +3894,17 @@ p, li { white-space: pre-wrap; } Původní mod: %1 - + Missing Masters - + Enabled Masters - + failed to restore load order for %1 @@ -4456,18 +4470,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements Prosím použijte "Pomoc" z panelu nástrojú pro instrukce ke všem elementům - - + + <Manage...> <Manage...> - + failed to parse profile %1: %2 Nezdařilo se rozebrat profil %1: %2 @@ -4512,14 +4526,14 @@ p, li { white-space: pre-wrap; } Chyba - - - + + + wrong file format špatný formát souboru - + failed to open %1 nepodařilo se otevřít %1 @@ -4534,17 +4548,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" nepodařilo se vytvořit "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4554,22 +4568,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + failed to spawn "%1": %2 nepodařilo se vytvořit "%1": %2 - + "%1" doesn't exist "%1" neexistuje - + failed to inject dll into "%1": %2 nepodařilo se vsunout dll do "%1": %2 - + failed to run "%1" nepodařilo se spustit "%1" @@ -4799,12 +4813,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + Confirm Potvrdit - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Zmena adresáře modu změní všechny tvoje profily! Nenalezené mody (nebo přejmenované) v nové lokaci budou deaktivovány ve všech profilech. Není možnosť návratu pokud si nezazálohujete profily ručně. Pokračovat? @@ -5451,26 +5465,26 @@ On Windows XP: - - - - + + + + Confirm Potvrdit - - + + Copy all save games of character "%1" to the profile? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. diff --git a/src/organizer_de.ts b/src/organizer_de.ts index d77f59b2..b67b3f47 100644 --- a/src/organizer_de.ts +++ b/src/organizer_de.ts @@ -1390,7 +1390,7 @@ p, li { white-space: pre-wrap; } - + Namefilter Namensfilter @@ -1576,8 +1576,8 @@ BSAs die du hier markierst werden hingegen anders geladen so dass die Installati - - + + Refresh Neu laden @@ -1625,12 +1625,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Wenn Sie im Kontextmenü auf &quot;Mods reparieren...&quot; klicken, wird Mod Organiser versuchen die entsprechenden Mods und ESPs zu aktivieren. Es werden dabei kein Eintrag deaktiviert!</span></p></body></html> - + Downloads Downloads - + This is a list of mods you downloaded from Nexus. Double click one to install it. Dies ist eine Liste der Mods die von Nexus heruntergeladen wurden. Doppelklicken Sie um zu installieren. @@ -1639,145 +1639,145 @@ p, li { white-space: pre-wrap; } Kompakt - + Show Hidden Verborgene anzeigen - + Tool Bar Werkzeugleiste - + Install Mod Mod installieren - + Install &Mod &Mod installieren - + Install a new mod from an archive Installiert eine Mod aus einem Archiv - + Ctrl+M Ctrl+M - + Profiles Profile - + &Profiles &Profile - + Configure Profiles Profile konfigurieren - + Ctrl+P Ctrl+P - + Executables Programme - + &Executables Programm&e - + Configure the executables that can be started through Mod Organizer Konfigurieren der Programme die von Mod Organiser gestartet werden können - + Ctrl+E Ctrl+E - + Tools Werkzeuge - + &Tools &Werkzeuge - + Ctrl+I Ctrl+I - + Settings Einstellungen - + &Settings Ein&stellungen - + Configure settings and workarounds Einstellungen und Workarounds verwalten - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Durchsuche die passende Seite des Nexus-Netzwerks nach weiteren Mods - + Ctrl+N Ctrl+N - - + + Update Aktualisierung - + Mod Organizer is up-to-date Mod Organizer ist auf dem neuesten Stand - - + + No Problems Keine Probleme - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1788,211 +1788,211 @@ Right now this has very limited functionality Diese Funktion ist noch sehr eingeschränkt - - + + Help Hilfe - + Ctrl+H Ctrl+H - + Endorse MO Endorsement für MO abgeben - - + + Endorse Mod Organizer Endorsement für Mod Organizer abgeben - + Copy Log to Clipboard - + Ctrl+C - + Toolbar Werkzeugleiste - + Desktop Desktop - + Start Menu Startmenü - + Problems Probleme - + There are potential problems with your setup Es bestehen möglicherweise Probleme mit Ihrer Konfiguration - + Everything seems to be in order Alles in bester Ordnung - + Help on UI Hilfe zur Oberfläche - + Documentation Wiki Wiki Dokumentation - + Report Issue Fehler melden - + Tutorials Tutorials - + About Über - + About Qt - + failed to save load order: %1 Reihenfolge konnt nicht gespeichert werden: %1 - + Name Name - + Please enter a name for the new profile Bitte geben Sie einen Namen für das neue Profil an - + failed to create profile: %1 Erstellen des Profils fehlgeschlagen: %1 - + Show tutorial? Tutorial anzeigen? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. Sie starten Mod Organizer zum ersten mal. Wollen Sie ein Tutorial über die grundlegenden Funktionen sehen? Wenn Sie "nein" wählen können Sie das Tutorial aus dem "Hilfe"-Menü starten. - + Downloads in progress Download in Bearbeitung - + There are still downloads in progress, do you really want to quit? Es gibt noch unfertige Downloads, wollen Sie wirklich das Programm beenden? - + failed to read savegame: %1 Spielstand konnte nicht gelesen werden: %1 - + Plugin "%1" failed: %2 Plugin "%1" fehlgeschlagen: %2 - + Plugin "%1" failed Plugin "%1" fehlgeschlagen - + failed to init plugin %1: %2 Konnte das Plugin %1 nicht initialisieren: %2 - + Plugin error Plugin fehler - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) Es scheint als ob das Plugin "%1" beim letzten Programmstart nicht geladen werden konnte und einen Absturz von MO verursacht hat. Wollen sie das Plugin deaktivieren? (Bitte beachten: Wenn dies das erste mal ist dass sie diese Meldung für dieses Plugin sehen macht es vielleicht Sinn ihm eine zweite Chance zu geben. Das Plugin selber hat vielleicht eine Möglichkeit solche Fehler zu korrigieren) - + Failed to start "%1" Konnte "%1" nicht starten - + Waiting Warte - + Please press OK once you're logged into steam. Bitte drücken sie OK sobald sie bei Steam angemeldet sind. - + Start Steam? Steam starten? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam muss laufen um das Spiel korrekt zu starten. Soll MO versuchen Steam zu starten? - + Also in: <br> Auch in: <br> - + No conflict Keine Konflikte - + <Edit...> <Bearbeiten...> - + This bsa is enabled in the ini file so it may be required! Dieses Archiv ist in der ini Konfiguration gelistet, daher ist es möglicherweise erforderlich! @@ -2001,237 +2001,245 @@ Diese Funktion ist noch sehr eingeschränkt Dieses Archiv wird vom Spiel geladen werden da ein Plugin mit dem selben Namen aktiv ist aber die enthaltenen Dateien werden sich nicht an Ihre Installations-Reihenfolge halten! - + Activating Network Proxy Netzwerk Proxy aktivieren - - + + Installation successful Installation erfolgreich - - + + Configure Mod Mod konfigurieren - - + + This mod contains ini tweaks. Do you want to configure them now? Diese Mod enthält Anpassungen für die Ini datei. Wollen Sie diese nun konfigurieren? - - + + mod "%1" not found mod "%1" nicht gefunden - - + + Installation cancelled Installation abgebrochen - - + + The mod was not installed completely. Die mod wurde nicht erfolgreich installiert. - + Some plugins could not be loaded Einige Plugins konnten nicht geladen werden - + Too many esps and esms enabled Zu viele esps und esms aktiv - - + + Description missing Beschreibung fehlt - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Die folgenden Plugins konnten nicht geladen werden. Der Grund könnte eine fehlende Abhängigkeit sein (z.B. python) oder eine veraltete Version der Abhängigkeiten: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> Das Spiel unterstützt nicht mehr als 255 aktive Plugins (inklusive der offiziellen). Sie müssen einige unbenutzte Plugins deaktivieren oder mehrere Plugins kombinieren. Hier finden sie eine Anleitung: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Mod wählen - + Mod Archive Mod Archiv - + Start Tutorial? Tutorial starten? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Du bist dabei ein Tutorial zu starten. Aus technischen Gründen ist es nicht möglich das Tutorial abzubrechen. Fortsetzen? - - + + Download started Download gestartet - + failed to update mod list: %1 Aktualisieren der Modliste fehlgeschlagen: %1 - + failed to spawn notepad.exe: %1 notepad.exe konnte nicht aufgerufen werden: %1 - + failed to open %1 %1 konnte nicht geöffnet werden - + failed to change origin name: %1 konnte den Namen der Dateiquelle nicht ändern: %1 - + Executable "%1" not found - + Failed to refresh list of esps: %1 Konnte die Plugin Liste nicht aktualisieren: %s - + failed to move "%1" from mod "%2" to "%3": %4 - + <Checked> <Markierte> - + <Unchecked> <Nicht markierte> - + <Update> <Update> - + <No category> <Ohne Kategorie> - + <Conflicted> <Überschneidungen> - + <Not Endorsed> <Nicht Endorsed> - + failed to rename mod: %1 konnte die Mod nicht umbenennen: %1 - + Overwrite? Überschreiben? - + This will replace the existing mod "%1". Continue? Dies wird die existierende Mod "%1" ersetzen. Fortsetzen? - + failed to remove mod "%1" konnte die mod "%1" nicht löschen - - - + + + failed to rename "%1" to "%2" konnte "%1" nicht in "%2" umbenennen - + Multiple esps activated, please check that they don't conflict. Mehrere esps aktiv, bitte überprüfen sie, dass diese nicht miteinander kollidieren. - - - - + + + + Confirm Bestätigen - + Remove the following mods?<br><ul>%1</ul> Die folgenden Mods entfernen?<br><ul>%1</ul> - + failed to remove mod: %1 konnte die mod nicht entfernen: %1 - - + + Failed Fehlgeschlagen - + Installation file no longer exists Installationsdatei existiert nicht mehr - + Mods installed with old versions of MO can't be reinstalled in this way. Mods die mit alten Versionen von MO installiert wurden können nicht auf diese Weise neu-installiert werden. - - + + You need to be logged in with Nexus to endorse Sie müssen bei Nexus eingeloggt sein um Endorsements zu vergeben + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + - + Extract BSA BSA extrahieren @@ -2242,634 +2250,642 @@ Diese Funktion ist noch sehr eingeschränkt (Das BSA wird danach gelöscht. Wenn Sie nicht wissen was BSAs sind wählen Sie am besten "nein") - - + + failed to read %1: %2 konnte %1 nicht lesen: %2 - + This archive contains invalid hashes. Some files may be broken. Dieses Archiv enthält ungültige Prüfsummen. Einige Dateien sind evtl. defekt. - + Nexus ID for this Mod is unknown Nexus ID für diese Mod unbekannt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... Mod erstellen... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Es werden alle Dateien von "Overwrite" in eine neue, reguläre Mod verschoben. Bitte wählen sie dafür einen Namen: - + A mod with this name already exists Eine Mod mit diesem Name existiert bereits - + Continue? Fortfahren? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. Das Versionsschema bestimmt, welche version neuer als eine andere identifiziert wird. Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die installierte Version veraltet ist. - - + + Sorry Entschuldigung - + I don't know a versioning scheme where %1 is newer than %2. Es ist kein Versionsschema bekannt bei welchem %1 neuer ist als %2. - + Really enable all visible mods? Alle angezeigten Mods aktivieren? - + Really disable all visible mods? Alle angezeigten Mods deaktivieren? - + Choose what to export Bitte wählen Sie was sie exportieren wollen - + Everything Alles - + All installed mods are included in the list Alle installierten Modifikationen sind in dieser Liste enthalten - + Active Mods Aktive Mods - + Only active (checked) mods from your current profile are included Ausschließlich aktive Mods aus ihrem aktuellen Profil sind enthalten - + Visible Sichtbar - + All mods visible in the mod list are included Alle sichtbaren Mods in der Mod Liste sind enthalten - + export failed: %1 Exportieren fehlgeschlagen: %1 - + Install Mod... Mod installieren... - + Enable all visible Alle sichtbaren aktvieren - + Disable all visible Alle sichtbaren deaktvieren - + Check all for update Alle auf Aktualisierungen überprüfen - + Export to csv... Als CSV exportieren... - + All Mods - + Sync to Mods... Mods synchronisieren... - + Restore Backup Backup wiederherstellen - + Remove Backup... Backup entfernen... - + Add/Remove Categories Kategorien hinzufügen/entfernen - + Replace Categories Kategorien ersetzen - + Primary Category Primäre Kategorie - + Change versioning scheme Versionsschema ändern - + Un-ignore update Update nicht mehr ignorieren - + Ignore update Dieses Update ignorieren - + Rename Mod... Mod umbenennen... - + Remove Mod... Mod entfernen... - + Reinstall Mod Mod neu installieren - + Un-Endorse Endorsement zurückziehen - - + + Endorse Endorsement vergeben - + Won't endorse Niemals "Endorsement" vergeben - + Endorsement state unknown "Endorsement"-stand unbekannt - + Ignore missing data Fehlende Daten ignorieren - + Visit on Nexus Auf Nexus besuchen - + Open in explorer In Explorer öffnen - + Information... Informationen... - - + + Exception: Ausnahme: - - + + Unknown exception Unbekannte Ausnahme - + <All> <Alle> - + <Multiple> <Mehrere> - - Really delete "%1"? - - - - + Fix Mods... Mods reparieren... + + + Delete %n save(s) + Delete save(s) + + + + + - Delete - Löschen + Löschen - - + + failed to remove %1 %1 konnte nicht entfernt werden - - + + failed to create %1 %1 konnte nicht erstellt werden - + Can't change download directory while downloads are in progress! Das download verzeichnis kann nicht geändert werden solange Downloads laufen! - + Download failed Download fehlgeschlagen - + failed to write to file %1 Speichern in Datei "%1" fehlgeschlagen - + %1 written "%1" gespeichert - + Select binary Binary wählen - + Binary Ausführbare Datei - + Enter Name Namen eingeben - + Please enter a name for the executable Bitte geben Sie einen Namen für die Anwendungsdatei ein - + Not an executable Datei ist nicht ausführbar - + This is not a recognized executable. Dies Datei wird nicht als ausführbare Datei erkannt. - - + + Replace file? Datei ersetzen? - + There already is a hidden version of this file. Replace it? Es existiert bereits eine versteckte Variante von dieser Datei. Soll diese ersetzt werden? - - + + File operation failed Dateioperation fehlgeschlagen - - + + Failed to remove "%1". Maybe you lack the required file permissions? Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? - + There already is a visible version of this file. Replace it? Es existiert bereits eine sichtbare Variante dieser Datei. Soll diese ersetzt werden? - + file not found: %1 esp nicht gefunden: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available Aktualisierung verfügbar - + Open/Execute Öffnen/Ausführen - + Add as Executable Als Anwendung hinzufügen - + Preview - + Un-Hide Sichtbar machen - + Hide Verstecken - + Write To File... In Datei speichern... - + Do you want to endorse Mod Organizer on %1 now? Willst du Mod Organizer auf %1 ein Endorsement geben? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 Anfrage an Nexus fehlgeschlagen: %1 - - + + login successful Login erfolgreich - + login failed: %1. Trying to download anyway login fehlgeschlagen: %1. Der Download scheitert vermutlich - + login failed: %1 login fehlgeschlagen: %1 - + login failed: %1. You need to log-in with Nexus to update MO. login fehlgeschlagen: %1. Sie müssen bei Nexus eingeloggt sein um das update herunterzuladen. - + Error Fehler - + failed to extract %1 (errorcode %2) konnte "%1" nicht extrahieren (fehlercode %2) - + Extract... Extrahieren... - + Edit Categories... Kategorien ändern... - + Deselect filter - + Remove Entfernen - + Enable all Alle aktivieren - + Disable all Alle deaktivieren - + Unlock load order Sperrung der Ladereihenfolge aufheben - + Lock load order Ladereihenfolge sperren - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2899,7 +2915,7 @@ Diese Funktion wird das Versionsschema erraten, unter der Annahme dass die insta ModInfoBackup - + This is the backup of a mod Dies ist das Backup einer Mod @@ -3216,227 +3232,227 @@ p, li { white-space: pre-wrap; } Schliessen - + &Delete &Löschen - + &Rename &Umbenennen - + &Hide &Verstecken - + &Unhide &Wiederherstellen - + &Open &Öffnen - + &New Folder &Neuer Ordner - - + + Save changes? Änderungen speichern? - - + + Save changes to "%1"? Änderungen an "%1" speichern? - + File Exists Datei existiert bereits - + A file with that name exists, please enter a new one Eine Datei mit diesem Namen existiert bereits, bitte wählen Sie einen anderen Namen - + failed to move file Verschieben der Datei fehlgeschlagen - + failed to create directory "optional" Erstellen des Verzeichnis "optional" fehlgeschlagen - - + + Info requested, please wait Information wird abgerufen, bitte warten - + Main Primär - + Update Aktualisierung - + Optional Optional - + Old Alt - + Misc Sonstiges - + Unknown Unbekannt - + Current Version: %1 Aktuelle Version: %1 - + No update available Keine neue Version verfügbar - + (description incomplete, please visit nexus) (Beschreibung unvollständig. Bitte besuche die Nexus-Seite) - + <a href="%1">Visit on Nexus</a> <a href="%1">Auf Nexus öffnen</a> - + Failed to delete %1 "%1" konnte nicht gelöscht werden - - + + Confirm Bestätigen - + Are sure you want to delete "%1"? Sind Sie sicher, dass Sie "%1" löschen wollen? - + Are sure you want to delete the selected files? Sind Sie sicher, dass Sie die ausgewählten Dateien löschen wollen? - - + + New Folder Neuer Ordner - + Failed to create "%1" "%1" konnte nicht erstellt werden - - + + Replace file? Datei ersetzen? - + There already is a hidden version of this file. Replace it? Es existiert bereits eine versteckte Variante von dieser Datei. Soll diese ersetzt werden? - - + + File operation failed Dateioperation fehlgeschlagen - - + + Failed to remove "%1". Maybe you lack the required file permissions? Konnte "%1" nicht löschen. Fehlen Ihnen evtl. die nötigen Berechtigungen? - - + + failed to rename %1 to %2 "%1" konnte nicht zu "%2" umbenannt werden - + There already is a visible version of this file. Replace it? Es existiert bereits eine sichtbare Variante dieser Datei. Soll diese ersetzt werden? - + Un-Hide Sichtbar machen - + Hide Verstecken - + Name Name - + Please enter a name Bitte geben Sie einen Namen ein. - + Error Fehler - + Invalid name. Must be a valid file name Ungültiger Name. Dies muss ein gültiger Dateiname sein - + A tweak by that name exists Ein "Tweak" mit diesem Namen existiert bereits - + Create Tweak "Tweak" anlegen @@ -3444,7 +3460,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3452,7 +3468,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Diese Pseudo-Mod enthält Dateien des virtuellen Verzeichnisses die modifiziert wurden (z.B. durch den Construction Kit) @@ -3464,18 +3480,18 @@ p, li { white-space: pre-wrap; } konnte %1/meta.ini nicht schreiben: %2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 enthält keine esp / esm Dateien und keine Resourcen (Texturen, Netze, Oberfläche...) - + Categories: <br> Kategorien: <br> @@ -3483,164 +3499,164 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) Dieser Eintrag enthält Dateien die innerhalb des virtuellen Verzeichnisses erstellt wurden (z.B. durch den Construction Kit) - + Backup Backup - + No valid game data Keine gültigen Spieldaten - + Not endorsed yet Noch kein Endorsement vergeben - + Overwrites files Dateien werden überschrieben - + Overwritten files Überschriebene Dateien - + Overwrites & Overwritten Überschreibt & Wird überschrieben - + Redundant Redundant - + Non-MO - + invalid ungültig - + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 installierte Version: %1, neueste Version: %2 - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". Die neueste Version auf Nexus scheint älter zu sein als die die sie installiert haben. Dies könnte bedeuten dass die Version die sie installiert haben entfernt wurde (z.B. weigen eines Bugs) oder der Autor verwendet ein nicht-standardisiertes Versionierungssystem und ihre Version ist in Wirklichkeit doch veraltet. In beiden Fällen ist es empfehlenswert die Version von Nexus zu installieren. - + Categories: <br> Kategorien: <br> - + Invalid name Ungültiger Name - + drag&drop failed: %1 Drag&Drop fehlgeschlagen: %1 - + Confirm Bestätigen - + Are you sure you want to remove "%1"? Sind Sie sicher dass Sie "%1" löschen wollen? - + Flags Markierungen - + Mod Name Mod Name - + Version Version - + Priority Priorität - + Category Kategorie - + Nexus ID Nexus ID - + Installation Installation - - + + unknown unbekannt - + Name of your mods Name Ihrer Mods - + Version of the mod (if available) Version des Mod (wenn verfügbar) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Installations-Priorität Ihres Mods. Je höher, desto wichtiger ist dieser Mod und überschreibt damit Dateien von Mods mit niedrigerer Priorität. - + Category of the mod. Kategorie der Mod. - + Id of the mod as used on Nexus. Id der Mod von Nexus. - + Emblemes to highlight things that might require attention. Symbole um Dinge hervorzuheben die evtl. Aufmerksamkeit erfordern. - + Time this mod was installed Zeitpunkt an dem die Mod installiert wurde @@ -3857,22 +3873,22 @@ p, li { white-space: pre-wrap; } Einige Ihrer Plugins haben ungültige Namen! Diese Plugins können nicht vom Spiel geladen werden. Die Datei mo_interface.log enthält eine Liste der betroffenen Plugins. Bitte benennen Sie diese um. - + <b>Origin</b>: %1 - + Author Autor - + Description Beschreibung - + This plugin can't be disabled (enforced by the game) Dieses Plugin kann nicht deaktiviert werden (vom Spiel benötigt) @@ -3881,17 +3897,17 @@ p, li { white-space: pre-wrap; } Ursprung: %1 - + Missing Masters Fehlende Master - + Enabled Masters Aktivierte Master - + failed to restore load order for %1 Konnte die Ladereihenfolge für %1 nicht wiederherstellen @@ -4457,18 +4473,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements Bitte verwenden Sie die "Hilfe" Funktion um Hinweise zu Nutzung aller Elemete zu bekommen - - + + <Manage...> <Verwalten...> - + failed to parse profile %1: %2 Konnte Profil %1 nicht verarbeiten: %2 @@ -4510,14 +4526,14 @@ p, li { white-space: pre-wrap; } Fehler - - - + + + wrong file format Falsches Dateiformat - + failed to open %1 %1 konnte nicht geöffnet werden @@ -4532,17 +4548,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" "%1" konnte nicht erzeugt werden - + Elevation required Mehr Rechte erforderlich - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4557,22 +4573,22 @@ nicht ohne diese Rechte lauffähig ist. Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.exe erlauben wollen Systemänderungen durchzuführen) - + failed to spawn "%1": %2 "%1" konnte nicht erzeugt werden: %2 - + "%1" doesn't exist "%1" existiert nicht - + failed to inject dll into "%1": %2 Konnte dll nicht in "%1" einspeisen: %2 - + failed to run "%1" "%1" konnte nicht ausgeführt werden @@ -4802,12 +4818,12 @@ Trotzdem fortfahren? (sie werden vom System gefragt werden ob sie ModOrganizer.e es wurde versucht, Einstellungen für das unbekanntes Plugin "%1" zu speichern - + Confirm Bestätigen - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Das Mod Verzeichnis zu wechseln wirkt sich auf alle Profile aus! Mods die im neuen Verzeichnis nicht existieren (oder dort anders heißen) werden in allen Profilen deaktiviert. Dies kann nicht rückgängig gemacht werden außer Sie haben ihre Profile manuell gesichert. Fortfahren? @@ -5481,26 +5497,26 @@ Unter Windows XP: Datei "%1" überschreiben - - - - + + + + Confirm Bestätigen - - + + Copy all save games of character "%1" to the profile? Alle Spielstände des Charakters "%1" ins Profil kopieren? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Ale Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Alle Spielstände des Charakters "%1" an die globale Stelle verschieben? Bitte beachten sie dass dies die laufende Nummer der Spielstände durcheinander bringt. diff --git a/src/organizer_en_US.ts b/src/organizer_en_US.ts new file mode 100644 index 00000000..46daf493 --- /dev/null +++ b/src/organizer_en_US.ts @@ -0,0 +1,5343 @@ + + + + + AboutDialog + + + + About + + + + + Revision: + + + + + Used Software + + + + + Credits + + + + + Translators + + + + + Others + + + + + Close + + + + + No license + + + + + ActivateModsDialog + + + Activate Mods + + + + + This is a list of esps and esms that were active when the save game was created. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you hit Ok, all the mods selected in the right columns and all missing esps that have become available will be activated.</span></p></body></html> + + + + + Missing ESP + + + + + Mod + + + + + not found + + + + + BainComplexInstallerDialog + + + BAIN Package Installer + + + + + Name + + + + + This looks like a Package prepared for Installation through BAIN. You can select options from the list below. If there is a package.txt file, it should contain detailed information about the options. + + + + + Components of this package. + + + + + Components of this package. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author. + + + + + + The package.txt is often part of BAIN packages and contains details about the options available. + + + + + Package.txt + + + + + + Opens a Dialog that allows custom modifications. + + + + + Manual + + + + + Ok + + + + + Cancel + + + + + BrowserDialog + + + Some Page + + + + + Search + + + + + new + + + + + failed to start download + + + + + CategoriesDialog + + + Categories + + + + + ID + + + + + Internal ID for the category. + + + + + Internal ID for the category. The categories a mod belongs to are stored by this ID. It is recommended you use new IDs for categories you add instead of re-using existing ones. + + + + + Name + + + + + + Name of the Categorie used for display. + + + + + Nexus IDs + + + + + Comma-Separated list of Nexus IDs to be matched to the internal ID. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> + + + + + Parent ID + + + + + If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID. + + + + + Add + + + + + Remove + + + + + CredentialsDialog + + + Login + + + + + This feature may not work unless you're logged in with Nexus + + + + + Username + + + + + Password + + + + + Remember + + + + + Never ask again + + + + + DirectoryRefresher + + + failed to read bsa: %1 + + + + + DownloadList + + + Name + + + + + Filetime + + + + + Done + + + + + Information missing, please select "Query Info" from the context menu to re-retrieve. + + + + + pending download + + + + + DownloadListWidget + + + + Placeholder + + + + + + + Done - Double Click to install + + + + + + Paused - Double Click to resume + + + + + + Installed - Double Click to re-install + + + + + + Uninstalled - Double Click to re-install + + + + + DownloadListWidgetCompact + + + + Placeholder + + + + + Done + + + + + DownloadListWidgetCompactDelegate + + + < mod %1 file %2 > + + + + + Pending + + + + + Paused + + + + + Fetching Info 1 + + + + + Fetching Info 2 + + + + + Installed + + + + + Uninstalled + + + + + Done + + + + + + + + Are you sure? + + + + + This will remove all finished downloads from this list and from disk. + + + + + This will remove all installed downloads from this list and from disk. + + + + + This will permanently remove all finished downloads from this list (but NOT from disk). + + + + + This will permanently remove all installed downloads from this list (but NOT from disk). + + + + + Install + + + + + Query Info + + + + + Delete + + + + + Un-Hide + + + + + Remove from View + + + + + Cancel + + + + + Pause + + + + + Remove + + + + + Resume + + + + + Delete Installed... + + + + + Delete All... + + + + + Remove Installed... + + + + + Remove All... + + + + + DownloadListWidgetDelegate + + + < mod %1 file %2 > + + + + + Pending + + + + + Fetching Info 1 + + + + + Fetching Info 2 + + + + + + + + Are you sure? + + + + + This will remove all finished downloads from this list and from disk. + + + + + This will remove all installed downloads from this list and from disk. + + + + + This will remove all finished downloads from this list (but NOT from disk). + + + + + This will remove all installed downloads from this list (but NOT from disk). + + + + + Install + + + + + Query Info + + + + + Delete + + + + + Un-Hide + + + + + Remove from View + + + + + Cancel + + + + + Pause + + + + + Remove + + + + + Resume + + + + + Delete Installed... + + + + + Delete All... + + + + + Remove Installed... + + + + + Remove All... + + + + + DownloadManager + + + failed to rename "%1" to "%2" + + + + + Memory allocation error (in refreshing directory). + + + + + Download again? + + + + + A file with the same name has already been downloaded. Do you want to download it again? The new file will receive a different name. + + + + + failed to download %1: could not open output file: %2 + + + + + Wrong Game + + + + + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". + + + + + + + + + + + + + + + + + + + + invalid index + + + + + failed to delete %1 + + + + + failed to delete meta file for %1 + + + + + + + + + + invalid index %1 + + + + + No known download urls. Sorry, this download can't be resumed. + + + + + Please enter the nexus mod id + + + + + Mod ID: + + + + + Main + + + + + Update + + + + + Optional + + + + + Old + + + + + Misc + + + + + Unknown + + + + + Memory allocation error (in processing progress event). + + + + + Memory allocation error (in processing downloaded data). + + + + + Information updated + + + + + + No matching file found on Nexus! Maybe this file is no longer available or it was renamed? + + + + + No file on Nexus matches the selected file by name. Please manually choose the correct one. + + + + + No download server available. Please try again later. + + + + + Failed to request file info from nexus: %1 + + + + + Download failed. Server reported: %1 + + + + + Download failed: %1 (%2) + + + + + failed to re-open %1 + + + + + EditExecutablesDialog + + + Modify Executables + + + + + List of configured executables + + + + + This is a list of your configured executables. Executables in grey are automatically recognised and can not be modified. + + + + + Title + + + + + + Name of the executable. This is only for display purposes. + + + + + Binary + + + + + + Binary to run + + + + + Browse filesystem + + + + + Browse filesystem for the executable to run. + + + + + + ... + + + + + Start in + + + + + Arguments + + + + + + Arguments to pass to the application + + + + + Allow the Steam AppID to be used for this executable to be changed. + + + + + Allow the Steam AppID to be used for this executable to be changed. +Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game. +Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID. This overwrite is already preconfigured. + + + + + Overwrite Steam AppID + + + + + Steam AppID to use for this executable that differs from the games AppID. + + + + + Steam AppID to use for this executable that differs from the games AppID. +Every game/tool distributed through Steam has a unique ID. MO needs to know this ID to start those programs directly, otherwise the program is started by steam and then MO will not work. By default, MO will use the AppID for the game (usually 72850). +Right now the only case I know of where this needs to be overwritten is for the Skyrim Creation Kit which has its own AppID (usually 202480). This overwrite is already preconfigured. + + + + + + + If checked, MO will be closed once the specified executable is run. + + + + + Close MO when started + + + + + + Add an executable + + + + + + Add + + + + + + Remove the selected executable + + + + + Remove + + + + + Close + + + + + Select a binary + + + + + Executable (%1) + + + + + Java (32-bit) required + + + + + MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. + + + + + Select a directory + + + + + Confirm + + + + + Really remove "%1" from executables? + + + + + Modify + + + + + + Save Changes? + + + + + + You made changes to the current executable, do you want to save them? + + + + + MO must be kept running or this application will not work correctly. + + + + + FindDialog + + + Find + + + + + Find what: + + + + + + Search term + + + + + + Find next occurence from current file position. + + + + + &Find Next + + + + + + + Close + + + + + FomodInstallerDialog + + + FOMOD Installation Dialog + + + + + Name + + + + + Author + + + + + Version + + + + + Website + + + + + <a href="#">Link</a> + + + + + Manual + + + + + Back + + + + + Next + + + + + Cancel + + + + + InstallDialog + + + Install Mods + + + + + New Mod + + + + + Name + + + + + Pick a name for the mod + + + + + Pick a name for the mod. This is also used as a directory name, so please don't use characters that are illegal in file names. + + + + + Content + + + + + Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking... + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> + + + + + Placeholder + + + + + OK + + + + + Cancel + + + + + InstallationManager + + + archive.dll not loaded: "%1" + + + + + Password required + + + + + Password + + + + + + + Extracting files + + + + + failed to create backup + + + + + Mod Name + + + + + Name + + + + + Invalid name + + + + + The name you entered is invalid, please enter a different one. + + + + + File format "%1" not supported + + + + + None of the available installer plugins were able to handle that archive + + + + + no error + + + + + 7z.dll not found + + + + + 7z.dll isn't valid + + + + + archive not found + + + + + failed to open archive + + + + + unsupported archive type + + + + + internal library error + + + + + archive invalid + + + + + unknown archive error + + + + + LockedDialog + + + Locked + + + + + This dialog should disappear automatically if the application/game is done. Click unlock if it didn't. + + + + + MO is locked while the executable is running. + + + + + Unlock + + + + + LogBuffer + + + failed to write log to %1: %2 + + + + + MOApplication + + + an error occured: %1 + + + + + an error occured + + + + + MainWindow + + + + Categories + + + + + Click blank area to deselect + + + + + If checked, only mods that match all selected categories are displayed. + + + + + And + + + + + If checked, all mods that match at least one of the selected categories are displayed. + + + + + Or + + + + + Profile + + + + + Pick a module collection + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> + + + + + Open list options... + + + + + Refresh list. This is usually not necessary unless you modified data outside the program. + + + + + + Restore Backup... + + + + + + Create Backup + + + + + List of available mods. + + + + + This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. + + + + + Filter + + + + + No groups + + + + + Nexus IDs + + + + + + + Namefilter + + + + + Pick a program to run. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> + + + + + Run program + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> + + + + + Run + + + + + Create a shortcut in your start menu or on the desktop to the specified program + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> + + + + + Shortcut + + + + + Plugins + + + + + Sort + + + + + List of available esp/esm files + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> + + + + + Archives + + + + + <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> + + + + + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> + + + + + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. + + + + + BSA files are archives (comparable to .zip files) that contain data assets (meshes, textures, ...) to be used by the game. As such they "compete" with loose files in your data directory over which is loaded. +By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! + +BSAs checked here are loaded in such a way that your installation order is obeyed properly. + + + + + + File + + + + + Data + + + + + refresh data-directory overview + + + + + Refresh the overview. This may take a moment. + + + + + + + Refresh + + + + + This is an overview of your data directory as visible to the game (and tools). + + + + + Mod + + + + + + Filter the above list so that only conflicts are displayed. + + + + + Show only conflicts + + + + + Saves + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> + + + + + Downloads + + + + + This is a list of mods you downloaded from Nexus. Double click one to install it. + + + + + Show Hidden + + + + + Tool Bar + + + + + Install Mod + + + + + Install &Mod + + + + + Install a new mod from an archive + + + + + Ctrl+M + + + + + Profiles + + + + + &Profiles + + + + + Configure Profiles + + + + + Ctrl+P + + + + + Executables + + + + + &Executables + + + + + Configure the executables that can be started through Mod Organizer + + + + + Ctrl+E + + + + + + Tools + + + + + &Tools + + + + + Ctrl+I + + + + + Settings + + + + + &Settings + + + + + Configure settings and workarounds + + + + + Ctrl+S + + + + + Nexus + + + + + Search nexus network for more mods + + + + + Ctrl+N + + + + + + Update + + + + + Mod Organizer is up-to-date + + + + + + No Problems + + + + + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. + +!Work in progress! +Right now this has very limited functionality + + + + + + Help + + + + + Ctrl+H + + + + + Endorse MO + + + + + + Endorse Mod Organizer + + + + + Copy Log to Clipboard + + + + + Ctrl+C + + + + + Toolbar + + + + + Desktop + + + + + Start Menu + + + + + Problems + + + + + There are potential problems with your setup + + + + + Everything seems to be in order + + + + + Help on UI + + + + + Documentation Wiki + + + + + Report Issue + + + + + Tutorials + + + + + About + + + + + About Qt + + + + + failed to save load order: %1 + + + + + Name + + + + + Please enter a name for the new profile + + + + + failed to create profile: %1 + + + + + Show tutorial? + + + + + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. + + + + + Downloads in progress + + + + + There are still downloads in progress, do you really want to quit? + + + + + failed to read savegame: %1 + + + + + Plugin "%1" failed: %2 + + + + + Plugin "%1" failed + + + + + Download? + + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + + + + + failed to init plugin %1: %2 + + + + + Plugin error + + + + + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? +(Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) + + + + + Failed to start "%1" + + + + + Waiting + + + + + Please press OK once you're logged into steam. + + + + + Executable "%1" not found + + + + + Start Steam? + + + + + Steam is required to be running already to correctly start the game. Should MO try to start steam now? + + + + + Also in: <br> + + + + + No conflict + + + + + <Edit...> + + + + + Failed to refresh list of esps: %1 + + + + + This bsa is enabled in the ini file so it may be required! + + + + + Activating Network Proxy + + + + + + Failed to write settings + + + + + + An error occured trying to write back MO settings: %1 + + + + + File is write protected + + + + + Invalid file format (probably a bug) + + + + + Unknown error %1 + + + + + Some plugins could not be loaded + + + + + Too many esps and esms enabled + + + + + + Description missing + + + + + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: + + + + + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> + + + + + Choose Mod + + + + + Mod Archive + + + + + + Installation successful + + + + + + Configure Mod + + + + + + This mod contains ini tweaks. Do you want to configure them now? + + + + + + mod "%1" not found + + + + + + Installation cancelled + + + + + + The mod was not installed completely. + + + + + Start Tutorial? + + + + + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? + + + + + + Download started + + + + + failed to update mod list: %1 + + + + + failed to spawn notepad.exe: %1 + + + + + failed to open %1 + + + + + failed to change origin name: %1 + + + + + failed to move "%1" from mod "%2" to "%3": %4 + + + + + <Checked> + + + + + <Unchecked> + + + + + <Update> + + + + + <Managed by MO> + + + + + <Managed outside MO> + + + + + <No category> + + + + + <Conflicted> + + + + + <Not Endorsed> + + + + + failed to rename mod: %1 + + + + + Overwrite? + + + + + This will replace the existing mod "%1". Continue? + + + + + failed to remove mod "%1" + + + + + + + failed to rename "%1" to "%2" + + + + + Multiple esps activated, please check that they don't conflict. + + + + + + + + Confirm + + + + + Remove the following mods?<br><ul>%1</ul> + + + + + failed to remove mod: %1 + + + + + + Failed + + + + + Installation file no longer exists + + + + + Mods installed with old versions of MO can't be reinstalled in this way. + + + + + You need to be logged in with Nexus to resume a download + + + + + + You need to be logged in with Nexus to endorse + + + + + Nexus ID for this Mod is unknown + + + + + + Create Mod... + + + + + This will move all files from overwrite into a new, regular mod. +Please enter a name: + + + + + A mod with this name already exists + + + + + Continue? + + + + + The versioning scheme decides which version is considered newer than another. +This function will guess the versioning scheme under the assumption that the installed version is outdated. + + + + + + Sorry + + + + + I don't know a versioning scheme where %1 is newer than %2. + + + + + Really enable all visible mods? + + + + + Really disable all visible mods? + + + + + Choose what to export + + + + + Everything + + + + + All installed mods are included in the list + + + + + Active Mods + + + + + Only active (checked) mods from your current profile are included + + + + + Visible + + + + + All mods visible in the mod list are included + + + + + export failed: %1 + + + + + Install Mod... + + + + + Enable all visible + + + + + Disable all visible + + + + + Check all for update + + + + + Export to csv... + + + + + All Mods + + + + + Sync to Mods... + + + + + Restore Backup + + + + + Remove Backup... + + + + + Add/Remove Categories + + + + + Replace Categories + + + + + Primary Category + + + + + Change versioning scheme + + + + + Un-ignore update + + + + + Ignore update + + + + + Rename Mod... + + + + + Remove Mod... + + + + + Reinstall Mod + + + + + Un-Endorse + + + + + + Endorse + + + + + Won't endorse + + + + + Endorsement state unknown + + + + + Ignore missing data + + + + + Visit on Nexus + + + + + Open in explorer + + + + + Information... + + + + + + Exception: + + + + + + Unknown exception + + + + + <All> + + + + + <Multiple> + + + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + Are you sure you want to remove the following save?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + Are you sure you want to remove the following %n saves?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + Fix Mods... + + + + + Delete %n save(s) + Delete save(s) + + Delete %n save + Delete %n saves + + + + + + failed to remove %1 + + + + + + failed to create %1 + + + + + Can't change download directory while downloads are in progress! + + + + + Download failed + + + + + failed to write to file %1 + + + + + %1 written + + + + + Select binary + + + + + Binary + + + + + Enter Name + + + + + Please enter a name for the executable + + + + + Not an executable + + + + + This is not a recognized executable. + + + + + + Replace file? + + + + + There already is a hidden version of this file. Replace it? + + + + + + File operation failed + + + + + + Failed to remove "%1". Maybe you lack the required file permissions? + + + + + There already is a visible version of this file. Replace it? + + + + + file not found: %1 + + + + + failed to generate preview for %1 + + + + + Sorry, can't preview anything. This function currently does not support extracting from bsas. + + + + + Update available + + + + + Open/Execute + + + + + Add as Executable + + + + + Preview + + + + + Un-Hide + + + + + Hide + + + + + Write To File... + + + + + Do you want to endorse Mod Organizer on %1 now? + + + + + Thank you! + + + + + Thank you for your endorsement! + + + + + Request to Nexus failed: %1 + + + + + + login successful + + + + + login failed: %1. Trying to download anyway + + + + + login failed: %1 + + + + + login failed: %1. You need to log-in with Nexus to update MO. + + + + + + failed to read %1: %2 + + + + + Error + + + + + failed to extract %1 (errorcode %2) + + + + + Extract BSA + + + + + This archive contains invalid hashes. Some files may be broken. + + + + + Extract... + + + + + Edit Categories... + + + + + Deselect filter + + + + + Remove + + + + + Enable all + + + + + Disable all + + + + + Unlock load order + + + + + Lock load order + + + + + depends on missing "%1" + + + + + No profile set + + + + + LOOT working + + + + + loot failed. Exit code was: %1 + + + + + failed to start loot + + + + + failed to run loot: %1 + + + + + Errors occured + + + + + Backup of load order created + + + + + Choose backup to restore + + + + + No Backups + + + + + There are no backups to restore + + + + + + Restore failed + + + + + + Failed to restore the backup. Errorcode: %1 + + + + + Backup of modlist created + + + + + MessageDialog + + + + Placeholder + + + + + ModInfo + + + + invalid index %1 + + + + + ModInfoBackup + + + This is the backup of a mod + + + + + ModInfoDialog + + + Mod Info + + + + + Textfiles + + + + + A list of text-files in the mod directory. + + + + + A list of text-files in the mod directory like readmes. + + + + + + Save + + + + + INI-Files + + + + + Ini Files + + + + + This is a list of .ini files in the mod. + + + + + This is a list of .ini files in the mod. These are usually used to configure the behaviour of mods if there are configurable parameters. + + + + + Ini Tweaks + + + + + This is a list of ini tweaks (ini modifications that can be toggled). + + + + + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. + + + + + Save changes to the file. + + + + + Save changes to the file. This overwrites the original. There is no automatic backup! + + + + + Images + + + + + Images located in the mod. + + + + + This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. + + + + + + Optional ESPs + + + + + List of esps and esms that can not be loaded by the game. + + + + + List of esps and esms contained in this plugin that currently can not be loaded by the game. They will not even appear in the esp-list in the main MO-window. +They usually contain optional functionality, see the readme. + +Most mods do not have optional esps, so chances are good you are looking at an empty list. + + + + + Make the selected mod in the lower list unavailable. + + + + + The selected esp (in the lower list) will be pushed into a subdirectory of the mod and will thus become "invisible" to the game. It can then no longer be activated. + + + + + Move a file to the data directory. + + + + + This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. + + + + + ESPs in the data directory and thus visible to the game. + + + + + These are the mod files that are in the (virtual) data directory of your game and will thus be selecteable in the esp list in the main window. + + + + + Available ESPs + + + + + Conflicts + + + + + The following conflicted files are provided by this mod + + + + + + File + + + + + Overwritten Mods + + + + + The following conflicted files are provided by other mods + + + + + Providing Mod + + + + + Non-Conflicted files + + + + + Categories + + + + + Primary Category + + + + + Nexus Info + + + + + Mod ID + + + + + Mod ID for this mod on Nexus. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> + + + + + Version + + + + + Refresh + + + + + Refresh all information from Nexus. + + + + + Description + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> + + + + + Endorse + + + + + Notes + + + + + Filetree + + + + + A directory view of this mod + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> + + + + + Previous + + + + + Next + + + + + Close + + + + + &Delete + + + + + &Rename + + + + + &Hide + + + + + &Unhide + + + + + &Open + + + + + &New Folder + + + + + + Save changes? + + + + + + Save changes to "%1"? + + + + + File Exists + + + + + A file with that name exists, please enter a new one + + + + + failed to move file + + + + + failed to create directory "optional" + + + + + + Info requested, please wait + + + + + Main + + + + + Update + + + + + Optional + + + + + Old + + + + + Misc + + + + + Unknown + + + + + Current Version: %1 + + + + + No update available + + + + + (description incomplete, please visit nexus) + + + + + <a href="%1">Visit on Nexus</a> + + + + + Failed to delete %1 + + + + + + Confirm + + + + + Are sure you want to delete "%1"? + + + + + Are sure you want to delete the selected files? + + + + + + New Folder + + + + + Failed to create "%1" + + + + + + Replace file? + + + + + There already is a hidden version of this file. Replace it? + + + + + + File operation failed + + + + + + Failed to remove "%1". Maybe you lack the required file permissions? + + + + + + failed to rename %1 to %2 + + + + + There already is a visible version of this file. Replace it? + + + + + Un-Hide + + + + + Hide + + + + + Name + + + + + Please enter a name + + + + + + Error + + + + + Invalid name. Must be a valid file name + + + + + A tweak by that name exists + + + + + Create Tweak + + + + + ModInfoForeign + + + This pseudo mod represents content managed outside MO. It isn't modified by MO. + + + + + ModInfoOverwrite + + + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) + + + + + ModInfoRegular + + + + failed to write %1/meta.ini: error %2 + + + + + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory + + + + + Categories: <br> + + + + + ModList + + + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) + + + + + Backup + + + + + No valid game data + + + + + Not endorsed yet + + + + + Overwrites files + + + + + Overwritten files + + + + + Overwrites & Overwritten + + + + + Redundant + + + + + Non-MO + + + + + invalid + + + + + installed version: "%1", newest version: "%2" + + + + + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". + + + + + Categories: <br> + + + + + Invalid name + + + + + drag&drop failed: %1 + + + + + Confirm + + + + + Are you sure you want to remove "%1"? + + + + + Flags + + + + + Mod Name + + + + + Version + + + + + Priority + + + + + Category + + + + + Nexus ID + + + + + Installation + + + + + + unknown + + + + + Name of your mods + + + + + Version of the mod (if available) + + + + + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. + + + + + Category of the mod. + + + + + Id of the mod as used on Nexus. + + + + + Emblemes to highlight things that might require attention. + + + + + Time this mod was installed + + + + + MotDDialog + + + Message of the Day + + + + + OK + + + + + MyFileSystemModel + + + Overwrites + + + + + not implemented + + + + + NXMAccessManager + + + Logging into Nexus + + + + + timeout + + + + + Unknown error + + + + + Please check your password + + + + + NexusInterface + + + Failed to guess mod id for "%1", please pick the correct one + + + + + empty response + + + + + invalid response + + + + + OverwriteInfoDialog + + + Overwrite + + + + + You can use drag&drop to move files and directories to regular mods. + + + + + &Delete + + + + + &Rename + + + + + &Open + + + + + &New Folder + + + + + Failed to delete "%1" + + + + + + Confirm + + + + + Are sure you want to delete "%1"? + + + + + Are sure you want to delete the selected files? + + + + + + New Folder + + + + + Failed to create "%1" + + + + + PluginList + + + Name + + + + + Priority + + + + + Mod Index + + + + + Flags + + + + + + unknown + + + + + Name of your mods + + + + + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. + + + + + The modindex determins the formids of objects originating from this mods. + + + + + failed to update esp info for file %1 (source id: %2), error: %3 + + + + + esp not found: %1 + + + + + + Confirm + + + + + Really enable all plugins? + + + + + Really disable all plugins? + + + + + The file containing locked plugin indices is broken + + + + + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. + + + + + This plugin can't be disabled (enforced by the game) + + + + + <b>Origin</b>: %1 + + + + + Author + + + + + Description + + + + + Missing Masters + + + + + Enabled Masters + + + + + failed to restore load order for %1 + + + + + PreviewDialog + + + Preview + + + + + Close + + + + + ProblemsDialog + + + Problems + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + Close + + + + + + Fix + + + + + No guided fix + + + + + Profile + + + invalid profile name %1 + + + + + failed to create %1 + + + + + failed to write mod list: %1 + + + + + failed to update tweaked ini file, wrong settings may be used: %1 + + + + + failed to create tweaked ini: %1 + + + + + "%1" is missing or inaccessible + + + + + + + + + invalid index %1 + + + + + Overwrite directory couldn't be parsed + + + + + invalid priority %1 + + + + + failed to parse ini file (%1) + + + + + failed to parse ini file (%1): %2 + + + + + + failed to modify "%1" + + + + + Delete savegames? + + + + + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) + + + + + ProfileInputDialog + + + Dialog + + + + + Please enter a name for the new profile + + + + + If checked, the new profile will use the default game settings. + + + + + If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. + + + + + Default Game Settings + + + + + ProfilesDialog + + + Profiles + + + + + List of Profiles + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> + + + + + + If checked, savegames are local to this profile and will not appear when starting with a different profile. + + + + + Local Savegames + + + + + This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">With Skyrim this bug seems to be fixed to a degree but whether a mod becomes active still depends on file dates. Therefore, it still makes sense to activate this.</span></p></body></html> + + + + + Automatic Archive Invalidation + + + + + + Create a new profile from scratch + + + + + Create + + + + + Clone the selected profile + + + + + This creates a new profile with the same settings and active mods as the selected one. + + + + + Copy + + + + + + Delete the selected Profile. This can not be un-done! + + + + + Remove + + + + + Rename + + + + + + Transfer save games to the selected profile. + + + + + Transfer Saves + + + + + Close + + + + + Archive invalidation isn't required for this game. + + + + + + failed to create profile: %1 + + + + + Name + + + + + Please enter a name for the new profile + + + + + failed to copy profile: %1 + + + + + Invalid name + + + + + Invalid profile name + + + + + Confirm + + + + + Are you sure you want to remove this profile (including local savegames if any)? + + + + + Profile broken + + + + + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? + + + + + Rename Profile + + + + + New Name + + + + + failed to change archive invalidation state: %1 + + + + + failed to determine if invalidation is active: %1 + + + + + QObject + + + Failed to save custom categories + + + + + + + + invalid index %1 + + + + + invalid category id %1 + + + + + invalid field name "%1" + + + + + invalid type for "%1" (should be integer) + + + + + invalid type for "%1" (should be string) + + + + + invalid type for "%1" (should be float) + + + + + no fields set up yet! + + + + + field not set "%1" + + + + + invalid character in field "%1" + + + + + empty field name + + + + + invalid game type %1 + + + + + helper failed + + + + + + failed to determine account name + + + + + + invalid 7-zip32.dll: %1 + + + + + failed to open %1: %2 + + + + + + %1 not found + + + + + Failed to delete %1 + + + + + Failed to deactivate script extender loading + + + + + Failed to remove %1: %2 + + + + + + Failed to rename %1 to %2 + + + + + Failed to deactivate proxy-dll loading + + + + + + + Failed to copy %1 to %2 + + + + + Failed to set up script extender loading + + + + + Failed to delete old proxy-dll %1 + + + + + Failed to overwrite %1 + + + + + Failed to set up proxy-dll loading + + + + + Permissions required + + + + + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. + + + + + + Woops + + + + + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened + + + + + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 + + + + + + Mod Organizer + + + + + An instance of Mod Organizer is already running + + + + + No game identified in "%1". The directory is required to contain the game binary and its launcher. + + + + + + Please select the game to manage + + + + + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) + + + + + failed to start application: %1 + + + + + Please use "Help" from the toolbar to get usage instructions to all elements + + + + + + <Manage...> + + + + + failed to parse profile %1: %2 + + + + + failed to find "%1" + + + + + failed to access %1 + + + + + failed to set file time %1 + + + + + failed to create %1 + + + + + "%1" is missing or inaccessible + + + + + Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! + + + + + + Error + + + + + failed to open temporary file + + + + + + + wrong file format + + + + + failed to open %1 + + + + + Script Extender + + + + + Proxy DLL + + + + + failed to spawn "%1" + + + + + Elevation required + + + + + This process requires elevation to run. +This is a potential security risk so I highly advice you to investigate if +"%1" +can be installed to work without elevation. + +Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) + + + + + failed to spawn "%1": %2 + + + + + "%1" doesn't exist + + + + + failed to inject dll into "%1": %2 + + + + + failed to run "%1" + + + + + QueryOverwriteDialog + + + Mod Exists + + + + + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. + + + + + Keep Backup + + + + + Merge + + + + + Replace + + + + + Rename + + + + + Cancel + + + + + SaveGameInfoWidget + + + Save # + + + + + Character + + + + + Level + + + + + Location + + + + + Date + + + + + SaveGameInfoWidgetGamebryo + + + Missing ESPs + + + + + SaveTextAsDialog + + + Dialog + + + + + Copy To Clipboard + + + + + Save As... + + + + + Close + + + + + Save CSV + + + + + Text Files + + + + + failed to open "%1" for writing + + + + + SelectionDialog + + + Select + + + + + Placeholder + + + + + Cancel + + + + + SelfUpdater + + + archive.dll not loaded: "%1" + + + + + + + + Update + + + + + An update is available (newest version: %1), do you want to install it? + + + + + Download in progress + + + + + Download failed: %1 + + + + + Failed to install update: %1 + + + + + failed to open archive "%1": %2 + + + + + failed to move outdated files: %1. Please update manually. + + + + + Update installed, Mod Organizer will now be restarted. + + + + + Error + + + + + Failed to parse response. Please report this as a bug and include the file mo_interface.log. + + + + + No incremental update available for this version, the complete package needs to be downloaded (%1 kB) + + + + + no file for update found. Please update manually. + + + + + Failed to retrieve update information: %1 + + + + + No download server available. Please try again later. + + + + + Settings + + + + attempt to store setting for unknown plugin "%1" + + + + + Confirm + + + + + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? + + + + + SettingsDialog + + + Settings + + + + + General + + + + + Language + + + + + The display language + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> + + + + + Style + + + + + graphical style + + + + + graphical style of the MO user interface + + + + + Log Level + + + + + Decides the amount of data printed to "ModOrganizer.log" + + + + + Decides the amount of data printed to "ModOrganizer.log". +"Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. + + + + + Debug + + + + + Info + + + + + Error + + + + + Advanced + + + + + + Directory where downloads are stored. + + + + + Mod Directory + + + + + Directory where mods are stored. + + + + + Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). + + + + + Download Directory + + + + + Cache Directory + + + + + User interface + + + + + If checked, the download interface will be more compact. + + + + + Compact Download Interface + + + + + If checked, the download list will display meta information instead of file names. + + + + + Download Meta Information + + + + + Reset stored information from dialogs. + + + + + This will make all dialogs show up again where you checked the "Remember selection"-box. + + + + + Reset Dialogs + + + + + + Modify the categories available to arrange your mods. + + + + + Configure Mod Categories + + + + + + Nexus + + + + + Allows automatic log-in when the Nexus-Page for the game is clicked. + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> + + + + + If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. + + + + + Automatically Log-In to Nexus + + + + + Username + + + + + Password + + + + + Disable automatic internet features + + + + + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) + + + + + Offline Mode + + + + + Use a proxy for network connections. + + + + + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. + + + + + Use HTTP Proxy (Uses System Settings) + + + + + Associate with "Download with manager" links + + + + + Known Servers (updated on download) + + + + + Preferred Servers (Drag & Drop) + + + + + Plugins + + + + + Author: + + + + + Version: + + + + + Description: + + + + + Key + + + + + Value + + + + + Blacklisted Plugins (use <del> to remove): + + + + + Workarounds + + + + + Steam App ID + + + + + The Steam AppID for your game + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">1. Navigate to the game library in steam</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">2. right-click on the game you need the id for and choose </span><span style=" font-size:8pt; font-weight:600;">Create desktop shortcut</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">3. right-click on the newly created shortcut on your desktop and select </span><span style=" font-size:8pt; font-weight:600;">Properties</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">4. in the URL-field you should see something like this: </span><span style=" font-size:8pt; font-style:italic;">steam://rungameid/22380</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">22380 is the id you're looking for.</span></p></body></html> + + + + + Load Mechanism + + + + + Select loading mechanism. See help for details. + + + + + Mod Organizer needs a dll to be injected into the game so all mods are visible to it. +There are several means to do this: +*Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. +*Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. + +If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. + + + + + NMM Version + + + + + The Version of Nexus Mod Manager to impersonate. + + + + + Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. + +tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. + + + + + Enforces that inactive ESPs and ESMs are never loaded. + + + + + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. +I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. + + + + + Hide inactive ESPs/ESMs + + + + + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) + + + + + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) +Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. + + + + + Force-enable game files + + + + + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. + + + + + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. + +If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. + + + + + Display mods installed outside MO + + + + + + For Skyrim, this can be used instead of Archive Invalidation. It should make AI redundant for all Profiles. +For the other games this is not a sufficient replacement for AI! + + + + + Back-date BSAs + + + + + These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. + + + + + Select download directory + + + + + Select mod directory + + + + + Select cache directory + + + + + Confirm? + + + + + This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? + + + + + SimpleInstallDialog + + + Quick Install + + + + + Name + + + + + + Opens a Dialog that allows custom modifications. + + + + + Manual + + + + + OK + + + + + Cancel + + + + + SingleInstance + + + SHM error: %1 + + + + + failed to connect to running instance: %1 + + + + + failed to communicate with running instance: %1 + + + + + failed to receive data from secondary instance: %1 + + + + + SyncOverwriteDialog + + + Sync Overwrite + + + + + Name + + + + + Sync To + + + + + <don't sync> + + + + + failed to remove %1 + + + + + failed to move %1 to %2 + + + + + TransferSavesDialog + + + Transfer Savegames + + + + + Global Characters + + + + + This is a list of characters in the global location. + + + + + This is a list of characters in the global location. + +On Windows Vista/Windows 7: + C:\Users\[UserName]\Documents\My Games\Skyrim\Saves + +On Windows XP: + C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves + + + + + + This is a list of save games for the selected character in the global location. + +On Windows Vista/Windows 7: + C:\Users\[UserName]\Documents\My Games\Skyrim\Saves + +On Windows XP: + C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves + + + + + + + Move -> + + + + + Copy -> + + + + + <- Move + + + + + <- Copy + + + + + Done + + + + + Profile Characters + + + + + Overwrite + + + + + Overwrite the file "%1" + + + + + + + + Confirm + + + + + + Copy all save games of character "%1" to the profile? + + + + + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + + + + + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. + + + + diff --git a/src/organizer_es.ts b/src/organizer_es.ts index 99071b5c..5ffce293 100644 --- a/src/organizer_es.ts +++ b/src/organizer_es.ts @@ -1400,7 +1400,7 @@ p, li { white-space: pre-wrap; } - + Namefilter Nombre del filtro @@ -1561,8 +1561,8 @@ BSA marcado aquí se cargan de tal manera que su orden de instalación se cumple - - + + Refresh Recargar @@ -1610,12 +1610,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si haces clic en &quot;Fix Mods...&quot; en el menú contextual, MO tratará de activar todos los mods y para arreglar esos esps desaparecidos. No va a desactivar ninguna otra cosa!</span></p></body></html> - + Downloads Descargas - + This is a list of mods you downloaded from Nexus. Double click one to install it. Esta es la lista de los mods que has descargado desde Nexus. Doble Click para instalar. @@ -1624,145 +1624,145 @@ p, li { white-space: pre-wrap; } Compactar - + Show Hidden Mostrar Ocultos - + Tool Bar Barra Herramientas - + Install Mod Instalar Mod - + Install &Mod Instalar &Mod - + Install a new mod from an archive Instalar un nuevo Mod desde un archivo - + Ctrl+M Ctrl+M - + Profiles Perfiles - + &Profiles &Perfiles - + Configure Profiles Configurar Perfiles - + Ctrl+P Ctrl+P - + Executables Ejecutables - + &Executables &Ejecutables - + Configure the executables that can be started through Mod Organizer Configura el ejecutable que sera iniciado desde Mod Orgenizer - + Ctrl+E Ctrl+E - + Tools Herramientas - + &Tools &Herramientas - + Ctrl+I Ctrl+I - + Settings Configuracion - + &Settings &Configuracion - + Configure settings and workarounds Configurar opciones y soluciones - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Buscar en la red de Nexus mas Mods - + Ctrl+N Ctrl+N - - + + Update Actualizacion - + Mod Organizer is up-to-date Mod Organizer esta actualizado - - + + No Problems Sin problemas - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1773,94 +1773,94 @@ Right now this has very limited functionality Ahora esto tiene una funcionalidad muy limitada - - + + Help Ayuda - + Ctrl+H Ctrl+H - + Endorse MO Avalar MO - - + + Endorse Mod Organizer Avalar Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar Barra de herramientas - + Desktop Escritorio - + Start Menu Menú de Inicio - + Problems Problemas - + There are potential problems with your setup Hay posibles problemas con su configuración - + Everything seems to be in order Todo parece estar en orden - + Help on UI Ayuda sobre UI - + Documentation Wiki Documentación Wiki - + Report Issue Informar de un Problema - + Tutorials Tutoriales - + About Sobre - + About Qt Sobre Qt @@ -1869,119 +1869,119 @@ Ahora esto tiene una funcionalidad muy limitada Fallo al salvar la orden de archivos, , ¿tiene permiso de escritura en "%1"? - + failed to save load order: %1 Fallo guardando el orden de carga: %1 - + Name Nombre - + Please enter a name for the new profile Por favor introduzca un nombre para el nuevo perfil - + failed to create profile: %1 Fallo al crear el perfil: %1 - + Show tutorial? ¿Mostrar tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. Están comenzando Mod Organizer por primera vez. ¿Quieres mostrar un tutorial de sus características básicas? Si decides que no siempre podras empezar el tutorial desde la "Ayuda" del menú. - + Downloads in progress Descarga en progreso - + There are still downloads in progress, do you really want to quit? Aun hay descargas en progreso, estas seguro que quieres salir? - + failed to read savegame: %1 Fallo al leer la partida guardada: %1 - + Plugin "%1" failed: %2 Plugin "%1" fallido: %2 - + Plugin "%1" failed Plugin "%1" fallido - + failed to init plugin %1: %2 fallo al iniciar plugin %1: %2 - + Plugin error Error de plugin - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) Parece que el plugin "%1" Fallo al cargar el último inicio y causó que MO se bloqueara. ¿Quieres desactivarlo? (Nota: Si es la primera vez que ve este mensaje en este plugin es posible que desees intentarlo otra vez. El plugin podria ser capaz de recuperarse del problema.) - + Failed to start "%1" Fallo al iniciar plugin %1: %2 - + Waiting Esperando - + Please press OK once you're logged into steam. Por favor, pulsa OK una vez que hayas iniciado sesión en steam. - + Start Steam? ¿Iniciar Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Steam es requerido para iniciar correctamente el juego. ¿Debería MO tratar de iniciar ahora steam? - + Also in: <br> También en: <br> - + No conflict Sin conflictos - + <Edit...> <Editar...> - + This bsa is enabled in the ini file so it may be required! Esta bsa está habilitada en el archivo ini, por lo que puede ser necesario @@ -1990,232 +1990,240 @@ Ahora esto tiene una funcionalidad muy limitada Este archivo se seguirá cargado, ya que hay un plugin con el mismo nombre, pero sus archivos no seguirá el orden de instalación! - + Activating Network Proxy Activación de proxy de red - - + + Installation successful Instalacion completada - - + + Configure Mod Configurar Mod - - + + This mod contains ini tweaks. Do you want to configure them now? Este mod contiene ajustes del ini. ¿Quieres configurarlos ahora? - - + + mod "%1" not found mod "%1" no encontrado - - + + Installation cancelled Instalación cancelada - - + + The mod was not installed completely. El mod no fue instalado completamente. - + Some plugins could not be loaded Algún plugins no se pudo cargar - + Too many esps and esms enabled Demasiados esps y esms habilitado - - + + Description missing Falta la descripción - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Los siguientes plugins no se pudieron cargar. La razón puede ser dependencias faltantes (es decir python) o una versión obsoleta: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> El juego no permite cargar más de 255 plugins activos (incluidos los oficiales). Tienes que desactivar algunos plugins no utilizados o fusionar algunos plugins en uno solo. Aquí podras encontrar una guía: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Seleccione Mod - + Mod Archive Archivo Mod - + Start Tutorial? Iniciar tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Estás a punto de iniciar un tutorial. Por razones técnicas, no es posible terminar el tutorial antes de tiempo. ¿Desea continuar? - - + + Download started Descarga iniciada - + failed to update mod list: %1 Fallo al actualizar la lista de Mods: %1 - + failed to spawn notepad.exe: %1 Fallo al cargar el Bloc de notas: %1 - + failed to open %1 Fallo al abrir %1 - + failed to change origin name: %1 fallo al cambiar el nombre original del fichero %1 - + Executable "%1" not found Ejecutable "%1" no encontrado - + Failed to refresh list of esps: %1 Fallo al actualizar la lista de esps: %1 - + <Checked> <Marcado> - + <Unchecked> <Desmarcado> - + <Update> <Actualizacion> - + <No category> <No categoría> - + <Conflicted> <En conflicto> - + <Not Endorsed> <No Avalado> - + failed to rename mod: %1 fallo al renombrar el mod: %1 - + Overwrite? ¿Sobrescribir? - + This will replace the existing mod "%1". Continue? Esto reemplazará el vigente mod "%1". ¿Desea continuar? - + failed to remove mod "%1" Fallo eliminando mod "%1" - - - + + + failed to rename "%1" to "%2" Fallo al renombrar "%1" a "%2" - + Multiple esps activated, please check that they don't conflict. Múltiples esps activados, por favor verifique que no entren en conflicto. - - - - + + + + Confirm Confirmar - + Remove the following mods?<br><ul>%1</ul> ¿Quitar el siguiente mods?<br><ul>%1</ul> - + failed to remove mod: %1 fallo al eliminar mod: %1 - - + + Failed Fallo - + Installation file no longer exists El archivo de instalación ya no existe - + Mods installed with old versions of MO can't be reinstalled in this way. Mods instalados con las viejas versiones de MO no pueden ser instalados de nuevo de este modo. - - + + You need to be logged in with Nexus to endorse Necesita estar conectado con Nexus para avalar + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + - + Extract BSA Extraer BSA @@ -2226,639 +2234,651 @@ Ahora esto tiene una funcionalidad muy limitada (Esto elimina la BSA después de su finalización. Si no sabe de BSAS, solamente no lo seleccione) - - + + failed to read %1: %2 fallo al leer %1: %2 - + This archive contains invalid hashes. Some files may be broken. Este archivo contiene hashes no válidos. Algunos archivos pueden estar rotos. - + Nexus ID for this Mod is unknown Se desconoce la ID en Nexus para este Mod - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... Crear Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Esto moverá todos los archivos de sobrescritura en un nuevo mod, regular. Por favor, introduzca un nombre: - + A mod with this name already exists Ya existe un mod con este nombre - + Continue? ¿Continuar? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. El esquema de versiones decide qué versión es considerada más nueva una que otra. Esta función adivinará el esquema de versiones bajo el supuesto de que la versión instalada es obsoleta. - - + + Sorry Lo siento - + I don't know a versioning scheme where %1 is newer than %2. Se desconoce un esquema de versiones donde %1 es más reciente que %2. - + Really enable all visible mods? ¿Permitir realmente todos los mods visibles? - + Really disable all visible mods? ¿Realmente desactivar todos los mods visibles? - + Choose what to export Elija un archivo a exportar - + Everything Todo - + All installed mods are included in the list Todos los mods instalados están incluidos en la lista - + Active Mods Mods Activos - + Only active (checked) mods from your current profile are included Mods sólo activos (Marcados) es incluido de su perfil actual - + Visible Visible - + All mods visible in the mod list are included Todo mods visible en la lista de mod son incluidos - + export failed: %1 Falló al exportar: %1 - + Install Mod... Instalar Mod... - + Enable all visible Activar todos los visibles - + Disable all visible Desactivar todo lo visible - + Check all for update Comprobar todo para actualizar - + Export to csv... Exportar a CSV... - + All Mods - + Sync to Mods... Sincronizar con Mods... - + Restore Backup Restaurar copia de seguridad - + Remove Backup... Eliminar copia de seguridad... - + Add/Remove Categories Añadir/Quitar Categorías - + Replace Categories Remplazar Categorías - + Primary Category Categoría Primaria - + Change versioning scheme Cambiar esquema de versiones - + Un-ignore update No ignorar actualización - + Ignore update No Ignorar actualización - + Rename Mod... Renombrar Mod... - + Remove Mod... Quitar Mod... - + Reinstall Mod Reinstalar Mod - + Un-Endorse No Avalado - - + + Endorse Avalado - + Won't endorse No avalar - + Endorsement state unknown Estado de avalado desconocido - + Ignore missing data Ignorar data desaparecido - + Visit on Nexus Visite Nexus - + Open in explorer Abrir en explorador - + Information... Informacion... - - + + Exception: Excepción: - - + + Unknown exception Excepción desconocida - + <All> <Todo> - + <Multiple> <Multiple> + + + Delete %n save(s) + Delete save(s) + + + + + - Really delete "%1"? - Realmente desea borrar "%1"? + Realmente desea borrar "%1"? - + Fix Mods... Fix Mods... - Delete - Eliminar + Eliminar - - + + failed to remove %1 Fallo eliminando %1 - - + + failed to create %1 Fallo al crear %1 - + Can't change download directory while downloads are in progress! No se puede cambiar el directorio de descarga, mientras que las descargas están en curso - + Download failed Descarga fallida - + failed to write to file %1 Fallo de escritura en el fichero %1 - + %1 written %1 escrito - + Select binary Selecciona el binario - + Binary Binario - + Enter Name Introducir Nombre - + Please enter a name for the executable Por favor, introduce un nombre para el ejecutable - + Not an executable No es un ejecutable - + This is not a recognized executable. Esto no es un ejecutable reconocido. - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación del archivo falló - - + + Failed to remove "%1". Maybe you lack the required file permissions? Fallo al eliminar "%1". ¿Tal vez no tengas los permisos necesarios? - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + file not found: %1 archivo no encontrado: %1 - + failed to generate preview for %1 fallo al generar vista anticipada para %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. Lo sentimos, no se puede obtener una vista previa de nada. Esta función no admite la extracción de bsas. - + Update available Actualización disponible - + Open/Execute Abrir/Ejecutar - + Add as Executable Añadir un ejecutable - + Preview Previsualizar - + Un-Hide Desocultar - + Hide Ocultar - + Write To File... Escribir al fichero... - + Do you want to endorse Mod Organizer on %1 now? ¿Quieres avalar Mod Organizer en %1 ahora? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 Solicitud de Nexus ha fallado: %1 - - + + login successful login correcto - + login failed: %1. Trying to download anyway login fallado: %1. Intentando descarga de todos modos - + login failed: %1 Falló el inicio de sesión: %1 - + login failed: %1. You need to log-in with Nexus to update MO. login fallido: %1. Necesitas hacer login con Nexus para actualizar MO. - + Error Error - + failed to extract %1 (errorcode %2) fallo al extraer %1 (Código de error %2) - + Extract... Extraer... - + Edit Categories... Editar Categorías... - + Deselect filter - + Remove Eliminar - + Enable all Activar todo - + Disable all Desactivar todos - + Unlock load order Desbloquear el orden de carga - + Lock load order Orden de carga bloqueado - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2892,7 +2912,7 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers ModInfoBackup - + This is the backup of a mod Esta es la copia de seguridad de un mod @@ -3213,227 +3233,227 @@ p, li { white-space: pre-wrap; } Cerrar - + &Delete &Delete - + &Rename &Rename - + &Hide &Ocultar - + &Unhide &Mostrar - + &Open &Abrir - + &New Folder &Nueva Carpeta - - + + Save changes? ¿Guardar cambios? - - + + Save changes to "%1"? ¿Guardar cambios a %1? - + File Exists Existe el fichero - + A file with that name exists, please enter a new one Un fichero con ese nombre ya existe, por favor selecciona otro nombre - + failed to move file Error al mover el fichero - + failed to create directory "optional" Error al crear el directorio "optional" - - + + Info requested, please wait Informacion solicitada, por favor espere - + Main Principal - + Update Actualizacion - + Optional Opcional - + Old Antiguo - + Misc Misc - + Unknown Desconocido - + Current Version: %1 Version actual: %1 - + No update available Sin actualizacion - + (description incomplete, please visit nexus) (descripción incompleta, por favor visite nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Visite en Nexus</a> - + Failed to delete %1 Error borrando %1 - - + + Confirm Confirma - + Are sure you want to delete "%1"? Estas seguro de querer borrar "%1"? - + Are sure you want to delete the selected files? Etas seguro de querer borrar los ficheros seleccionados? - - + + New Folder Nueva Carpeta - + Failed to create "%1" Fallo al crear "%1" - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación de archivo falló. - - + + Failed to remove "%1". Maybe you lack the required file permissions? Fallo al eliminar "% 1". Tal vez no tienes los permisos necesarios? - - + + failed to rename %1 to %2 Fallo al renombrar %1 a %2 - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + Un-Hide Desocultar - + Hide Ocultar - + Name Nombre - + Please enter a name Por favor, introduzca un nombre - + Error Error - + Invalid name. Must be a valid file name Nombre no válido. Debe ser un nombre de archivo válido - + A tweak by that name exists Existe un ajuste con ese nombre - + Create Tweak Crear Ajuste Fino @@ -3441,7 +3461,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3449,7 +3469,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Esta pseudo-mod contiene archivos en el árbol de datos virtual que fue modificado (es decir, mediante el kit de construcción) @@ -3461,18 +3481,18 @@ p, li { white-space: pre-wrap; } fallo al escribir %1/meta.ini: % 2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 no contiene ningún esp/esm y ningún directorio activo (textures, meshes, interface, ...) - + Categories: <br> Categorias: <br> @@ -3480,164 +3500,164 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) Esta entrada contiene los archivos que se han creado en el interior del árbol de datos virtual (es decir, mediante el kit de construcción) - + Backup Copia de seguridad - + No valid game data No hay datos válidos - + Not endorsed yet No avalado aún - + Overwrites files Sobrescribe archivos - + Overwritten files ¿Sobrescribir archivo? - + Overwrites & Overwritten Sobrescribe & sobrescrito - + Redundant Redundante - + Non-MO - + invalid inválido - + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 version instalada: "%1", nueva version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". La nueva versión en Nexus parece ser más antigua que la que has instalado. Esto podría significar, que la versión ha sido retirada (es decir, debido a un error) o el autor utiliza un esquema de control de versiones no estándar y que la versión más reciente es en realidad la más reciente. De cualquier manera si lo deseas, puedes "actualizar". - + Categories: <br> Categorias: <br> - + Invalid name Nombre no válido. - + drag&drop failed: %1 fallo al arrastrar y soltar: %1 - + Confirm Confirma - + Are you sure you want to remove "%1"? Estas seguro de querer borrar "%1"? - + Flags Banderas - + Mod Name Nombre del Mod - + Version Versión - + Priority Prioridad - + Category Categoría - + Nexus ID Nexus IDs - + Installation Instalación - - + + unknown Desconocido - + Name of your mods Nombre de tus mods - + Version of the mod (if available) Version del mod (si esta disponible) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Prioridad de instalacion de tu mod. Cuanto mas alto sea pisara los mods con menos prioridad. - + Category of the mod. Categoría del mod. - + Id of the mod as used on Nexus. Id del mod tal como se utiliza en Nexus. - + Emblemes to highlight things that might require attention. Emblemas para destacar las cosas que podrían requerir atención. - + Time this mod was installed Tiempo que fue instalado el mod @@ -3854,17 +3874,17 @@ p, li { white-space: pre-wrap; } Algunos de los plugins tienen nombres no válidos! Estos plugins no pueden ser cargados por el juego. Por favor, consulte mo_interface.log para ver una lista de plugins afectados y cambiarles el nombre. - + <b>Origin</b>: %1 - + Author Autor - + Description Descripcion @@ -3873,7 +3893,7 @@ p, li { white-space: pre-wrap; } BOSS dll incompatible - + This plugin can't be disabled (enforced by the game) Este plugin no se puede desactivar (impuesto por el juego) @@ -3882,17 +3902,17 @@ p, li { white-space: pre-wrap; } Origen: %1 - + Missing Masters Maestros Desaparecidos - + Enabled Masters Activar Maestros - + failed to restore load order for %1 fallo al restaurar el orden de carga 1% @@ -4462,18 +4482,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements Por favor utilice "Ayuda" en la barra superior para obtener informacion sobre todos los elementos - - + + <Manage...> <Definir...> - + failed to parse profile %1: %2 no se pudo analizar el perfil % 1: %2 @@ -4515,14 +4535,14 @@ p, li { white-space: pre-wrap; } Error - - - + + + wrong file format formato de fichero erroneo - + failed to open %1 Fallo al abrir %1 @@ -4537,17 +4557,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" Fallo al crear "%1" - + Elevation required Elevación requerida - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4562,22 +4582,22 @@ puede ser instalado para trabajar sin elevación. ¿Comenzar elevación de todos modos? (se le preguntará si desea permitir a Mod Organizer.exe realizar cambios en el sistema) - + failed to spawn "%1": %2 Fallo al crear "%1": %2 - + "%1" doesn't exist "%1% no existe - + failed to inject dll into "%1": %2 Fallo al injectar la dll en "%1": %2 - + failed to run "%1" Fallo al abrir %1 @@ -4807,12 +4827,12 @@ puede ser instalado para trabajar sin elevación. tratando de almacenar la configuración para plugin desconocido "%1" - + Confirm Confirmar - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? ¡Cambiar el directorio mod afecta a todos los perfiles! Mods que no están presentes (o de nombres diferentes) en la nueva ubicación se desactivarán en todos los perfiles. No hay manera de deshacer esto a menos que se realice la copia de seguridad de los perfiles manualmente. ¿Proceder? @@ -5486,26 +5506,26 @@ C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves Sobrescribir el fichero "%1" - - - - + + + + Confirm Confirmar - - + + Copy all save games of character "%1" to the profile? Copiar todos los caracteres del juego salvado "%1" para el perfil? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. diff --git a/src/organizer_fr.ts b/src/organizer_fr.ts index 4671d9e8..0fc91a88 100644 --- a/src/organizer_fr.ts +++ b/src/organizer_fr.ts @@ -1403,7 +1403,7 @@ p, li { white-space: pre-wrap; } - + Namefilter Filtre de nom @@ -1527,8 +1527,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh Actualiser @@ -1576,12 +1576,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez &quot;Réparer Mods...&quot; dans le menu contextuel, MO tentera d'activer tous les mods, ESPs et ESMs nécessaires pour résoudre le problème. Rien ne sera désactivé!</span></p></body></html> - + Downloads Téléchargements - + This is a list of mods you downloaded from Nexus. Double click one to install it. Ceci est une liste de mods que vous avez téléchargé de Nexus. Double-cliquez en un pour l'installer. @@ -1628,145 +1628,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar Barre d'outils - + Install Mod Installer mod - + Install &Mod Installer &mod - + Install a new mod from an archive Installer un nouveau mod à partir d'une archive - + Ctrl+M Ctrl+M - + Profiles Profils - + &Profiles &Profils - + Configure Profiles Configurer les profils - + Ctrl+P Ctrl+P - + Executables Programmes - + &Executables Programm&es - + Configure the executables that can be started through Mod Organizer Configure les programmes pouvant être lancés via Mod Organizer - + Ctrl+E Ctrl+E - + Tools - + &Tools - + Ctrl+I Ctrl+H - + Settings Réglages - + &Settings Réglage&s - + Configure settings and workarounds Configurer les réglages et solutions de rechange - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Effectuer une recherche sur Nexus pour plus de mods - + Ctrl+N Ctrl+N - - + + Update Mise-à-jour - + Mod Organizer is up-to-date Mod Organizer est à jour - - + + No Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1774,170 +1774,170 @@ Right now this has very limited functionality - - + + Help Aide - + Ctrl+H Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar Barre d'outils - + Desktop - + Start Menu - + Problems - + There are potential problems with your setup - + Everything seems to be in order - + Help on UI - + Documentation Wiki - + Report Issue - + Tutorials - + failed to save load order: %1 impossible d'enregistrer l'ordre de chargement: %1 - + Name Nom - + Please enter a name for the new profile Veuillez inscrire un nom pour le nouveau profil - + failed to create profile: %1 impossible de créer le profil: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress Téléchargements en cours - + There are still downloads in progress, do you really want to quit? Il encore des téléchargements en cours, voulez-vous vraiment quitter? - + failed to read savegame: %1 impossible de lire la sauvegarde: %1 - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + failed to init plugin %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" impossible de lancer "%1" - + Waiting Attente - + Please press OK once you're logged into steam. Veuillez cliquer OK une fois connecté à steam. @@ -1946,907 +1946,923 @@ Right now this has very limited functionality "%1" introuvable - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Also in: <br> - + No conflict Aucun conflit - + <Edit...> <Modifier...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - - + + Installation successful Installation réussie - - + + Configure Mod Configurer mod - - + + This mod contains ini tweaks. Do you want to configure them now? Ce mod contient des ajustement pour les fichiers ini. Désirez-vous les configurer maintenant? - - + + mod "%1" not found "%1" introuvable - - + + Installation cancelled - - + + The mod was not installed completely. - + Some plugins could not be loaded - + Too many esps and esms enabled - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Choisir mod - + Mod Archive Archive de mod - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - - + + Download started Téléchargement commencé - + failed to update mod list: %1 impossible de mettre à jour la liste de mods: %1 - + failed to spawn notepad.exe: %1 impossible de lancer notepad.exe: %1 - + failed to open %1 impossible d'ouvrir %1 - + failed to change origin name: %1 impossible de changer le nom d'origine: %1 - + <Checked> <Cochés> - + <Unchecked> <Décochés> - + <Update> <Rafraichir> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 impossible de renommer le mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" Impossible de supprimer %1 - - - + + + failed to rename "%1" to "%2" Impossible de renommer %1 en %2 - + Multiple esps activated, please check that they don't conflict. - - - - + + + + Confirm Confirmer - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 impossible de renommer le mod: %1 - - + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - - + + You need to be logged in with Nexus to endorse + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + - + Extract BSA - - + + failed to read %1: %2 Échec de lecture %1: %2 + + + Delete %n save(s) + Delete save(s) + + + + + - + This archive contains invalid hashes. Some files may be broken. - + Nexus ID for this Mod is unknown - + About - + About Qt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - + Executable "%1" not found - + Failed to refresh list of esps: %1 - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + A mod with this name already exists - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Choose what to export - + Everything - + All installed mods are included in the list - + Active Mods Activer Mods - + Only active (checked) mods from your current profile are included - + Visible - + All mods visible in the mod list are included - + export failed: %1 - + Install Mod... Installer mod... - + Enable all visible Activer tous les mods visibles - + Disable all visible Désactiver tous les mods visibles - + Check all for update Vérifier toutes les mises à jour - + Export to csv... - + All Mods - + Sync to Mods... - + Restore Backup - + Remove Backup... - + Add/Remove Categories - + Replace Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... Renommer mod... - + Remove Mod... Supprimer mod... - + Reinstall Mod Installer mod - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Visit on Nexus - + Open in explorer - + Information... Information... - - + + Exception: - - + + Unknown exception - + <All> <Tous> - + <Multiple> - - Really delete "%1"? - - - - + Fix Mods... Réparer mods... - Delete - Supprimer + Supprimer - - + + failed to remove %1 Impossible de supprimer %1 - - + + failed to create %1 impossible de créer %1 - + Can't change download directory while downloads are in progress! - + Download failed Téléchargement commencé - + failed to write to file %1 impossible d'écrire dans le fichier %1 - + %1 written %1 écrit - + Select binary Choisir un programme - + Binary Programme - + Enter Name - + Please enter a name for the executable Veuillez inscrire un nom pour le nouveau profil - + Not an executable Ajouter un programme - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available Mise à jour disponible - + Open/Execute - + Add as Executable Ajouter un programme - + Preview - + Un-Hide - + Hide - + Write To File... Écriture du fichier... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + login successful - + login failed: %1. Trying to download anyway - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + Error Erreur - + failed to extract %1 (errorcode %2) - + Extract... - + Edit Categories... - + Deselect filter - + Remove Supprimer - + Enable all - + Disable all - + Unlock load order - + Lock load order - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2872,7 +2888,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod @@ -3186,227 +3202,227 @@ p, li { white-space: pre-wrap; } Fermer - + &Delete Supprimer - + &Rename &Renommer - + &Hide - + &Unhide - + &Open &Ouvrir - + &New Folder &Nouveau dossier - - + + Save changes? Enregistrer les changements? - - + + Save changes to "%1"? - + File Exists Un fichier du même nom existe - + A file with that name exists, please enter a new one Un fichier ainsi nommé existe déjà, veuillez entrer un nouveau nom - + failed to move file impossible de déplacer le fchier - + failed to create directory "optional" Impossible de créer le dossier "optional" - - + + Info requested, please wait Info demandée, veuillez patienter - + Main Principal - + Update Mise-à-jour - + Optional Optionnel - + Old Ancien - + Misc Divers - + Unknown Inconnu - + Current Version: %1 Version courante: %1 - + No update available Aucune mise-à-jour disponible - + (description incomplete, please visit nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Visiter sur Nexus</a> - + Failed to delete %1 impossible d'effacer %1 - - + + Confirm Confirmer - + Are sure you want to delete "%1"? Voulez-vous vraiment supprimer "%1"? - + Are sure you want to delete the selected files? Voulez-vous vraiment supprimer les fichiers sélectionnés? - - + + New Folder Nouveau dossier - + Failed to create "%1" Impossible de créer "%1" - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - - + + failed to rename %1 to %2 Impossible de renommer %1 en %2 - + There already is a visible version of this file. Replace it? - + Un-Hide - + Hide - + Name Nom - + Please enter a name - + Error Erreur - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3414,7 +3430,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3422,7 +3438,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) @@ -3430,18 +3446,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 ne contient ni esp/esm, ni dossier d'éléments de jeu (textures, meshes, interface, ...) - + Categories: <br> @@ -3449,52 +3465,52 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + Non-MO - + invalid @@ -3503,113 +3519,113 @@ p, li { white-space: pre-wrap; } Version installée: %1, dernière version: %2 - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> - + Invalid name Nom incorrect - + drag&drop failed: %1 - + Confirm Confirmer - + Are you sure you want to remove "%1"? Voulez-vous vraiment supprimer "%1"? - + Flags - + Mod Name Nom du mod - + Version Version - + Priority Priorité - + Category - + Nexus ID IDs Nexus - + Installation - - + + unknown Inconnu - + Name of your mods - + Version of the mod (if available) Version du mod (si disponible) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Priorité d'installation de vos mods. Plus elle est ellevée, plus le mod est "important" et écrasera les fichiers des mods de priorité inférieure. - + Category of the mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Time this mod was installed @@ -3821,17 +3837,17 @@ p, li { white-space: pre-wrap; } - + <b>Origin</b>: %1 - + Author Auteur - + Description Description @@ -3841,22 +3857,22 @@ p, li { white-space: pre-wrap; } - + This plugin can't be disabled (enforced by the game) - + Missing Masters - + Enabled Masters - + failed to restore load order for %1 @@ -4422,18 +4438,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements Veuillez utiliser l'aide dans la barre d'outil pour obtenir des instructions à propos de tous les éléments - - + + <Manage...> <Gérer...> - + failed to parse profile %1: %2 impossible d'analyser le profil %1: %2 @@ -4478,14 +4494,14 @@ p, li { white-space: pre-wrap; } Erreur - - - + + + wrong file format mauvais format de fichier - + failed to open %1 impossible d'ouvrir %1 @@ -4500,17 +4516,17 @@ p, li { white-space: pre-wrap; } DLL par procuration - + failed to spawn "%1" impossible de lancer "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4520,22 +4536,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + failed to spawn "%1": %2 impossible de lancer "%1": %2 - + "%1" doesn't exist "%1" inexistant - + failed to inject dll into "%1": %2 impossible d'injecter le DLL dans "%1": %2 - + failed to run "%1" impossible de lancer "%1" @@ -4765,12 +4781,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + Confirm Confirmer - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? @@ -5417,26 +5433,26 @@ On Windows XP: - - - - + + + + Confirm Confirmer - - + + Copy all save games of character "%1" to the profile? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. diff --git a/src/organizer_ru.ts b/src/organizer_ru.ts index f9e9b493..0dec4291 100644 --- a/src/organizer_ru.ts +++ b/src/organizer_ru.ts @@ -1390,7 +1390,7 @@ p, li { white-space: pre-wrap; } - + Namefilter Фильтр по имени @@ -1576,8 +1576,8 @@ BSA, отмеченные здесь, загружаются так, чтобы - - + + Refresh Обновить @@ -1625,12 +1625,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Если вы выберете в контекстном меню пункт &quot;Исправить моды...&quot;, MO попытается подключить все моды и esp, чтобы исправить эти отсутствующие esp. Это ничего не отключит!</span></p></body></html> - + Downloads Загрузки - + This is a list of mods you downloaded from Nexus. Double click one to install it. Список модов, загруженных с Nexus. Двойной клик для установки. @@ -1639,145 +1639,145 @@ p, li { white-space: pre-wrap; } Компактно - + Show Hidden Показывать скрытые - + Tool Bar Панель инструментов - + Install Mod Установить мод - + Install &Mod Установить &мод - + Install a new mod from an archive Установить новый мод из архива - + Ctrl+M Ctrl+M - + Profiles Профили - + &Profiles &Профили - + Configure Profiles Настройка профилей - + Ctrl+P Ctrl+P - + Executables Программы - + &Executables &Программы - + Configure the executables that can be started through Mod Organizer Настройка программ, которые могут быть запущены через Mod Organizer - + Ctrl+E Ctrl+E - + Tools Инструменты - + &Tools &Инструменты - + Ctrl+I Ctrl+I - + Settings Настройки - + &Settings &Настройки - + Configure settings and workarounds Настройка параметров и способов обхода - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods Поиск дополнительных модов на Nexus - + Ctrl+N Ctrl+N - - + + Update Обновление - + Mod Organizer is up-to-date Mod Organizer обновлен - - + + No Problems Проблем не обнаружено - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1788,211 +1788,211 @@ Right now this has very limited functionality Прямо сейчас этот функционал сильно ограничен - - + + Help Справка - + Ctrl+H Ctrl+H - + Endorse MO Одобрить MO - - + + Endorse Mod Organizer Одобрить Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar Панель инструментов - + Desktop Рабочий стол - + Start Menu Меню Пуск - + Problems Проблемы - + There are potential problems with your setup Есть возможные проблемы с вашей установкой - + Everything seems to be in order Кажется всё в порядке - + Help on UI Справка по интерфейсу - + Documentation Wiki Wiki-документация - + Report Issue Сообщить о проблеме - + Tutorials Уроки - + About О программе - + About Qt О библиотеке Qt - + failed to save load order: %1 не удалось сохранить порядок загрузки: %1 - + Name Имя - + Please enter a name for the new profile Введите имя нового профиля - + failed to create profile: %1 не удалось создать профиль: %1 - + Show tutorial? Показать урок? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. Вы запустили Mod Organizer в первый раз. Вы хотите просмотреть уроки по основным возможностям? В случае отказа вы всегда можете открыть уроки из меню "Помощь". - + Downloads in progress Загрузки в процессе - + There are still downloads in progress, do you really want to quit? Загрузки всё ещё в процессе, вы правда хотите выйти? - + failed to read savegame: %1 не удалось прочесть сохранение: %1 - + Plugin "%1" failed: %2 Плагин "%1" не удалось: %2 - + Plugin "%1" failed Плагин "%1" не удалось - + failed to init plugin %1: %2 не удалось инициализировать плагин %1: %2 - + Plugin error Ошибка плагина - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) Кажется, что при последнем запуске не удалось загрузить плагин "%1" и это привело к падению MO. Вы хотите отключить его? (Замечание: Если это первый раз, когда вы видите такое сообщение для этого плагина, вероятно вы захотите сделать ещё одну попытка. Плагин может восстановиться после проблемы) - + Failed to start "%1" Не удалось запустить "%1" - + Waiting Ожидание - + Please press OK once you're logged into steam. Нажмите OK как только вы войдете в Steam. - + Start Steam? Запустить Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? Требуется запущенный Steam, для корректного запуска игры. Должен ли MO попытаться запустить Steam сейчас? - + Also in: <br> Также в: <br> - + No conflict Конфликтов нет - + <Edit...> <Правка...> - + This bsa is enabled in the ini file so it may be required! Этот bsa подключен через ini, так что он может быть необходим! @@ -2001,237 +2001,246 @@ Right now this has very limited functionality Этот архив все равно будет загружен, так как есть плагин с одноименным названием, но его файлы не будут следовать порядку установки! - + Activating Network Proxy Подключение сетевого прокси - - + + Installation successful Установка завершена - - + + Configure Mod Настройка мода - - + + This mod contains ini tweaks. Do you want to configure them now? Этот мод включает настройки ini. Вы хотите настроить их сейчас? - - + + mod "%1" not found мод "%1" не найден - - + + Installation cancelled Установка отменена - - + + The mod was not installed completely. Мод не был установлен полностью. - + Some plugins could not be loaded Некоторые плагины не могут быть загружены - + Too many esps and esms enabled Подключено слишком много esp и esm - - + + Description missing Описание отсутствует - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Следующие плагины не могут быть загружены. Причина возможно в отсутствующих зависимостях (таких как python) или в устаревшей версии: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> Игра не позволяет загрузить больше 255 активных плагинов (включая официальные). Вам нужно отключить некоторые ненужные плагины или объединить несколько небольших плагинов в один. Инструкция может быть найдена здесь: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Выберете мод - + Mod Archive Архив мода - + Start Tutorial? Начать урок? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Вы собираетесь открыть урок. По техническим причинам будет невозможно закончить его досрочно. Продолжить? - - + + Download started Загрузка начата - + failed to update mod list: %1 не удалось обновить список модов: %1 - + failed to spawn notepad.exe: %1 не удалось вызвать notepad.exe: %1 - + failed to open %1 не удалось открыть %1 - + failed to change origin name: %1 не удалось изменить оригинальное имя: %1 - + Executable "%1" not found Исполняемый файл "%1" не найден - + Failed to refresh list of esps: %1 Не удалось обновить список esp: %1 - + failed to move "%1" from mod "%2" to "%3": %4 не удалось переместить "%1" из мода "%2" в "%3": %4 - + <Checked> <Подключен> - + <Unchecked> <Отключен> - + <Update> <Обновлен> - + <No category> <Без категории> - + <Conflicted> <Конфликтует> - + <Not Endorsed> <Не одобрено> - + failed to rename mod: %1 не удалось переименовать мод: %1 - + Overwrite? Перезаписать? - + This will replace the existing mod "%1". Continue? Это заменит существующий мод "%1". Продолжить? - + failed to remove mod "%1" не удалось удалить мод "%1" - - - + + + failed to rename "%1" to "%2" не удалось переименовать "%1" в "%2" - + Multiple esps activated, please check that they don't conflict. Подключено несколько esp, выберете из них не конфликтующие. - - - - + + + + Confirm Подтверждение - + Remove the following mods?<br><ul>%1</ul> Удалить следующие моды?<br><ul>%1</ul> - + failed to remove mod: %1 не удалось удалить мод: %1 - - + + Failed Неудача - + Installation file no longer exists Установочный файл больше не существует - + Mods installed with old versions of MO can't be reinstalled in this way. Моды, установленные с использованием старых версий MO не могут быть переустановленны таким образом. - - + + You need to be logged in with Nexus to endorse Вы должны быть авторизированы на Nexus, чтобы одобрять. + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + + + - + Extract BSA Распаковать BSA @@ -2242,634 +2251,647 @@ Right now this has very limited functionality (Это удалит BSA после завершения. Если вы не знаете ничего о BSAs, просто откажитесь) - - + + failed to read %1: %2 не удалось прочесть %1: %2 - + This archive contains invalid hashes. Some files may be broken. Архив содержит неверные хеш-суммы. Некоторые файлы могут быть испорчены. - + Nexus ID for this Mod is unknown Nexus ID для этого мода неизвестен - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... Создать мод... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Это переместит все файлы из перезаписи в новый, стандартный мод. Пожалуйста введите имя: - + A mod with this name already exists Мод с таким именем уже существует - + Continue? Продолжить? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. Схема управления версиями принимает решение, какая версия считается новее другой. Функция может попробовать угадать схему управления версиями, при условии, что установленная версия является устаревшей. - - + + Sorry Извините - + I don't know a versioning scheme where %1 is newer than %2. Мне неизвестна схема управления версиями, где %1 новее %2. - + Really enable all visible mods? Действительно подключить все видимые моды? - + Really disable all visible mods? Действительно отключить все видимые моды? - + Choose what to export Выберете, что экспортировать - + Everything Всё - + All installed mods are included in the list Все установленные моды, включенные в список - + Active Mods Активные моды - + Only active (checked) mods from your current profile are included Включены все активные (подключенные) моды вашего текущего профиля - + Visible Видимые - + All mods visible in the mod list are included Включены все моды, видимые в списке модов - + export failed: %1 экспорт не удался: %1 - + Install Mod... Установить мод... - + Enable all visible Включить все видимые - + Disable all visible Отключить все видимые - + Check all for update Проверить все на обновления - + Export to csv... Экспорт в csv... - + All Mods - + Sync to Mods... Синхронизировать с модами... - + Restore Backup Восстановить из резервной копии - + Remove Backup... Удалить резервную копию... - + Add/Remove Categories Добавить/Удалить категории - + Replace Categories Заменить категории - + Primary Category Основная категория - + Change versioning scheme Изменить схему управления версиями - + Un-ignore update Снять игнорирование обновления - + Ignore update Игнорировать обновление - + Rename Mod... Переименовать мод... - + Remove Mod... Удалить мод... - + Reinstall Mod Переустановить мод - + Un-Endorse Отменить одобрение - - + + Endorse Одобрить - + Won't endorse Не одобрять - + Endorsement state unknown Статус одобрения неизвестен - + Ignore missing data Игнорировать отсутствующие данные - + Visit on Nexus Перейти на Nexus - + Open in explorer Открыть в проводнике - + Information... Информация... - - + + Exception: Исключение: - - + + Unknown exception Неизвестное исключение - + <All> <Все> - + <Multiple> <Несколько> + + + Delete %n save(s) + Delete save(s) + + + + + + - Really delete "%1"? - Действительно удалить "%1"? + Действительно удалить "%1"? - + Fix Mods... Исправить моды... - Delete - Удалить + Удалить - - + + failed to remove %1 не удалось удалить %1 - - + + failed to create %1 не удалось создать %1 - + Can't change download directory while downloads are in progress! Нельзя изменить каталог для загрузок, когда загрузки ещё не завершены! - + Download failed Загрузка не удалась - + failed to write to file %1 ошибка записи в файл %1 - + %1 written %1 записан - + Select binary Выберете исполняемый файл - + Binary Исполняемый файл - + Enter Name Введите имя - + Please enter a name for the executable Введите название для программы - + Not an executable Не является исполняемым - + This is not a recognized executable. Это неверный исполняемый файл. - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Уже существует скрытая версия этого файла. Заменить? - - + + File operation failed Операция с файлом не удалась - - + + Failed to remove "%1". Maybe you lack the required file permissions? Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + file not found: %1 файл не найден: %1 - + failed to generate preview for %1 не удалось получить предосмотр для %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. Невозможно получить предосмотр чего-либо. Функция на данный момент не поддерживает извлечение из bsa. - + Update available Доступно обновление - + Open/Execute Открыть/Выполнить - + Add as Executable Добавить как исполняемый - + Preview Предосмотр - + Un-Hide Показать - + Hide Скрыть - + Write To File... Записать в файл... - + Do you want to endorse Mod Organizer on %1 now? Вы хотите одобрить Mod Organizer на %1 сейчас? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 Запрос на Nexus не удался: %1 - - + + login successful успешный вход - + login failed: %1. Trying to download anyway вход не удался: %1. Пытаюсь загрузить всё равно - + login failed: %1 войти не удалось: %1 - + login failed: %1. You need to log-in with Nexus to update MO. войти не удалось: %1. Вам нужно войти на Nexus, чтобы обновить MO. - + Error Ошибка - + failed to extract %1 (errorcode %2) не удалось распаковать %1 (код ошибки %2) - + Extract... Распаковать... - + Edit Categories... Изменить категории... - + Deselect filter - + Remove Удалить - + Enable all Включить все - + Disable all Отключить все - + Unlock load order Снять фиксацию порядка загрузки - + Lock load order Зафиксировать порядок загрузки - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2903,7 +2925,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod Это резервная копия мода @@ -3224,227 +3246,227 @@ p, li { white-space: pre-wrap; } Закрыть - + &Delete &Удалить - + &Rename &Переименовать - + &Hide &Скрыть - + &Unhide &Показать - + &Open &Открыть - + &New Folder &Новая папка - - + + Save changes? Сохранить изменения? - - + + Save changes to "%1"? Сохранить изменения в "%1"? - + File Exists Файл уже существует - + A file with that name exists, please enter a new one Файл с таким именем уже существует, укажите другое - + failed to move file не удалось переместить файл - + failed to create directory "optional" не удалось создать папку "optional" - - + + Info requested, please wait Информация запрошена, пожалуйста, подождите - + Main Главное - + Update Обновление - + Optional Опционально - + Old Старые - + Misc Разное - + Unknown Неизвестно - + Current Version: %1 Текущая версия: %1 - + No update available Нет доступных обновлений - + (description incomplete, please visit nexus) (описание не завершено, смотрите на nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Перейти на Nexus</a> - + Failed to delete %1 Не удалось удалить %1 - - + + Confirm Подтверждение - + Are sure you want to delete "%1"? Вы уверены, что хотите удалить "%1"? - + Are sure you want to delete the selected files? Вы уверены, что хотите удалить выбранные файлы? - - + + New Folder Новая папка - + Failed to create "%1" Не удалось создать "%1" - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Скрытая версия этого файла уже существует. Заменить? - - + + File operation failed Не удалась операция с файлом - - + + Failed to remove "%1". Maybe you lack the required file permissions? Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - - + + failed to rename %1 to %2 не удалось переименовать %1 в %2 - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + Un-Hide Показать - + Hide Скрыть - + Name Имя - + Please enter a name Пожалуйста, введите имя - + Error Ошибка - + Invalid name. Must be a valid file name Неверное имя. Необходимо допустимое имя файла. - + A tweak by that name exists Настройка с таким именем существует - + Create Tweak Создать настройку @@ -3452,7 +3474,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3460,7 +3482,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Этот псевдо-мод содержит файлы из виртуального древа данных, которые были изменены (в Construction Kit и других программах) @@ -3472,18 +3494,18 @@ p, li { white-space: pre-wrap; } не удалось записать %1/meta.ini: %2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 не содержит ни esp/esm, ни папок ресурсов (textures, meshes, interface, ...) - + Categories: <br> Категории: <br> @@ -3491,164 +3513,164 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) Эта запись включает файлы, которые были созданы внутри виртуального древа (с помощью Construction Kit и др. программ) - + Backup Резервная копия - + No valid game data Неверные игровые данные - + Not endorsed yet Еще не одобрено - + Overwrites files Заменяет файлы - + Overwritten files Замененные файлы - + Overwrites & Overwritten Заменяет и заменяется - + Redundant Избыточные - + Non-MO - + invalid неверные - + installed version: "%1", newest version: "%2" installed version: %1, newest version: %2 установлена версия: %1, новейшая версия: %2 - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". Новейшая версия на Nexus кажется старее той, что установлена у вас. Это может означать, что ваша версия была снята (в связи с ошибкой и т.п.) или автор использует нестандартную схему версий, а новейшая версия на самом деле выше. В любом случае, вы можете "обновить". - + Categories: <br> Категории: <br> - + Invalid name Недопустимое имя - + drag&drop failed: %1 перетаскивание не удалось: %1 - + Confirm Подтверждение - + Are you sure you want to remove "%1"? Вы действительно хотите удалить "%1"? - + Flags Флаги - + Mod Name Имя мода - + Version Версия - + Priority Приоритет - + Category Категория - + Nexus ID Nexus ID - + Installation Установка - - + + unknown неизвестный - + Name of your mods Имя ваших модов - + Version of the mod (if available) Версия мода (если доступно) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Приоритет установки для ваших модов. Файлы модов с большим приоритетом перезапишут файлы модов с меньшим. - + Category of the mod. Категория мода. - + Id of the mod as used on Nexus. ID мода, используемый на Nexus. - + Emblemes to highlight things that might require attention. Выделяет подсветкой вещи, которые могут потребовать внимания. - + Time this mod was installed Время, когда мод был установлен. @@ -3865,17 +3887,17 @@ p, li { white-space: pre-wrap; } Некоторые из ваших плагинов имеют неверные имена. Эти плагины не могут быть загружены игрой. Смотрите mo_interface.log для получения списка таких плагинов и переименуйте их. - + <b>Origin</b>: %1 - + Author Автор - + Description Описание @@ -3884,7 +3906,7 @@ p, li { white-space: pre-wrap; } BOSS dll несовместим - + This plugin can't be disabled (enforced by the game) Этот плагин не может быть отключен (грузится игрой принудительно) @@ -3893,17 +3915,17 @@ p, li { white-space: pre-wrap; } Источник: %1 - + Missing Masters Отсутствующие мастерфайлы - + Enabled Masters Подключенные мастерфайлы - + failed to restore load order for %1 не удалось восстановить порядок загрузки для %1 @@ -4473,18 +4495,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements Используйте пункт "Справка" на панели инструментов, чтобы получить инструкции по использованию всех элементов. - - + + <Manage...> <Управлять...> - + failed to parse profile %1: %2 не удалось обработать профиль %1: %2 @@ -4526,14 +4548,14 @@ p, li { white-space: pre-wrap; } Ошибка - - - + + + wrong file format неверный формат файла - + failed to open %1 не удалось открыть %1 @@ -4548,17 +4570,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" не удалось вызвать "%1" - + Elevation required Требуется повышение прав - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4573,22 +4595,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Запустить с повышенными правами в любом случае? (будет выведен запрос о разрешении ModOrganizer.exe сделать изменения в системе) - + failed to spawn "%1": %2 не удалось вызвать "%1": %2 - + "%1" doesn't exist "%1" не существует - + failed to inject dll into "%1": %2 не удалось подключить dll к "%1": %2 - + failed to run "%1" не удалось запустить "%1" @@ -4818,12 +4840,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe попытка сохранить настройку для неизвестного плагина "%1" - + Confirm Подтверждение - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? Изменение каталога для модов отразится на всех ваших профилях. Нельзя будет отменить это, если только вы не сохранили резервные копии ваших профилей вручную. Продолжить? @@ -5497,26 +5519,26 @@ On Windows XP: Перезаписать файл "%1" - - - - + + + + Confirm Подтверждение - - + + Copy all save games of character "%1" to the profile? Скопировать все игры с персонажем "%1" в профиль? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Переместить все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. Скопировать все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. diff --git a/src/organizer_tr.ts b/src/organizer_tr.ts index 73942e96..4b3be328 100644 --- a/src/organizer_tr.ts +++ b/src/organizer_tr.ts @@ -1394,7 +1394,7 @@ p, li { white-space: pre-wrap; } - + Namefilter @@ -1501,8 +1501,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh @@ -1544,12 +1544,12 @@ p, li { white-space: pre-wrap; } - + Downloads - + This is a list of mods you downloaded from Nexus. Double click one to install it. @@ -1596,145 +1596,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar - + Install Mod - + Install &Mod - + Install a new mod from an archive - + Ctrl+M - + Profiles - + &Profiles - + Configure Profiles - + Ctrl+P - + Executables - + &Executables - + Configure the executables that can be started through Mod Organizer - + Ctrl+E - + Tools - + &Tools - + Ctrl+I - + Settings - + &Settings - + Configure settings and workarounds - + Ctrl+S - + Nexus - + Search nexus network for more mods - + Ctrl+N - - + + Update - + Mod Organizer is up-to-date - - + + No Problems - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1742,1075 +1742,1085 @@ Right now this has very limited functionality - - + + Help - + Ctrl+H - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar - + Desktop - + Start Menu - + Problems - + There are potential problems with your setup - + Everything seems to be in order - + Help on UI - + Documentation Wiki - + Report Issue - + Tutorials - + failed to save load order: %1 - + Name İsim - + Please enter a name for the new profile - + failed to create profile: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress - + There are still downloads in progress, do you really want to quit? - + failed to read savegame: %1 - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + failed to init plugin %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" - + Waiting - + Please press OK once you're logged into steam. - + Start Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Also in: <br> - + No conflict - + <Edit...> - + This bsa is enabled in the ini file so it may be required! - + Activating Network Proxy - - + + Installation successful - - + + Configure Mod - - + + This mod contains ini tweaks. Do you want to configure them now? - - + + mod "%1" not found - - + + Installation cancelled - - + + The mod was not installed completely. - + Some plugins could not be loaded - + Too many esps and esms enabled - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod - + Mod Archive - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - - + + Download started - + failed to update mod list: %1 - + failed to spawn notepad.exe: %1 - + failed to open %1 - + failed to change origin name: %1 - + <Checked> - + <Unchecked> - + <Update> - + <No category> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 - + Overwrite? - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" - - - + + + failed to rename "%1" to "%2" "%1"yi "%2" olarak yeniden adlandırma başarılı olamadı. - + Multiple esps activated, please check that they don't conflict. - - - - + + + + Confirm Onayla - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 - - + + Failed - + Installation file no longer exists - + Mods installed with old versions of MO can't be reinstalled in this way. - - + + You need to be logged in with Nexus to endorse + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + - + Extract BSA - - + + failed to read %1: %2 %1: %2 okunamadı - + This archive contains invalid hashes. Some files may be broken. - + Nexus ID for this Mod is unknown - + About - + About Qt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - + Executable "%1" not found - + Failed to refresh list of esps: %1 - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + A mod with this name already exists - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? - + Really disable all visible mods? - + Choose what to export - + Everything - + All installed mods are included in the list - + Active Mods - + Only active (checked) mods from your current profile are included - + Visible - + All mods visible in the mod list are included - + export failed: %1 - + Install Mod... - + Enable all visible - + Disable all visible - + Check all for update - + Export to csv... - + All Mods - + Sync to Mods... - + Restore Backup - + Remove Backup... - + Add/Remove Categories - + Replace Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... - + Remove Mod... - + Reinstall Mod - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Visit on Nexus - + Open in explorer - + Information... - - + + Exception: - - + + Unknown exception - + <All> - + <Multiple> - - Really delete "%1"? - - - - + Fix Mods... - - - Delete - + + + Delete %n save(s) + Delete save(s) + + + - - + + failed to remove %1 - - + + failed to create %1 - + Can't change download directory while downloads are in progress! - + Download failed - + failed to write to file %1 - + %1 written - + Select binary - + Binary İkili değer - + Enter Name - + Please enter a name for the executable - + Not an executable - + This is not a recognized executable. - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + There already is a visible version of this file. Replace it? - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available - + Open/Execute - + Add as Executable - + Preview - + Un-Hide - + Hide - + Write To File... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + login successful - + login failed: %1. Trying to download anyway - + login failed: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + Error - + failed to extract %1 (errorcode %2) - + Extract... - + Edit Categories... - + Deselect filter - + Remove - + Enable all - + Disable all - + Unlock load order - + Lock load order - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2836,7 +2846,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod @@ -3137,227 +3147,227 @@ p, li { white-space: pre-wrap; } Kapat - + &Delete - + &Rename - + &Hide - + &Unhide - + &Open &Aç - + &New Folder - - + + Save changes? - - + + Save changes to "%1"? - + File Exists - + A file with that name exists, please enter a new one - + failed to move file - + failed to create directory "optional" - - + + Info requested, please wait - + Main - + Update - + Optional - + Old - + Misc - + Unknown - + Current Version: %1 - + No update available - + (description incomplete, please visit nexus) - + <a href="%1">Visit on Nexus</a> - + Failed to delete %1 - - + + Confirm Onayla - + Are sure you want to delete "%1"? - + Are sure you want to delete the selected files? - - + + New Folder - + Failed to create "%1" - - + + Replace file? - + There already is a hidden version of this file. Replace it? - - + + File operation failed - - + + Failed to remove "%1". Maybe you lack the required file permissions? - - + + failed to rename %1 to %2 - + There already is a visible version of this file. Replace it? - + Un-Hide - + Hide - + Name İsim - + Please enter a name - + Error - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3365,7 +3375,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3373,7 +3383,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) @@ -3381,18 +3391,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - + Categories: <br> @@ -3400,163 +3410,163 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files - + Overwrites & Overwritten - + Redundant - + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> - + Invalid name - + drag&drop failed: %1 - + Confirm Onayla - + Are you sure you want to remove "%1"? - + Flags - + Mod Name - + Version Versiyon - + Priority - + Category - + Nexus ID - + Installation - - + + unknown - + Name of your mods - + Version of the mod (if available) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. - + Category of the mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Time this mod was installed @@ -3768,17 +3778,17 @@ p, li { white-space: pre-wrap; } - + <b>Origin</b>: %1 - + Author Yaratıcı - + Description @@ -3788,22 +3798,22 @@ p, li { white-space: pre-wrap; } - + This plugin can't be disabled (enforced by the game) - + Missing Masters - + Enabled Masters - + failed to restore load order for %1 @@ -4356,18 +4366,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements - - + + <Manage...> - + failed to parse profile %1: %2 @@ -4408,14 +4418,14 @@ p, li { white-space: pre-wrap; } - - - + + + wrong file format - + failed to open %1 @@ -4430,17 +4440,17 @@ p, li { white-space: pre-wrap; } - + failed to spawn "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4450,22 +4460,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + failed to spawn "%1": %2 - + "%1" doesn't exist - + failed to inject dll into "%1": %2 - + failed to run "%1" @@ -4695,12 +4705,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + Confirm Onayla - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? @@ -5326,26 +5336,26 @@ On Windows XP: - - - - + + + + Confirm Onayla - - + + Copy all save games of character "%1" to the profile? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. diff --git a/src/organizer_zh_CN.ts b/src/organizer_zh_CN.ts index 333d9ce7..291be657 100644 --- a/src/organizer_zh_CN.ts +++ b/src/organizer_zh_CN.ts @@ -1404,7 +1404,7 @@ p, li { white-space: pre-wrap; } - + Namefilter 名称过滤器 @@ -1531,8 +1531,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh 刷新 @@ -1580,12 +1580,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右键菜单中点击“修复 Mod”,那么 MO 便会尝试激活所有 Mod 和 esp 来修复那些缺失的 esp,它并不会禁用任何东西!</span></p></body></html> - + Downloads 下载 - + This is a list of mods you downloaded from Nexus. Double click one to install it. 这是从Nexus已下载的模组的列表,双击进行安装。 @@ -1636,145 +1636,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar 工具栏 - + Install Mod 安装模组 - + Install &Mod 安装 &Mod - + Install a new mod from an archive 通过压缩包来安装一个新 Mod - + Ctrl+M Ctrl+M - + Profiles 配置文件 - + &Profiles &配置文件 - + Configure Profiles 设置配置文件 - + Ctrl+P Ctrl+P - + Executables 可执行程序 - + &Executables &可执行程序 - + Configure the executables that can be started through Mod Organizer 配置可通过 MO 来启动的程序 - + Ctrl+E Ctrl+E - + Tools 工具 - + &Tools 工具(&T) - + Ctrl+I Ctrl+I - + Settings 设置 - + &Settings &设置 - + Configure settings and workarounds 配置设定和解决方案 - + Ctrl+S Ctrl+S - + Nexus Nexus - + Search nexus network for more mods 搜索nexus网以获取更多 Mod - + Ctrl+N Ctrl+N - - + + Update 更新 - + Mod Organizer is up-to-date Mod Organizer 现在是最新版本 - - + + No Problems 没有问题 - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1785,84 +1785,84 @@ Right now this has very limited functionality 当前此项所能提供的功能非常有限 - - + + Help 帮助 - + Ctrl+H Ctrl+H - + Endorse MO 称赞 MO - - + + Endorse Mod Organizer 称赞 Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar 工具栏 - + Desktop 桌面 - + Start Menu 开始菜单 - + Problems 问题 - + There are potential problems with your setup 您的安装中存在潜在的问题 - + Everything seems to be in order 一切井然有序 - + Help on UI 界面帮助 - + Documentation Wiki 说明文档 (维基) - + Report Issue 报告问题 - + Tutorials 教程 @@ -1871,88 +1871,88 @@ Right now this has very limited functionality 无法保存档案顺序,您确定您有权限更改 "%1"? - + failed to save load order: %1 无法保存加载顺序: %1 - + Name 名称 - + Please enter a name for the new profile 请为新配置文件输入一个名称 - + failed to create profile: %1 无法创建配置文件: %1 - + Show tutorial? 显示教程? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. 你正在第一次使用 Mod Organizer。是否希望显示教程以了解它的基本特性?如果选择否你也可以从“帮助”菜单中开始教程。 - + Downloads in progress 正在下载 - + There are still downloads in progress, do you really want to quit? 仍有正在进行中的下载,您确定要退出吗? - + failed to read savegame: %1 无法读取存档: %1 - + Plugin "%1" failed: %2 插件 "%1" 失败: %2 - + Plugin "%1" failed 插件 "%1" 失败 - + failed to init plugin %1: %2 插件初始化失败 %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" 无法启动 "%1" - + Waiting 稍等 - + Please press OK once you're logged into steam. 当您登录 Steam 时请点击确定。 @@ -1961,32 +1961,32 @@ Right now this has very limited functionality "%1" 未找到 - + Start Steam? 启动 Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? 想要正确地启动游戏,Steam 必须处于运行状态。需要MO尝试启动 Steam 吗? - + Also in: <br> 也在: <br> - + No conflict 没有冲突 - + <Edit...> <编辑...> - + This bsa is enabled in the ini file so it may be required! 该 BSA 已在 ini 文件中启用,因此它可能是必需的。 @@ -1995,222 +1995,229 @@ Right now this has very limited functionality 此档案还是会被加载,因为存在同名插件。不过它所包含的的文件不会遵循安装顺序! - + Activating Network Proxy 激活网络代理 - - + + Installation successful 安装成功 - - + + Configure Mod 配置 Mod - - + + This mod contains ini tweaks. Do you want to configure them now? 此 Mod 中包含 ini 设定文件,您想现在就对它们进行配置吗? - - + + mod "%1" not found Mod "%1" 未找到 - - + + Installation cancelled 安装已取消 - - + + The mod was not installed completely. 该模组没有完全安装。 - + Some plugins could not be loaded 一些插件无法载入 - + Too many esps and esms enabled - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod 选择模组 - + Mod Archive Mod 压缩包 - + Start Tutorial? 开始教程? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? 即将开始帮助教程。因为技术原因可能无法随时中断。是否继续? - - + + Download started 开始下载 - + failed to update mod list: %1 无法更新 Mod 列表: %1 - + failed to spawn notepad.exe: %1 无法生成 notepad.exe: %1 - + failed to open %1 无法打开 %1 - + failed to change origin name: %1 无法更改原始文件名: %1 - + <Checked> <已勾选> - + <Unchecked> <未勾选> - + <Update> <有更新> - + <No category> <无类别> - + <Conflicted> <有冲突> - + <Not Endorsed> - + failed to rename mod: %1 无法重命名 Mod: %1 - + Overwrite? 覆盖 - + This will replace the existing mod "%1". Continue? 这将会覆盖已存在的mod "%1"。是否继续? - + failed to remove mod "%1" 无法移动 Mod: %1 - - - + + + failed to rename "%1" to "%2" 重命名 "%1 "为 "%2" 时出错 - + Multiple esps activated, please check that they don't conflict. 多个esp已激活,请检查以确保不冲突。 - - - - + + + + Confirm 确认 - + Remove the following mods?<br><ul>%1</ul> 是否删除下列mod?<br><ul>%1</ul> - + failed to remove mod: %1 无法移动 Mod: %1 - - + + Failed 失败 - + Installation file no longer exists 安装文件不复存在 - + Mods installed with old versions of MO can't be reinstalled in this way. 旧版 MO 安装的 Mod 无法使用此方法重新安装。 - - + + You need to be logged in with Nexus to endorse 你必须登录 Nexus 才能点“称赞” + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + - + Extract BSA 解压 BSA @@ -2221,657 +2228,660 @@ Right now this has very limited functionality (解压完成后,BSA 文件将会被删除。如果您不了解 BSA 的话,请选择“否”) - - + + failed to read %1: %2 无法读取 %1: %2 - + This archive contains invalid hashes. Some files may be broken. 压缩包 Hash 值错误。部分文件可能已经损坏。 - + Nexus ID for this Mod is unknown 此模组的Nexus ID未知 - + About - + About Qt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - + Executable "%1" not found - + Failed to refresh list of esps: %1 - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... 创建Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + A mod with this name already exists 同名模组已存在。 - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? 确定要启用全部可见的模组吗? - + Really disable all visible mods? 确定要禁用全部可见的模组吗? - + Choose what to export 选择要导出的内容 - + Everything 全部 - + All installed mods are included in the list 所有包含在列表的已安装mod - + Active Mods 激活模组 - + Only active (checked) mods from your current profile are included 仅包含当前配置文件中已激活(打勾)的mod - + Visible 可见的 - + All mods visible in the mod list are included 包含列表中所有可见的mod - + export failed: %1 导出失败: %1 - + Install Mod... 安装模组... - + Enable all visible 启用所有可见项目 - + Disable all visible 禁用所有可见项目 - + Check all for update 检查所有更新 - + Export to csv... 导出为 CSV... - + All Mods - + Sync to Mods... 同步到 Mod... - + Restore Backup 还原备份 - + Remove Backup... 还原备份... - + Add/Remove Categories - + Replace Categories - + Primary Category 主分类 - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... 重命名模组... - + Remove Mod... 移除模组... - + Reinstall Mod 重新安装模组 - + Un-Endorse 取消称赞 - - + + Endorse 称赞 - + Won't endorse 不想称赞 - + Endorsement state unknown 称赞状态不明 - + Ignore missing data 忽略丢失的数据 - + Visit on Nexus 在Nexus上浏览 - + Open in explorer 在资源管理器中打开 - + Information... 信息... - - + + Exception: 例外: - - + + Unknown exception 未知的例外 - + <All> <全部> - + <Multiple> XX - - Really delete "%1"? - - - - + Fix Mods... 修复模组... - - - Delete - + + + Delete %n save(s) + Delete save(s) + + + - - + + failed to remove %1 无法删除 %1 - - + + failed to create %1 无法创建 %1 - + Can't change download directory while downloads are in progress! 下载文件时不能修改下载目录! - + Download failed 下载失败 - + failed to write to file %1 无法写入文件 %1 - + %1 written 已写入 %1 - + Select binary 选择可执行文件 - + Binary 程序 - + Enter Name 输入名称 - + Please enter a name for the executable 请为该可执行程序输入一个名称 - + Not an executable 不是可执行程序 - + This is not a recognized executable. 无法识别的可执行文件 - - + + Replace file? 替换文件? - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 无法移除 "%1"。也许您需要足够的文件权限? - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available 更新可用 - + Open/Execute 打开/执行 - + Add as Executable 添加为可执行文件 - + Preview - + Un-Hide 取消隐藏 - + Hide 隐藏 - + Write To File... 写入文件... - + Do you want to endorse Mod Organizer on %1 now? 是否现在就在 %1 点赞支持 Mod Organizer? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 发往 Nexus 的请求失败: %1 - - + + login successful 登录成功 - + login failed: %1. Trying to download anyway 登录失败: %1,请尝试使用别的方法下载 - + login failed: %1 无法登录: %1 - + login failed: %1. You need to log-in with Nexus to update MO. 登录失败: %1。您需要登录到N网才能更新 MO - + Error 错误 - + failed to extract %1 (errorcode %2) 无法解压 %1 (错误代码 %2) - + Extract... 解压... - + Edit Categories... 编辑类别... - + Deselect filter - + Remove 移除 - + Enable all 全部启用 - + Disable all 全部禁用 - + Unlock load order 解锁加载顺序 - + Lock load order 锁定加载顺序 - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2897,7 +2907,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod 这是模组的备份 @@ -3211,227 +3221,227 @@ p, li { white-space: pre-wrap; } 关闭 - + &Delete &删除 - + &Rename &重命名 - + &Hide &隐藏 - + &Unhide &取消隐藏 - + &Open &打开 - + &New Folder &新建文件夹 - - + + Save changes? 保存更改吗? - - + + Save changes to "%1"? 将更改保存到“%1%”吗? - + File Exists 文件已存在 - + A file with that name exists, please enter a new one 文件名已存在,请输入其它名称 - + failed to move file 无法移动文件 - + failed to create directory "optional" 无法创建 "optional" 目录 - - + + Info requested, please wait 请求信息已发出,请稍后 - + Main 主要文件 - + Update 更新 - + Optional 可选文件 - + Old 旧档 - + Misc 杂项 - + Unknown 未知 - + Current Version: %1 当前版本: %1 - + No update available 没有可用的更新 - + (description incomplete, please visit nexus) (描述信息不完整,请访问N网) - + <a href="%1">Visit on Nexus</a> <a href="%1">访问N网</a> - + Failed to delete %1 无法删除 %1 - - + + Confirm 确认 - + Are sure you want to delete "%1"? 确定要删除 "%1" 吗? - + Are sure you want to delete the selected files? 确定要删除所选的文件吗? - - + + New Folder 新建文件夹 - + Failed to create "%1" 无法创建 "%1" - - + + Replace file? 替换文件? - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 无法移除 "%1"。也许您需要足够的文件权限? - - + + failed to rename %1 to %2 无法重命名 %1 为 %2 - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + Un-Hide 取消隐藏 - + Hide 隐藏 - + Name 名称 - + Please enter a name - + Error 错误 - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3439,7 +3449,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3447,7 +3457,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) 此虚拟安装包内包含来自虚拟 Data 树的文件,但文件发生了变化 (例: 被CK修改了) @@ -3459,18 +3469,18 @@ p, li { white-space: pre-wrap; } 无法写入 %1/meta.ini: %2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 中未包含 esp 或 esm 和有效的目录 (textures, meshes, interface, ...) - + Categories: <br> 种类: <br> @@ -3478,52 +3488,52 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) 此项目内检测到了虚拟 Data 树的文件发生了变化 (例如: 被 CK 修改了) - + Backup 备份 - + No valid game data 无效游戏数据 - + Not endorsed yet 尚未点赞支持 - + Overwrites files Overwrites文件 - + Overwritten files 覆盖的 Mod - + Overwrites & Overwritten 覆盖 & 被覆盖 - + Redundant 冗余 - + Non-MO - + invalid 无效 @@ -3532,113 +3542,113 @@ p, li { white-space: pre-wrap; } 当前版本: %1,最新版本: %2 - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> 种类: <br> - + Invalid name 无效的名称 - + drag&drop failed: %1 拖拽失败: %1 - + Confirm 确认 - + Are you sure you want to remove "%1"? 确定要移除 "%1" 吗? - + Flags 标志 - + Mod Name Mod 名称 - + Version 版本 - + Priority 优先级 - + Category 分类 - + Nexus ID Nexus网 ID - + Installation 安装 - - + + unknown 未知 - + Name of your mods 你的mod名称 - + Version of the mod (if available) Mod 版本 (如果可用) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Mod 的安装优先级。越高就表示越“重要”,从而覆盖掉低优先级的 Mod 文件。 - + Category of the mod. mod的分类 - + Id of the mod as used on Nexus. mod在 Nexus 网上的ID编号 - + Emblemes to highlight things that might require attention. 需要注意被标记为高亮的 - + Time this mod was installed @@ -3850,17 +3860,17 @@ p, li { white-space: pre-wrap; } - + <b>Origin</b>: %1 - + Author 作者 - + Description 描述 @@ -3874,7 +3884,7 @@ p, li { white-space: pre-wrap; } 您的一些插件名称无效!这些插件无法被游戏载入。请查看 mo_interface.log 来确认那些受影响的插件并重命名它们。 - + This plugin can't be disabled (enforced by the game) 这个插件不能被禁用 (由游戏执行) @@ -3883,17 +3893,17 @@ p, li { white-space: pre-wrap; } 隶属于: %1 - + Missing Masters - + Enabled Masters - + failed to restore load order for %1 恢复 %1 加载顺序失败 @@ -4459,18 +4469,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements 请使用工具栏上的“帮助”来获得所有元素的使用说明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 无法解析配置文件 %1: %2 @@ -4515,14 +4525,14 @@ p, li { white-space: pre-wrap; } 错误 - - - + + + wrong file format 错误的文件格式 - + failed to open %1 无法打开 %1 @@ -4537,17 +4547,17 @@ p, li { white-space: pre-wrap; } 代理DLL - + failed to spawn "%1" 无法生成 "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4557,22 +4567,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + failed to spawn "%1": %2 无法生成 "%1": %2 - + "%1" doesn't exist "%1" 不存在 - + failed to inject dll into "%1": %2 无法注入 dll 到 "%1": %2 - + failed to run "%1" 无法运行 "%1" @@ -4802,12 +4812,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + Confirm 确认 - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? 修改 Mod 目录将会影响您的配置!新目录中不存在 (或者名称不同) 的 Mod 将在所有配置中被禁止掉。此操作无法撤销,所以执行此操作前建议先备份下自己的配置。立即执行? @@ -5462,26 +5472,26 @@ On Windows XP: 覆盖文件 "%1" - - - - + + + + Confirm 确认 - - + + Copy all save games of character "%1" to the profile? 是否复制角色 "%1" 的所有游戏存档到这个配置中? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. 是否移动角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. 是否拷贝角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 diff --git a/src/organizer_zh_TW.ts b/src/organizer_zh_TW.ts index b7e4a346..9d767b3d 100644 --- a/src/organizer_zh_TW.ts +++ b/src/organizer_zh_TW.ts @@ -1404,7 +1404,7 @@ p, li { white-space: pre-wrap; } - + Namefilter @@ -1531,8 +1531,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh 重新整理 @@ -1580,12 +1580,12 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右鍵菜單中點擊“修復 Mod”,那麼 MO 便會嘗試激活所有 Mod 和 esp 來修復那些缺失的 esp,它並不會禁用任何東西!</span></p></body></html> - + Downloads 下載 - + This is a list of mods you downloaded from Nexus. Double click one to install it. 這是當前已下載的 Mod 的列表,雙擊進行安裝。 @@ -1636,145 +1636,145 @@ p, li { white-space: pre-wrap; } - + Show Hidden - + Tool Bar 工具欄 - + Install Mod 安裝 Mod - + Install &Mod 安裝 &Mod - + Install a new mod from an archive 通過壓縮包來安裝一個新 Mod - + Ctrl+M Ctrl+M - + Profiles 配置檔案 - + &Profiles &配置檔案 - + Configure Profiles 設定配置檔案 - + Ctrl+P Ctrl+P - + Executables 可執行程式 - + &Executables &可執行程式 - + Configure the executables that can be started through Mod Organizer 配置可通過 MO 來啟動的程式 - + Ctrl+E Ctrl+E - + Tools - + &Tools - + Ctrl+I Ctrl+I - + Settings 設定 - + &Settings &設定 - + Configure settings and workarounds 配置設定和解決方案 - + Ctrl+S Ctrl+S - + Nexus N網 - + Search nexus network for more mods 搜尋N網以獲取更多 Mod - + Ctrl+N Ctrl+N - - + + Update 更新 - + Mod Organizer is up-to-date Mod Organizer 現在是最新版本 - - + + No Problems 沒有問題 - + This button will be highlighted if MO discovered potential problems in your setup and provide tips on how to fix them. !Work in progress! @@ -1785,84 +1785,84 @@ Right now this has very limited functionality 當前此功能所能提供的項目非常有限 - - + + Help 幫助 - + Ctrl+H Ctrl+M - + Endorse MO - - + + Endorse Mod Organizer - + Copy Log to Clipboard - + Ctrl+C - + Toolbar 工具欄 - + Desktop - + Start Menu - + Problems 問題 - + There are potential problems with your setup 您的安裝中存在潛在的問題 - + Everything seems to be in order 一切井然有序 - + Help on UI 介面幫助 - + Documentation Wiki 說明文檔 (維基) - + Report Issue 報告問題 - + Tutorials @@ -1871,88 +1871,88 @@ Right now this has very limited functionality 無法儲存檔案順序,您確定您有權限更改 "%1"? - + failed to save load order: %1 無法儲存加載順序: %1 - + Name 名稱 - + Please enter a name for the new profile 請為新配置檔案輸入一個名稱 - + failed to create profile: %1 無法建立配置檔案: %1 - + Show tutorial? - + You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Downloads in progress 正在下載 - + There are still downloads in progress, do you really want to quit? 仍有正在進行中的下載,您確定要退出嗎? - + failed to read savegame: %1 無法讀取存檔: %1 - + Plugin "%1" failed: %2 - + Plugin "%1" failed - + failed to init plugin %1: %2 - + Plugin error - + It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" 無法啟動 "%1" - + Waiting 稍等 - + Please press OK once you're logged into steam. 當您登入 Steam 時請點擊確定。 @@ -1961,32 +1961,32 @@ Right now this has very limited functionality "%1" 未找到 - + Start Steam? 啟動 Steam? - + Steam is required to be running already to correctly start the game. Should MO try to start steam now? 想要正確地啟動遊戲,Steam 必須處於運行狀態,MO 要立即啟動 Steam 嗎? - + Also in: <br> 也在: <br> - + No conflict 沒有衝突 - + <Edit...> <編輯...> - + This bsa is enabled in the ini file so it may be required! 該 BSA 已在 Ini 檔案中啟用,因此它可能是必需的。 @@ -1995,222 +1995,229 @@ Right now this has very limited functionality 此檔案還是會被加載,因為存在同名插件。不過它所包含的的檔案不會遵循安裝順序! - + Activating Network Proxy - - + + Installation successful 安裝成功 - - + + Configure Mod 配置 Mod - - + + This mod contains ini tweaks. Do you want to configure them now? 此 Mod 中包含 Ini 設定檔案,您想現在就對它們進行配置嗎? - - + + mod "%1" not found Mod "%1" 未找到 - - + + Installation cancelled 安裝已取消 - - + + The mod was not installed completely. Mod 沒有完全安裝。 - + Some plugins could not be loaded - + Too many esps and esms enabled - - + + Description missing - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod 選擇 Mod - + Mod Archive Mod 壓縮包 - + Start Tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - - + + Download started 開始下載 - + failed to update mod list: %1 無法更新 Mod 列表: %1 - + failed to spawn notepad.exe: %1 無法生成 notepad.exe: %1 - + failed to open %1 無法開啟 %1 - + failed to change origin name: %1 無法更改原始檔案名: %1 - + <Checked> <已勾選> - + <Unchecked> <未勾選> - + <Update> <有更新> - + <No category> <無類別> - + <Conflicted> - + <Not Endorsed> - + failed to rename mod: %1 無法重新命名 Mod: %1 - + Overwrite? 覆蓋 - + This will replace the existing mod "%1". Continue? - + failed to remove mod "%1" 無法移動 Mod: %1 - - - + + + failed to rename "%1" to "%2" 重新命名 "%1 "為 "%2" 時出錯 - + Multiple esps activated, please check that they don't conflict. - - - - + + + + Confirm 確認 - + Remove the following mods?<br><ul>%1</ul> - + failed to remove mod: %1 無法移動 Mod: %1 - - + + Failed 失敗 - + Installation file no longer exists 安裝檔案不複存在 - + Mods installed with old versions of MO can't be reinstalled in this way. 舊版 MO 安裝的 Mod 無法使用此方法重新安裝。 - - + + You need to be logged in with Nexus to endorse + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + + + + - + Extract BSA 解壓 BSA @@ -2221,657 +2228,664 @@ Right now this has very limited functionality (解壓完成後,BSA 檔案將會被刪除。如果您不瞭解 BSA 的話,請選擇“否”) - - + + failed to read %1: %2 無法讀取 %1: %2 - + This archive contains invalid hashes. Some files may be broken. 壓縮包 Hash 值錯誤。部分檔案可能已經損壞。 - + Nexus ID for this Mod is unknown 此 Mod 的N網 ID 未知 - + About - + About Qt - + Download? - + A download has been started but no installed page plugin recognizes it. If you download anyway no information (i.e. version) will be associated with the download. Continue? - + Browse Mod Page - + Executable "%1" not found - + Failed to refresh list of esps: %1 - - + + Failed to write settings - - + + An error occured trying to write back MO settings: %1 - + File is write protected - + Invalid file format (probably a bug) - + Unknown error %1 - + failed to move "%1" from mod "%2" to "%3": %4 - + <Managed by MO> - + <Managed outside MO> - + You need to be logged in with Nexus to resume a download - - + + Create Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + A mod with this name already exists - + Continue? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - - + + Sorry - + I don't know a versioning scheme where %1 is newer than %2. - + Really enable all visible mods? 確定要啟用全部可見的 Mod 嗎? - + Really disable all visible mods? 確定要禁用全部可見的 Mod 嗎? - + Choose what to export - + Everything - + All installed mods are included in the list - + Active Mods 激活 Mod - + Only active (checked) mods from your current profile are included - + Visible - + All mods visible in the mod list are included - + export failed: %1 - + Install Mod... 安裝 Mod... - + Enable all visible 啟用所有可見項目 - + Disable all visible 禁用所有可見項目 - + Check all for update 檢查更新 - + Export to csv... - + All Mods - + Sync to Mods... 同步到 Mod... - + Restore Backup - + Remove Backup... - + Add/Remove Categories - + Replace Categories - + Primary Category - + Change versioning scheme - + Un-ignore update - + Ignore update - + Rename Mod... 重新命名... - + Remove Mod... 移除 Mod... - + Reinstall Mod 重新安裝 Mod - + Un-Endorse - - + + Endorse - + Won't endorse - + Endorsement state unknown - + Ignore missing data - + Visit on Nexus 在N網上流覽 - + Open in explorer 在檔案總管中開啟 - + Information... 訊息... - - + + Exception: 例外: - - + + Unknown exception 未知的例外 - + <All> <全部> - + <Multiple> - - Really delete "%1"? - - - - + Fix Mods... 修復 Mod... + + + Delete %n save(s) + Delete save(s) + + + + - Delete - &刪除 + &刪除 - - + + failed to remove %1 無法刪除 %1 - - + + failed to create %1 無法建立 %1 - + Can't change download directory while downloads are in progress! 下載檔案時不能修改下載目錄! - + Download failed 下載失敗 - + failed to write to file %1 無法寫入檔案 %1 - + %1 written 已寫入 %1 - + Select binary 選擇可執行檔案 - + Binary 程式 - + Enter Name 輸入名稱 - + Please enter a name for the executable 請為程式輸入一個名稱 - + Not an executable 不是可執行程式 - + This is not a recognized executable. 無法識別的可執行檔案 - - + + Replace file? 取代檔案? - + There already is a hidden version of this file. Replace it? 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed 檔案操作錯誤 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 無法移除 "%1"。也許您需要足夠的檔案權限? - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + file not found: %1 - + failed to generate preview for %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Update available 更新可用 - + Open/Execute 開啟/執行 - + Add as Executable 添加為可執行檔案 - + Preview - + Un-Hide 取消隱藏 - + Hide 隱藏 - + Write To File... 寫入檔案... - + Do you want to endorse Mod Organizer on %1 now? - + Thank you! - + Thank you for your endorsement! - + Request to Nexus failed: %1 - - + + login successful 登入成功 - + login failed: %1. Trying to download anyway 登入失敗: %1,請嘗試使用別的方法下載 - + login failed: %1 無法登入: %1 - + login failed: %1. You need to log-in with Nexus to update MO. 登入失敗: %1。您需要登入到N網才能更新 MO - + Error 錯誤 - + failed to extract %1 (errorcode %2) 無法解壓 %1 (錯誤代碼 %2) - + Extract... 解壓... - + Edit Categories... 編輯類別... - + Deselect filter - + Remove 移除 - + Enable all 全部啟用 - + Disable all 全部禁用 - + Unlock load order - + Lock load order - + depends on missing "%1" - + No profile set - + LOOT working - + loot failed. Exit code was: %1 - + + failed to start loot + + + + failed to run loot: %1 - + Errors occured - + Backup of load order created - + Choose backup to restore - + No Backups - + There are no backups to restore - - + + Restore failed - - + + Failed to restore the backup. Errorcode: %1 - + Backup of modlist created @@ -2897,7 +2911,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod @@ -3211,227 +3225,227 @@ p, li { white-space: pre-wrap; } 關閉 - + &Delete &刪除 - + &Rename &重新命名 - + &Hide &隱藏 - + &Unhide &取消隱藏 - + &Open &開啟 - + &New Folder &新增資料夾 - - + + Save changes? 儲存更改嗎? - - + + Save changes to "%1"? - + File Exists 檔案已存在 - + A file with that name exists, please enter a new one 檔案名已存在,請輸入其它名稱 - + failed to move file 無法移動檔案 - + failed to create directory "optional" 無法建立 "optional" 目錄 - - + + Info requested, please wait 請求訊息已發出,請稍後 - + Main 主要檔案 - + Update 更新 - + Optional 可選檔案 - + Old 舊檔 - + Misc 雜項 - + Unknown 未知 - + Current Version: %1 當前版本: %1 - + No update available 沒有可用的更新 - + (description incomplete, please visit nexus) (描述訊息不完整,請訪問N網) - + <a href="%1">Visit on Nexus</a> <a href="%1">訪問N網</a> - + Failed to delete %1 無法刪除 %1 - - + + Confirm 確認 - + Are sure you want to delete "%1"? 確定要刪除 "%1" 嗎? - + Are sure you want to delete the selected files? 確定要刪除所選的檔案嗎? - - + + New Folder 新增資料夾 - + Failed to create "%1" 無法建立 "%1" - - + + Replace file? 取代檔案? - + There already is a hidden version of this file. Replace it? 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed 檔案操作錯誤 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 無法移除 "%1"。也許您需要足夠的檔案權限? - - + + failed to rename %1 to %2 無法重新命名 %1 為 %2 - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + Un-Hide 取消隱藏 - + Hide 隱藏 - + Name 名稱 - + Please enter a name - + Error 錯誤 - + Invalid name. Must be a valid file name - + A tweak by that name exists - + Create Tweak @@ -3439,7 +3453,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. @@ -3447,7 +3461,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) 此虛擬安裝包內包含來自虛擬 Data 樹的檔案,但檔案發生了變化 (例: 被CK修改了) @@ -3459,18 +3473,18 @@ p, li { white-space: pre-wrap; } 無法寫入 %1/meta.ini: %2 - - + + failed to write %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 中未包含 esp 或 esm 和有效的目錄 (textures, meshes, interface, ...) - + Categories: <br> 種類: <br> @@ -3478,52 +3492,52 @@ p, li { white-space: pre-wrap; } ModList - + This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) 此項目內檢測到了虛擬 Data 樹的檔案發生了變化 (例如:被 CK 修改了) - + Backup - + No valid game data - + Not endorsed yet - + Overwrites files - + Overwritten files 覆蓋的 Mod - + Overwrites & Overwritten - + Redundant - + Non-MO - + invalid @@ -3532,113 +3546,113 @@ p, li { white-space: pre-wrap; } 當前版本: %1,最新版本: %2 - + installed version: "%1", newest version: "%2" - + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + Categories: <br> 種類: <br> - + Invalid name - + drag&drop failed: %1 - + Confirm 確認 - + Are you sure you want to remove "%1"? 確定要移除 "%1" 吗? - + Flags - + Mod Name - + Version 版本 - + Priority 優先級 - + Category - + Nexus ID N網 ID - + Installation - - + + unknown 未知 - + Name of your mods - + Version of the mod (if available) Mod 版本 (如果可用) - + Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority. Mod 的安裝優先級。越高就表示越“重要”,從而覆蓋掉低優先級的 Mod 檔案。 - + Category of the mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Time this mod was installed @@ -3850,17 +3864,17 @@ p, li { white-space: pre-wrap; } - + <b>Origin</b>: %1 - + Author 作者 - + Description 描述 @@ -3874,7 +3888,7 @@ p, li { white-space: pre-wrap; } 您的一些插件名稱無效!這些插件無法被遊戲載入。請查看 mo_interface.log 來確認那些受影響的插件並重新命名它們。 - + This plugin can't be disabled (enforced by the game) 這個插件不能被禁用 (由遊戲執行) @@ -3883,17 +3897,17 @@ p, li { white-space: pre-wrap; } 隸屬於: %1 - + Missing Masters - + Enabled Masters - + failed to restore load order for %1 @@ -4459,18 +4473,18 @@ p, li { white-space: pre-wrap; } - + Please use "Help" from the toolbar to get usage instructions to all elements 請使用工具列上的“幫助”來獲得所有元素的使用說明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 無法解析配置檔案 %1: %2 @@ -4515,14 +4529,14 @@ p, li { white-space: pre-wrap; } 錯誤 - - - + + + wrong file format 錯誤的檔案格式 - + failed to open %1 無法開啟 %1 @@ -4537,17 +4551,17 @@ p, li { white-space: pre-wrap; } 代理DLL - + failed to spawn "%1" 無法生成 "%1" - + Elevation required - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4557,22 +4571,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + failed to spawn "%1": %2 無法生成 "%1": %2 - + "%1" doesn't exist "%1" 不存在 - + failed to inject dll into "%1": %2 無法注入 dll 到 "%1": %2 - + failed to run "%1" 無法運行 "%1" @@ -4802,12 +4816,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe - + Confirm 確認 - + Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? 修改 Mod 目錄將會影響您的配置!新目錄中不存在 (或者名稱不同) 的 Mod 將在所有配置中被禁止掉。此操作無法撤銷,所以執行此操作前建議先備份下自己的配置。立即執行? @@ -5458,26 +5472,26 @@ On Windows XP: - - - - + + + + Confirm 確認 - - + + Copy all save games of character "%1" to the profile? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. diff --git a/src/savegame.cpp b/src/savegame.cpp index d09f291c..06e028e2 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -21,10 +21,11 @@ along with Mod Organizer. If not, see . #include #include #include -#include "gameinfo.h" #include #include +#include #include +#include "gameinfo.h" SaveGame::SaveGame(QObject *parent) @@ -68,11 +69,14 @@ SaveGame::~SaveGame() QStringList SaveGame::attachedFiles() const { QStringList result; - QString seFileFile = fileName().mid(0).replace(".ess", ".skse"); - QFileInfo seFile(seFileFile); - if (seFile.exists()) { - result.append(seFile.absoluteFilePath()); + foreach (const std::wstring &ext, MOShared::GameInfo::instance().getSavegameAttachmentExtensions()) { + QFileInfo fi(fileName()); + fi.setFile(fi.canonicalPath() + "/" + fi.completeBaseName() + "." + MOBase::ToQString(ext)); + if (fi.exists()) { + result.append(fi.filePath()); + } } + return result; } diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index 68fd515e..ea73c7f6 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -122,6 +122,11 @@ std::vector Fallout3Info::getDLCPlugins() ; } +std::vector Fallout3Info::getSavegameAttachmentExtensions() +{ + return std::vector(); +} + std::vector Fallout3Info::getIniFileNames() { return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini"); diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index e9a818e2..7c61ca6d 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -59,6 +59,7 @@ public: virtual std::vector getVanillaBSAs(); virtual std::vector getDLCPlugins(); + virtual std::vector getSavegameAttachmentExtensions(); // file name of this games ini (no path) virtual std::vector getIniFileNames(); diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 366c72c3..1715912d 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -127,13 +127,16 @@ std::vector FalloutNVInfo::getDLCPlugins() ; } +std::vector FalloutNVInfo::getSavegameAttachmentExtensions() +{ + return std::vector(); +} std::vector FalloutNVInfo::getIniFileNames() { return boost::assign::list_of(L"fallout.ini")(L"falloutprefs.ini"); } - std::wstring FalloutNVInfo::getSaveGameExtension() { return L"*.fos"; diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index e6f6b5d0..4de67a19 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -61,6 +61,7 @@ public: virtual std::vector getVanillaBSAs(); virtual std::vector getDLCPlugins(); + virtual std::vector getSavegameAttachmentExtensions(); // file name of this games ini (no path) virtual std::vector getIniFileNames(); diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index 69cd38f6..33467cb9 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -130,6 +130,9 @@ public: virtual std::vector getVanillaBSAs() = 0; + // get a list of file extensions for additional files belonging to a save game + virtual std::vector getSavegameAttachmentExtensions() = 0; + // get a set of esp/esm files that are part of known dlcs virtual std::vector getDLCPlugins() = 0; diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index 85f31042..532e49b8 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -133,6 +133,12 @@ std::vector OblivionInfo::getDLCPlugins() } +std::vector OblivionInfo::getSavegameAttachmentExtensions() +{ + return boost::assign::list_of(L"obse"); +} + + std::vector OblivionInfo::getIniFileNames() { return boost::assign::list_of(L"oblivion.ini")(L"oblivionprefs.ini"); diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index f9c8fa47..121cad43 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -57,6 +57,7 @@ public: virtual std::vector getVanillaBSAs(); virtual std::vector getDLCPlugins(); + virtual std::vector getSavegameAttachmentExtensions(); // file name of this games ini (no path) virtual std::vector getIniFileNames(); diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 5711e2fd..5017da38 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -158,6 +158,11 @@ std::vector SkyrimInfo::getDLCPlugins() ; } +std::vector SkyrimInfo::getSavegameAttachmentExtensions() +{ + return boost::assign::list_of(L"skse"); +} + std::vector SkyrimInfo::getIniFileNames() { return boost::assign::list_of(L"skyrim.ini")(L"skyrimprefs.ini"); diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index 2794555f..3b3b6d8e 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -65,6 +65,8 @@ public: virtual std::vector getVanillaBSAs(); virtual std::vector getDLCPlugins(); + virtual std::vector getSavegameAttachmentExtensions(); + // file name of this games ini (no path) virtual std::vector getIniFileNames(); -- cgit v1.3.1 From e24f3fef770d250a40290a65bebd17f66121fc29 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 17 Jul 2014 20:04:22 +0200 Subject: - bugfix: broke qt4 compatibility in trying to support qt5 - bugfix: overwrite dialog displayed system drives if the overwrite directory didn't exist - bugfix: ini tweak activation wasn't saved --- src/mainwindow.cpp | 22 ++++++++++++++-------- src/modinfodialog.cpp | 2 +- src/overwriteinfodialog.cpp | 18 +++++++++++++----- src/overwriteinfodialog.h | 2 ++ src/shared/directoryentry.cpp | 2 +- 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19cefa89..cf283b49 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3221,14 +3221,20 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, std::vector flags = modInfo->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { QDialog *dialog = this->findChild("__overwriteDialog"); - if (dialog == NULL) { - dialog = new OverwriteInfoDialog(modInfo, this); - dialog->setObjectName("__overwriteDialog"); - } - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + try { + if (dialog == NULL) { + dialog = new OverwriteInfoDialog(modInfo, this); + dialog->setObjectName("__overwriteDialog"); + } else { + qobject_cast(dialog)->setModInfo(modInfo); + } + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + } catch (const std::exception &e) { + reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); + } } else { modInfo->saveMeta(); ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 02ba0d38..96a575a0 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -104,7 +104,6 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_IMAGES, false); } else { initFiletree(modInfo); - initINITweaks(); addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); refreshPrimaryCategoriesBox(); ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); @@ -112,6 +111,7 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); } refreshLists(); + initINITweaks(); ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL); diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 2ba81633..16242506 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -72,19 +72,17 @@ private: OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL), - m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL), - m_ModInfo(modInfo) + m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL) { ui->setupUi(this); this->setWindowModality(Qt::NonModal); - QString path = modInfo->absolutePath(); m_FileSystemModel = new MyFileSystemModel(this); m_FileSystemModel->setReadOnly(false); - m_FileSystemModel->setRootPath(path); + setModInfo(modInfo); ui->filesView->setModel(m_FileSystemModel); - ui->filesView->setRootIndex(m_FileSystemModel->index(path)); + ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); ui->filesView->setColumnWidth(0, 250); m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); @@ -102,6 +100,16 @@ OverwriteInfoDialog::~OverwriteInfoDialog() delete ui; } +void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) +{ + m_ModInfo = modInfo; + if (QDir(modInfo->absolutePath()).exists()) { + m_FileSystemModel->setRootPath(modInfo->absolutePath()); + } else { + throw MyException(tr("%1 not found").arg(modInfo->absolutePath())); + } +} + bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) { for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 34e86219..0726f1f9 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -39,6 +39,8 @@ public: ModInfo::Ptr modInfo() const { return m_ModInfo; } + void setModInfo(ModInfo::Ptr modInfo); + private: void openFile(const QModelIndex &index); diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 87431ea8..aead0cca 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -499,7 +499,7 @@ static bool SupportOptimizedFind() ::VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL); - bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask); + bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE; return res; } -- cgit v1.3.1 From f6ef5477e718b14af99bd22436f66dee0b9d22cd Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 17 Jul 2014 22:02:15 +0200 Subject: normalized eol style (all files should now have windows line endings) --- src/activatemodsdialog.cpp | 194 +- src/activatemodsdialog.h | 140 +- src/bbcode.cpp | 468 +- src/bbcode.h | 80 +- src/browserdialog.cpp | 540 +- src/browserdialog.h | 248 +- src/browserview.cpp | 152 +- src/browserview.h | 160 +- src/categories.cpp | 602 +- src/categories.h | 400 +- src/categoriesdialog.cpp | 486 +- src/categoriesdialog.h | 142 +- src/credentialsdialog.cpp | 38 +- src/credentialsdialog.h | 38 +- src/directoryrefresher.cpp | 312 +- src/directoryrefresher.h | 294 +- src/downloadlist.cpp | 228 +- src/downloadlist.h | 192 +- src/downloadlistsortproxy.cpp | 130 +- src/downloadlistsortproxy.h | 84 +- src/downloadlistwidget.cpp | 702 +-- src/downloadlistwidget.h | 242 +- src/downloadlistwidgetcompact.cpp | 682 +-- src/downloadlistwidgetcompact.h | 244 +- src/downloadmanager.cpp | 2942 ++++----- src/downloadmanager.h | 984 +-- src/dummybsa.cpp | 488 +- src/dummybsa.h | 38 +- src/editexecutablesdialog.cpp | 578 +- src/editexecutablesdialog.h | 196 +- src/executableslist.cpp | 406 +- src/executableslist.h | 310 +- src/filedialogmemory.cpp | 188 +- src/filedialogmemory.h | 38 +- src/gameinfoimpl.cpp | 108 +- src/gameinfoimpl.h | 78 +- src/helper.cpp | 38 +- src/helper.h | 38 +- src/icondelegate.cpp | 140 +- src/icondelegate.h | 104 +- src/installationmanager.cpp | 1604 ++--- src/installationmanager.h | 432 +- src/json.cpp | 1044 ++-- src/json.h | 188 +- src/loadmechanism.cpp | 580 +- src/loadmechanism.h | 38 +- src/lockeddialog.cpp | 150 +- src/lockeddialog.h | 134 +- src/logbuffer.cpp | 522 +- src/logbuffer.h | 198 +- src/loghighlighter.cpp | 38 +- src/loghighlighter.h | 38 +- src/main.cpp | 1162 ++-- src/mainwindow.cpp | 11202 +++++++++++++++++------------------ src/mainwindow.h | 1230 ++-- src/messagedialog.cpp | 184 +- src/messagedialog.h | 134 +- src/moapplication.cpp | 296 +- src/moapplication.h | 102 +- src/modeltest.cpp | 1166 ++-- src/modeltest.h | 188 +- src/modinfo.cpp | 2006 +++---- src/modinfo.h | 2056 +++---- src/modinfodialog.cpp | 2512 ++++---- src/modinfodialog.h | 474 +- src/modlist.cpp | 1986 +++---- src/modlist.h | 586 +- src/modlistsortproxy.cpp | 752 +-- src/modlistsortproxy.h | 242 +- src/motddialog.cpp | 98 +- src/motddialog.h | 66 +- src/nexusinterface.cpp | 1244 ++-- src/nexusinterface.h | 644 +- src/nxmaccessmanager.cpp | 424 +- src/nxmaccessmanager.h | 206 +- src/overwriteinfodialog.cpp | 520 +- src/overwriteinfodialog.h | 146 +- src/pluginlist.cpp | 2318 ++++---- src/pluginlist.h | 688 +-- src/pluginlistsortproxy.cpp | 304 +- src/pluginlistsortproxy.h | 142 +- src/profile.cpp | 1564 ++--- src/profile.h | 660 +-- src/profileinputdialog.cpp | 96 +- src/profileinputdialog.h | 38 +- src/profilesdialog.cpp | 640 +- src/profilesdialog.h | 192 +- src/queryoverwritedialog.cpp | 38 +- src/queryoverwritedialog.h | 38 +- src/report.cpp | 106 +- src/report.h | 38 +- src/savegame.cpp | 212 +- src/savegame.h | 262 +- src/savegamegamebryo.cpp | 676 +-- src/savegamegamebyro.h | 204 +- src/savegameinfowidget.cpp | 124 +- src/savegameinfowidget.h | 38 +- src/savegameinfowidgetgamebryo.cpp | 146 +- src/savegameinfowidgetgamebryo.h | 38 +- src/selectiondialog.cpp | 166 +- src/selectiondialog.h | 136 +- src/selfupdater.cpp | 932 +-- src/selfupdater.h | 290 +- src/settings.cpp | 1542 ++--- src/settings.h | 626 +- src/settingsdialog.cpp | 346 +- src/settingsdialog.h | 168 +- src/shared/appconfig.cpp | 2 +- src/shared/inject.h | 62 +- src/singleinstance.cpp | 214 +- src/singleinstance.h | 38 +- src/spawn.cpp | 422 +- src/spawn.h | 136 +- src/syncoverwritedialog.cpp | 332 +- src/syncoverwritedialog.h | 110 +- src/transfersavesdialog.cpp | 656 +- src/transfersavesdialog.h | 164 +- 117 files changed, 31484 insertions(+), 31484 deletions(-) diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index 8d463fba..be6eef54 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -1,97 +1,97 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "activatemodsdialog.h" -#include "ui_activatemodsdialog.h" - -#include -#include - -ActivateModsDialog::ActivateModsDialog(const std::map > &missingPlugins, QWidget *parent) - : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) -{ - ui->setupUi(this); - - QTableWidget *modsTable = findChild("modsTable"); - QHeaderView *headerView = modsTable->horizontalHeader(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); -#else - headerView->setResizeMode(0, QHeaderView::Stretch); - headerView->setResizeMode(1, QHeaderView::Interactive); -#endif - - int row = 0; - - modsTable->setRowCount(missingPlugins.size()); - - for (std::map >::const_iterator espIter = missingPlugins.begin(); - espIter != missingPlugins.end(); ++espIter, ++row) { - modsTable->setCellWidget(row, 0, new QLabel(espIter->first)); - if (espIter->second.size() == 0) { - modsTable->setCellWidget(row, 1, new QLabel(tr("not found"))); - } else { - QComboBox* combo = new QComboBox(); - for (std::vector::const_iterator modIter = espIter->second.begin(); - modIter != espIter->second.end(); ++modIter) { - combo->addItem(*modIter); - } - modsTable->setCellWidget(row, 1, combo); - } - } -} - - -ActivateModsDialog::~ActivateModsDialog() -{ - delete ui; -} - - -std::set ActivateModsDialog::getModsToActivate() -{ - std::set result; - QTableWidget *modsTable = findChild("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); - if (comboBox != NULL) { - result.insert(comboBox->currentText()); - } - } - return result; -} - - -std::set ActivateModsDialog::getESPsToActivate() -{ - std::set result; - QTableWidget *modsTable = findChild("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); - if (comboBox != NULL) { - QLabel *espName = dynamic_cast(modsTable->cellWidget(row, 0)); - - result.insert(espName->text()); - } - } - return result; -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "activatemodsdialog.h" +#include "ui_activatemodsdialog.h" + +#include +#include + +ActivateModsDialog::ActivateModsDialog(const std::map > &missingPlugins, QWidget *parent) + : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) +{ + ui->setupUi(this); + + QTableWidget *modsTable = findChild("modsTable"); + QHeaderView *headerView = modsTable->horizontalHeader(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Interactive); +#else + headerView->setResizeMode(0, QHeaderView::Stretch); + headerView->setResizeMode(1, QHeaderView::Interactive); +#endif + + int row = 0; + + modsTable->setRowCount(missingPlugins.size()); + + for (std::map >::const_iterator espIter = missingPlugins.begin(); + espIter != missingPlugins.end(); ++espIter, ++row) { + modsTable->setCellWidget(row, 0, new QLabel(espIter->first)); + if (espIter->second.size() == 0) { + modsTable->setCellWidget(row, 1, new QLabel(tr("not found"))); + } else { + QComboBox* combo = new QComboBox(); + for (std::vector::const_iterator modIter = espIter->second.begin(); + modIter != espIter->second.end(); ++modIter) { + combo->addItem(*modIter); + } + modsTable->setCellWidget(row, 1, combo); + } + } +} + + +ActivateModsDialog::~ActivateModsDialog() +{ + delete ui; +} + + +std::set ActivateModsDialog::getModsToActivate() +{ + std::set result; + QTableWidget *modsTable = findChild("modsTable"); + + for (int row = 0; row < modsTable->rowCount(); ++row) { + QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); + if (comboBox != NULL) { + result.insert(comboBox->currentText()); + } + } + return result; +} + + +std::set ActivateModsDialog::getESPsToActivate() +{ + std::set result; + QTableWidget *modsTable = findChild("modsTable"); + + for (int row = 0; row < modsTable->rowCount(); ++row) { + QComboBox *comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); + if (comboBox != NULL) { + QLabel *espName = dynamic_cast(modsTable->cellWidget(row, 0)); + + result.insert(espName->text()); + } + } + return result; +} diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index 845167aa..08dbad8d 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -1,70 +1,70 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef ACTIVATEMODSDIALOG_H -#define ACTIVATEMODSDIALOG_H - -#include "tutorabledialog.h" -#include -#include - -namespace Ui { - class ActivateModsDialog; -} - -/** - * @brief Dialog that is used to batch activate/deactivate mods and plugins - **/ -class ActivateModsDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param missingPlugins a map containing missing plugins that need to be activated - * @param parent ... Defaults to 0. - **/ - explicit ActivateModsDialog(const std::map > &missingPlugins, QWidget *parent = 0); - ~ActivateModsDialog(); - - /** - * @brief get a list of mods that the user chose to activate - * - * @note This can of ocurse only be called after the dialog has been displayed - * - * @return set< QString > the mods to activate - **/ - std::set getModsToActivate(); - - /** - * @brief get a list of plugins that should be activated - * - * @return set< QString > the plugins to activate. This contains only plugins that become available after enabling the mods retrieved with getModsToActivate - **/ - std::set getESPsToActivate(); - -private slots: - -private: - Ui::ActivateModsDialog *ui; -}; - -#endif // ACTIVATEMODSDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef ACTIVATEMODSDIALOG_H +#define ACTIVATEMODSDIALOG_H + +#include "tutorabledialog.h" +#include +#include + +namespace Ui { + class ActivateModsDialog; +} + +/** + * @brief Dialog that is used to batch activate/deactivate mods and plugins + **/ +class ActivateModsDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param missingPlugins a map containing missing plugins that need to be activated + * @param parent ... Defaults to 0. + **/ + explicit ActivateModsDialog(const std::map > &missingPlugins, QWidget *parent = 0); + ~ActivateModsDialog(); + + /** + * @brief get a list of mods that the user chose to activate + * + * @note This can of ocurse only be called after the dialog has been displayed + * + * @return set< QString > the mods to activate + **/ + std::set getModsToActivate(); + + /** + * @brief get a list of plugins that should be activated + * + * @return set< QString > the plugins to activate. This contains only plugins that become available after enabling the mods retrieved with getModsToActivate + **/ + std::set getESPsToActivate(); + +private slots: + +private: + Ui::ActivateModsDialog *ui; +}; + +#endif // ACTIVATEMODSDIALOG_H diff --git a/src/bbcode.cpp b/src/bbcode.cpp index f87b5d85..18f2beb1 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -1,234 +1,234 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "bbcode.h" - -#include -#include -#include -#include - - -namespace BBCode { - - -class BBCodeMap { - - typedef std::map > TagMap; - -public: - - static BBCodeMap &instance() { - static BBCodeMap s_Instance; - return s_Instance; - } - - QString convertTag(const QString &input, int &length) - { - // extract the tag name - m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); - QString tagName = m_TagNameExp.cap(0).toLower(); - TagMap::iterator tagIter = m_TagMap.find(tagName); - if (tagIter != m_TagMap.end()) { - // recognized tag - if (tagName.endsWith('=')) { - tagName.chop(1); - } - QString closeTag = tagName == "*" ? "
    " - : QString("[/%1]").arg(tagName); - - int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); - //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos); - - if (closeTagPos > -1) { - length = closeTagPos + closeTag.length(); - QString temp = input.mid(0, length); - if (tagIter->second.first.indexIn(temp) == 0) { - if (tagIter->second.second.isEmpty()) { - if (tagName == "color") { - QString color = tagIter->second.first.cap(1); - QString content = tagIter->second.first.cap(2); - auto colIter = m_ColorMap.find(color.toLower()); - if (colIter != m_ColorMap.end()) { - color = colIter->second; - } - return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); - } else { - qWarning("don't know how to deal with tag %s", qPrintable(tagName)); - } - } else { - return temp.replace(tagIter->second.first, tagIter->second.second); - } - } else { - // expression doesn't match. either the input string is invalid - // or the expression is - qWarning("%s doesn't match the expression for %s", - temp.toUtf8().constData(), tagName.toUtf8().constData()); - length = 0; - return QString(); - } - } - } - - // not a recognized tag or tag invalid - length = 0; - return QString(); - } - -private: - BBCodeMap() - : m_TagNameExp("^[a-zA-Z*]*=?") - { - m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), - "\\1"); - m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), - "\\1"); - m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), - "\\1"); - m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), - "\\1"); - m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), - "\\1"); - m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), - "\\1"); - m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), - "\\2"); - m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), - ""); - m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "\\2"); - m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), - "
    \\1
    "); - m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), - "
    \"\\1\"
    "); - m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), - "
    \"\\2\"
    --\\1

    "); - m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"), - "
    \\1
    "); - m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), - "

    \\1

    "); - - // lists - m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), - "
      \\1
    "); - m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), - "
      \\1
    "); - m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), - "
      \\1
    "); - m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), - "
      \\1
    "); - m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), - "
  • \\1
  • "); - m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)
    "), - "
  • \\1
  • "); - - // tables - m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), - "\\1
    "); - m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), - "\\1"); - m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), - "\\1"); - m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), - "\\1"); - - // web content - m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), - "\\1"); - m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), - "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " "); - m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " "); - m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), - "\\2"); - m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), - "http://www.youtube.com/v/\\1"); - - m_ColorMap.insert(std::make_pair("red", "FF0000")); - m_ColorMap.insert(std::make_pair("green", "00FF00")); - m_ColorMap.insert(std::make_pair("blue", "0000FF")); - m_ColorMap.insert(std::make_pair("black", "000000")); - m_ColorMap.insert(std::make_pair("gray", "7F7F7F")); - m_ColorMap.insert(std::make_pair("white", "FFFFFF")); - m_ColorMap.insert(std::make_pair("yellow", "FFFF00")); - m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); - m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); - m_ColorMap.insert(std::make_pair("brown", "A52A2A")); - m_ColorMap.insert(std::make_pair("orange", "FFCC00")); - - // make all patterns non-greedy and case-insensitive - for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { - iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); - iter->second.first.setMinimal(true); - } - } - -private: - - QRegExp m_TagNameExp; - TagMap m_TagMap; - std::map m_ColorMap; -}; - - -QString convertToHTML(const QString &inputParam) -{ - // this code goes over the input string once and replaces all bbtags - // it encounters. This function is called recursively for every replaced - // string to convert nested tags. - // - // This could be implemented simpler by applying a set of regular expressions - // for each recognized bb-tag one after the other but that would probably be - // very inefficient (O(n^2)). - - QString input = inputParam.mid(0).replace("\r\n", "
    "); - input.replace("\\\"", "\"").replace("\\'", "'"); - - QString result; - int lastBlock = 0; - int pos = 0; - - // iterate over the input buffer - while ((pos = input.indexOf('[', lastBlock)) != -1) { - // append everything between the previous tag-block and the current one - result.append(input.midRef(lastBlock, pos - lastBlock)); - - // convert the tag and content if necessary - int length = -1; - QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); - if (length != 0) { - QString temp = convertToHTML(replacement); - result.append(temp); - // length contains the number of characters in the original tag - pos += length; - } else { - // nothing replaced - result.append('['); - ++pos; - } - lastBlock = pos; - } - - // append the remainder (everything after the last tag) - result.append(input.midRef(lastBlock)); - return result; -} - -} // namespace BBCode - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "bbcode.h" + +#include +#include +#include +#include + + +namespace BBCode { + + +class BBCodeMap { + + typedef std::map > TagMap; + +public: + + static BBCodeMap &instance() { + static BBCodeMap s_Instance; + return s_Instance; + } + + QString convertTag(const QString &input, int &length) + { + // extract the tag name + m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); + QString tagName = m_TagNameExp.cap(0).toLower(); + TagMap::iterator tagIter = m_TagMap.find(tagName); + if (tagIter != m_TagMap.end()) { + // recognized tag + if (tagName.endsWith('=')) { + tagName.chop(1); + } + QString closeTag = tagName == "*" ? "
    " + : QString("[/%1]").arg(tagName); + + int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); + //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos); + + if (closeTagPos > -1) { + length = closeTagPos + closeTag.length(); + QString temp = input.mid(0, length); + if (tagIter->second.first.indexIn(temp) == 0) { + if (tagIter->second.second.isEmpty()) { + if (tagName == "color") { + QString color = tagIter->second.first.cap(1); + QString content = tagIter->second.first.cap(2); + auto colIter = m_ColorMap.find(color.toLower()); + if (colIter != m_ColorMap.end()) { + color = colIter->second; + } + return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); + } else { + qWarning("don't know how to deal with tag %s", qPrintable(tagName)); + } + } else { + return temp.replace(tagIter->second.first, tagIter->second.second); + } + } else { + // expression doesn't match. either the input string is invalid + // or the expression is + qWarning("%s doesn't match the expression for %s", + temp.toUtf8().constData(), tagName.toUtf8().constData()); + length = 0; + return QString(); + } + } + } + + // not a recognized tag or tag invalid + length = 0; + return QString(); + } + +private: + BBCodeMap() + : m_TagNameExp("^[a-zA-Z*]*=?") + { + m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), + "\\1"); + m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), + "\\1"); + m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), + "\\1"); + m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), + "\\1"); + m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), + "\\1"); + m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), + "\\1"); + m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), + "\\2"); + m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), + ""); + m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), + "\\2"); + m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), + "
    \\1
    "); + m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), + "
    \"\\1\"
    "); + m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), + "
    \"\\2\"
    --\\1

    "); + m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"), + "
    \\1
    "); + m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), + "

    \\1

    "); + + // lists + m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), + "
      \\1
    "); + m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), + "
      \\1
    "); + m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), + "
  • \\1
  • "); + m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)
    "), + "
  • \\1
  • "); + + // tables + m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), + "\\1
    "); + m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), + "\\1"); + m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), + "\\1"); + m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), + "\\1"); + + // web content + m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), + "\\1"); + m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), + "\\2"); + m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " "); + m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " "); + m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), + "\\2"); + m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), + "http://www.youtube.com/v/\\1"); + + m_ColorMap.insert(std::make_pair("red", "FF0000")); + m_ColorMap.insert(std::make_pair("green", "00FF00")); + m_ColorMap.insert(std::make_pair("blue", "0000FF")); + m_ColorMap.insert(std::make_pair("black", "000000")); + m_ColorMap.insert(std::make_pair("gray", "7F7F7F")); + m_ColorMap.insert(std::make_pair("white", "FFFFFF")); + m_ColorMap.insert(std::make_pair("yellow", "FFFF00")); + m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); + m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); + m_ColorMap.insert(std::make_pair("brown", "A52A2A")); + m_ColorMap.insert(std::make_pair("orange", "FFCC00")); + + // make all patterns non-greedy and case-insensitive + for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { + iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); + iter->second.first.setMinimal(true); + } + } + +private: + + QRegExp m_TagNameExp; + TagMap m_TagMap; + std::map m_ColorMap; +}; + + +QString convertToHTML(const QString &inputParam) +{ + // this code goes over the input string once and replaces all bbtags + // it encounters. This function is called recursively for every replaced + // string to convert nested tags. + // + // This could be implemented simpler by applying a set of regular expressions + // for each recognized bb-tag one after the other but that would probably be + // very inefficient (O(n^2)). + + QString input = inputParam.mid(0).replace("\r\n", "
    "); + input.replace("\\\"", "\"").replace("\\'", "'"); + + QString result; + int lastBlock = 0; + int pos = 0; + + // iterate over the input buffer + while ((pos = input.indexOf('[', lastBlock)) != -1) { + // append everything between the previous tag-block and the current one + result.append(input.midRef(lastBlock, pos - lastBlock)); + + // convert the tag and content if necessary + int length = -1; + QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); + if (length != 0) { + QString temp = convertToHTML(replacement); + result.append(temp); + // length contains the number of characters in the original tag + pos += length; + } else { + // nothing replaced + result.append('['); + ++pos; + } + lastBlock = pos; + } + + // append the remainder (everything after the last tag) + result.append(input.midRef(lastBlock)); + return result; +} + +} // namespace BBCode + diff --git a/src/bbcode.h b/src/bbcode.h index 0d4d8003..f88e9a6c 100644 --- a/src/bbcode.h +++ b/src/bbcode.h @@ -1,40 +1,40 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef BBCODE_H -#define BBCODE_H - - -#include - - -namespace BBCode { - -/** - * @brief convert a string with BB Code-Tags to HTML - * @param input the input string with BB tags - * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced - * @return the same string in html representation - **/ -QString convertToHTML(const QString &input); - -} - - -#endif // BBCODE_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef BBCODE_H +#define BBCODE_H + + +#include + + +namespace BBCode { + +/** + * @brief convert a string with BB Code-Tags to HTML + * @param input the input string with BB tags + * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced + * @return the same string in html representation + **/ +QString convertToHTML(const QString &input); + +} + + +#endif // BBCODE_H diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 4aa4e969..4897ea4f 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -1,270 +1,270 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "browserdialog.h" -#include "ui_browserdialog.h" - -#include "messagedialog.h" -#include "report.h" -#include "json.h" -#include "persistentcookiejar.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - - -BrowserDialog::BrowserDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::BrowserDialog) - , m_AccessManager(new QNetworkAccessManager(this)) -{ - ui->setupUi(this); - - m_AccessManager->setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/cookies.dat", this)); - - Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; - Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; - flags = flags & (~helpFlag); - setWindowFlags(flags); - - m_Tabs = this->findChild("browserTabWidget"); - - connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); -} - - -BrowserDialog::~BrowserDialog() -{ - delete ui; -} - -void BrowserDialog::closeEvent(QCloseEvent *event) -{ -// m_AccessManager->showCookies(); - QDialog::closeEvent(event); -} - -void BrowserDialog::initTab(BrowserView *newView) -{ - newView->page()->setNetworkAccessManager(m_AccessManager); - newView->page()->setForwardUnsupportedContent(true); - - connect(newView, SIGNAL(loadProgress(int)), this, SLOT(progress(int))); - connect(newView, SIGNAL(titleChanged(QString)), this, SLOT(titleChanged(QString))); - connect(newView, SIGNAL(initTab(BrowserView*)), this, SLOT(initTab(BrowserView*))); - connect(newView, SIGNAL(startFind()), this, SLOT(startSearch())); - connect(newView, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl))); - connect(newView, SIGNAL(openUrlInNewTab(QUrl)), this, SLOT(openInNewTab(QUrl))); - connect(newView->page(), SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest))); - connect(newView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*))); - - ui->backBtn->setEnabled(false); - ui->fwdBtn->setEnabled(false); - m_Tabs->addTab(newView, tr("new")); - newView->settings()->setAttribute(QWebSettings::PluginsEnabled, true); - newView->settings()->setAttribute(QWebSettings::AutoLoadImages, true); -} - - -void BrowserDialog::openInNewTab(const QUrl &url) -{ - BrowserView *newView = new BrowserView(this); - initTab(newView); - newView->setUrl(url); -} - - -BrowserView *BrowserDialog::getCurrentView() -{ - return qobject_cast(m_Tabs->currentWidget()); -} - - -void BrowserDialog::urlChanged(const QUrl&) -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } -} - - -void BrowserDialog::openUrl(const QUrl &url) -{ - if (isHidden()) { - show(); - } - openInNewTab(url); -} - - -void BrowserDialog::maximizeWidth() -{ - int viewportWidth = getCurrentView()->page()->viewportSize().width(); - int frameWidth = width() - viewportWidth; - - int contentWidth = getCurrentView()->page()->mainFrame()->contentsSize().width(); - - QDesktopWidget screen; - int currentScreen = screen.screenNumber(this); - int screenWidth = screen.screenGeometry(currentScreen).size().width(); - - int targetWidth = std::min(std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); - this->resize(targetWidth, height()); -} - - -void BrowserDialog::progress(int value) -{ - ui->loadProgress->setValue(value); - if (value == 100) { - maximizeWidth(); - ui->loadProgress->setVisible(false); - } else { - ui->loadProgress->setVisible(true); - } -} - - -void BrowserDialog::titleChanged(const QString &title) -{ - BrowserView *view = qobject_cast(sender()); - for (int i = 0; i < m_Tabs->count(); ++i) { - if (m_Tabs->widget(i) == view) { - m_Tabs->setTabText(i, title.mid(0, 15)); - m_Tabs->setTabToolTip(i, title); - } - } -} - - -QString BrowserDialog::guessFileName(const QString &url) -{ - QRegExp uploadsExp(QString("http://.+/uploads/([^/]+)$")); - if (uploadsExp.indexIn(url) != -1) { - // these seem to be premium downloads - return uploadsExp.cap(1); - } - - QRegExp filesExp(QString("http://.+\\?file=([^&]+)")); - if (filesExp.indexIn(url) != -1) { - // a regular manual download? - return filesExp.cap(1); - } - return "unknown"; -} - -void BrowserDialog::unsupportedContent(QNetworkReply *reply) -{ - try { - QWebPage *page = qobject_cast(sender()); - if (page == NULL) { - qCritical("sender not a page"); - return; - } - BrowserView *view = qobject_cast(page->view()); - if (view == NULL) { - qCritical("no view?"); - return; - } - - qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData()); - emit requestDownload(view->url(), reply); - } catch (const std::exception &e) { - if (isVisible()) { - MessageDialog::showMessage(tr("failed to start download"), this); - } - qCritical("exception downloading unsupported content: %s", e.what()); - } -} - - -void BrowserDialog::downloadRequested(const QNetworkRequest &request) -{ - qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); -} - - -void BrowserDialog::tabCloseRequested(int index) -{ - if (m_Tabs->count() == 1) { - this->close(); - } else { - m_Tabs->widget(index)->deleteLater(); - m_Tabs->removeTab(index); - } -} - -void BrowserDialog::on_backBtn_clicked() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - currentView->back(); - } -} - -void BrowserDialog::on_fwdBtn_clicked() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - currentView->forward(); - } -} - - -void BrowserDialog::startSearch() -{ - ui->searchEdit->setFocus(); -} - - -void BrowserDialog::on_searchEdit_returnPressed() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument); - } -} - -void BrowserDialog::on_refreshBtn_clicked() -{ - getCurrentView()->reload(); -} - -void BrowserDialog::on_browserTabWidget_currentChanged(int index) -{ - BrowserView *currentView = qobject_cast(ui->browserTabWidget->widget(index)); - if (currentView != NULL) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "browserdialog.h" +#include "ui_browserdialog.h" + +#include "messagedialog.h" +#include "report.h" +#include "json.h" +#include "persistentcookiejar.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +BrowserDialog::BrowserDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::BrowserDialog) + , m_AccessManager(new QNetworkAccessManager(this)) +{ + ui->setupUi(this); + + m_AccessManager->setCookieJar(new PersistentCookieJar( + QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/cookies.dat", this)); + + Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; + Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; + flags = flags & (~helpFlag); + setWindowFlags(flags); + + m_Tabs = this->findChild("browserTabWidget"); + + connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); +} + + +BrowserDialog::~BrowserDialog() +{ + delete ui; +} + +void BrowserDialog::closeEvent(QCloseEvent *event) +{ +// m_AccessManager->showCookies(); + QDialog::closeEvent(event); +} + +void BrowserDialog::initTab(BrowserView *newView) +{ + newView->page()->setNetworkAccessManager(m_AccessManager); + newView->page()->setForwardUnsupportedContent(true); + + connect(newView, SIGNAL(loadProgress(int)), this, SLOT(progress(int))); + connect(newView, SIGNAL(titleChanged(QString)), this, SLOT(titleChanged(QString))); + connect(newView, SIGNAL(initTab(BrowserView*)), this, SLOT(initTab(BrowserView*))); + connect(newView, SIGNAL(startFind()), this, SLOT(startSearch())); + connect(newView, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl))); + connect(newView, SIGNAL(openUrlInNewTab(QUrl)), this, SLOT(openInNewTab(QUrl))); + connect(newView->page(), SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest))); + connect(newView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*))); + + ui->backBtn->setEnabled(false); + ui->fwdBtn->setEnabled(false); + m_Tabs->addTab(newView, tr("new")); + newView->settings()->setAttribute(QWebSettings::PluginsEnabled, true); + newView->settings()->setAttribute(QWebSettings::AutoLoadImages, true); +} + + +void BrowserDialog::openInNewTab(const QUrl &url) +{ + BrowserView *newView = new BrowserView(this); + initTab(newView); + newView->setUrl(url); +} + + +BrowserView *BrowserDialog::getCurrentView() +{ + return qobject_cast(m_Tabs->currentWidget()); +} + + +void BrowserDialog::urlChanged(const QUrl&) +{ + BrowserView *currentView = getCurrentView(); + if (currentView != NULL) { + ui->backBtn->setEnabled(currentView->history()->canGoBack()); + ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); + } +} + + +void BrowserDialog::openUrl(const QUrl &url) +{ + if (isHidden()) { + show(); + } + openInNewTab(url); +} + + +void BrowserDialog::maximizeWidth() +{ + int viewportWidth = getCurrentView()->page()->viewportSize().width(); + int frameWidth = width() - viewportWidth; + + int contentWidth = getCurrentView()->page()->mainFrame()->contentsSize().width(); + + QDesktopWidget screen; + int currentScreen = screen.screenNumber(this); + int screenWidth = screen.screenGeometry(currentScreen).size().width(); + + int targetWidth = std::min(std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); + this->resize(targetWidth, height()); +} + + +void BrowserDialog::progress(int value) +{ + ui->loadProgress->setValue(value); + if (value == 100) { + maximizeWidth(); + ui->loadProgress->setVisible(false); + } else { + ui->loadProgress->setVisible(true); + } +} + + +void BrowserDialog::titleChanged(const QString &title) +{ + BrowserView *view = qobject_cast(sender()); + for (int i = 0; i < m_Tabs->count(); ++i) { + if (m_Tabs->widget(i) == view) { + m_Tabs->setTabText(i, title.mid(0, 15)); + m_Tabs->setTabToolTip(i, title); + } + } +} + + +QString BrowserDialog::guessFileName(const QString &url) +{ + QRegExp uploadsExp(QString("http://.+/uploads/([^/]+)$")); + if (uploadsExp.indexIn(url) != -1) { + // these seem to be premium downloads + return uploadsExp.cap(1); + } + + QRegExp filesExp(QString("http://.+\\?file=([^&]+)")); + if (filesExp.indexIn(url) != -1) { + // a regular manual download? + return filesExp.cap(1); + } + return "unknown"; +} + +void BrowserDialog::unsupportedContent(QNetworkReply *reply) +{ + try { + QWebPage *page = qobject_cast(sender()); + if (page == NULL) { + qCritical("sender not a page"); + return; + } + BrowserView *view = qobject_cast(page->view()); + if (view == NULL) { + qCritical("no view?"); + return; + } + + qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData()); + emit requestDownload(view->url(), reply); + } catch (const std::exception &e) { + if (isVisible()) { + MessageDialog::showMessage(tr("failed to start download"), this); + } + qCritical("exception downloading unsupported content: %s", e.what()); + } +} + + +void BrowserDialog::downloadRequested(const QNetworkRequest &request) +{ + qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); +} + + +void BrowserDialog::tabCloseRequested(int index) +{ + if (m_Tabs->count() == 1) { + this->close(); + } else { + m_Tabs->widget(index)->deleteLater(); + m_Tabs->removeTab(index); + } +} + +void BrowserDialog::on_backBtn_clicked() +{ + BrowserView *currentView = getCurrentView(); + if (currentView != NULL) { + currentView->back(); + } +} + +void BrowserDialog::on_fwdBtn_clicked() +{ + BrowserView *currentView = getCurrentView(); + if (currentView != NULL) { + currentView->forward(); + } +} + + +void BrowserDialog::startSearch() +{ + ui->searchEdit->setFocus(); +} + + +void BrowserDialog::on_searchEdit_returnPressed() +{ + BrowserView *currentView = getCurrentView(); + if (currentView != NULL) { + currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument); + } +} + +void BrowserDialog::on_refreshBtn_clicked() +{ + getCurrentView()->reload(); +} + +void BrowserDialog::on_browserTabWidget_currentChanged(int index) +{ + BrowserView *currentView = qobject_cast(ui->browserTabWidget->widget(index)); + if (currentView != NULL) { + ui->backBtn->setEnabled(currentView->history()->canGoBack()); + ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); + } +} diff --git a/src/browserdialog.h b/src/browserdialog.h index bd27661b..c6e0aab6 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -1,124 +1,124 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef BROWSERDIALOG_H -#define BROWSERDIALOG_H - -#include "browserview.h" -#include "tutorialcontrol.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace Ui { - class BrowserDialog; -} - - -/** - * @brief a dialog containing a webbrowser that is intended to browse the nexus network - **/ -class BrowserDialog : public QDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param accessManager the access manager to use for network requests - * @param parent parent widget - **/ - explicit BrowserDialog(QWidget *parent = 0); - ~BrowserDialog(); - - /** - * @brief set the url to open. If automatic login is enabled, the url is opened after login - * - * @param url the url to open - **/ - void openUrl(const QUrl &url); - -signals: - - /** - * @brief emitted when the user starts a download - * @param pageUrl url of the current web site from which the download was started - * @param reply network reply of the started download - */ - void requestDownload(const QUrl &pageUrl, QNetworkReply *reply); - -protected: - - virtual void closeEvent(QCloseEvent *); - -private slots: - - void initTab(BrowserView *newView); - void openInNewTab(const QUrl &url); - - void progress(int value); - - void titleChanged(const QString &title); - void unsupportedContent(QNetworkReply *reply); - void downloadRequested(const QNetworkRequest &request); - - void tabCloseRequested(int index); - - void urlChanged(const QUrl &url); - - void on_backBtn_clicked(); - - void on_fwdBtn_clicked(); - - void on_searchEdit_returnPressed(); - - void startSearch(); - - void on_refreshBtn_clicked(); - - void on_browserTabWidget_currentChanged(int index); - -private: - - QString guessFileName(const QString &url); - - BrowserView *getCurrentView(); - - void maximizeWidth(); - -private: - - Ui::BrowserDialog *ui; - - QNetworkAccessManager *m_AccessManager; - - QTabWidget *m_Tabs; - -}; - -#endif // BROWSERDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef BROWSERDIALOG_H +#define BROWSERDIALOG_H + +#include "browserview.h" +#include "tutorialcontrol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Ui { + class BrowserDialog; +} + + +/** + * @brief a dialog containing a webbrowser that is intended to browse the nexus network + **/ +class BrowserDialog : public QDialog +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param accessManager the access manager to use for network requests + * @param parent parent widget + **/ + explicit BrowserDialog(QWidget *parent = 0); + ~BrowserDialog(); + + /** + * @brief set the url to open. If automatic login is enabled, the url is opened after login + * + * @param url the url to open + **/ + void openUrl(const QUrl &url); + +signals: + + /** + * @brief emitted when the user starts a download + * @param pageUrl url of the current web site from which the download was started + * @param reply network reply of the started download + */ + void requestDownload(const QUrl &pageUrl, QNetworkReply *reply); + +protected: + + virtual void closeEvent(QCloseEvent *); + +private slots: + + void initTab(BrowserView *newView); + void openInNewTab(const QUrl &url); + + void progress(int value); + + void titleChanged(const QString &title); + void unsupportedContent(QNetworkReply *reply); + void downloadRequested(const QNetworkRequest &request); + + void tabCloseRequested(int index); + + void urlChanged(const QUrl &url); + + void on_backBtn_clicked(); + + void on_fwdBtn_clicked(); + + void on_searchEdit_returnPressed(); + + void startSearch(); + + void on_refreshBtn_clicked(); + + void on_browserTabWidget_currentChanged(int index); + +private: + + QString guessFileName(const QString &url); + + BrowserView *getCurrentView(); + + void maximizeWidth(); + +private: + + Ui::BrowserDialog *ui; + + QNetworkAccessManager *m_AccessManager; + + QTabWidget *m_Tabs; + +}; + +#endif // BROWSERDIALOG_H diff --git a/src/browserview.cpp b/src/browserview.cpp index f1b61e66..bd43a18e 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -1,76 +1,76 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "browserview.h" - -#include -#include -#include -#include -#include -#include -#include -#include "utility.h" - -BrowserView::BrowserView(QWidget *parent) - : QWebView(parent) -{ - installEventFilter(this); - - page()->settings()->setMaximumPagesInCache(10); -} - - -QWebView *BrowserView::createWindow(QWebPage::WebWindowType) -{ - BrowserView *newView = new BrowserView(parentWidget()); - emit initTab(newView); - return newView; -} - - -bool BrowserView::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::ShortcutOverride) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->matches(QKeySequence::Find)) { - emit startFind(); - } else if (keyEvent->matches(QKeySequence::FindNext)) { - emit findAgain(); - } - } else if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::MidButton) { - mouseEvent->ignore(); - return true; - } - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::MidButton) { - QWebHitTestResult hitTest = page()->frameAt(mouseEvent->pos())->hitTestContent(mouseEvent->pos()); - if (hitTest.linkUrl().isValid()) { - emit openUrlInNewTab(hitTest.linkUrl()); - } - mouseEvent->ignore(); - - return true; - } - } - return QWebView::eventFilter(obj, event); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "browserview.h" + +#include +#include +#include +#include +#include +#include +#include +#include "utility.h" + +BrowserView::BrowserView(QWidget *parent) + : QWebView(parent) +{ + installEventFilter(this); + + page()->settings()->setMaximumPagesInCache(10); +} + + +QWebView *BrowserView::createWindow(QWebPage::WebWindowType) +{ + BrowserView *newView = new BrowserView(parentWidget()); + emit initTab(newView); + return newView; +} + + +bool BrowserView::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ShortcutOverride) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->matches(QKeySequence::Find)) { + emit startFind(); + } else if (keyEvent->matches(QKeySequence::FindNext)) { + emit findAgain(); + } + } else if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::MidButton) { + mouseEvent->ignore(); + return true; + } + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::MidButton) { + QWebHitTestResult hitTest = page()->frameAt(mouseEvent->pos())->hitTestContent(mouseEvent->pos()); + if (hitTest.linkUrl().isValid()) { + emit openUrlInNewTab(hitTest.linkUrl()); + } + mouseEvent->ignore(); + + return true; + } + } + return QWebView::eventFilter(obj, event); +} diff --git a/src/browserview.h b/src/browserview.h index 3468276b..4f002ffc 100644 --- a/src/browserview.h +++ b/src/browserview.h @@ -1,80 +1,80 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef NEXUSVIEW_H -#define NEXUSVIEW_H - -#include "finddialog.h" - -#include -#include -#include - -/** - * @brief web view used to display a nexus page - **/ -class BrowserView : public QWebView -{ - Q_OBJECT - -public: - - explicit BrowserView(QWidget *parent = 0); - -signals: - - /** - * @brief emitted when the user opens a new window to be displayed in another tab - * - * @param newView the view for the newly opened window - **/ - void initTab(BrowserView *newView); - - /** - * @brief emitted when the user requests a link to be opened in a new tab by middle-clicking - * - * @param url the url to open - */ - void openUrlInNewTab(const QUrl &url); - - /** - * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility - */ - void startFind(); - - /** - * @brief F3 was pressed. The containing dialog should search again - */ - void findAgain(); - -protected: - - virtual QWebView *createWindow(QWebPage::WebWindowType type); - - virtual bool eventFilter(QObject *obj, QEvent *event); - - -private: - - QString m_FindPattern; - bool m_MiddleClick; - -}; - -#endif // NEXUSVIEW_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef NEXUSVIEW_H +#define NEXUSVIEW_H + +#include "finddialog.h" + +#include +#include +#include + +/** + * @brief web view used to display a nexus page + **/ +class BrowserView : public QWebView +{ + Q_OBJECT + +public: + + explicit BrowserView(QWidget *parent = 0); + +signals: + + /** + * @brief emitted when the user opens a new window to be displayed in another tab + * + * @param newView the view for the newly opened window + **/ + void initTab(BrowserView *newView); + + /** + * @brief emitted when the user requests a link to be opened in a new tab by middle-clicking + * + * @param url the url to open + */ + void openUrlInNewTab(const QUrl &url); + + /** + * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility + */ + void startFind(); + + /** + * @brief F3 was pressed. The containing dialog should search again + */ + void findAgain(); + +protected: + + virtual QWebView *createWindow(QWebPage::WebWindowType type); + + virtual bool eventFilter(QObject *obj, QEvent *event); + + +private: + + QString m_FindPattern; + bool m_MiddleClick; + +}; + +#endif // NEXUSVIEW_H diff --git a/src/categories.cpp b/src/categories.cpp index 27720829..79c646b3 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -1,301 +1,301 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "categories.h" -#include -#include "report.h" -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -CategoryFactory* CategoryFactory::s_Instance = NULL; - - -QString CategoryFactory::categoriesFilePath() -{ - return QCoreApplication::applicationDirPath() + "/categories.dat"; -} - - -CategoryFactory::CategoryFactory() -{ - atexit(&cleanup); - reset(); - - QFile categoryFile(categoriesFilePath()); - - if (!categoryFile.open(QIODevice::ReadOnly)) { - loadDefaultCategories(); - } else { - int lineNum = 0; - while (!categoryFile.atEnd()) { - QByteArray line = categoryFile.readLine(); - ++lineNum; - QList cells = line.split('|'); - if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", - lineNum, line.constData(), cells.count()); - } else { - std::vector nexusIDs; - if (cells[2].length() > 0) { - QList nexusIDStrings = cells[2].split(','); - for (QList::iterator iter = nexusIDStrings.begin(); - iter != nexusIDStrings.end(); ++iter) { - bool ok = false; - int temp = iter->toInt(&ok); - if (!ok) { - qCritical("invalid id %s", iter->constData()); - } - nexusIDs.push_back(temp); - } - } - bool cell0Ok = true; - bool cell3Ok = true; - int id = cells[0].toInt(&cell0Ok); - int parentID = cells[3].trimmed().toInt(&cell3Ok); - if (!cell0Ok || !cell3Ok) { - qCritical("invalid category line %d: %s", - lineNum, line.constData()); - } - addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); - } - } - categoryFile.close(); - } - std::sort(m_Categories.begin(), m_Categories.end()); - setParents(); -} - - -CategoryFactory &CategoryFactory::instance() -{ - if (s_Instance == NULL) { - s_Instance = new CategoryFactory; - } - return *s_Instance; -} - - -void CategoryFactory::reset() -{ - m_Categories.clear(); - m_IDMap.clear(); - addCategory(0, "None", MakeVector(2, 28, 87), 0); -} - - -void CategoryFactory::setParents() -{ - for (std::vector::iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - iter->m_HasChildren = false; - } - - for (std::vector::const_iterator categoryIter = m_Categories.begin(); - categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->m_ParentID != 0) { - std::map::const_iterator iter = m_IDMap.find(categoryIter->m_ParentID); - if (iter != m_IDMap.end()) { - m_Categories[iter->second].m_HasChildren = true; - } - } - } -} - -void CategoryFactory::cleanup() -{ - delete s_Instance; - s_Instance = NULL; -} - - -void CategoryFactory::saveCategories() -{ - QFile categoryFile(categoriesFilePath()); - - if (!categoryFile.open(QIODevice::WriteOnly)) { - reportError(QObject::tr("Failed to save custom categories")); - return; - } - - categoryFile.resize(0); - for (std::vector::const_iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - if (iter->m_ID == 0) { - continue; - } - QByteArray line; - line.append(QByteArray::number(iter->m_ID)).append("|") - .append(iter->m_Name.toUtf8()).append("|") - .append(VectorJoin(iter->m_NexusIDs, ",")).append("|") - .append(QByteArray::number(iter->m_ParentID)).append("\n"); - categoryFile.write(line); - } - categoryFile.close(); -} - - -unsigned int CategoryFactory::countCategories(std::tr1::function filter) -{ - unsigned int result = 0; - for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if (filter(*iter)) { - ++result; - } - } - return result; -} - - -void CategoryFactory::addCategory(int id, const QString &name, const std::vector &nexusIDs, int parentID) -{ - int index = m_Categories.size(); - m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); - for (std::vector::const_iterator iter = nexusIDs.begin(); - iter != nexusIDs.end(); ++iter) { - m_NexusMap[*iter] = index; - } - m_IDMap[id] = index; -} - - -void CategoryFactory::loadDefaultCategories() -{ - // the order here is relevant as it defines the order in which the - // mods appear in the combo box - addCategory(1, "Animations", MakeVector(1, 51), 0); - addCategory(2, "Armour", MakeVector(1, 54), 0); - addCategory(3, "Audio", MakeVector(1, 61), 0); - addCategory(5, "Clothing", MakeVector(1, 60), 0); - addCategory(6, "Collectables", MakeVector(1, 92), 0); - addCategory(7, "Creatures", MakeVector(2, 83, 65), 0); - addCategory(8, "Factions", MakeVector(1, 25), 0); - addCategory(9, "Gameplay", MakeVector(1, 24), 0); - addCategory(10, "Hair", MakeVector(1, 26), 0); - addCategory(11, "Items", MakeVector(2, 27, 85), 0); - addCategory(19, "Weapons", MakeVector(2, 36, 55), 11); - addCategory(12, "Locations", MakeVector(7, 22, 30, 70, 88, 89, 90, 91), 0); - addCategory(4, "Cities", MakeVector(1, 53), 12); - addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); - addCategory(21, "Models & Textures", MakeVector(1, 29), 0); - addCategory(13, "NPCs", MakeVector(1, 33), 0); - addCategory(14, "Patches", MakeVector(2, 79, 84), 0); - addCategory(15, "Quests", MakeVector(1, 35), 0); - addCategory(16, "Races & Classes", MakeVector(1, 34), 0); - addCategory(22, "Skills", MakeVector(1, 73), 0); - addCategory(17, "UI", MakeVector(1, 42), 0); - addCategory(18, "Visuals", MakeVector(1, 62), 0); -} - - -int CategoryFactory::getParentID(unsigned int index) const -{ - if ((index < 0) || (index >= m_Categories.size())) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - - return m_Categories[index].m_ParentID; -} - - -bool CategoryFactory::categoryExists(int id) const -{ - return m_IDMap.find(id) != m_IDMap.end(); -} - - -bool CategoryFactory::isDecendantOf(int id, int parentID) const -{ - std::map::const_iterator iter = m_IDMap.find(id); - if (iter != m_IDMap.end()) { - unsigned int index = iter->second; - if (m_Categories[index].m_ParentID == 0) { - return false; - } else if (m_Categories[index].m_ParentID == parentID) { - return true; - } else { - return isDecendantOf(m_Categories[index].m_ParentID, parentID); - } - } else { - qWarning("%d is no valid category id", id); - return false; - } -} - - -bool CategoryFactory::hasChildren(unsigned int index) const -{ - if ((index < 0) || (index >= m_Categories.size())) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - - return m_Categories[index].m_HasChildren; -} - - -QString CategoryFactory::getCategoryName(unsigned int index) const -{ - if ((index < 0) || (index >= m_Categories.size())) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - - return m_Categories[index].m_Name; -} - - -int CategoryFactory::getCategoryID(unsigned int index) const -{ - if ((index < 0) || (index >= m_Categories.size())) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - - return m_Categories[index].m_ID; -} - - -int CategoryFactory::getCategoryIndex(int ID) const -{ - std::map::const_iterator iter = m_IDMap.find(ID); - if (iter == m_IDMap.end()) { - throw MyException(QObject::tr("invalid category id %1").arg(ID)); - } - return iter->second; -} - - - -unsigned int CategoryFactory::resolveNexusID(int nexusID) const -{ - std::map::const_iterator iter = m_NexusMap.find(nexusID); - if (iter != m_NexusMap.end()) { - qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); - return iter->second; - } else { - qDebug("nexus category id %d not mapped", nexusID); - return 0U; - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "categories.h" +#include +#include "report.h" +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +CategoryFactory* CategoryFactory::s_Instance = NULL; + + +QString CategoryFactory::categoriesFilePath() +{ + return QCoreApplication::applicationDirPath() + "/categories.dat"; +} + + +CategoryFactory::CategoryFactory() +{ + atexit(&cleanup); + reset(); + + QFile categoryFile(categoriesFilePath()); + + if (!categoryFile.open(QIODevice::ReadOnly)) { + loadDefaultCategories(); + } else { + int lineNum = 0; + while (!categoryFile.atEnd()) { + QByteArray line = categoryFile.readLine(); + ++lineNum; + QList cells = line.split('|'); + if (cells.count() != 4) { + qCritical("invalid category line %d: %s (%d cells)", + lineNum, line.constData(), cells.count()); + } else { + std::vector nexusIDs; + if (cells[2].length() > 0) { + QList nexusIDStrings = cells[2].split(','); + for (QList::iterator iter = nexusIDStrings.begin(); + iter != nexusIDStrings.end(); ++iter) { + bool ok = false; + int temp = iter->toInt(&ok); + if (!ok) { + qCritical("invalid id %s", iter->constData()); + } + nexusIDs.push_back(temp); + } + } + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[3].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + qCritical("invalid category line %d: %s", + lineNum, line.constData()); + } + addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); + } + } + categoryFile.close(); + } + std::sort(m_Categories.begin(), m_Categories.end()); + setParents(); +} + + +CategoryFactory &CategoryFactory::instance() +{ + if (s_Instance == NULL) { + s_Instance = new CategoryFactory; + } + return *s_Instance; +} + + +void CategoryFactory::reset() +{ + m_Categories.clear(); + m_IDMap.clear(); + addCategory(0, "None", MakeVector(2, 28, 87), 0); +} + + +void CategoryFactory::setParents() +{ + for (std::vector::iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + iter->m_HasChildren = false; + } + + for (std::vector::const_iterator categoryIter = m_Categories.begin(); + categoryIter != m_Categories.end(); ++categoryIter) { + if (categoryIter->m_ParentID != 0) { + std::map::const_iterator iter = m_IDMap.find(categoryIter->m_ParentID); + if (iter != m_IDMap.end()) { + m_Categories[iter->second].m_HasChildren = true; + } + } + } +} + +void CategoryFactory::cleanup() +{ + delete s_Instance; + s_Instance = NULL; +} + + +void CategoryFactory::saveCategories() +{ + QFile categoryFile(categoriesFilePath()); + + if (!categoryFile.open(QIODevice::WriteOnly)) { + reportError(QObject::tr("Failed to save custom categories")); + return; + } + + categoryFile.resize(0); + for (std::vector::const_iterator iter = m_Categories.begin(); + iter != m_Categories.end(); ++iter) { + if (iter->m_ID == 0) { + continue; + } + QByteArray line; + line.append(QByteArray::number(iter->m_ID)).append("|") + .append(iter->m_Name.toUtf8()).append("|") + .append(VectorJoin(iter->m_NexusIDs, ",")).append("|") + .append(QByteArray::number(iter->m_ParentID)).append("\n"); + categoryFile.write(line); + } + categoryFile.close(); +} + + +unsigned int CategoryFactory::countCategories(std::tr1::function filter) +{ + unsigned int result = 0; + for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if (filter(*iter)) { + ++result; + } + } + return result; +} + + +void CategoryFactory::addCategory(int id, const QString &name, const std::vector &nexusIDs, int parentID) +{ + int index = m_Categories.size(); + m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); + for (std::vector::const_iterator iter = nexusIDs.begin(); + iter != nexusIDs.end(); ++iter) { + m_NexusMap[*iter] = index; + } + m_IDMap[id] = index; +} + + +void CategoryFactory::loadDefaultCategories() +{ + // the order here is relevant as it defines the order in which the + // mods appear in the combo box + addCategory(1, "Animations", MakeVector(1, 51), 0); + addCategory(2, "Armour", MakeVector(1, 54), 0); + addCategory(3, "Audio", MakeVector(1, 61), 0); + addCategory(5, "Clothing", MakeVector(1, 60), 0); + addCategory(6, "Collectables", MakeVector(1, 92), 0); + addCategory(7, "Creatures", MakeVector(2, 83, 65), 0); + addCategory(8, "Factions", MakeVector(1, 25), 0); + addCategory(9, "Gameplay", MakeVector(1, 24), 0); + addCategory(10, "Hair", MakeVector(1, 26), 0); + addCategory(11, "Items", MakeVector(2, 27, 85), 0); + addCategory(19, "Weapons", MakeVector(2, 36, 55), 11); + addCategory(12, "Locations", MakeVector(7, 22, 30, 70, 88, 89, 90, 91), 0); + addCategory(4, "Cities", MakeVector(1, 53), 12); + addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); + addCategory(21, "Models & Textures", MakeVector(1, 29), 0); + addCategory(13, "NPCs", MakeVector(1, 33), 0); + addCategory(14, "Patches", MakeVector(2, 79, 84), 0); + addCategory(15, "Quests", MakeVector(1, 35), 0); + addCategory(16, "Races & Classes", MakeVector(1, 34), 0); + addCategory(22, "Skills", MakeVector(1, 73), 0); + addCategory(17, "UI", MakeVector(1, 42), 0); + addCategory(18, "Visuals", MakeVector(1, 62), 0); +} + + +int CategoryFactory::getParentID(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_ParentID; +} + + +bool CategoryFactory::categoryExists(int id) const +{ + return m_IDMap.find(id) != m_IDMap.end(); +} + + +bool CategoryFactory::isDecendantOf(int id, int parentID) const +{ + std::map::const_iterator iter = m_IDMap.find(id); + if (iter != m_IDMap.end()) { + unsigned int index = iter->second; + if (m_Categories[index].m_ParentID == 0) { + return false; + } else if (m_Categories[index].m_ParentID == parentID) { + return true; + } else { + return isDecendantOf(m_Categories[index].m_ParentID, parentID); + } + } else { + qWarning("%d is no valid category id", id); + return false; + } +} + + +bool CategoryFactory::hasChildren(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_HasChildren; +} + + +QString CategoryFactory::getCategoryName(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_Name; +} + + +int CategoryFactory::getCategoryID(unsigned int index) const +{ + if ((index < 0) || (index >= m_Categories.size())) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_ID; +} + + +int CategoryFactory::getCategoryIndex(int ID) const +{ + std::map::const_iterator iter = m_IDMap.find(ID); + if (iter == m_IDMap.end()) { + throw MyException(QObject::tr("invalid category id %1").arg(ID)); + } + return iter->second; +} + + + +unsigned int CategoryFactory::resolveNexusID(int nexusID) const +{ + std::map::const_iterator iter = m_NexusMap.find(nexusID); + if (iter != m_NexusMap.end()) { + qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); + return iter->second; + } else { + qDebug("nexus category id %d not mapped", nexusID); + return 0U; + } +} diff --git a/src/categories.h b/src/categories.h index bfebe1cb..96e436e5 100644 --- a/src/categories.h +++ b/src/categories.h @@ -1,200 +1,200 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef CATEGORIES_H -#define CATEGORIES_H - - -#include -#include -#include -#include - - -/** - * @brief Manage the available mod categories - * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, - * optimized to where the request comes from. Therefore be very careful which of the two you have available - **/ -class CategoryFactory { - - friend class CategoriesDialog; - -public: - - static const int CATEGORY_NONE = 0; - - static const int CATEGORY_SPECIAL_FIRST = 10000; - static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; - static const int CATEGORY_SPECIAL_UNCHECKED = 10001; - static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; - static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; - static const int CATEGORY_SPECIAL_CONFLICT = 10004; - static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; - static const int CATEGORY_SPECIAL_MANAGED = 10006; - static const int CATEGORY_SPECIAL_UNMANAGED = 10007; - -public: - - struct Category { - Category(int sortValue, int id, const QString &name, const std::vector &nexusIDs, int parentID) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), - m_NexusIDs(nexusIDs), m_ParentID(parentID) {} - int m_SortValue; - int m_ID; - int m_ParentID; - bool m_HasChildren; - QString m_Name; - std::vector m_NexusIDs; - - friend bool operator<(const Category &LHS, const Category &RHS) { - return LHS.m_SortValue < RHS.m_SortValue; - } - }; - -public: - - /** - * @brief reset the list of categories - **/ - void reset(); - - /** - * @brief save the categories to the categories.dat file - **/ - void saveCategories(); - - /** - * @brief retrieve the number of available categories - * - * @return unsigned int number of categories - **/ - unsigned numCategories() const { return m_Categories.size(); } - - /** - * @brief count all categories that match a specified filter - * @param filter the filter to test - * @return number of matching categories - */ - unsigned int countCategories(std::tr1::function filter); - - /** - * @brief get the id of the parent category - * - * @param index the index to look up - * @return int id of the parent category - **/ - int getParentID(unsigned int index) const; - - /** - * @brief determine if a category exists (by id) - * - * @param id the id to check for existance - * @return true if the category exists, false otherwise - **/ - bool categoryExists(int id) const; - - /** - * @brief test if a category is child of a second one - * @param id the presumed child id - * @param parentID the parent id to test for - * @return true if id is a child of parentID - **/ - bool isDecendantOf(int id, int parentID) const; - - /** - * @brief test if the specified category has child categories - * - * @param index index of the category to look up - * @return bool true if the category has child categories - **/ - bool hasChildren(unsigned int index) const; - - /** - * @brief retrieve the name of a category - * - * @param index index of the category to look up - * @return QString name of the category - **/ - QString getCategoryName(unsigned int index) const; - - /** - * @brief look up the id of a category by its index - * - * @param index index of the category to look up - * @return int id of the category - **/ - int getCategoryID(unsigned int index) const; - - /** - * @brief look up the index of a category by its id - * - * @param id index of the category to look up - * @return unsigned int index of the category - **/ - int getCategoryIndex(int ID) const; - - /** - * @brief retrieve the index of a category by its nexus id - * - * @param nexusID nexus id of the category to look up - * @return unsigned int index of the category or 0 if no category matches - **/ - unsigned int resolveNexusID(int nexusID) const; - -public: - - /** - * @brief retrieve a reference to the singleton instance - * - * @return the reference to the singleton - **/ - static CategoryFactory &instance(); - - /** - * @return path to the file that contains the categories list - */ - static QString categoriesFilePath(); - -private: - - CategoryFactory(); - - void loadDefaultCategories(); - - void addCategory(int id, const QString &name, const std::vector &nexusID, int parentID); - - void setParents(); - - static void cleanup(); - -private: - - static CategoryFactory *s_Instance; - - std::vector m_Categories; - std::map m_IDMap; - std::map m_NexusMap; - -private: - -}; - - -#endif // CATEGORIES_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef CATEGORIES_H +#define CATEGORIES_H + + +#include +#include +#include +#include + + +/** + * @brief Manage the available mod categories + * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, + * optimized to where the request comes from. Therefore be very careful which of the two you have available + **/ +class CategoryFactory { + + friend class CategoriesDialog; + +public: + + static const int CATEGORY_NONE = 0; + + static const int CATEGORY_SPECIAL_FIRST = 10000; + static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; + static const int CATEGORY_SPECIAL_UNCHECKED = 10001; + static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; + static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; + static const int CATEGORY_SPECIAL_CONFLICT = 10004; + static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; + static const int CATEGORY_SPECIAL_MANAGED = 10006; + static const int CATEGORY_SPECIAL_UNMANAGED = 10007; + +public: + + struct Category { + Category(int sortValue, int id, const QString &name, const std::vector &nexusIDs, int parentID) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), + m_NexusIDs(nexusIDs), m_ParentID(parentID) {} + int m_SortValue; + int m_ID; + int m_ParentID; + bool m_HasChildren; + QString m_Name; + std::vector m_NexusIDs; + + friend bool operator<(const Category &LHS, const Category &RHS) { + return LHS.m_SortValue < RHS.m_SortValue; + } + }; + +public: + + /** + * @brief reset the list of categories + **/ + void reset(); + + /** + * @brief save the categories to the categories.dat file + **/ + void saveCategories(); + + /** + * @brief retrieve the number of available categories + * + * @return unsigned int number of categories + **/ + unsigned numCategories() const { return m_Categories.size(); } + + /** + * @brief count all categories that match a specified filter + * @param filter the filter to test + * @return number of matching categories + */ + unsigned int countCategories(std::tr1::function filter); + + /** + * @brief get the id of the parent category + * + * @param index the index to look up + * @return int id of the parent category + **/ + int getParentID(unsigned int index) const; + + /** + * @brief determine if a category exists (by id) + * + * @param id the id to check for existance + * @return true if the category exists, false otherwise + **/ + bool categoryExists(int id) const; + + /** + * @brief test if a category is child of a second one + * @param id the presumed child id + * @param parentID the parent id to test for + * @return true if id is a child of parentID + **/ + bool isDecendantOf(int id, int parentID) const; + + /** + * @brief test if the specified category has child categories + * + * @param index index of the category to look up + * @return bool true if the category has child categories + **/ + bool hasChildren(unsigned int index) const; + + /** + * @brief retrieve the name of a category + * + * @param index index of the category to look up + * @return QString name of the category + **/ + QString getCategoryName(unsigned int index) const; + + /** + * @brief look up the id of a category by its index + * + * @param index index of the category to look up + * @return int id of the category + **/ + int getCategoryID(unsigned int index) const; + + /** + * @brief look up the index of a category by its id + * + * @param id index of the category to look up + * @return unsigned int index of the category + **/ + int getCategoryIndex(int ID) const; + + /** + * @brief retrieve the index of a category by its nexus id + * + * @param nexusID nexus id of the category to look up + * @return unsigned int index of the category or 0 if no category matches + **/ + unsigned int resolveNexusID(int nexusID) const; + +public: + + /** + * @brief retrieve a reference to the singleton instance + * + * @return the reference to the singleton + **/ + static CategoryFactory &instance(); + + /** + * @return path to the file that contains the categories list + */ + static QString categoriesFilePath(); + +private: + + CategoryFactory(); + + void loadDefaultCategories(); + + void addCategory(int id, const QString &name, const std::vector &nexusID, int parentID); + + void setParents(); + + static void cleanup(); + +private: + + static CategoryFactory *s_Instance; + + std::vector m_Categories; + std::map m_IDMap; + std::map m_NexusMap; + +private: + +}; + + +#endif // CATEGORIES_H diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 793ece66..881179a4 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -1,243 +1,243 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "categoriesdialog.h" -#include "ui_categoriesdialog.h" -#include "categories.h" -#include "utility.h" -#include -#include -#include -#include - - -class NewIDValidator : public QIntValidator { -public: - NewIDValidator(const std::set &ids) - : m_UsedIDs(ids) {} - virtual State validate(QString &input, int &pos) const { - State intRes = QIntValidator::validate(input, pos); - if (intRes == Acceptable) { - bool ok = false; - int id = input.toInt(&ok); - if (m_UsedIDs.find(id) != m_UsedIDs.end()) { - return QValidator::Intermediate; - } - } - return intRes; - } -private: - const std::set &m_UsedIDs; -}; - - -class ExistingIDValidator : public QIntValidator { -public: - ExistingIDValidator(const std::set &ids) - : m_UsedIDs(ids) {} - virtual State validate(QString &input, int &pos) const { - State intRes = QIntValidator::validate(input, pos); - if (intRes == Acceptable) { - bool ok = false; - int id = input.toInt(&ok); - if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) { - return QValidator::Acceptable; - } else { - return QValidator::Intermediate; - } - } else { - return intRes; - } - } -private: - const std::set &m_UsedIDs; -}; - - -class ValidatingDelegate : public QItemDelegate { - -public: - ValidatingDelegate(QObject *parent, QValidator *validator) - : QItemDelegate(parent), m_Validator(validator) {} - - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const - { - QLineEdit *edit = new QLineEdit(parent); - edit->setValidator(m_Validator); - return edit; - } - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const - { - QLineEdit *edit = qobject_cast(editor); - int pos = 0; - QString editText = edit->text(); - if (m_Validator->validate(editText, pos) == QValidator::Acceptable) { - QItemDelegate::setModelData(editor, model, index); - } - } -private: - QValidator *m_Validator; -}; - - -CategoriesDialog::CategoriesDialog(QWidget *parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) -{ - ui->setupUi(this); - fillTable(); - connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); -} - -CategoriesDialog::~CategoriesDialog() -{ - delete ui; -} - - -void CategoriesDialog::cellChanged(int row, int) -{ - int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); - if (currentID > m_HighestID) { - m_HighestID = currentID; - } -} - - -void CategoriesDialog::commitChanges() -{ - CategoryFactory &categories = CategoryFactory::instance(); - categories.reset(); - - for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { - int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); - QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); - QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts); - std::vector nexusIDs; - for (QStringList::iterator iter = nexusIDStringList.begin(); - iter != nexusIDStringList.end(); ++iter) { - nexusIDs.push_back(iter->toInt()); - } - - categories.addCategory( - ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), - nexusIDs, - ui->categoriesTable->item(index, 3)->text().toInt()); - } - categories.setParents(); - - categories.saveCategories(); -} - - -void CategoriesDialog::refreshIDs() -{ - m_HighestID = 0; - for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { - int id = ui->categoriesTable->item(i, 0)->text().toInt(); - if (id > m_HighestID) { - m_HighestID = id; - } - m_IDs.insert(id); - } -} - - -void CategoriesDialog::fillTable() -{ - CategoryFactory &categories = CategoryFactory::instance(); - QTableWidget *table = ui->categoriesTable; - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); - table->verticalHeader()->setSectionsMovable(true); - table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); -#else - table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); - table->verticalHeader()->setMovable(true); - table->verticalHeader()->setResizeMode(QHeaderView::Fixed); -#endif - - - table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); - table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); - - int row = 0; - for (std::vector::const_iterator iter = categories.m_Categories.begin(); - iter != categories.m_Categories.end(); ++iter, ++row) { - const CategoryFactory::Category &category = *iter; - if (category.m_ID == 0) { - --row; - continue; - } - table->insertRow(row); -// table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - - QScopedPointer idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, category.m_ID); - - QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); - QScopedPointer nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); - QScopedPointer parentIDItem(new QTableWidgetItem()); - parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); - - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, nexusIDItem.take()); - table->setItem(row, 3, parentIDItem.take()); - } - - refreshIDs(); -} - - -void CategoriesDialog::addCategory_clicked() -{ - int row = m_ContextRow >= 0 ? m_ContextRow : 0; - ui->categoriesTable->insertRow(row); - ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); - ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID))); - ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); - ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("")); - ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); -} - - -void CategoriesDialog::removeCategory_clicked() -{ - ui->categoriesTable->removeRow(m_ContextRow); -} - - -void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint &pos) -{ - m_ContextRow = ui->categoriesTable->rowAt(pos.y()); - QMenu menu; - menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); - menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); - - menu.exec(ui->categoriesTable->mapToGlobal(pos)); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "categoriesdialog.h" +#include "ui_categoriesdialog.h" +#include "categories.h" +#include "utility.h" +#include +#include +#include +#include + + +class NewIDValidator : public QIntValidator { +public: + NewIDValidator(const std::set &ids) + : m_UsedIDs(ids) {} + virtual State validate(QString &input, int &pos) const { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if (m_UsedIDs.find(id) != m_UsedIDs.end()) { + return QValidator::Intermediate; + } + } + return intRes; + } +private: + const std::set &m_UsedIDs; +}; + + +class ExistingIDValidator : public QIntValidator { +public: + ExistingIDValidator(const std::set &ids) + : m_UsedIDs(ids) {} + virtual State validate(QString &input, int &pos) const { + State intRes = QIntValidator::validate(input, pos); + if (intRes == Acceptable) { + bool ok = false; + int id = input.toInt(&ok); + if ((id == 0) || (m_UsedIDs.find(id) != m_UsedIDs.end())) { + return QValidator::Acceptable; + } else { + return QValidator::Intermediate; + } + } else { + return intRes; + } + } +private: + const std::set &m_UsedIDs; +}; + + +class ValidatingDelegate : public QItemDelegate { + +public: + ValidatingDelegate(QObject *parent, QValidator *validator) + : QItemDelegate(parent), m_Validator(validator) {} + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const + { + QLineEdit *edit = new QLineEdit(parent); + edit->setValidator(m_Validator); + return edit; + } + virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const + { + QLineEdit *edit = qobject_cast(editor); + int pos = 0; + QString editText = edit->text(); + if (m_Validator->validate(editText, pos) == QValidator::Acceptable) { + QItemDelegate::setModelData(editor, model, index); + } + } +private: + QValidator *m_Validator; +}; + + +CategoriesDialog::CategoriesDialog(QWidget *parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) +{ + ui->setupUi(this); + fillTable(); + connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); +} + +CategoriesDialog::~CategoriesDialog() +{ + delete ui; +} + + +void CategoriesDialog::cellChanged(int row, int) +{ + int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); + if (currentID > m_HighestID) { + m_HighestID = currentID; + } +} + + +void CategoriesDialog::commitChanges() +{ + CategoryFactory &categories = CategoryFactory::instance(); + categories.reset(); + + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int index = ui->categoriesTable->verticalHeader()->logicalIndex(i); + QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); + QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts); + std::vector nexusIDs; + for (QStringList::iterator iter = nexusIDStringList.begin(); + iter != nexusIDStringList.end(); ++iter) { + nexusIDs.push_back(iter->toInt()); + } + + categories.addCategory( + ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), + nexusIDs, + ui->categoriesTable->item(index, 3)->text().toInt()); + } + categories.setParents(); + + categories.saveCategories(); +} + + +void CategoriesDialog::refreshIDs() +{ + m_HighestID = 0; + for (int i = 0; i < ui->categoriesTable->rowCount(); ++i) { + int id = ui->categoriesTable->item(i, 0)->text().toInt(); + if (id > m_HighestID) { + m_HighestID = id; + } + m_IDs.insert(id); + } +} + + +void CategoriesDialog::fillTable() +{ + CategoryFactory &categories = CategoryFactory::instance(); + QTableWidget *table = ui->categoriesTable; + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setSectionsMovable(true); + table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); +#else + table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setMovable(true); + table->verticalHeader()->setResizeMode(QHeaderView::Fixed); +#endif + + + table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + + int row = 0; + for (std::vector::const_iterator iter = categories.m_Categories.begin(); + iter != categories.m_Categories.end(); ++iter, ++row) { + const CategoryFactory::Category &category = *iter; + if (category.m_ID == 0) { + --row; + continue; + } + table->insertRow(row); +// table->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, category.m_ID); + + QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, nexusIDItem.take()); + table->setItem(row, 3, parentIDItem.take()); + } + + refreshIDs(); +} + + +void CategoriesDialog::addCategory_clicked() +{ + int row = m_ContextRow >= 0 ? m_ContextRow : 0; + ui->categoriesTable->insertRow(row); + ui->categoriesTable->setVerticalHeaderItem(row, new QTableWidgetItem(" ")); + ui->categoriesTable->setItem(row, 0, new QTableWidgetItem(QString::number(++m_HighestID))); + ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); + ui->categoriesTable->setItem(row, 2, new QTableWidgetItem("")); + ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); +} + + +void CategoriesDialog::removeCategory_clicked() +{ + ui->categoriesTable->removeRow(m_ContextRow); +} + + +void CategoriesDialog::on_categoriesTable_customContextMenuRequested(const QPoint &pos) +{ + m_ContextRow = ui->categoriesTable->rowAt(pos.y()); + QMenu menu; + menu.addAction(tr("Add"), this, SLOT(addCategory_clicked())); + menu.addAction(tr("Remove"), this, SLOT(removeCategory_clicked())); + + menu.exec(ui->categoriesTable->mapToGlobal(pos)); +} diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 67851618..72d2154d 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -1,71 +1,71 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef CATEGORIESDIALOG_H -#define CATEGORIESDIALOG_H - -#include "tutorabledialog.h" -#include - -namespace Ui { -class CategoriesDialog; -} - -/** - * @brief Dialog that allows users to configure mod categories - **/ -class CategoriesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - explicit CategoriesDialog(QWidget *parent = 0); - ~CategoriesDialog(); - - /** - * @brief store changes here to the global categories store (categories.h) - * - **/ - void commitChanges(); - -private slots: - - void on_categoriesTable_customContextMenuRequested(const QPoint &pos); - void addCategory_clicked(); - void removeCategory_clicked(); - void cellChanged(int row, int column); - -private: - - void refreshIDs(); - void fillTable(); - -private: - - Ui::CategoriesDialog *ui; - int m_ContextRow; - - int m_HighestID; - std::set m_IDs; - -}; - -#endif // CATEGORIESDIALOG_H - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef CATEGORIESDIALOG_H +#define CATEGORIESDIALOG_H + +#include "tutorabledialog.h" +#include + +namespace Ui { +class CategoriesDialog; +} + +/** + * @brief Dialog that allows users to configure mod categories + **/ +class CategoriesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + explicit CategoriesDialog(QWidget *parent = 0); + ~CategoriesDialog(); + + /** + * @brief store changes here to the global categories store (categories.h) + * + **/ + void commitChanges(); + +private slots: + + void on_categoriesTable_customContextMenuRequested(const QPoint &pos); + void addCategory_clicked(); + void removeCategory_clicked(); + void cellChanged(int row, int column); + +private: + + void refreshIDs(); + void fillTable(); + +private: + + Ui::CategoriesDialog *ui; + int m_ContextRow; + + int m_HighestID; + std::set m_IDs; + +}; + +#endif // CATEGORIESDIALOG_H + diff --git a/src/credentialsdialog.cpp b/src/credentialsdialog.cpp index d9438c75..04774548 100644 --- a/src/credentialsdialog.cpp +++ b/src/credentialsdialog.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #include "credentialsdialog.h" #include "ui_credentialsdialog.h" diff --git a/src/credentialsdialog.h b/src/credentialsdialog.h index 1d65447b..8a68c7d8 100644 --- a/src/credentialsdialog.h +++ b/src/credentialsdialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef CREDENTIALSDIALOG_H #define CREDENTIALSDIALOG_H diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index a00907a1..2186f9a5 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -1,156 +1,156 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "directoryrefresher.h" -#include "utility.h" -#include "report.h" -#include "modinfo.h" -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -DirectoryRefresher::DirectoryRefresher() - : m_DirectoryStructure(NULL) -{ -} - -DirectoryRefresher::~DirectoryRefresher() -{ - delete m_DirectoryStructure; -} - -DirectoryEntry *DirectoryRefresher::getDirectoryStructure() -{ - QMutexLocker locker(&m_RefreshLock); - DirectoryEntry *result = m_DirectoryStructure; - m_DirectoryStructure = NULL; - return result; -} - -void DirectoryRefresher::setMods(const std::vector > &mods - , const std::set &managedArchives) -{ - QMutexLocker locker(&m_RefreshLock); - - m_Mods.clear(); - for (auto mod = mods.begin(); mod != mods.end(); ++mod) { - QString name = std::get<0>(*mod); - ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); - m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod))); - } - - m_EnabledArchives = managedArchives; -} - -void DirectoryRefresher::cleanStructure(DirectoryEntry *structure) -{ - static wchar_t *files[] = { L"meta.ini", L"readme.txt" }; - for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { - structure->removeFile(files[i]); - } - - static wchar_t *dirs[] = { L"fomod" }; - for (int i = 0; i < sizeof(dirs) / sizeof(wchar_t*); ++i) { - structure->removeDir(std::wstring(dirs[i])); - } -} - -void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives) -{ - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - - foreach (const QString &archive, archives) { - QFileInfo fileInfo(archive); - if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { - directoryStructure->addFromBSA(ToWString(modName), directoryW, - ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); - } - } -} - -void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles) -{ - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - - if (stealFiles.length() > 0) { - // instead of adding all the files of the target directory, we just change the root of the specified - // files to this mod - FilesOrigin origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); - foreach (const QString &filename, stealFiles) { - QFileInfo fileInfo(filename); - FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName())); - if (file.get() != NULL) { - if (file->getOrigin() == 0) { - // replace data as the origin on this bsa - file->removeOrigin(0); - file->addOrigin(origin.getID(), file->getFileTime(), L""); - } - } - } - } else { - directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority); - } -} - -void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure - , const QString &modName - , int priority - , const QString &directory - , const QStringList &stealFiles - , const QStringList &archives) -{ - addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles); - addModBSAToStructure(directoryStructure, modName, priority, directory, archives); -} - -void DirectoryRefresher::refresh() -{ - QMutexLocker locker(&m_RefreshLock); - - delete m_DirectoryStructure; - - m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0); - - std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data"; - m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0); - - // TODO what was the point of having the priority in this tuple? the list is already sorted by priority - auto iter = m_Mods.begin(); - - //TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted! - for (int i = 1; iter != m_Mods.end(); ++iter, ++i) { - try { - addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives); - } catch (const std::exception &e) { - emit error(tr("failed to read bsa: %1").arg(e.what())); - } - emit progress((i * 100) / m_Mods.size() + 1); - } - - emit progress(100); - - cleanStructure(m_DirectoryStructure); - - emit refreshed(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "directoryrefresher.h" +#include "utility.h" +#include "report.h" +#include "modinfo.h" +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +DirectoryRefresher::DirectoryRefresher() + : m_DirectoryStructure(NULL) +{ +} + +DirectoryRefresher::~DirectoryRefresher() +{ + delete m_DirectoryStructure; +} + +DirectoryEntry *DirectoryRefresher::getDirectoryStructure() +{ + QMutexLocker locker(&m_RefreshLock); + DirectoryEntry *result = m_DirectoryStructure; + m_DirectoryStructure = NULL; + return result; +} + +void DirectoryRefresher::setMods(const std::vector > &mods + , const std::set &managedArchives) +{ + QMutexLocker locker(&m_RefreshLock); + + m_Mods.clear(); + for (auto mod = mods.begin(); mod != mods.end(); ++mod) { + QString name = std::get<0>(*mod); + ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); + m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod))); + } + + m_EnabledArchives = managedArchives; +} + +void DirectoryRefresher::cleanStructure(DirectoryEntry *structure) +{ + static wchar_t *files[] = { L"meta.ini", L"readme.txt" }; + for (int i = 0; i < sizeof(files) / sizeof(wchar_t*); ++i) { + structure->removeFile(files[i]); + } + + static wchar_t *dirs[] = { L"fomod" }; + for (int i = 0; i < sizeof(dirs) / sizeof(wchar_t*); ++i) { + structure->removeDir(std::wstring(dirs[i])); + } +} + +void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + foreach (const QString &archive, archives) { + QFileInfo fileInfo(archive); + if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { + directoryStructure->addFromBSA(ToWString(modName), directoryW, + ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); + } + } +} + +void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + if (stealFiles.length() > 0) { + // instead of adding all the files of the target directory, we just change the root of the specified + // files to this mod + FilesOrigin origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); + foreach (const QString &filename, stealFiles) { + QFileInfo fileInfo(filename); + FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName())); + if (file.get() != NULL) { + if (file->getOrigin() == 0) { + // replace data as the origin on this bsa + file->removeOrigin(0); + file->addOrigin(origin.getID(), file->getFileTime(), L""); + } + } + } + } else { + directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority); + } +} + +void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure + , const QString &modName + , int priority + , const QString &directory + , const QStringList &stealFiles + , const QStringList &archives) +{ + addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles); + addModBSAToStructure(directoryStructure, modName, priority, directory, archives); +} + +void DirectoryRefresher::refresh() +{ + QMutexLocker locker(&m_RefreshLock); + + delete m_DirectoryStructure; + + m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0); + + std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data"; + m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0); + + // TODO what was the point of having the priority in this tuple? the list is already sorted by priority + auto iter = m_Mods.begin(); + + //TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted! + for (int i = 1; iter != m_Mods.end(); ++iter, ++i) { + try { + addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives); + } catch (const std::exception &e) { + emit error(tr("failed to read bsa: %1").arg(e.what())); + } + emit progress((i * 100) / m_Mods.size() + 1); + } + + emit progress(100); + + cleanStructure(m_DirectoryStructure); + + emit refreshed(); +} diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 66e85465..53709e1e 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -1,147 +1,147 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DIRECTORYREFRESHER_H -#define DIRECTORYREFRESHER_H - -#include -#include -#include -#include -#include -#include -#include - - -/** - * @brief used to asynchronously generate the virtual view of the combined data directory - **/ -class DirectoryRefresher : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - * - **/ - DirectoryRefresher(); - - ~DirectoryRefresher(); - - /** - * @brief retrieve the updated directory structure - * - * returns a pointer to the updated directory structure. DirectoryRefresher - * deletes its own pointer and the caller takes custody of the pointer - * - * @return updated directory structure - **/ - MOShared::DirectoryEntry *getDirectoryStructure(); - - /** - * @brief sets up the mods to be included in the directory structure - * - * @param mods list of the mods to include - **/ - void setMods(const std::vector > &mods, const std::set &managedArchives); - - /** - * @brief sets up the directory where mods are stored - * @param modDirectory the mod directory - * @note this function could be obsoleted easily by storing absolute paths in the parameter to setMods. This is legacy - */ - void setModDirectory(const QString &modDirectory); - - /** - * @brief remove files from the directory structure that are known to be irrelevant to the game - * @param the structure to clean - */ - static void cleanStructure(MOShared::DirectoryEntry *structure); - - /** - * @brief add files for a mod to the directory structure, including bsas - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param stealFiles - * @param archives - */ - void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles, const QStringList &archives); - - /** - * @brief add only the bsas of a mod to the directory structure - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param archives - */ - void addModBSAToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives); - - /** - * @brief add only regular files ofr a mod to the directory structure - * @param directoryStructure - * @param modName - * @param priority - * @param directory - * @param stealFiles - */ - void addModFilesToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles); - -public slots: - - /** - * @brief generate a directory structure from the mods set earlier - **/ - void refresh(); - -signals: - - void progress(int progress); - void error(const QString &error); - void refreshed(); - -private: - - struct EntryInfo { - EntryInfo(const QString &modName, const QString &absolutePath, - const QStringList &stealFiles, const QStringList &archives, int priority) - : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles) - , archives(archives), priority(priority) {} - QString modName; - QString absolutePath; - QStringList stealFiles; - QStringList archives; - int priority; - }; - -private: - - std::vector m_Mods; - std::set m_EnabledArchives; - MOShared::DirectoryEntry *m_DirectoryStructure; - QMutex m_RefreshLock; - -}; - -#endif // DIRECTORYREFRESHER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DIRECTORYREFRESHER_H +#define DIRECTORYREFRESHER_H + +#include +#include +#include +#include +#include +#include +#include + + +/** + * @brief used to asynchronously generate the virtual view of the combined data directory + **/ +class DirectoryRefresher : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + * + **/ + DirectoryRefresher(); + + ~DirectoryRefresher(); + + /** + * @brief retrieve the updated directory structure + * + * returns a pointer to the updated directory structure. DirectoryRefresher + * deletes its own pointer and the caller takes custody of the pointer + * + * @return updated directory structure + **/ + MOShared::DirectoryEntry *getDirectoryStructure(); + + /** + * @brief sets up the mods to be included in the directory structure + * + * @param mods list of the mods to include + **/ + void setMods(const std::vector > &mods, const std::set &managedArchives); + + /** + * @brief sets up the directory where mods are stored + * @param modDirectory the mod directory + * @note this function could be obsoleted easily by storing absolute paths in the parameter to setMods. This is legacy + */ + void setModDirectory(const QString &modDirectory); + + /** + * @brief remove files from the directory structure that are known to be irrelevant to the game + * @param the structure to clean + */ + static void cleanStructure(MOShared::DirectoryEntry *structure); + + /** + * @brief add files for a mod to the directory structure, including bsas + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param stealFiles + * @param archives + */ + void addModToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles, const QStringList &archives); + + /** + * @brief add only the bsas of a mod to the directory structure + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param archives + */ + void addModBSAToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &archives); + + /** + * @brief add only regular files ofr a mod to the directory structure + * @param directoryStructure + * @param modName + * @param priority + * @param directory + * @param stealFiles + */ + void addModFilesToStructure(MOShared::DirectoryEntry *directoryStructure, const QString &modName, int priority, const QString &directory, const QStringList &stealFiles); + +public slots: + + /** + * @brief generate a directory structure from the mods set earlier + **/ + void refresh(); + +signals: + + void progress(int progress); + void error(const QString &error); + void refreshed(); + +private: + + struct EntryInfo { + EntryInfo(const QString &modName, const QString &absolutePath, + const QStringList &stealFiles, const QStringList &archives, int priority) + : modName(modName), absolutePath(absolutePath), stealFiles(stealFiles) + , archives(archives), priority(priority) {} + QString modName; + QString absolutePath; + QStringList stealFiles; + QStringList archives; + int priority; + }; + +private: + + std::vector m_Mods; + std::set m_EnabledArchives; + MOShared::DirectoryEntry *m_DirectoryStructure; + QMutex m_RefreshLock; + +}; + +#endif // DIRECTORYREFRESHER_H diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index e19e2e7b..ef2db91b 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -1,114 +1,114 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "downloadlist.h" -#include "downloadmanager.h" -#include - -#include - - -DownloadList::DownloadList(DownloadManager *manager, QObject *parent) - : QAbstractTableModel(parent), m_Manager(manager) -{ - connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); - connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); -} - - -int DownloadList::rowCount(const QModelIndex&) const -{ - return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads(); -} - - -int DownloadList::columnCount(const QModelIndex&) const -{ - return 3; -} - - -QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - - -QModelIndex DownloadList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - - -QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int role) const -{ - if ((role == Qt::DisplayRole) && - (orientation == Qt::Horizontal)) { - switch (section) { - case COL_NAME: return tr("Name"); - case COL_FILETIME: return tr("Filetime"); - default: return tr("Done"); - } - } else { - return QAbstractItemModel::headerData(section, orientation, role); - } -} - - -QVariant DownloadList::data(const QModelIndex &index, int role) const -{ - if (role == Qt::DisplayRole) { - return index.row(); - } else if (role == Qt::ToolTipRole) { - if (index.row() < m_Manager->numTotalDownloads()) { - QString text = m_Manager->getFileName(index.row()) + "\n"; - if (m_Manager->isInfoIncomplete(index.row())) { - text += tr("Information missing, please select \"Query Info\" from the context menu to re-retrieve."); - } else { - const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); - return QString("%1 (ID %2) %3
    %4").arg(info->modName).arg(m_Manager->getModID(index.row())).arg(info->version.canonicalString()).arg(info->description); - } - return text; - } else { - return tr("pending download"); - } - } else { - return QVariant(); - } -} - - -void DownloadList::aboutToUpdate() -{ - emit beginResetModel(); -} - - -void DownloadList::update(int row) -{ - if (row < 0) { - emit endResetModel(); - } else if (row < this->rowCount()) { -#pragma message("updating only the one column is a hack") - emit dataChanged(this->index(row, 2, QModelIndex()), this->index(row, 2, QModelIndex())); - } else { - qCritical("invalid row %d in download list, update failed", row); - } -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "downloadlist.h" +#include "downloadmanager.h" +#include + +#include + + +DownloadList::DownloadList(DownloadManager *manager, QObject *parent) + : QAbstractTableModel(parent), m_Manager(manager) +{ + connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); + connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); +} + + +int DownloadList::rowCount(const QModelIndex&) const +{ + return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads(); +} + + +int DownloadList::columnCount(const QModelIndex&) const +{ + return 3; +} + + +QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + + +QModelIndex DownloadList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + + +QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((role == Qt::DisplayRole) && + (orientation == Qt::Horizontal)) { + switch (section) { + case COL_NAME: return tr("Name"); + case COL_FILETIME: return tr("Filetime"); + default: return tr("Done"); + } + } else { + return QAbstractItemModel::headerData(section, orientation, role); + } +} + + +QVariant DownloadList::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + return index.row(); + } else if (role == Qt::ToolTipRole) { + if (index.row() < m_Manager->numTotalDownloads()) { + QString text = m_Manager->getFileName(index.row()) + "\n"; + if (m_Manager->isInfoIncomplete(index.row())) { + text += tr("Information missing, please select \"Query Info\" from the context menu to re-retrieve."); + } else { + const MOBase::ModRepositoryFileInfo *info = m_Manager->getFileInfo(index.row()); + return QString("%1 (ID %2) %3
    %4").arg(info->modName).arg(m_Manager->getModID(index.row())).arg(info->version.canonicalString()).arg(info->description); + } + return text; + } else { + return tr("pending download"); + } + } else { + return QVariant(); + } +} + + +void DownloadList::aboutToUpdate() +{ + emit beginResetModel(); +} + + +void DownloadList::update(int row) +{ + if (row < 0) { + emit endResetModel(); + } else if (row < this->rowCount()) { +#pragma message("updating only the one column is a hack") + emit dataChanged(this->index(row, 2, QModelIndex()), this->index(row, 2, QModelIndex())); + } else { + qCritical("invalid row %d in download list, update failed", row); + } +} + diff --git a/src/downloadlist.h b/src/downloadlist.h index ba0c287d..2316dddc 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -1,96 +1,96 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DOWNLOADLIST_H -#define DOWNLOADLIST_H - -#include - - -class DownloadManager; - - -/** - * @brief model of the list of active and completed downloads - **/ -class DownloadList : public QAbstractTableModel -{ - - Q_OBJECT - -public: - - enum EColumn { - COL_NAME = 0, - COL_FILETIME, - COL_STATUS - }; - -public: - - /** - * @brief constructor - * - * @param manager the download manager processing downloads - * @param parent parent object Defaults to 0. - **/ - explicit DownloadList(DownloadManager *manager, QObject *parent = 0); - - /** - * @brief retrieve the number of rows to display. Invoked by Qt - * - * @param parent not relevant for this implementation - * @return number of rows to display - **/ - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - - virtual int columnCount(const QModelIndex &parent) const; - - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - /** - * @brief retrieve the data to display in a specific row. Invoked by Qt - * - * @param index location to look up - * @param role ... Defaults to Qt::DisplayRole. - * @return this implementation only returns the row, the QItemDelegate implementation is expected to fetch its information from the DownloadManager - **/ - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - -public slots: - - /** - * @brief used to inform the model that data has changed - * - * @param row the row that changed. This can be negative to update the whole view - **/ - void update(int row); - - void aboutToUpdate(); - -private: - - DownloadManager *m_Manager; - -}; - -#endif // DOWNLOADLIST_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DOWNLOADLIST_H +#define DOWNLOADLIST_H + +#include + + +class DownloadManager; + + +/** + * @brief model of the list of active and completed downloads + **/ +class DownloadList : public QAbstractTableModel +{ + + Q_OBJECT + +public: + + enum EColumn { + COL_NAME = 0, + COL_FILETIME, + COL_STATUS + }; + +public: + + /** + * @brief constructor + * + * @param manager the download manager processing downloads + * @param parent parent object Defaults to 0. + **/ + explicit DownloadList(DownloadManager *manager, QObject *parent = 0); + + /** + * @brief retrieve the number of rows to display. Invoked by Qt + * + * @param parent not relevant for this implementation + * @return number of rows to display + **/ + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + + virtual int columnCount(const QModelIndex &parent) const; + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + /** + * @brief retrieve the data to display in a specific row. Invoked by Qt + * + * @param index location to look up + * @param role ... Defaults to Qt::DisplayRole. + * @return this implementation only returns the row, the QItemDelegate implementation is expected to fetch its information from the DownloadManager + **/ + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + +public slots: + + /** + * @brief used to inform the model that data has changed + * + * @param row the row that changed. This can be negative to update the whole view + **/ + void update(int row); + + void aboutToUpdate(); + +private: + + DownloadManager *m_Manager; + +}; + +#endif // DOWNLOADLIST_H diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 7abe8579..9eb22655 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -1,65 +1,65 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "downloadlistsortproxy.h" -#include "downloadlist.h" - -DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent) - : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter() -{ -} - -void DownloadListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - invalidateFilter(); -} - - -bool DownloadListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - int leftIndex = sourceModel()->data(left).toInt(); - int rightIndex = sourceModel()->data(right).toInt(); - if (leftIndex < m_Manager->numTotalDownloads()) { - if (left.column() == DownloadList::COL_NAME) { - return m_Manager->getFileName(leftIndex).compare(m_Manager->getFileName(rightIndex), Qt::CaseInsensitive) < 0; - } else if (left.column() == DownloadList::COL_FILETIME) { - return m_Manager->getFileTime(leftIndex) < m_Manager->getFileTime(rightIndex); - } else if (left.column() == DownloadList::COL_STATUS) { - return m_Manager->getState(leftIndex) < m_Manager->getState(rightIndex); - } else { - return leftIndex < rightIndex; - } - } else { - return leftIndex < rightIndex; - } -} - - -bool DownloadListSortProxy::filterAcceptsRow(int source_row, const QModelIndex&) const -{ - if (m_CurrentFilter.length() == 0) { - return true; - } else if (source_row < m_Manager->numTotalDownloads()) { - return m_Manager->getFileName(source_row).contains(m_CurrentFilter, Qt::CaseInsensitive); - } else { - return false; - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "downloadlistsortproxy.h" +#include "downloadlist.h" + +DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent) + : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter() +{ +} + +void DownloadListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + invalidateFilter(); +} + + +bool DownloadListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + int leftIndex = sourceModel()->data(left).toInt(); + int rightIndex = sourceModel()->data(right).toInt(); + if (leftIndex < m_Manager->numTotalDownloads()) { + if (left.column() == DownloadList::COL_NAME) { + return m_Manager->getFileName(leftIndex).compare(m_Manager->getFileName(rightIndex), Qt::CaseInsensitive) < 0; + } else if (left.column() == DownloadList::COL_FILETIME) { + return m_Manager->getFileTime(leftIndex) < m_Manager->getFileTime(rightIndex); + } else if (left.column() == DownloadList::COL_STATUS) { + return m_Manager->getState(leftIndex) < m_Manager->getState(rightIndex); + } else { + return leftIndex < rightIndex; + } + } else { + return leftIndex < rightIndex; + } +} + + +bool DownloadListSortProxy::filterAcceptsRow(int source_row, const QModelIndex&) const +{ + if (m_CurrentFilter.length() == 0) { + return true; + } else if (source_row < m_Manager->numTotalDownloads()) { + return m_Manager->getFileName(source_row).contains(m_CurrentFilter, Qt::CaseInsensitive); + } else { + return false; + } +} diff --git a/src/downloadlistsortproxy.h b/src/downloadlistsortproxy.h index 36c72609..e2c1bfb6 100644 --- a/src/downloadlistsortproxy.h +++ b/src/downloadlistsortproxy.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef DOWNLOADLISTSORTPROXY_H #define DOWNLOADLISTSORTPROXY_H @@ -29,28 +29,28 @@ class DownloadListSortProxy : public QSortFilterProxyModel { Q_OBJECT public: - - explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0); - -public slots: - - void updateFilter(const QString &filter); - -protected: - - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; - -signals: - + + explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0); + +public slots: + + void updateFilter(const QString &filter); + +protected: + + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +signals: + public slots: -private: - - const DownloadManager *m_Manager; - QString m_CurrentFilter; - - -}; - -#endif // DOWNLOADLISTSORTPROXY_H +private: + + const DownloadManager *m_Manager; + QString m_CurrentFilter; + + +}; + +#endif // DOWNLOADLISTSORTPROXY_H diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 953cdacd..9d276de9 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -1,351 +1,351 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "downloadlistwidget.h" -#include "ui_downloadlistwidget.h" -#include "downloadmanager.h" -#include -#include -#include -#include -#include - - -DownloadListWidget::DownloadListWidget(QWidget *parent) - : QWidget(parent), ui(new Ui::DownloadListWidget) -{ - ui->setupUi(this); -} - - -DownloadListWidget::~DownloadListWidget() -{ - delete ui; -} - - -DownloadListWidgetDelegate::DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) - : QItemDelegate(parent) - , m_Manager(manager) - , m_MetaDisplay(metaDisplay) - , m_ItemWidget(new DownloadListWidget) - , m_ContextRow(0) - , m_View(view) -{ - m_NameLabel = m_ItemWidget->findChild("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); - m_Progress = m_ItemWidget->findChild("downloadProgress"); - m_InstallLabel = m_ItemWidget->findChild("installLabel"); - - m_InstallLabel->setVisible(false); - - connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), this, SLOT(stateChanged(int,DownloadManager::DownloadState))); - connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); -} - - -DownloadListWidgetDelegate::~DownloadListWidgetDelegate() -{ - delete m_ItemWidget; -} - - -void DownloadListWidgetDelegate::stateChanged(int row,DownloadManager::DownloadState) -{ - m_Cache.remove(row); -} - - -void DownloadListWidgetDelegate::resetCache(int) -{ - m_Cache.clear(); -} - - -void DownloadListWidgetDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const -{ - QRect rect = option.rect; - rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); - painter->drawPixmap(rect, cache); -} - - -void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const -{ - std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); - m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); - m_SizeLabel->setText("???"); - m_InstallLabel->setVisible(true); - m_InstallLabel->setText(tr("Pending")); - m_Progress->setVisible(false); -} - - -void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const -{ - QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); - if (name.length() > 53) { - name.truncate(50); - name.append("..."); - } - m_NameLabel->setText(name); - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1024)); - DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - QPalette labelPalette; - m_InstallLabel->setVisible(true); - m_Progress->setVisible(false); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0)); -#else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0, QApplication::UnicodeUTF8)); -#endif - labelPalette.setColor(QPalette::WindowText, Qt::darkRed); - m_InstallLabel->setPalette(labelPalette); - } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { - m_InstallLabel->setText(tr("Fetching Info 1")); - m_Progress->setVisible(false); - } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { - m_InstallLabel->setText(tr("Fetching Info 2")); - m_Progress->setVisible(false); - } else if (state >= DownloadManager::STATE_READY) { - QPalette labelPalette; - m_InstallLabel->setVisible(true); - m_Progress->setVisible(false); - if (state == DownloadManager::STATE_INSTALLED) { - // the tr-macro doesn't work here, maybe because the translation is actually associated with DownloadListWidget instead - // of DownloadListWidgetDelegate? -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0)); -#else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0, QApplication::UnicodeUTF8)); -#endif - labelPalette.setColor(QPalette::WindowText, Qt::darkGray); - } else if (state == DownloadManager::STATE_UNINSTALLED) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0)); -#else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0, QApplication::UnicodeUTF8)); -#endif - labelPalette.setColor(QPalette::WindowText, Qt::lightGray); - } else { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0)); -#else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0, QApplication::UnicodeUTF8)); -#endif - labelPalette.setColor(QPalette::WindowText, Qt::darkGreen); - } - m_InstallLabel->setPalette(labelPalette); - if (m_Manager->isInfoIncomplete(downloadIndex)) { - m_NameLabel->setText(" " + m_NameLabel->text()); - } - } else { - m_InstallLabel->setVisible(false); - m_Progress->setVisible(true); - m_Progress->setValue(m_Manager->getProgress(downloadIndex)); - } -} - -void DownloadListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - try { - auto iter = m_Cache.find(index.row()); - if (iter != m_Cache.end()) { - drawCache(painter, option, *iter); - return; - } - - m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); - - int downloadIndex = index.data().toInt(); - - if (downloadIndex >= m_Manager->numTotalDownloads()) { - paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); - } else { - paintRegularDownload(downloadIndex); - } - -#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") -// if (state >= DownloadManager::STATE_READY) { - if (false) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QPixmap cache = m_ItemWidget->grab(); -#else - QPixmap cache = QPixmap::grabWidget(m_ItemWidget); -#endif - m_Cache[index.row()] = cache; - drawCache(painter, option, cache); - } else { - painter->save(); - painter->translate(QPoint(0, option.rect.topLeft().y())); - - m_ItemWidget->render(painter); - painter->restore(); - } - } catch (const std::exception &e) { - qCritical("failed to paint download list: %s", e.what()); - } -} - -QSize DownloadListWidgetDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const -{ - const int width = m_ItemWidget->minimumWidth(); - const int height = m_ItemWidget->height(); - return QSize(width, height); -} - - -void DownloadListWidgetDelegate::issueInstall() -{ - emit installDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueQueryInfo() -{ - emit queryInfo(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueDelete() -{ - emit removeDownload(m_ContextRow, true); -} - -void DownloadListWidgetDelegate::issueRemoveFromView() -{ - emit removeDownload(m_ContextRow, false); -} - -void DownloadListWidgetDelegate::issueRestoreToView() -{ - emit restoreDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueCancel() -{ - emit cancelDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issuePause() -{ - emit pauseDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueResume() -{ - emit resumeDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueDeleteAll() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all finished downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, true); - } -} - -void DownloadListWidgetDelegate::issueDeleteCompleted() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all installed downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, true); - } -} - -void DownloadListWidgetDelegate::issueRemoveFromViewAll() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all finished downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, false); - } -} - -void DownloadListWidgetDelegate::issueRemoveFromViewCompleted() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all installed downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, false); - } -} - -bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - try { - if (event->type() == QEvent::MouseButtonDblClick) { - QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); - if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { - emit installDownload(sourceIndex.row()); - } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { - emit resumeDownload(sourceIndex.row()); - } - return true; - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu(m_View); - bool hidden = false; - m_ContextRow = qobject_cast(model)->mapToSource(index).row(); - if (m_ContextRow < m_Manager->numTotalDownloads()) { - DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); - hidden = m_Manager->isHidden(m_ContextRow); - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextRow)) { - menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); - } - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - if (hidden) { - menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); - } else { - menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView())); - } - } else if (state == DownloadManager::STATE_DOWNLOADING){ - menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); - menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - menu.addAction(tr("Remove"), this, SLOT(issueDelete())); - menu.addAction(tr("Resume"), this, SLOT(issueResume())); - } - - menu.addSeparator(); - } - menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); - menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll())); - } - menu.exec(mouseEvent->globalPos()); - - event->accept(); - return true; - } - } - } catch (const std::exception &e) { - qCritical("failed to handle editor event: %s", e.what()); - } - return QItemDelegate::editorEvent(event, model, option, index); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "downloadlistwidget.h" +#include "ui_downloadlistwidget.h" +#include "downloadmanager.h" +#include +#include +#include +#include +#include + + +DownloadListWidget::DownloadListWidget(QWidget *parent) + : QWidget(parent), ui(new Ui::DownloadListWidget) +{ + ui->setupUi(this); +} + + +DownloadListWidget::~DownloadListWidget() +{ + delete ui; +} + + +DownloadListWidgetDelegate::DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) + : QItemDelegate(parent) + , m_Manager(manager) + , m_MetaDisplay(metaDisplay) + , m_ItemWidget(new DownloadListWidget) + , m_ContextRow(0) + , m_View(view) +{ + m_NameLabel = m_ItemWidget->findChild("nameLabel"); + m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); + m_Progress = m_ItemWidget->findChild("downloadProgress"); + m_InstallLabel = m_ItemWidget->findChild("installLabel"); + + m_InstallLabel->setVisible(false); + + connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), this, SLOT(stateChanged(int,DownloadManager::DownloadState))); + connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); +} + + +DownloadListWidgetDelegate::~DownloadListWidgetDelegate() +{ + delete m_ItemWidget; +} + + +void DownloadListWidgetDelegate::stateChanged(int row,DownloadManager::DownloadState) +{ + m_Cache.remove(row); +} + + +void DownloadListWidgetDelegate::resetCache(int) +{ + m_Cache.clear(); +} + + +void DownloadListWidgetDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const +{ + QRect rect = option.rect; + rect.setLeft(0); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + painter->drawPixmap(rect, cache); +} + + +void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const +{ + std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); + m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); + m_SizeLabel->setText("???"); + m_InstallLabel->setVisible(true); + m_InstallLabel->setText(tr("Pending")); + m_Progress->setVisible(false); +} + + +void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const +{ + QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); + if (name.length() > 53) { + name.truncate(50); + name.append("..."); + } + m_NameLabel->setText(name); + m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1024)); + DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + QPalette labelPalette; + m_InstallLabel->setVisible(true); + m_Progress->setVisible(false); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0)); +#else + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0, QApplication::UnicodeUTF8)); +#endif + labelPalette.setColor(QPalette::WindowText, Qt::darkRed); + m_InstallLabel->setPalette(labelPalette); + } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { + m_InstallLabel->setText(tr("Fetching Info 1")); + m_Progress->setVisible(false); + } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { + m_InstallLabel->setText(tr("Fetching Info 2")); + m_Progress->setVisible(false); + } else if (state >= DownloadManager::STATE_READY) { + QPalette labelPalette; + m_InstallLabel->setVisible(true); + m_Progress->setVisible(false); + if (state == DownloadManager::STATE_INSTALLED) { + // the tr-macro doesn't work here, maybe because the translation is actually associated with DownloadListWidget instead + // of DownloadListWidgetDelegate? +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0)); +#else + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0, QApplication::UnicodeUTF8)); +#endif + labelPalette.setColor(QPalette::WindowText, Qt::darkGray); + } else if (state == DownloadManager::STATE_UNINSTALLED) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0)); +#else + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0, QApplication::UnicodeUTF8)); +#endif + labelPalette.setColor(QPalette::WindowText, Qt::lightGray); + } else { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0)); +#else + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0, QApplication::UnicodeUTF8)); +#endif + labelPalette.setColor(QPalette::WindowText, Qt::darkGreen); + } + m_InstallLabel->setPalette(labelPalette); + if (m_Manager->isInfoIncomplete(downloadIndex)) { + m_NameLabel->setText(" " + m_NameLabel->text()); + } + } else { + m_InstallLabel->setVisible(false); + m_Progress->setVisible(true); + m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + } +} + +void DownloadListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + try { + auto iter = m_Cache.find(index.row()); + if (iter != m_Cache.end()) { + drawCache(painter, option, *iter); + return; + } + + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + + int downloadIndex = index.data().toInt(); + + if (downloadIndex >= m_Manager->numTotalDownloads()) { + paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); + } else { + paintRegularDownload(downloadIndex); + } + +#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") +// if (state >= DownloadManager::STATE_READY) { + if (false) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QPixmap cache = m_ItemWidget->grab(); +#else + QPixmap cache = QPixmap::grabWidget(m_ItemWidget); +#endif + m_Cache[index.row()] = cache; + drawCache(painter, option, cache); + } else { + painter->save(); + painter->translate(QPoint(0, option.rect.topLeft().y())); + + m_ItemWidget->render(painter); + painter->restore(); + } + } catch (const std::exception &e) { + qCritical("failed to paint download list: %s", e.what()); + } +} + +QSize DownloadListWidgetDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const +{ + const int width = m_ItemWidget->minimumWidth(); + const int height = m_ItemWidget->height(); + return QSize(width, height); +} + + +void DownloadListWidgetDelegate::issueInstall() +{ + emit installDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueQueryInfo() +{ + emit queryInfo(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueDelete() +{ + emit removeDownload(m_ContextRow, true); +} + +void DownloadListWidgetDelegate::issueRemoveFromView() +{ + emit removeDownload(m_ContextRow, false); +} + +void DownloadListWidgetDelegate::issueRestoreToView() +{ + emit restoreDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueCancel() +{ + emit cancelDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issuePause() +{ + emit pauseDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueResume() +{ + emit resumeDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueDeleteAll() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all finished downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, true); + } +} + +void DownloadListWidgetDelegate::issueDeleteCompleted() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all installed downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, true); + } +} + +void DownloadListWidgetDelegate::issueRemoveFromViewAll() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all finished downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, false); + } +} + +void DownloadListWidgetDelegate::issueRemoveFromViewCompleted() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all installed downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, false); + } +} + +bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + try { + if (event->type() == QEvent::MouseButtonDblClick) { + QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); + if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { + emit installDownload(sourceIndex.row()); + } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { + emit resumeDownload(sourceIndex.row()); + } + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + QMenu menu(m_View); + bool hidden = false; + m_ContextRow = qobject_cast(model)->mapToSource(index).row(); + if (m_ContextRow < m_Manager->numTotalDownloads()) { + DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); + hidden = m_Manager->isHidden(m_ContextRow); + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), this, SLOT(issueInstall())); + if (m_Manager->isInfoIncomplete(m_ContextRow)) { + menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); + } + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + if (hidden) { + menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); + } else { + menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView())); + } + } else if (state == DownloadManager::STATE_DOWNLOADING){ + menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); + menu.addAction(tr("Pause"), this, SLOT(issuePause())); + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + menu.addAction(tr("Remove"), this, SLOT(issueDelete())); + menu.addAction(tr("Resume"), this, SLOT(issueResume())); + } + + menu.addSeparator(); + } + menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll())); + } + menu.exec(mouseEvent->globalPos()); + + event->accept(); + return true; + } + } + } catch (const std::exception &e) { + qCritical("failed to handle editor event: %s", e.what()); + } + return QItemDelegate::editorEvent(event, model, option, index); +} diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index fa7cc845..a8e702e3 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -1,121 +1,121 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DOWNLOADLISTWIDGET_H -#define DOWNLOADLISTWIDGET_H - -#include -#include -#include -#include -#include - -#include "downloadmanager.h" - -namespace Ui { - class DownloadListWidget; -} - -class DownloadListWidget : public QWidget -{ - Q_OBJECT - -public: - explicit DownloadListWidget(QWidget *parent = 0); - ~DownloadListWidget(); - - -private: - Ui::DownloadListWidget *ui; -}; - -class DownloadManager; - -class DownloadListWidgetDelegate : public QItemDelegate -{ - - Q_OBJECT - -public: - - DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); - ~DownloadListWidgetDelegate(); - - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; - - void paintPendingDownload(int downloadIndex) const; - void paintRegularDownload(int downloadIndex) const; - -signals: - - void installDownload(int index); - void queryInfo(int index); - void removeDownload(int index, bool deleteFile); - void restoreDownload(int index); - void cancelDownload(int index); - void pauseDownload(int index); - void resumeDownload(int index); - -protected: - - bool editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index); - -private: - - void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; - -private slots: - - void issueInstall(); - void issueDelete(); - void issueRemoveFromView(); - void issueRestoreToView(); - void issueCancel(); - void issuePause(); - void issueResume(); - void issueDeleteAll(); - void issueDeleteCompleted(); - void issueRemoveFromViewAll(); - void issueRemoveFromViewCompleted(); - void issueQueryInfo(); - - void stateChanged(int row, DownloadManager::DownloadState); - void resetCache(int); - -private: - - DownloadListWidget *m_ItemWidget; - DownloadManager *m_Manager; - - bool m_MetaDisplay; - - QLabel *m_NameLabel; - QLabel *m_SizeLabel; - QProgressBar *m_Progress; - QLabel *m_InstallLabel; - int m_ContextRow; - - QTreeView *m_View; - - mutable QMap m_Cache; -}; - -#endif // DOWNLOADLISTWIDGET_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DOWNLOADLISTWIDGET_H +#define DOWNLOADLISTWIDGET_H + +#include +#include +#include +#include +#include + +#include "downloadmanager.h" + +namespace Ui { + class DownloadListWidget; +} + +class DownloadListWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DownloadListWidget(QWidget *parent = 0); + ~DownloadListWidget(); + + +private: + Ui::DownloadListWidget *ui; +}; + +class DownloadManager; + +class DownloadListWidgetDelegate : public QItemDelegate +{ + + Q_OBJECT + +public: + + DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); + ~DownloadListWidgetDelegate(); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + void paintPendingDownload(int downloadIndex) const; + void paintRegularDownload(int downloadIndex) const; + +signals: + + void installDownload(int index); + void queryInfo(int index); + void removeDownload(int index, bool deleteFile); + void restoreDownload(int index); + void cancelDownload(int index); + void pauseDownload(int index); + void resumeDownload(int index); + +protected: + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + +private: + + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; + +private slots: + + void issueInstall(); + void issueDelete(); + void issueRemoveFromView(); + void issueRestoreToView(); + void issueCancel(); + void issuePause(); + void issueResume(); + void issueDeleteAll(); + void issueDeleteCompleted(); + void issueRemoveFromViewAll(); + void issueRemoveFromViewCompleted(); + void issueQueryInfo(); + + void stateChanged(int row, DownloadManager::DownloadState); + void resetCache(int); + +private: + + DownloadListWidget *m_ItemWidget; + DownloadManager *m_Manager; + + bool m_MetaDisplay; + + QLabel *m_NameLabel; + QLabel *m_SizeLabel; + QProgressBar *m_Progress; + QLabel *m_InstallLabel; + int m_ContextRow; + + QTreeView *m_View; + + mutable QMap m_Cache; +}; + +#endif // DOWNLOADLISTWIDGET_H diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 818d339b..21a690ab 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -1,341 +1,341 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "downloadlistwidgetcompact.h" -#include "ui_downloadlistwidgetcompact.h" -#include -#include -#include -#include -#include -#include "downloadmanager.h" - - -DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget *parent) : - QWidget(parent), - ui(new Ui::DownloadListWidgetCompact) -{ - ui->setupUi(this); -} - -DownloadListWidgetCompact::~DownloadListWidgetCompact() -{ - delete ui; -} - - -DownloadListWidgetCompactDelegate::DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) - : QItemDelegate(parent) - , m_Manager(manager) - , m_MetaDisplay(metaDisplay) - , m_ItemWidget(new DownloadListWidgetCompact) - , m_View(view) -{ - m_NameLabel = m_ItemWidget->findChild("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); - m_Progress = m_ItemWidget->findChild("downloadProgress"); - m_DoneLabel = m_ItemWidget->findChild("doneLabel"); - - m_DoneLabel->setVisible(false); - - connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), this, SLOT(stateChanged(int,DownloadManager::DownloadState))); - connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); -} - - -DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() -{ - delete m_ItemWidget; -} - - -void DownloadListWidgetCompactDelegate::stateChanged(int row,DownloadManager::DownloadState) -{ - m_Cache.remove(row); -} - - -void DownloadListWidgetCompactDelegate::resetCache(int) -{ - m_Cache.clear(); -} - - -void DownloadListWidgetCompactDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const -{ - QRect rect = option.rect; - rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); - painter->drawPixmap(rect, cache); -} - - -void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) const -{ - std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); - m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); - if (m_SizeLabel != NULL) { - m_SizeLabel->setText("???"); - } - m_DoneLabel->setVisible(true); - m_DoneLabel->setText(tr("Pending")); - m_Progress->setVisible(false); -} - - -void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) const -{ - QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); - if (name.length() > 53) { - name.truncate(50); - name.append("..."); - } - m_NameLabel->setText(name); - - DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - - if ((m_SizeLabel != NULL) && (state >= DownloadManager::STATE_READY)) { - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); - } - - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - m_DoneLabel->setVisible(true); - m_Progress->setVisible(false); - m_DoneLabel->setText(tr("Paused")); - m_DoneLabel->setForegroundRole(QPalette::Link); - } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { - m_DoneLabel->setText(tr("Fetching Info 1")); - } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { - m_DoneLabel->setText(tr("Fetching Info 2")); - } else if (state >= DownloadManager::STATE_READY) { - m_DoneLabel->setVisible(true); - m_Progress->setVisible(false); - if (state == DownloadManager::STATE_INSTALLED) { - m_DoneLabel->setText(tr("Installed")); - m_DoneLabel->setForegroundRole(QPalette::Mid); - } else if (state == DownloadManager::STATE_UNINSTALLED) { - m_DoneLabel->setText(tr("Uninstalled")); - m_DoneLabel->setForegroundRole(QPalette::Dark); - } else { - m_DoneLabel->setText(tr("Done")); - m_DoneLabel->setForegroundRole(QPalette::WindowText); - } - if (m_Manager->isInfoIncomplete(downloadIndex)) { - m_NameLabel->setText(" " + m_NameLabel->text()); - } - } else { - m_DoneLabel->setVisible(false); - m_Progress->setVisible(true); - m_Progress->setValue(m_Manager->getProgress(downloadIndex)); - } -} - -void DownloadListWidgetCompactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ -#pragma message("This is quite costy - room for optimization?") - try { - auto iter = m_Cache.find(index.row()); - if (iter != m_Cache.end()) { - drawCache(painter, option, *iter); - return; - } - - m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); - if (index.row() % 2 == 1) { - m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); - } else { - m_ItemWidget->setBackgroundRole(QPalette::Base); - } - - int downloadIndex = index.data().toInt(); - if (downloadIndex >= m_Manager->numTotalDownloads()) { - paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); - } else { - paintRegularDownload(downloadIndex); - } - -#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") - if (false) { -// if (state >= DownloadManager::STATE_READY) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QPixmap cache = m_ItemWidget->grab(); -#else - QPixmap cache = QPixmap::grabWidget(m_ItemWidget); -#endif - m_Cache[index.row()] = cache; - drawCache(painter, option, cache); - } else { - painter->save(); - painter->translate(QPoint(0, option.rect.topLeft().y())); - - m_ItemWidget->render(painter); - painter->restore(); - } - } catch (const std::exception &e) { - qCritical("failed to paint download list item %d: %s", index.row(), e.what()); - } -} - -QSize DownloadListWidgetCompactDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const -{ - const int width = m_ItemWidget->minimumWidth(); - const int height = m_ItemWidget->height(); - return QSize(width, height); -} - - -void DownloadListWidgetCompactDelegate::issueInstall() -{ - emit installDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueQueryInfo() -{ - emit queryInfo(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueDelete() -{ - emit removeDownload(m_ContextIndex.row(), true); -} - -void DownloadListWidgetCompactDelegate::issueRemoveFromView() -{ - emit removeDownload(m_ContextIndex.row(), false); -} - -void DownloadListWidgetCompactDelegate::issueRestoreToView() -{ - emit restoreDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueCancel() -{ - emit cancelDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issuePause() -{ - emit pauseDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueResume() -{ - emit resumeDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueDeleteAll() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all finished downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, true); - } -} - -void DownloadListWidgetCompactDelegate::issueDeleteCompleted() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will remove all installed downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, true); - } -} - -void DownloadListWidgetCompactDelegate::issueRemoveFromViewAll() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will permanently remove all finished downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, false); - } -} - -void DownloadListWidgetCompactDelegate::issueRemoveFromViewCompleted() -{ - if (QMessageBox::question(NULL, tr("Are you sure?"), - tr("This will permanently remove all installed downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, false); - } -} - - -bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - try { - if (event->type() == QEvent::MouseButtonDblClick) { - QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); - if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { - emit installDownload(sourceIndex.row()); - } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { - emit resumeDownload(sourceIndex.row()); - } - return true; - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu; - bool hidden = false; - m_ContextIndex = qobject_cast(model)->mapToSource(index); - if (m_ContextIndex.row() < m_Manager->numTotalDownloads()) { - DownloadManager::DownloadState state = m_Manager->getState(m_ContextIndex.row()); - hidden = m_Manager->isHidden(m_ContextIndex.row()); - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextIndex.row())) { - menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); - } - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - if (hidden) { - menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); - } else { - menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView())); - } - } else if (state == DownloadManager::STATE_DOWNLOADING){ - menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); - menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - menu.addAction(tr("Remove"), this, SLOT(issueDelete())); - menu.addAction(tr("Resume"), this, SLOT(issueResume())); - } - - menu.addSeparator(); - } - menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); - menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll())); - } - menu.exec(mouseEvent->globalPos()); - - event->accept(); - return false; - } - } - } catch (const std::exception &e) { - qCritical("failed to handle editor event: %s", e.what()); - } - - return QItemDelegate::editorEvent(event, model, option, index); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "downloadlistwidgetcompact.h" +#include "ui_downloadlistwidgetcompact.h" +#include +#include +#include +#include +#include +#include "downloadmanager.h" + + +DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget *parent) : + QWidget(parent), + ui(new Ui::DownloadListWidgetCompact) +{ + ui->setupUi(this); +} + +DownloadListWidgetCompact::~DownloadListWidgetCompact() +{ + delete ui; +} + + +DownloadListWidgetCompactDelegate::DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) + : QItemDelegate(parent) + , m_Manager(manager) + , m_MetaDisplay(metaDisplay) + , m_ItemWidget(new DownloadListWidgetCompact) + , m_View(view) +{ + m_NameLabel = m_ItemWidget->findChild("nameLabel"); + m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); + m_Progress = m_ItemWidget->findChild("downloadProgress"); + m_DoneLabel = m_ItemWidget->findChild("doneLabel"); + + m_DoneLabel->setVisible(false); + + connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), this, SLOT(stateChanged(int,DownloadManager::DownloadState))); + connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); +} + + +DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() +{ + delete m_ItemWidget; +} + + +void DownloadListWidgetCompactDelegate::stateChanged(int row,DownloadManager::DownloadState) +{ + m_Cache.remove(row); +} + + +void DownloadListWidgetCompactDelegate::resetCache(int) +{ + m_Cache.clear(); +} + + +void DownloadListWidgetCompactDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const +{ + QRect rect = option.rect; + rect.setLeft(0); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + painter->drawPixmap(rect, cache); +} + + +void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) const +{ + std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); + m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); + if (m_SizeLabel != NULL) { + m_SizeLabel->setText("???"); + } + m_DoneLabel->setVisible(true); + m_DoneLabel->setText(tr("Pending")); + m_Progress->setVisible(false); +} + + +void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) const +{ + QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); + if (name.length() > 53) { + name.truncate(50); + name.append("..."); + } + m_NameLabel->setText(name); + + DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); + + if ((m_SizeLabel != NULL) && (state >= DownloadManager::STATE_READY)) { + m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); + } + + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + m_DoneLabel->setVisible(true); + m_Progress->setVisible(false); + m_DoneLabel->setText(tr("Paused")); + m_DoneLabel->setForegroundRole(QPalette::Link); + } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { + m_DoneLabel->setText(tr("Fetching Info 1")); + } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { + m_DoneLabel->setText(tr("Fetching Info 2")); + } else if (state >= DownloadManager::STATE_READY) { + m_DoneLabel->setVisible(true); + m_Progress->setVisible(false); + if (state == DownloadManager::STATE_INSTALLED) { + m_DoneLabel->setText(tr("Installed")); + m_DoneLabel->setForegroundRole(QPalette::Mid); + } else if (state == DownloadManager::STATE_UNINSTALLED) { + m_DoneLabel->setText(tr("Uninstalled")); + m_DoneLabel->setForegroundRole(QPalette::Dark); + } else { + m_DoneLabel->setText(tr("Done")); + m_DoneLabel->setForegroundRole(QPalette::WindowText); + } + if (m_Manager->isInfoIncomplete(downloadIndex)) { + m_NameLabel->setText(" " + m_NameLabel->text()); + } + } else { + m_DoneLabel->setVisible(false); + m_Progress->setVisible(true); + m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + } +} + +void DownloadListWidgetCompactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ +#pragma message("This is quite costy - room for optimization?") + try { + auto iter = m_Cache.find(index.row()); + if (iter != m_Cache.end()) { + drawCache(painter, option, *iter); + return; + } + + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + if (index.row() % 2 == 1) { + m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); + } else { + m_ItemWidget->setBackgroundRole(QPalette::Base); + } + + int downloadIndex = index.data().toInt(); + if (downloadIndex >= m_Manager->numTotalDownloads()) { + paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); + } else { + paintRegularDownload(downloadIndex); + } + +#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") + if (false) { +// if (state >= DownloadManager::STATE_READY) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QPixmap cache = m_ItemWidget->grab(); +#else + QPixmap cache = QPixmap::grabWidget(m_ItemWidget); +#endif + m_Cache[index.row()] = cache; + drawCache(painter, option, cache); + } else { + painter->save(); + painter->translate(QPoint(0, option.rect.topLeft().y())); + + m_ItemWidget->render(painter); + painter->restore(); + } + } catch (const std::exception &e) { + qCritical("failed to paint download list item %d: %s", index.row(), e.what()); + } +} + +QSize DownloadListWidgetCompactDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const +{ + const int width = m_ItemWidget->minimumWidth(); + const int height = m_ItemWidget->height(); + return QSize(width, height); +} + + +void DownloadListWidgetCompactDelegate::issueInstall() +{ + emit installDownload(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issueQueryInfo() +{ + emit queryInfo(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issueDelete() +{ + emit removeDownload(m_ContextIndex.row(), true); +} + +void DownloadListWidgetCompactDelegate::issueRemoveFromView() +{ + emit removeDownload(m_ContextIndex.row(), false); +} + +void DownloadListWidgetCompactDelegate::issueRestoreToView() +{ + emit restoreDownload(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issueCancel() +{ + emit cancelDownload(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issuePause() +{ + emit pauseDownload(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issueResume() +{ + emit resumeDownload(m_ContextIndex.row()); +} + +void DownloadListWidgetCompactDelegate::issueDeleteAll() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all finished downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, true); + } +} + +void DownloadListWidgetCompactDelegate::issueDeleteCompleted() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will remove all installed downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, true); + } +} + +void DownloadListWidgetCompactDelegate::issueRemoveFromViewAll() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will permanently remove all finished downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, false); + } +} + +void DownloadListWidgetCompactDelegate::issueRemoveFromViewCompleted() +{ + if (QMessageBox::question(NULL, tr("Are you sure?"), + tr("This will permanently remove all installed downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, false); + } +} + + +bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + try { + if (event->type() == QEvent::MouseButtonDblClick) { + QModelIndex sourceIndex = qobject_cast(model)->mapToSource(index); + if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_READY) { + emit installDownload(sourceIndex.row()); + } else if (m_Manager->getState(sourceIndex.row()) >= DownloadManager::STATE_PAUSED) { + emit resumeDownload(sourceIndex.row()); + } + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + QMenu menu; + bool hidden = false; + m_ContextIndex = qobject_cast(model)->mapToSource(index); + if (m_ContextIndex.row() < m_Manager->numTotalDownloads()) { + DownloadManager::DownloadState state = m_Manager->getState(m_ContextIndex.row()); + hidden = m_Manager->isHidden(m_ContextIndex.row()); + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), this, SLOT(issueInstall())); + if (m_Manager->isInfoIncomplete(m_ContextIndex.row())) { + menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); + } + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + if (hidden) { + menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); + } else { + menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView())); + } + } else if (state == DownloadManager::STATE_DOWNLOADING){ + menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); + menu.addAction(tr("Pause"), this, SLOT(issuePause())); + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + menu.addAction(tr("Remove"), this, SLOT(issueDelete())); + menu.addAction(tr("Resume"), this, SLOT(issueResume())); + } + + menu.addSeparator(); + } + menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll())); + } + menu.exec(mouseEvent->globalPos()); + + event->accept(); + return false; + } + } + } catch (const std::exception &e) { + qCritical("failed to handle editor event: %s", e.what()); + } + + return QItemDelegate::editorEvent(event, model, option, index); +} + diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index c3cd5c11..bf855d5f 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -1,122 +1,122 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DOWNLOADLISTWIDGETCOMPACT_H -#define DOWNLOADLISTWIDGETCOMPACT_H - -#include -#include -#include -#include -#include -#include "downloadmanager.h" - - -namespace Ui { -class DownloadListWidgetCompact; -} - -class DownloadListWidgetCompact : public QWidget -{ - Q_OBJECT - -public: - explicit DownloadListWidgetCompact(QWidget *parent = 0); - ~DownloadListWidgetCompact(); - -private: - Ui::DownloadListWidgetCompact *ui; - int m_ContextRow; -}; - -class DownloadManager; - -class DownloadListWidgetCompactDelegate : public QItemDelegate -{ - - Q_OBJECT - -public: - - DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); - ~DownloadListWidgetCompactDelegate(); - - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; - -signals: - - void installDownload(int index); - void queryInfo(int index); - void removeDownload(int index, bool deleteFile); - void restoreDownload(int index); - void cancelDownload(int index); - void pauseDownload(int index); - void resumeDownload(int index); - -protected: - - bool editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index); - -private: - - void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; - void paintPendingDownload(int downloadIndex) const; - void paintRegularDownload(int downloadIndex) const; - -private slots: - - void issueInstall(); - void issueDelete(); - void issueRemoveFromView(); - void issueRestoreToView(); - void issueCancel(); - void issuePause(); - void issueResume(); - void issueDeleteAll(); - void issueDeleteCompleted(); - void issueRemoveFromViewAll(); - void issueRemoveFromViewCompleted(); - void issueQueryInfo(); - - void stateChanged(int row, DownloadManager::DownloadState); - void resetCache(int); -private: - - DownloadListWidgetCompact *m_ItemWidget; - DownloadManager *m_Manager; - - bool m_MetaDisplay; - - QLabel *m_NameLabel; - QLabel *m_SizeLabel; - QProgressBar *m_Progress; - QLabel *m_DoneLabel; - - QModelIndex m_ContextIndex; - - QTreeView *m_View; - - mutable QMap m_Cache; - -}; - -#endif // DOWNLOADLISTWIDGETCOMPACT_H - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DOWNLOADLISTWIDGETCOMPACT_H +#define DOWNLOADLISTWIDGETCOMPACT_H + +#include +#include +#include +#include +#include +#include "downloadmanager.h" + + +namespace Ui { +class DownloadListWidgetCompact; +} + +class DownloadListWidgetCompact : public QWidget +{ + Q_OBJECT + +public: + explicit DownloadListWidgetCompact(QWidget *parent = 0); + ~DownloadListWidgetCompact(); + +private: + Ui::DownloadListWidgetCompact *ui; + int m_ContextRow; +}; + +class DownloadManager; + +class DownloadListWidgetCompactDelegate : public QItemDelegate +{ + + Q_OBJECT + +public: + + DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); + ~DownloadListWidgetCompactDelegate(); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + +signals: + + void installDownload(int index); + void queryInfo(int index); + void removeDownload(int index, bool deleteFile); + void restoreDownload(int index); + void cancelDownload(int index); + void pauseDownload(int index); + void resumeDownload(int index); + +protected: + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); + +private: + + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; + void paintPendingDownload(int downloadIndex) const; + void paintRegularDownload(int downloadIndex) const; + +private slots: + + void issueInstall(); + void issueDelete(); + void issueRemoveFromView(); + void issueRestoreToView(); + void issueCancel(); + void issuePause(); + void issueResume(); + void issueDeleteAll(); + void issueDeleteCompleted(); + void issueRemoveFromViewAll(); + void issueRemoveFromViewCompleted(); + void issueQueryInfo(); + + void stateChanged(int row, DownloadManager::DownloadState); + void resetCache(int); +private: + + DownloadListWidgetCompact *m_ItemWidget; + DownloadManager *m_Manager; + + bool m_MetaDisplay; + + QLabel *m_NameLabel; + QLabel *m_SizeLabel; + QProgressBar *m_Progress; + QLabel *m_DoneLabel; + + QModelIndex m_ContextIndex; + + QTreeView *m_View; + + mutable QMap m_Cache; + +}; + +#endif // DOWNLOADLISTWIDGETCOMPACT_H + diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 2d2b85c8..391ac7b9 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,1471 +1,1471 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "downloadmanager.h" -#include "report.h" -#include "nxmurl.h" -#include -#include -#include -#include "utility.h" -#include "json.h" -#include "selectiondialog.h" -#include "bbcode.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; - - -// TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads - - -static const char UNFINISHED[] = ".unfinished"; - -unsigned int DownloadManager::DownloadInfo::s_NextDownloadID = 1U; - - -DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo *fileInfo, const QStringList &URLs) -{ - DownloadInfo *info = new DownloadInfo; - info->m_DownloadID = s_NextDownloadID++; - info->m_StartTime.start(); - info->m_PreResumeSize = 0LL; - info->m_Progress = 0; - info->m_ResumePos = 0; - info->m_FileInfo = new ModRepositoryFileInfo(*fileInfo); - info->m_Urls = URLs; - info->m_CurrentUrl = 0; - info->m_Tries = AUTOMATIC_RETRIES; - info->m_State = STATE_STARTED; - info->m_TaskProgressId = TaskProgressManager::instance().getId(); - - return info; -} - -DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(const QString &filePath, bool showHidden) -{ - DownloadInfo *info = new DownloadInfo; - - QString metaFileName = filePath + ".meta"; - QSettings metaFile(metaFileName, QSettings::IniFormat); - if (!showHidden && metaFile.value("removed", false).toBool()) { - return NULL; - } else { - info->m_Hidden = metaFile.value("removed", false).toBool(); - } - - QString fileName = QFileInfo(filePath).fileName(); - - if (fileName.endsWith(UNFINISHED)) { - info->m_FileName = fileName.mid(0, fileName.length() - strlen(UNFINISHED)); - info->m_State = STATE_PAUSED; - } else { - info->m_FileName = fileName; - - if (metaFile.value("paused", false).toBool()) { - info->m_State = STATE_PAUSED; - } else if (metaFile.value("uninstalled", false).toBool()) { - info->m_State = STATE_UNINSTALLED; - } else if (metaFile.value("installed", false).toBool()) { - info->m_State = STATE_INSTALLED; - } else { - info->m_State = STATE_READY; - } - } - - info->m_DownloadID = s_NextDownloadID++; - info->m_Output.setFileName(filePath); - info->m_TotalSize = QFileInfo(filePath).size(); - info->m_PreResumeSize = info->m_TotalSize; - info->m_CurrentUrl = 0; - info->m_Urls = metaFile.value("url", "").toString().split(";"); - info->m_Tries = 0; - info->m_TaskProgressId = TaskProgressManager::instance().getId(); - int modID = metaFile.value("modID", 0).toInt(); - int fileID = metaFile.value("fileID", 0).toInt(); - info->m_FileInfo = new ModRepositoryFileInfo(modID, fileID); - info->m_FileInfo->name = metaFile.value("name", "").toString(); - if (info->m_FileInfo->name == "0") { - // bug in earlier version - info->m_FileInfo->name = ""; - } - info->m_FileInfo->modName = metaFile.value("modName", "").toString(); - info->m_FileInfo->modID = modID; - info->m_FileInfo->fileID = fileID; - info->m_FileInfo->description = metaFile.value("description").toString(); - info->m_FileInfo->version.parse(metaFile.value("version", "0").toString()); - info->m_FileInfo->newestVersion.parse(metaFile.value("newestVersion", "0").toString()); - info->m_FileInfo->categoryID = metaFile.value("category", 0).toInt(); - info->m_FileInfo->fileCategory = metaFile.value("fileCategory", 0).toInt(); - info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); - info->m_FileInfo->userData = metaFile.value("userData").toMap(); - - return info; -} - -void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) -{ - QString oldMetaFileName = QString("%1.meta").arg(m_FileName); - m_FileName = QFileInfo(newName).fileName(); - if ((m_State == DownloadManager::STATE_STARTED) || - (m_State == DownloadManager::STATE_DOWNLOADING)) { - newName.append(UNFINISHED); - } - if (renameFile) { - if ((newName != m_Output.fileName()) && !m_Output.rename(newName)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(m_Output.fileName()).arg(newName)); - return; - } - - QFile metaFile(oldMetaFileName); - if (metaFile.exists()) { - metaFile.rename(newName.mid(0).append(".meta")); - } - } - if (!m_Output.isOpen()) { - // can't set file name if it's open - m_Output.setFileName(newName); - } -} - -bool DownloadManager::DownloadInfo::isPausedState() -{ - return m_State == STATE_PAUSED || m_State == STATE_ERROR; -} - -QString DownloadManager::DownloadInfo::currentURL() -{ - return m_Urls[m_CurrentUrl]; -} - - -DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), - m_DateExpression("/Date\\((\\d+)\\)/") -{ - connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); -} - - -DownloadManager::~DownloadManager() -{ - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { - delete *iter; - } - m_ActiveDownloads.clear(); -} - - -bool DownloadManager::downloadsInProgress() -{ - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { - if ((*iter)->m_State < STATE_READY) { - return true; - } - } - return false; -} - -void DownloadManager::pauseAll() -{ - // first loop: pause all downloads - for (int i = 0; i < m_ActiveDownloads.count(); ++i) { - if (m_ActiveDownloads[i]->m_State < STATE_READY) { - pauseDownload(i); - } - } - - ::Sleep(100); - - bool done = false; - QTime startTime = QTime::currentTime(); - // further loops: busy waiting for all downloads to complete. This could be neater... - while (!done && (startTime.secsTo(QTime::currentTime()) < 5)) { - QCoreApplication::processEvents(); - done = true; - foreach (DownloadInfo *info, m_ActiveDownloads) { - if ((info->m_State < STATE_CANCELED) || - (info->m_State == STATE_FETCHINGFILEINFO) || - (info->m_State == STATE_FETCHINGMODINFO)) { - done = false; - break; - } - } - if (!done) { - ::Sleep(100); - } - } -} - - -void DownloadManager::setOutputDirectory(const QString &outputDirectory) -{ - QStringList directories = m_DirWatcher.directories(); - if (directories.length() != 0) { - m_DirWatcher.removePaths(directories); - } - m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory); - refreshList(); - m_DirWatcher.addPath(m_OutputDirectory); -} - - -void DownloadManager::setPreferredServers(const std::map &preferredServers) -{ - m_PreferredServers = preferredServers; -} - - -void DownloadManager::setSupportedExtensions(const QStringList &extensions) -{ - m_SupportedExtensions = extensions; - refreshList(); -} - -void DownloadManager::setShowHidden(bool showHidden) -{ - m_ShowHidden = showHidden; - refreshList(); -} - -void DownloadManager::refreshList() -{ - try { - int downloadsBefore = m_ActiveDownloads.size(); - - // remove finished downloads - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { - if (((*iter)->m_State == STATE_READY) || ((*iter)->m_State == STATE_INSTALLED) || ((*iter)->m_State == STATE_UNINSTALLED)) { - delete *iter; - iter = m_ActiveDownloads.erase(iter); - } else { - ++iter; - } - } - - QStringList nameFilters(m_SupportedExtensions); - foreach (const QString &extension, m_SupportedExtensions) { - nameFilters.append("*." + extension); - } - - nameFilters.append(QString("*").append(UNFINISHED)); - QDir dir(QDir::fromNativeSeparators(m_OutputDirectory)); - - // find orphaned meta files and delete them (sounds cruel but it's better for everyone) - QStringList orphans; - QStringList metaFiles = dir.entryList(QStringList() << "*.meta"); - foreach (const QString &metaFile, metaFiles) { - QString baseFile = metaFile.left(metaFile.length() - 5); - if (!QFile::exists(dir.absoluteFilePath(baseFile))) { - orphans.append(dir.absoluteFilePath(metaFile)); - } - } - if (orphans.size() > 0) { - qDebug("%d orphaned meta files will be deleted", orphans.size()); - shellDelete(orphans, true); - } - - // add existing downloads to list - foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) { - bool Exists = false; - for (QVector::const_iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end() && !Exists; ++Iter) { - if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) { - Exists = true; - } else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, Qt::CaseInsensitive) == 0) { - Exists = true; - } - } - if (Exists) { - continue; - } - - QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file; - - DownloadInfo *info = DownloadInfo::createFromMeta(fileName, m_ShowHidden); - if (info != NULL) { - m_ActiveDownloads.push_front(info); - } - } - - if (m_ActiveDownloads.size() != downloadsBefore) { - qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); - } - emit update(-1); - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in refreshing directory).")); - } -} - - -bool DownloadManager::addDownload(const QStringList &URLs, - int modID, int fileID, const ModRepositoryFileInfo *fileInfo) -{ - QString fileName = QFileInfo(URLs.first()).fileName(); - if (fileName.isEmpty()) { - fileName = "unknown"; - } - - QNetworkRequest request(URLs.first()); - return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); -} - - -bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo) -{ - QString fileName = getFileNameFromNetworkReply(reply); - if (fileName.isEmpty()) { - fileName = "unknown"; - } - - return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->modID, fileInfo->fileID, fileInfo); -} - - -bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, - int modID, int fileID, const ModRepositoryFileInfo *fileInfo) -{ - // download invoked from an already open network reply (i.e. download link in the browser) - DownloadInfo *newDownload = DownloadInfo::createNew(fileInfo, URLs); - - QString baseName = fileName; - - if (!fileInfo->fileName.isEmpty()) { - baseName = fileInfo->fileName; - } else { - QString dispoName = getFileNameFromNetworkReply(reply); - - if (!dispoName.isEmpty()) { - baseName = dispoName; - } - } - - if (QFile::exists(m_OutputDirectory + "/" + baseName) && - (QMessageBox::question(NULL, tr("Download again?"), tr("A file with the same name has already been downloaded. " - "Do you want to download it again? The new file will receive a different name."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - removePending(modID, fileID); - delete newDownload; - return false; - } - - newDownload->setName(getDownloadFileName(baseName), false); - - startDownload(reply, newDownload, false); - -// emit update(-1); - return true; -} - - -void DownloadManager::removePending(int modID, int fileID) -{ - emit aboutToUpdate(); - for (auto iter = m_PendingDownloads.begin(); iter != m_PendingDownloads.end(); ++iter) { - if ((iter->first == modID) && (iter->second == fileID)) { - m_PendingDownloads.erase(iter); - break; - } - } - emit update(-1); -} - - -void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume) -{ - reply->setReadBufferSize(1024 * 1024); // don't read more than 1MB at once to avoid memory troubles - newDownload->m_Reply = reply; - setState(newDownload, STATE_DOWNLOADING); - if (newDownload->m_Urls.count() == 0) { - newDownload->m_Urls = QStringList(reply->url().toString()); - } - - QIODevice::OpenMode mode = QIODevice::WriteOnly; - if (resume) { - mode |= QIODevice::Append; - } - - newDownload->m_StartTime.start(); - createMetaFile(newDownload); - - if (!newDownload->m_Output.open(mode)) { - reportError(tr("failed to download %1: could not open output file: %2") - .arg(reply->url().toString()).arg(newDownload->m_Output.fileName())); - return; - } - - connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(newDownload->m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); - connect(newDownload->m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); - connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged())); - - if (!resume) { - newDownload->m_PreResumeSize = newDownload->m_Output.size(); - removePending(newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); - - emit aboutToUpdate(); - m_ActiveDownloads.append(newDownload); - - emit update(-1); - emit downloadAdded(); - - if (reply->isFinished()) { - // it's possible the download has already finished before this function ran - downloadFinished(); - } - } -} - - -void DownloadManager::addNXMDownload(const QString &url) -{ - NXMUrl nxmInfo(url); - - QString managedGame = ToQString(MOShared::GameInfo::instance().getGameShortName()); - qDebug("add nxm download: %s", qPrintable(url)); - if (nxmInfo.game().compare(managedGame, Qt::CaseInsensitive) != 0) { - qDebug("download requested for wrong game (game: %s, url: %s)", qPrintable(managedGame), qPrintable(nxmInfo.game())); - QMessageBox::information(NULL, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " - "has been set up for \"%2\".").arg(nxmInfo.game()).arg(managedGame), QMessageBox::Ok); - return; - } - - emit aboutToUpdate(); - m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId())); - - emit update(-1); - emit downloadAdded(); - m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), "")); -} - - -void DownloadManager::removeFile(int index, bool deleteFile) -{ - if (index >= m_ActiveDownloads.size()) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *download = m_ActiveDownloads.at(index); - QString filePath = m_OutputDirectory + "/" + download->m_FileName; - if ((download->m_State == STATE_STARTED) || - (download->m_State == STATE_DOWNLOADING)) { - // shouldn't have been possible - qCritical("tried to remove active download"); - return; - } - - if ((download->m_State == STATE_PAUSED) || (download->m_State == STATE_ERROR)) { - filePath = download->m_Output.fileName(); - } - - if (deleteFile) { - if (!shellDelete(QStringList(filePath), true)) { - reportError(tr("failed to delete %1").arg(filePath)); - return; - } - - QFile metaFile(filePath.append(".meta")); - if (metaFile.exists() && !shellDelete(QStringList(filePath), true)) { - reportError(tr("failed to delete meta file for %1").arg(filePath)); - } - } else { - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", true); - } -} - -class LessThanWrapper -{ -public: - LessThanWrapper(DownloadManager *manager) : m_Manager(manager) {} - bool operator()(int LHS, int RHS) { - return m_Manager->getFileName(LHS).compare(m_Manager->getFileName(RHS), Qt::CaseInsensitive) < 0; - - } - -private: - DownloadManager *m_Manager; -}; - - -bool DownloadManager::ByName(int LHS, int RHS) -{ - return m_ActiveDownloads[LHS]->m_FileName < m_ActiveDownloads[RHS]->m_FileName; -} - - -void DownloadManager::refreshAlphabeticalTranslation() -{ - m_AlphabeticalTranslation.clear(); - int pos = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++pos) { - m_AlphabeticalTranslation.push_back(pos); - } - - qSort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); -} - - -void DownloadManager::restoreDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *download = m_ActiveDownloads.at(index); - download->m_Hidden = false; - - QString filePath = m_OutputDirectory + "/" + download->m_FileName; - - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", false); -} - - -void DownloadManager::removeDownload(int index, bool deleteFile) -{ - try { - emit aboutToUpdate(); - - if (index < 0) { - DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY; - 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("invalid index %1").arg(index)); - return; - } - - removeFile(index, deleteFile); - delete m_ActiveDownloads.at(index); - m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); - } - emit update(-1); - } catch (const std::exception &e) { - qCritical("failed to remove download: %s", e.what()); - } -} - - -void DownloadManager::cancelDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - - if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { - setState(m_ActiveDownloads.at(index), STATE_CANCELING); - } -} - - -void DownloadManager::pauseDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - - if (info->m_State == STATE_DOWNLOADING) { - if (info->m_Reply->isRunning()) { - setState(info, STATE_PAUSING); - } else { - setState(info, STATE_PAUSED); - } - } else if ((info->m_State == STATE_FETCHINGMODINFO) || (info->m_State == STATE_FETCHINGFILEINFO)) { - setState(info, STATE_READY); - } -} - -void DownloadManager::resumeDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - DownloadInfo *info = m_ActiveDownloads[index]; - info->m_Tries = AUTOMATIC_RETRIES; - resumeDownloadInt(index); -} - -void DownloadManager::resumeDownloadInt(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - DownloadInfo *info = m_ActiveDownloads[index]; - if (info->isPausedState()) { - if ((info->m_Urls.size() == 0) - || ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) { - emit showMessage(tr("No known download urls. Sorry, this download can't be resumed.")); - return; - } - if (info->m_State == STATE_ERROR) { - info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); - } - qDebug("request resume from url %s", qPrintable(info->currentURL())); - QNetworkRequest request(info->currentURL()); - info->m_ResumePos = info->m_Output.size(); - qDebug("resume at %lld bytes", info->m_ResumePos); - QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; - request.setRawHeader("Range", rangeHeader); - startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); - } - emit update(index); -} - - -DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id) -{ - auto iter = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), - [id](DownloadInfo *info) { return info->m_DownloadID == id; }); - if (iter != m_ActiveDownloads.end()) { - return *iter; - } else { - return NULL; - } -} - - -void DownloadManager::queryInfo(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - DownloadInfo *info = m_ActiveDownloads[index]; - - if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); - return; - } - - if (info->m_State < DownloadManager::STATE_READY) { - // UI shouldn't allow this - return; - } - - if (info->m_FileInfo->modID == 0UL) { - QString fileName = getFileName(index); - QString ignore; - NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true); - if (info->m_FileInfo->modID < 0) { - QString modIDString; - while (modIDString.isEmpty()) { - modIDString = QInputDialog::getText(NULL, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal, - QString(), NULL, 0, Qt::ImhFormattedNumbersOnly); - if (modIDString.isNull()) { - // canceled - return; - } else if (modIDString.contains(QRegExp("[^0-9]"))) { - qDebug("illegal character in mod-id"); - modIDString.clear(); - } - } - info->m_FileInfo->modID = modIDString.toInt(NULL, 10); - } - } - info->m_ReQueried = true; - setState(info, STATE_FETCHINGMODINFO); -} - - -int DownloadManager::numTotalDownloads() const -{ - return m_ActiveDownloads.size(); -} - -int DownloadManager::numPendingDownloads() const -{ - return m_PendingDownloads.size(); -} - -std::pair DownloadManager::getPendingDownload(int index) -{ - if ((index < 0) || (index >= m_PendingDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_PendingDownloads.at(index); -} - -QString DownloadManager::getFilePath(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName; -} - -QString DownloadManager::getFileTypeString(int fileType) -{ - switch (fileType) { - case 1: return tr("Main"); - case 2: return tr("Update"); - case 3: return tr("Optional"); - case 4: return tr("Old"); - case 5: return tr("Misc"); - default: return tr("Unknown"); - } -} - -QString DownloadManager::getDisplayName(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - - if (!info->m_FileInfo->name.isEmpty()) { - return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name) - .arg(getFileTypeString(info->m_FileInfo->fileCategory)) - .arg(info->m_FileInfo->version.displayString()); - } else { - return info->m_FileName; - } -} - -QString DownloadManager::getFileName(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_FileName; -} - -QDateTime DownloadManager::getFileTime(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - if (!info->m_Created.isValid()) { - info->m_Created = QFileInfo(info->m_Output).created(); - } - - return info->m_Created; -} - -qint64 DownloadManager::getFileSize(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_TotalSize; -} - - -int DownloadManager::getProgress(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_Progress; -} - - -DownloadManager::DownloadState DownloadManager::getState(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_State; -} - - -bool DownloadManager::isInfoIncomplete(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - if (info->m_FileInfo->repository != "Nexus") { - // other repositories currently don't support re-querying info anyway - return false; - } - return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0) || !info->m_FileInfo->version.isValid(); -} - - -int DownloadManager::getModID(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - return m_ActiveDownloads.at(index)->m_FileInfo->modID; -} - -bool DownloadManager::isHidden(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - return m_ActiveDownloads.at(index)->m_Hidden; -} - - -const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_FileInfo; -} - - -void DownloadManager::markInstalled(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); - metaFile.setValue("installed", true); - metaFile.setValue("uninstalled", false); - - setState(m_ActiveDownloads.at(index), STATE_INSTALLED); -} - - -void DownloadManager::markUninstalled(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); - metaFile.setValue("uninstalled", true); - - setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); -} - - -QString DownloadManager::getDownloadFileName(const QString &baseName) const -{ - QString fullPath = m_OutputDirectory + "/" + baseName; - if (QFile::exists(fullPath)) { - int i = 1; - while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { - ++i; - } - - fullPath = QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName); - } - return fullPath; -} - - -QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply) -{ - if (reply->hasRawHeader("Content-Disposition")) { - std::tr1::regex exp("filename=\"(.*)\""); - - std::tr1::cmatch result; - if (std::tr1::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) { - return QString::fromUtf8(result.str(1).c_str()); - } - } - - return QString(); -} - - -void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadManager::DownloadState state) -{ - int row = 0; - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i] == info) { - row = i; - break; - } - } - info->m_State = state; - switch (state) { - case STATE_PAUSED: - case STATE_ERROR: { - info->m_Reply->abort(); - } break; - case STATE_CANCELED: { - info->m_Reply->abort(); - } break; - case STATE_FETCHINGMODINFO: { - m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); - } break; - case STATE_FETCHINGFILEINFO: { - m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); - } break; - case STATE_READY: { - createMetaFile(info); - emit downloadComplete(row); - } break; - default: /* NOP */ break; - } - emit stateChanged(row, state); -} - - -DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const -{ - // reverse search as newer, thus more relevant, downloads are at the end - for (int i = m_ActiveDownloads.size() - 1; i >= 0; --i) { - if (m_ActiveDownloads[i]->m_Reply == reply) { - if (index != NULL) { - *index = i; - } - return m_ActiveDownloads[i]; - } - } - return NULL; -} - - -void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - if (bytesTotal == 0) { - return; - } - int index = 0; - try { - DownloadInfo *info = findDownload(this->sender(), &index); - if (info != NULL) { - if (info->m_State == STATE_CANCELING) { - setState(info, STATE_CANCELED); - } else if (info->m_State == STATE_PAUSING) { - setState(info, STATE_PAUSED); - } else { - if (bytesTotal > info->m_TotalSize) { - info->m_TotalSize = bytesTotal; - } - int oldProgress = info->m_Progress; - info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); - TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); - if (oldProgress != info->m_Progress) { - emit update(index); - } - } - } - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in processing progress event).")); - } -} - - -void DownloadManager::downloadReadyRead() -{ - try { - DownloadInfo *info = findDownload(this->sender()); - if (info != NULL) { - info->m_Output.write(info->m_Reply->readAll()); - } - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in processing downloaded data).")); - } -} - - -void DownloadManager::createMetaFile(DownloadInfo *info) -{ - QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); - metaFile.setValue("modID", info->m_FileInfo->modID); - metaFile.setValue("fileID", info->m_FileInfo->fileID); - metaFile.setValue("url", info->m_Urls.join(";")); - metaFile.setValue("name", info->m_FileInfo->name); - metaFile.setValue("description", info->m_FileInfo->description); - metaFile.setValue("modName", info->m_FileInfo->modName); - metaFile.setValue("version", info->m_FileInfo->version.canonicalString()); - metaFile.setValue("newestVersion", info->m_FileInfo->newestVersion.canonicalString()); - metaFile.setValue("fileTime", info->m_FileInfo->fileTime); - metaFile.setValue("fileCategory", info->m_FileInfo->fileCategory); - metaFile.setValue("category", info->m_FileInfo->categoryID); - metaFile.setValue("repository", info->m_FileInfo->repository); - metaFile.setValue("userData", info->m_FileInfo->userData); - metaFile.setValue("installed", info->m_State == DownloadManager::STATE_INSTALLED); - metaFile.setValue("uninstalled", info->m_State == DownloadManager::STATE_UNINSTALLED); - metaFile.setValue("paused", (info->m_State == DownloadManager::STATE_PAUSED) || - (info->m_State == DownloadManager::STATE_ERROR)); - metaFile.setValue("removed", info->m_Hidden); - - // slightly hackish... - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i] == info) { - emit update(i); - } - } -} - - -void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - QVariantMap result = resultData.toMap(); - - DownloadInfo *info = downloadInfoByID(userData.toInt()); - if (info == NULL) return; - info->m_FileInfo->categoryID = result["category_id"].toInt(); - info->m_FileInfo->modName = result["name"].toString().trimmed(); - info->m_FileInfo->newestVersion.parse(result["version"].toString()); - if (info->m_FileInfo->fileID != 0) { - setState(info, STATE_READY); - } else { - setState(info, STATE_FETCHINGFILEINFO); - } -} - - -QDateTime DownloadManager::matchDate(const QString &timeString) -{ - if (m_DateExpression.exactMatch(timeString)) { - return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong()); - } else { - qWarning("date not matched: %s", qPrintable(timeString)); - return QDateTime::currentDateTime(); - } -} - - -EFileCategory convertFileCategory(int id) -{ - // TODO: need to handle file categories in the mod page plugin - switch (id) { - case 0: return TYPE_MAIN; - case 1: return TYPE_UPDATE; - case 2: return TYPE_OPTION; - default: return TYPE_MAIN; - } -} - - -void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - DownloadInfo *info = downloadInfoByID(userData.toInt()); - if (info == NULL) return; - - QVariantList result = resultData.toList(); - - // MO sometimes prepends _ to the filename in case of duplicate downloads. - // this may muck up the file name comparison - QString alternativeLocalName = info->m_FileName; - - QRegExp expression("^\\d_(.*)$"); - if (expression.indexIn(alternativeLocalName) == 0) { - alternativeLocalName = expression.cap(1); - } - - bool found = false; - - foreach(QVariant file, result) { - QVariantMap fileInfo = file.toMap(); - QString fileName = fileInfo["uri"].toString(); - QString fileNameVariant = fileName.mid(0).replace(' ', '_'); - if ((fileName == info->m_FileName) || (fileName == alternativeLocalName) || - (fileNameVariant == info->m_FileName) || (fileNameVariant == alternativeLocalName)) { - info->m_FileInfo->name = fileInfo["name"].toString(); - info->m_FileInfo->version.parse(fileInfo["version"].toString()); - if (!info->m_FileInfo->version.isValid()) { - info->m_FileInfo->version = info->m_FileInfo->newestVersion; - } - info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); - info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString()); - info->m_FileInfo->fileID = fileInfo["id"].toInt(); - found = true; - break; - } - } - - if (info->m_ReQueried) { - if (found) { - emit showMessage(tr("Information updated")); - } else if (result.count() == 0) { - emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); - } else { - SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); - foreach(QVariant file, result) { - QVariantMap fileInfo = file.toMap(); - selection.addChoice(fileInfo["uri"].toString(), "", file); - } - if (selection.exec() == QDialog::Accepted) { - QVariantMap fileInfo = selection.getChoiceData().toMap(); - info->m_FileInfo->name = fileInfo["name"].toString(); - info->m_FileInfo->version.parse(fileInfo["version"].toString()); - info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); - info->m_FileInfo->fileID = fileInfo["id"].toInt(); - } else { - emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); - } - } - } else { - if (info->m_FileInfo->fileID == 0) { - qWarning("could not determine file id for %s (state %d)", - info->m_FileName.toUtf8().constData(), info->m_State); - } - } - - setState(info, STATE_READY); -} - - -void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - ModRepositoryFileInfo *info = new ModRepositoryFileInfo(); - - QVariantMap result = resultData.toMap(); - info->name = result["name"].toString(); - qDebug("file info received for %s", qPrintable(info->name)); - info->version.parse(result["version"].toString()); - if (!info->version.isValid()) { - info->version = info->newestVersion; - } - info->fileName = result["uri"].toString(); - info->fileCategory = result["category_id"].toInt(); - info->fileTime = matchDate(result["date"].toString()); - info->description = BBCode::convertToHTML(result["description"].toString()); - - info->repository = "Nexus"; - info->modID = modID; - info->fileID = fileID; - - QObject *test = info; - m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); -} - - -// sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) -{ - int LHSVal = 0; - int RHSVal = 0; - - QVariantMap LHSMap = LHS.toMap(); - QVariantMap RHSMap = RHS.toMap(); - - int LHSUsers = LHSMap["ConnectedUsers"].toInt(); - int RHSUsers = RHSMap["ConnectedUsers"].toInt(); - // 0 users is probably a sign that the server is offline. Since there is currently no - // mechanism to try a different server, we avoid those without users - if (LHSUsers == 0) { - LHSVal -= 500; - } else { - LHSVal -= LHSUsers; - } - if (RHSUsers == 0) { - RHSVal -= 500; - } else { - RHSVal -= RHSUsers; - } - - // user preference. This is a bit silly because the more servers on the preferred list the higher the boost - auto LHSPreference = preferredServers.find(LHSMap["Name"].toString()); - auto RHSPreference = preferredServers.find(RHSMap["Name"].toString()); - - if (LHSPreference != preferredServers.end()) { - LHSVal += 100 + LHSPreference->second * 20; - } - if (RHSPreference != preferredServers.end()) { - RHSVal += 100 + RHSPreference->second * 20; - } - - // premium isn't valued high because premium servers already get a massive boost for having few users online - if (LHSMap["IsPremium"].toBool()) LHSVal += 5; - if (RHSMap["IsPremium"].toBool()) RHSVal += 5; - - return RHSVal < LHSVal; -} - -int DownloadManager::startDownloadURLs(const QStringList &urls) -{ - addDownload(urls, -1, -1, nullptr); - return m_ActiveDownloads.size() - 1; -} - -int DownloadManager::startDownloadNexusFile(int modID, int fileID) -{ - int newID = m_ActiveDownloads.size(); - addNXMDownload(QString("nxm://%1/mods/%2/files/%3").arg(ToQString(MOShared::GameInfo::instance().getGameName())).arg(modID).arg(fileID)); - return newID; -} - -QString DownloadManager::downloadPath(int id) -{ - return getFilePath(id); -} - -int DownloadManager::indexByName(const QString &fileName) const -{ - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i]->m_FileName == fileName) { - return i; - } - } - return -1; -} - -void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - ModRepositoryFileInfo *info = qobject_cast(qvariant_cast(userData)); - QVariantList resultList = resultData.toList(); - if (resultList.length() == 0) { - removePending(modID, fileID); - emit showMessage(tr("No download server available. Please try again later.")); - return; - } - - std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); - - info->userData["downloadMap"] = resultList; - - QStringList URLs; - - foreach (const QVariant &server, resultList) { - URLs.append(server.toMap()["URI"].toString()); - } - addDownload(URLs, modID, fileID, info); -} - - -void DownloadManager::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - int index = 0; - - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++index) { - DownloadInfo *info = *iter; - if (info->m_FileInfo->modID == modID) { - if (info->m_State < STATE_FETCHINGMODINFO) { - m_ActiveDownloads.erase(iter); - delete info; - } else { - setState(info, STATE_READY); - } - emit update(index); - break; - } - } - - removePending(modID, fileID); - emit showMessage(tr("Failed to request file info from nexus: %1").arg(errorString)); -} - - -void DownloadManager::downloadFinished() -{ - int index = 0; - - DownloadInfo *info = findDownload(this->sender(), &index); - if (info != NULL) { - QNetworkReply *reply = info->m_Reply; - QByteArray data; - if (reply->isOpen()) { - data = reply->readAll(); - info->m_Output.write(data); - } - info->m_Output.close(); - TaskProgressManager::instance().forgetMe(info->m_TaskProgressId); - - bool error = false; - if ((info->m_State != STATE_CANCELING) && - (info->m_State != STATE_PAUSING)) { - bool textData = reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive); - if ((info->m_Output.size() == 0) || - ((reply->error() != QNetworkReply::NoError) && (reply->error() != QNetworkReply::OperationCanceledError)) || - textData) { - if (info->m_Tries == 0) { - if (textData && (reply->error() == QNetworkReply::NoError)) { - emit showMessage(tr("Download failed. Server reported: %1").arg(QString(data))); - } else { - emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); - } - } - error = true; - setState(info, STATE_PAUSING); - } - } - - if (info->m_State == STATE_CANCELING) { - setState(info, STATE_CANCELED); - } else if (info->m_State == STATE_PAUSING) { - info->m_Output.write(info->m_Reply->readAll()); - - if (error) { - setState(info, STATE_ERROR); - } else { - setState(info, STATE_PAUSED); - } - } - - if (info->m_State == STATE_CANCELED) { - emit aboutToUpdate(); - info->m_Output.remove(); - delete info; - m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); - emit update(-1); - } else if (info->isPausedState()) { - info->m_Output.close(); - createMetaFile(info); - emit update(index); - } else { - QString url = info->m_Urls[info->m_CurrentUrl]; - if (info->m_FileInfo->userData.contains("downloadMap")) { - foreach (const QVariant &server, info->m_FileInfo->userData["downloadMap"].toList()) { - QVariantMap serverMap = server.toMap(); - if (serverMap["URI"].toString() == url) { - int deltaTime = info->m_StartTime.secsTo(QTime::currentTime()); - if (deltaTime > 5) { - emit downloadSpeed(serverMap["Name"].toString(), (info->m_TotalSize - info->m_PreResumeSize) / deltaTime); - } // no division by zero please! Also, if the download is shorter than a few seconds, the result is way to inprecise - break; - } - } - } - - bool isNexus = info->m_FileInfo->repository == "Nexus"; - // need to change state before changing the file name, otherwise .unfinished is appended - if (isNexus) { - setState(info, STATE_FETCHINGMODINFO); - } else { - setState(info, STATE_NOFETCH); - } - - QString newName = getFileNameFromNetworkReply(reply); - QString oldName = QFileInfo(info->m_Output).fileName(); - if (!newName.isEmpty() && (newName != oldName)) { - info->setName(getDownloadFileName(newName), true); - } else { - info->setName(m_OutputDirectory + "/" + info->m_FileName, true); // don't rename but remove the ".unfinished" extension - } - - if (!isNexus) { - setState(info, STATE_READY); - } - - emit update(index); - } - reply->close(); - reply->deleteLater(); - - if ((info->m_Tries > 0) && error) { - --info->m_Tries; - resumeDownloadInt(index); - } - } else { - qWarning("no download index %d", index); - } -} - - -void DownloadManager::downloadError(QNetworkReply::NetworkError error) -{ - if (error != QNetworkReply::OperationCanceledError) { - qWarning("Download error occured: %d", error); - } -} - - -void DownloadManager::metaDataChanged() -{ - int index = 0; - - DownloadInfo *info = findDownload(this->sender(), &index); - if (info != NULL) { - QString newName = getFileNameFromNetworkReply(info->m_Reply); - if (!newName.isEmpty() && (newName != info->m_FileName)) { - info->setName(getDownloadFileName(newName), true); - refreshAlphabeticalTranslation(); - if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { - reportError(tr("failed to re-open %1").arg(info->m_FileName)); - setState(info, STATE_CANCELING); - } - } - } else { - qWarning("meta data event for unknown download"); - } -} - -void DownloadManager::directoryChanged(const QString&) -{ - refreshList(); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "downloadmanager.h" +#include "report.h" +#include "nxmurl.h" +#include +#include +#include +#include "utility.h" +#include "json.h" +#include "selectiondialog.h" +#include "bbcode.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; + + +// TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads + + +static const char UNFINISHED[] = ".unfinished"; + +unsigned int DownloadManager::DownloadInfo::s_NextDownloadID = 1U; + + +DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo *fileInfo, const QStringList &URLs) +{ + DownloadInfo *info = new DownloadInfo; + info->m_DownloadID = s_NextDownloadID++; + info->m_StartTime.start(); + info->m_PreResumeSize = 0LL; + info->m_Progress = 0; + info->m_ResumePos = 0; + info->m_FileInfo = new ModRepositoryFileInfo(*fileInfo); + info->m_Urls = URLs; + info->m_CurrentUrl = 0; + info->m_Tries = AUTOMATIC_RETRIES; + info->m_State = STATE_STARTED; + info->m_TaskProgressId = TaskProgressManager::instance().getId(); + + return info; +} + +DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(const QString &filePath, bool showHidden) +{ + DownloadInfo *info = new DownloadInfo; + + QString metaFileName = filePath + ".meta"; + QSettings metaFile(metaFileName, QSettings::IniFormat); + if (!showHidden && metaFile.value("removed", false).toBool()) { + return NULL; + } else { + info->m_Hidden = metaFile.value("removed", false).toBool(); + } + + QString fileName = QFileInfo(filePath).fileName(); + + if (fileName.endsWith(UNFINISHED)) { + info->m_FileName = fileName.mid(0, fileName.length() - strlen(UNFINISHED)); + info->m_State = STATE_PAUSED; + } else { + info->m_FileName = fileName; + + if (metaFile.value("paused", false).toBool()) { + info->m_State = STATE_PAUSED; + } else if (metaFile.value("uninstalled", false).toBool()) { + info->m_State = STATE_UNINSTALLED; + } else if (metaFile.value("installed", false).toBool()) { + info->m_State = STATE_INSTALLED; + } else { + info->m_State = STATE_READY; + } + } + + info->m_DownloadID = s_NextDownloadID++; + info->m_Output.setFileName(filePath); + info->m_TotalSize = QFileInfo(filePath).size(); + info->m_PreResumeSize = info->m_TotalSize; + info->m_CurrentUrl = 0; + info->m_Urls = metaFile.value("url", "").toString().split(";"); + info->m_Tries = 0; + info->m_TaskProgressId = TaskProgressManager::instance().getId(); + int modID = metaFile.value("modID", 0).toInt(); + int fileID = metaFile.value("fileID", 0).toInt(); + info->m_FileInfo = new ModRepositoryFileInfo(modID, fileID); + info->m_FileInfo->name = metaFile.value("name", "").toString(); + if (info->m_FileInfo->name == "0") { + // bug in earlier version + info->m_FileInfo->name = ""; + } + info->m_FileInfo->modName = metaFile.value("modName", "").toString(); + info->m_FileInfo->modID = modID; + info->m_FileInfo->fileID = fileID; + info->m_FileInfo->description = metaFile.value("description").toString(); + info->m_FileInfo->version.parse(metaFile.value("version", "0").toString()); + info->m_FileInfo->newestVersion.parse(metaFile.value("newestVersion", "0").toString()); + info->m_FileInfo->categoryID = metaFile.value("category", 0).toInt(); + info->m_FileInfo->fileCategory = metaFile.value("fileCategory", 0).toInt(); + info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); + info->m_FileInfo->userData = metaFile.value("userData").toMap(); + + return info; +} + +void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) +{ + QString oldMetaFileName = QString("%1.meta").arg(m_FileName); + m_FileName = QFileInfo(newName).fileName(); + if ((m_State == DownloadManager::STATE_STARTED) || + (m_State == DownloadManager::STATE_DOWNLOADING)) { + newName.append(UNFINISHED); + } + if (renameFile) { + if ((newName != m_Output.fileName()) && !m_Output.rename(newName)) { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(m_Output.fileName()).arg(newName)); + return; + } + + QFile metaFile(oldMetaFileName); + if (metaFile.exists()) { + metaFile.rename(newName.mid(0).append(".meta")); + } + } + if (!m_Output.isOpen()) { + // can't set file name if it's open + m_Output.setFileName(newName); + } +} + +bool DownloadManager::DownloadInfo::isPausedState() +{ + return m_State == STATE_PAUSED || m_State == STATE_ERROR; +} + +QString DownloadManager::DownloadInfo::currentURL() +{ + return m_Urls[m_CurrentUrl]; +} + + +DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) + : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + m_DateExpression("/Date\\((\\d+)\\)/") +{ + connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +} + + +DownloadManager::~DownloadManager() +{ + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + delete *iter; + } + m_ActiveDownloads.clear(); +} + + +bool DownloadManager::downloadsInProgress() +{ + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + if ((*iter)->m_State < STATE_READY) { + return true; + } + } + return false; +} + +void DownloadManager::pauseAll() +{ + // first loop: pause all downloads + for (int i = 0; i < m_ActiveDownloads.count(); ++i) { + if (m_ActiveDownloads[i]->m_State < STATE_READY) { + pauseDownload(i); + } + } + + ::Sleep(100); + + bool done = false; + QTime startTime = QTime::currentTime(); + // further loops: busy waiting for all downloads to complete. This could be neater... + while (!done && (startTime.secsTo(QTime::currentTime()) < 5)) { + QCoreApplication::processEvents(); + done = true; + foreach (DownloadInfo *info, m_ActiveDownloads) { + if ((info->m_State < STATE_CANCELED) || + (info->m_State == STATE_FETCHINGFILEINFO) || + (info->m_State == STATE_FETCHINGMODINFO)) { + done = false; + break; + } + } + if (!done) { + ::Sleep(100); + } + } +} + + +void DownloadManager::setOutputDirectory(const QString &outputDirectory) +{ + QStringList directories = m_DirWatcher.directories(); + if (directories.length() != 0) { + m_DirWatcher.removePaths(directories); + } + m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory); + refreshList(); + m_DirWatcher.addPath(m_OutputDirectory); +} + + +void DownloadManager::setPreferredServers(const std::map &preferredServers) +{ + m_PreferredServers = preferredServers; +} + + +void DownloadManager::setSupportedExtensions(const QStringList &extensions) +{ + m_SupportedExtensions = extensions; + refreshList(); +} + +void DownloadManager::setShowHidden(bool showHidden) +{ + m_ShowHidden = showHidden; + refreshList(); +} + +void DownloadManager::refreshList() +{ + try { + int downloadsBefore = m_ActiveDownloads.size(); + + // remove finished downloads + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { + if (((*iter)->m_State == STATE_READY) || ((*iter)->m_State == STATE_INSTALLED) || ((*iter)->m_State == STATE_UNINSTALLED)) { + delete *iter; + iter = m_ActiveDownloads.erase(iter); + } else { + ++iter; + } + } + + QStringList nameFilters(m_SupportedExtensions); + foreach (const QString &extension, m_SupportedExtensions) { + nameFilters.append("*." + extension); + } + + nameFilters.append(QString("*").append(UNFINISHED)); + QDir dir(QDir::fromNativeSeparators(m_OutputDirectory)); + + // find orphaned meta files and delete them (sounds cruel but it's better for everyone) + QStringList orphans; + QStringList metaFiles = dir.entryList(QStringList() << "*.meta"); + foreach (const QString &metaFile, metaFiles) { + QString baseFile = metaFile.left(metaFile.length() - 5); + if (!QFile::exists(dir.absoluteFilePath(baseFile))) { + orphans.append(dir.absoluteFilePath(metaFile)); + } + } + if (orphans.size() > 0) { + qDebug("%d orphaned meta files will be deleted", orphans.size()); + shellDelete(orphans, true); + } + + // add existing downloads to list + foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) { + bool Exists = false; + for (QVector::const_iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end() && !Exists; ++Iter) { + if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) { + Exists = true; + } else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, Qt::CaseInsensitive) == 0) { + Exists = true; + } + } + if (Exists) { + continue; + } + + QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file; + + DownloadInfo *info = DownloadInfo::createFromMeta(fileName, m_ShowHidden); + if (info != NULL) { + m_ActiveDownloads.push_front(info); + } + } + + if (m_ActiveDownloads.size() != downloadsBefore) { + qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); + } + emit update(-1); + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in refreshing directory).")); + } +} + + +bool DownloadManager::addDownload(const QStringList &URLs, + int modID, int fileID, const ModRepositoryFileInfo *fileInfo) +{ + QString fileName = QFileInfo(URLs.first()).fileName(); + if (fileName.isEmpty()) { + fileName = "unknown"; + } + + QNetworkRequest request(URLs.first()); + return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); +} + + +bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo) +{ + QString fileName = getFileNameFromNetworkReply(reply); + if (fileName.isEmpty()) { + fileName = "unknown"; + } + + return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->modID, fileInfo->fileID, fileInfo); +} + + +bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, + int modID, int fileID, const ModRepositoryFileInfo *fileInfo) +{ + // download invoked from an already open network reply (i.e. download link in the browser) + DownloadInfo *newDownload = DownloadInfo::createNew(fileInfo, URLs); + + QString baseName = fileName; + + if (!fileInfo->fileName.isEmpty()) { + baseName = fileInfo->fileName; + } else { + QString dispoName = getFileNameFromNetworkReply(reply); + + if (!dispoName.isEmpty()) { + baseName = dispoName; + } + } + + if (QFile::exists(m_OutputDirectory + "/" + baseName) && + (QMessageBox::question(NULL, tr("Download again?"), tr("A file with the same name has already been downloaded. " + "Do you want to download it again? The new file will receive a different name."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + removePending(modID, fileID); + delete newDownload; + return false; + } + + newDownload->setName(getDownloadFileName(baseName), false); + + startDownload(reply, newDownload, false); + +// emit update(-1); + return true; +} + + +void DownloadManager::removePending(int modID, int fileID) +{ + emit aboutToUpdate(); + for (auto iter = m_PendingDownloads.begin(); iter != m_PendingDownloads.end(); ++iter) { + if ((iter->first == modID) && (iter->second == fileID)) { + m_PendingDownloads.erase(iter); + break; + } + } + emit update(-1); +} + + +void DownloadManager::startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume) +{ + reply->setReadBufferSize(1024 * 1024); // don't read more than 1MB at once to avoid memory troubles + newDownload->m_Reply = reply; + setState(newDownload, STATE_DOWNLOADING); + if (newDownload->m_Urls.count() == 0) { + newDownload->m_Urls = QStringList(reply->url().toString()); + } + + QIODevice::OpenMode mode = QIODevice::WriteOnly; + if (resume) { + mode |= QIODevice::Append; + } + + newDownload->m_StartTime.start(); + createMetaFile(newDownload); + + if (!newDownload->m_Output.open(mode)) { + reportError(tr("failed to download %1: could not open output file: %2") + .arg(reply->url().toString()).arg(newDownload->m_Output.fileName())); + return; + } + + connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); + connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(newDownload->m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError))); + connect(newDownload->m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); + connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged())); + + if (!resume) { + newDownload->m_PreResumeSize = newDownload->m_Output.size(); + removePending(newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); + + emit aboutToUpdate(); + m_ActiveDownloads.append(newDownload); + + emit update(-1); + emit downloadAdded(); + + if (reply->isFinished()) { + // it's possible the download has already finished before this function ran + downloadFinished(); + } + } +} + + +void DownloadManager::addNXMDownload(const QString &url) +{ + NXMUrl nxmInfo(url); + + QString managedGame = ToQString(MOShared::GameInfo::instance().getGameShortName()); + qDebug("add nxm download: %s", qPrintable(url)); + if (nxmInfo.game().compare(managedGame, Qt::CaseInsensitive) != 0) { + qDebug("download requested for wrong game (game: %s, url: %s)", qPrintable(managedGame), qPrintable(nxmInfo.game())); + QMessageBox::information(NULL, tr("Wrong Game"), tr("The download link is for a mod for \"%1\" but this instance of MO " + "has been set up for \"%2\".").arg(nxmInfo.game()).arg(managedGame), QMessageBox::Ok); + return; + } + + emit aboutToUpdate(); + m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId())); + + emit update(-1); + emit downloadAdded(); + m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), "")); +} + + +void DownloadManager::removeFile(int index, bool deleteFile) +{ + if (index >= m_ActiveDownloads.size()) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *download = m_ActiveDownloads.at(index); + QString filePath = m_OutputDirectory + "/" + download->m_FileName; + if ((download->m_State == STATE_STARTED) || + (download->m_State == STATE_DOWNLOADING)) { + // shouldn't have been possible + qCritical("tried to remove active download"); + return; + } + + if ((download->m_State == STATE_PAUSED) || (download->m_State == STATE_ERROR)) { + filePath = download->m_Output.fileName(); + } + + if (deleteFile) { + if (!shellDelete(QStringList(filePath), true)) { + reportError(tr("failed to delete %1").arg(filePath)); + return; + } + + QFile metaFile(filePath.append(".meta")); + if (metaFile.exists() && !shellDelete(QStringList(filePath), true)) { + reportError(tr("failed to delete meta file for %1").arg(filePath)); + } + } else { + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + metaSettings.setValue("removed", true); + } +} + +class LessThanWrapper +{ +public: + LessThanWrapper(DownloadManager *manager) : m_Manager(manager) {} + bool operator()(int LHS, int RHS) { + return m_Manager->getFileName(LHS).compare(m_Manager->getFileName(RHS), Qt::CaseInsensitive) < 0; + + } + +private: + DownloadManager *m_Manager; +}; + + +bool DownloadManager::ByName(int LHS, int RHS) +{ + return m_ActiveDownloads[LHS]->m_FileName < m_ActiveDownloads[RHS]->m_FileName; +} + + +void DownloadManager::refreshAlphabeticalTranslation() +{ + m_AlphabeticalTranslation.clear(); + int pos = 0; + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++pos) { + m_AlphabeticalTranslation.push_back(pos); + } + + qSort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); +} + + +void DownloadManager::restoreDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *download = m_ActiveDownloads.at(index); + download->m_Hidden = false; + + QString filePath = m_OutputDirectory + "/" + download->m_FileName; + + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + metaSettings.setValue("removed", false); +} + + +void DownloadManager::removeDownload(int index, bool deleteFile) +{ + try { + emit aboutToUpdate(); + + if (index < 0) { + DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY; + 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("invalid index %1").arg(index)); + return; + } + + removeFile(index, deleteFile); + delete m_ActiveDownloads.at(index); + m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); + } + emit update(-1); + } catch (const std::exception &e) { + qCritical("failed to remove download: %s", e.what()); + } +} + + +void DownloadManager::cancelDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("invalid index %1").arg(index)); + return; + } + + if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { + setState(m_ActiveDownloads.at(index), STATE_CANCELING); + } +} + + +void DownloadManager::pauseDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("invalid index %1").arg(index)); + return; + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + + if (info->m_State == STATE_DOWNLOADING) { + if (info->m_Reply->isRunning()) { + setState(info, STATE_PAUSING); + } else { + setState(info, STATE_PAUSED); + } + } else if ((info->m_State == STATE_FETCHINGMODINFO) || (info->m_State == STATE_FETCHINGFILEINFO)) { + setState(info, STATE_READY); + } +} + +void DownloadManager::resumeDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("invalid index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + info->m_Tries = AUTOMATIC_RETRIES; + resumeDownloadInt(index); +} + +void DownloadManager::resumeDownloadInt(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("invalid index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + if (info->isPausedState()) { + if ((info->m_Urls.size() == 0) + || ((info->m_Urls.size() == 1) && (info->m_Urls[0].size() == 0))) { + emit showMessage(tr("No known download urls. Sorry, this download can't be resumed.")); + return; + } + if (info->m_State == STATE_ERROR) { + info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); + } + qDebug("request resume from url %s", qPrintable(info->currentURL())); + QNetworkRequest request(info->currentURL()); + info->m_ResumePos = info->m_Output.size(); + qDebug("resume at %lld bytes", info->m_ResumePos); + QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; + request.setRawHeader("Range", rangeHeader); + startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); + } + emit update(index); +} + + +DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id) +{ + auto iter = std::find_if(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), + [id](DownloadInfo *info) { return info->m_DownloadID == id; }); + if (iter != m_ActiveDownloads.end()) { + return *iter; + } else { + return NULL; + } +} + + +void DownloadManager::queryInfo(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("invalid index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + + if (info->m_FileInfo->repository != "Nexus") { + qWarning("re-querying file info is currently only possible with Nexus"); + return; + } + + if (info->m_State < DownloadManager::STATE_READY) { + // UI shouldn't allow this + return; + } + + if (info->m_FileInfo->modID == 0UL) { + QString fileName = getFileName(index); + QString ignore; + NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true); + if (info->m_FileInfo->modID < 0) { + QString modIDString; + while (modIDString.isEmpty()) { + modIDString = QInputDialog::getText(NULL, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal, + QString(), NULL, 0, Qt::ImhFormattedNumbersOnly); + if (modIDString.isNull()) { + // canceled + return; + } else if (modIDString.contains(QRegExp("[^0-9]"))) { + qDebug("illegal character in mod-id"); + modIDString.clear(); + } + } + info->m_FileInfo->modID = modIDString.toInt(NULL, 10); + } + } + info->m_ReQueried = true; + setState(info, STATE_FETCHINGMODINFO); +} + + +int DownloadManager::numTotalDownloads() const +{ + return m_ActiveDownloads.size(); +} + +int DownloadManager::numPendingDownloads() const +{ + return m_PendingDownloads.size(); +} + +std::pair DownloadManager::getPendingDownload(int index) +{ + if ((index < 0) || (index >= m_PendingDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_PendingDownloads.at(index); +} + +QString DownloadManager::getFilePath(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName; +} + +QString DownloadManager::getFileTypeString(int fileType) +{ + switch (fileType) { + case 1: return tr("Main"); + case 2: return tr("Update"); + case 3: return tr("Optional"); + case 4: return tr("Old"); + case 5: return tr("Misc"); + default: return tr("Unknown"); + } +} + +QString DownloadManager::getDisplayName(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + + if (!info->m_FileInfo->name.isEmpty()) { + return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name) + .arg(getFileTypeString(info->m_FileInfo->fileCategory)) + .arg(info->m_FileInfo->version.displayString()); + } else { + return info->m_FileName; + } +} + +QString DownloadManager::getFileName(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_ActiveDownloads.at(index)->m_FileName; +} + +QDateTime DownloadManager::getFileTime(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + if (!info->m_Created.isValid()) { + info->m_Created = QFileInfo(info->m_Output).created(); + } + + return info->m_Created; +} + +qint64 DownloadManager::getFileSize(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_ActiveDownloads.at(index)->m_TotalSize; +} + + +int DownloadManager::getProgress(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_ActiveDownloads.at(index)->m_Progress; +} + + +DownloadManager::DownloadState DownloadManager::getState(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_ActiveDownloads.at(index)->m_State; +} + + +bool DownloadManager::isInfoIncomplete(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + if (info->m_FileInfo->repository != "Nexus") { + // other repositories currently don't support re-querying info anyway + return false; + } + return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0) || !info->m_FileInfo->version.isValid(); +} + + +int DownloadManager::getModID(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + return m_ActiveDownloads.at(index)->m_FileInfo->modID; +} + +bool DownloadManager::isHidden(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + return m_ActiveDownloads.at(index)->m_Hidden; +} + + +const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + return m_ActiveDownloads.at(index)->m_FileInfo; +} + + +void DownloadManager::markInstalled(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("installed", true); + metaFile.setValue("uninstalled", false); + + setState(m_ActiveDownloads.at(index), STATE_INSTALLED); +} + + +void DownloadManager::markUninstalled(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("invalid index")); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("uninstalled", true); + + setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); +} + + +QString DownloadManager::getDownloadFileName(const QString &baseName) const +{ + QString fullPath = m_OutputDirectory + "/" + baseName; + if (QFile::exists(fullPath)) { + int i = 1; + while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { + ++i; + } + + fullPath = QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName); + } + return fullPath; +} + + +QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply) +{ + if (reply->hasRawHeader("Content-Disposition")) { + std::tr1::regex exp("filename=\"(.*)\""); + + std::tr1::cmatch result; + if (std::tr1::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) { + return QString::fromUtf8(result.str(1).c_str()); + } + } + + return QString(); +} + + +void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadManager::DownloadState state) +{ + int row = 0; + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i] == info) { + row = i; + break; + } + } + info->m_State = state; + switch (state) { + case STATE_PAUSED: + case STATE_ERROR: { + info->m_Reply->abort(); + } break; + case STATE_CANCELED: { + info->m_Reply->abort(); + } break; + case STATE_FETCHINGMODINFO: { + m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); + } break; + case STATE_FETCHINGFILEINFO: { + m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); + } break; + case STATE_READY: { + createMetaFile(info); + emit downloadComplete(row); + } break; + default: /* NOP */ break; + } + emit stateChanged(row, state); +} + + +DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const +{ + // reverse search as newer, thus more relevant, downloads are at the end + for (int i = m_ActiveDownloads.size() - 1; i >= 0; --i) { + if (m_ActiveDownloads[i]->m_Reply == reply) { + if (index != NULL) { + *index = i; + } + return m_ActiveDownloads[i]; + } + } + return NULL; +} + + +void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + if (bytesTotal == 0) { + return; + } + int index = 0; + try { + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != NULL) { + if (info->m_State == STATE_CANCELING) { + setState(info, STATE_CANCELED); + } else if (info->m_State == STATE_PAUSING) { + setState(info, STATE_PAUSED); + } else { + if (bytesTotal > info->m_TotalSize) { + info->m_TotalSize = bytesTotal; + } + int oldProgress = info->m_Progress; + info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); + TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); + if (oldProgress != info->m_Progress) { + emit update(index); + } + } + } + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in processing progress event).")); + } +} + + +void DownloadManager::downloadReadyRead() +{ + try { + DownloadInfo *info = findDownload(this->sender()); + if (info != NULL) { + info->m_Output.write(info->m_Reply->readAll()); + } + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in processing downloaded data).")); + } +} + + +void DownloadManager::createMetaFile(DownloadInfo *info) +{ + QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); + metaFile.setValue("modID", info->m_FileInfo->modID); + metaFile.setValue("fileID", info->m_FileInfo->fileID); + metaFile.setValue("url", info->m_Urls.join(";")); + metaFile.setValue("name", info->m_FileInfo->name); + metaFile.setValue("description", info->m_FileInfo->description); + metaFile.setValue("modName", info->m_FileInfo->modName); + metaFile.setValue("version", info->m_FileInfo->version.canonicalString()); + metaFile.setValue("newestVersion", info->m_FileInfo->newestVersion.canonicalString()); + metaFile.setValue("fileTime", info->m_FileInfo->fileTime); + metaFile.setValue("fileCategory", info->m_FileInfo->fileCategory); + metaFile.setValue("category", info->m_FileInfo->categoryID); + metaFile.setValue("repository", info->m_FileInfo->repository); + metaFile.setValue("userData", info->m_FileInfo->userData); + metaFile.setValue("installed", info->m_State == DownloadManager::STATE_INSTALLED); + metaFile.setValue("uninstalled", info->m_State == DownloadManager::STATE_UNINSTALLED); + metaFile.setValue("paused", (info->m_State == DownloadManager::STATE_PAUSED) || + (info->m_State == DownloadManager::STATE_ERROR)); + metaFile.setValue("removed", info->m_Hidden); + + // slightly hackish... + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i] == info) { + emit update(i); + } + } +} + + +void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + QVariantMap result = resultData.toMap(); + + DownloadInfo *info = downloadInfoByID(userData.toInt()); + if (info == NULL) return; + info->m_FileInfo->categoryID = result["category_id"].toInt(); + info->m_FileInfo->modName = result["name"].toString().trimmed(); + info->m_FileInfo->newestVersion.parse(result["version"].toString()); + if (info->m_FileInfo->fileID != 0) { + setState(info, STATE_READY); + } else { + setState(info, STATE_FETCHINGFILEINFO); + } +} + + +QDateTime DownloadManager::matchDate(const QString &timeString) +{ + if (m_DateExpression.exactMatch(timeString)) { + return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong()); + } else { + qWarning("date not matched: %s", qPrintable(timeString)); + return QDateTime::currentDateTime(); + } +} + + +EFileCategory convertFileCategory(int id) +{ + // TODO: need to handle file categories in the mod page plugin + switch (id) { + case 0: return TYPE_MAIN; + case 1: return TYPE_UPDATE; + case 2: return TYPE_OPTION; + default: return TYPE_MAIN; + } +} + + +void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + DownloadInfo *info = downloadInfoByID(userData.toInt()); + if (info == NULL) return; + + QVariantList result = resultData.toList(); + + // MO sometimes prepends _ to the filename in case of duplicate downloads. + // this may muck up the file name comparison + QString alternativeLocalName = info->m_FileName; + + QRegExp expression("^\\d_(.*)$"); + if (expression.indexIn(alternativeLocalName) == 0) { + alternativeLocalName = expression.cap(1); + } + + bool found = false; + + foreach(QVariant file, result) { + QVariantMap fileInfo = file.toMap(); + QString fileName = fileInfo["uri"].toString(); + QString fileNameVariant = fileName.mid(0).replace(' ', '_'); + if ((fileName == info->m_FileName) || (fileName == alternativeLocalName) || + (fileNameVariant == info->m_FileName) || (fileNameVariant == alternativeLocalName)) { + info->m_FileInfo->name = fileInfo["name"].toString(); + info->m_FileInfo->version.parse(fileInfo["version"].toString()); + if (!info->m_FileInfo->version.isValid()) { + info->m_FileInfo->version = info->m_FileInfo->newestVersion; + } + info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); + info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString()); + info->m_FileInfo->fileID = fileInfo["id"].toInt(); + found = true; + break; + } + } + + if (info->m_ReQueried) { + if (found) { + emit showMessage(tr("Information updated")); + } else if (result.count() == 0) { + emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); + } else { + SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); + foreach(QVariant file, result) { + QVariantMap fileInfo = file.toMap(); + selection.addChoice(fileInfo["uri"].toString(), "", file); + } + if (selection.exec() == QDialog::Accepted) { + QVariantMap fileInfo = selection.getChoiceData().toMap(); + info->m_FileInfo->name = fileInfo["name"].toString(); + info->m_FileInfo->version.parse(fileInfo["version"].toString()); + info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); + info->m_FileInfo->fileID = fileInfo["id"].toInt(); + } else { + emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); + } + } + } else { + if (info->m_FileInfo->fileID == 0) { + qWarning("could not determine file id for %s (state %d)", + info->m_FileName.toUtf8().constData(), info->m_State); + } + } + + setState(info, STATE_READY); +} + + +void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + ModRepositoryFileInfo *info = new ModRepositoryFileInfo(); + + QVariantMap result = resultData.toMap(); + info->name = result["name"].toString(); + qDebug("file info received for %s", qPrintable(info->name)); + info->version.parse(result["version"].toString()); + if (!info->version.isValid()) { + info->version = info->newestVersion; + } + info->fileName = result["uri"].toString(); + info->fileCategory = result["category_id"].toInt(); + info->fileTime = matchDate(result["date"].toString()); + info->description = BBCode::convertToHTML(result["description"].toString()); + + info->repository = "Nexus"; + info->modID = modID; + info->fileID = fileID; + + QObject *test = info; + m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); +} + + +// sort function to sort by best download server +bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +{ + int LHSVal = 0; + int RHSVal = 0; + + QVariantMap LHSMap = LHS.toMap(); + QVariantMap RHSMap = RHS.toMap(); + + int LHSUsers = LHSMap["ConnectedUsers"].toInt(); + int RHSUsers = RHSMap["ConnectedUsers"].toInt(); + // 0 users is probably a sign that the server is offline. Since there is currently no + // mechanism to try a different server, we avoid those without users + if (LHSUsers == 0) { + LHSVal -= 500; + } else { + LHSVal -= LHSUsers; + } + if (RHSUsers == 0) { + RHSVal -= 500; + } else { + RHSVal -= RHSUsers; + } + + // user preference. This is a bit silly because the more servers on the preferred list the higher the boost + auto LHSPreference = preferredServers.find(LHSMap["Name"].toString()); + auto RHSPreference = preferredServers.find(RHSMap["Name"].toString()); + + if (LHSPreference != preferredServers.end()) { + LHSVal += 100 + LHSPreference->second * 20; + } + if (RHSPreference != preferredServers.end()) { + RHSVal += 100 + RHSPreference->second * 20; + } + + // premium isn't valued high because premium servers already get a massive boost for having few users online + if (LHSMap["IsPremium"].toBool()) LHSVal += 5; + if (RHSMap["IsPremium"].toBool()) RHSVal += 5; + + return RHSVal < LHSVal; +} + +int DownloadManager::startDownloadURLs(const QStringList &urls) +{ + addDownload(urls, -1, -1, nullptr); + return m_ActiveDownloads.size() - 1; +} + +int DownloadManager::startDownloadNexusFile(int modID, int fileID) +{ + int newID = m_ActiveDownloads.size(); + addNXMDownload(QString("nxm://%1/mods/%2/files/%3").arg(ToQString(MOShared::GameInfo::instance().getGameName())).arg(modID).arg(fileID)); + return newID; +} + +QString DownloadManager::downloadPath(int id) +{ + return getFilePath(id); +} + +int DownloadManager::indexByName(const QString &fileName) const +{ + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i]->m_FileName == fileName) { + return i; + } + } + return -1; +} + +void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + ModRepositoryFileInfo *info = qobject_cast(qvariant_cast(userData)); + QVariantList resultList = resultData.toList(); + if (resultList.length() == 0) { + removePending(modID, fileID); + emit showMessage(tr("No download server available. Please try again later.")); + return; + } + + std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); + + info->userData["downloadMap"] = resultList; + + QStringList URLs; + + foreach (const QVariant &server, resultList) { + URLs.append(server.toMap()["URI"].toString()); + } + addDownload(URLs, modID, fileID, info); +} + + +void DownloadManager::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + int index = 0; + + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++index) { + DownloadInfo *info = *iter; + if (info->m_FileInfo->modID == modID) { + if (info->m_State < STATE_FETCHINGMODINFO) { + m_ActiveDownloads.erase(iter); + delete info; + } else { + setState(info, STATE_READY); + } + emit update(index); + break; + } + } + + removePending(modID, fileID); + emit showMessage(tr("Failed to request file info from nexus: %1").arg(errorString)); +} + + +void DownloadManager::downloadFinished() +{ + int index = 0; + + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != NULL) { + QNetworkReply *reply = info->m_Reply; + QByteArray data; + if (reply->isOpen()) { + data = reply->readAll(); + info->m_Output.write(data); + } + info->m_Output.close(); + TaskProgressManager::instance().forgetMe(info->m_TaskProgressId); + + bool error = false; + if ((info->m_State != STATE_CANCELING) && + (info->m_State != STATE_PAUSING)) { + bool textData = reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive); + if ((info->m_Output.size() == 0) || + ((reply->error() != QNetworkReply::NoError) && (reply->error() != QNetworkReply::OperationCanceledError)) || + textData) { + if (info->m_Tries == 0) { + if (textData && (reply->error() == QNetworkReply::NoError)) { + emit showMessage(tr("Download failed. Server reported: %1").arg(QString(data))); + } else { + emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); + } + } + error = true; + setState(info, STATE_PAUSING); + } + } + + if (info->m_State == STATE_CANCELING) { + setState(info, STATE_CANCELED); + } else if (info->m_State == STATE_PAUSING) { + info->m_Output.write(info->m_Reply->readAll()); + + if (error) { + setState(info, STATE_ERROR); + } else { + setState(info, STATE_PAUSED); + } + } + + if (info->m_State == STATE_CANCELED) { + emit aboutToUpdate(); + info->m_Output.remove(); + delete info; + m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); + emit update(-1); + } else if (info->isPausedState()) { + info->m_Output.close(); + createMetaFile(info); + emit update(index); + } else { + QString url = info->m_Urls[info->m_CurrentUrl]; + if (info->m_FileInfo->userData.contains("downloadMap")) { + foreach (const QVariant &server, info->m_FileInfo->userData["downloadMap"].toList()) { + QVariantMap serverMap = server.toMap(); + if (serverMap["URI"].toString() == url) { + int deltaTime = info->m_StartTime.secsTo(QTime::currentTime()); + if (deltaTime > 5) { + emit downloadSpeed(serverMap["Name"].toString(), (info->m_TotalSize - info->m_PreResumeSize) / deltaTime); + } // no division by zero please! Also, if the download is shorter than a few seconds, the result is way to inprecise + break; + } + } + } + + bool isNexus = info->m_FileInfo->repository == "Nexus"; + // need to change state before changing the file name, otherwise .unfinished is appended + if (isNexus) { + setState(info, STATE_FETCHINGMODINFO); + } else { + setState(info, STATE_NOFETCH); + } + + QString newName = getFileNameFromNetworkReply(reply); + QString oldName = QFileInfo(info->m_Output).fileName(); + if (!newName.isEmpty() && (newName != oldName)) { + info->setName(getDownloadFileName(newName), true); + } else { + info->setName(m_OutputDirectory + "/" + info->m_FileName, true); // don't rename but remove the ".unfinished" extension + } + + if (!isNexus) { + setState(info, STATE_READY); + } + + emit update(index); + } + reply->close(); + reply->deleteLater(); + + if ((info->m_Tries > 0) && error) { + --info->m_Tries; + resumeDownloadInt(index); + } + } else { + qWarning("no download index %d", index); + } +} + + +void DownloadManager::downloadError(QNetworkReply::NetworkError error) +{ + if (error != QNetworkReply::OperationCanceledError) { + qWarning("Download error occured: %d", error); + } +} + + +void DownloadManager::metaDataChanged() +{ + int index = 0; + + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != NULL) { + QString newName = getFileNameFromNetworkReply(info->m_Reply); + if (!newName.isEmpty() && (newName != info->m_FileName)) { + info->setName(getDownloadFileName(newName), true); + refreshAlphabeticalTranslation(); + if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { + reportError(tr("failed to re-open %1").arg(info->m_FileName)); + setState(info, STATE_CANCELING); + } + } + } else { + qWarning("meta data event for unknown download"); + } +} + +void DownloadManager::directoryChanged(const QString&) +{ + refreshList(); +} + diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 0d8d314a..95288884 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -1,492 +1,492 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef DOWNLOADMANAGER_H -#define DOWNLOADMANAGER_H - -#include "nexusinterface.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -/*! - * \brief manages downloading of files and provides progress information for gui elements - **/ -class DownloadManager : public MOBase::IDownloadManager -{ - Q_OBJECT - -public: - - enum DownloadState { - STATE_STARTED = 0, - STATE_DOWNLOADING, - STATE_CANCELING, - STATE_PAUSING, - STATE_CANCELED, - STATE_PAUSED, - STATE_ERROR, - STATE_FETCHINGMODINFO, - STATE_FETCHINGFILEINFO, - STATE_NOFETCH, - STATE_READY, - STATE_INSTALLED, - STATE_UNINSTALLED - }; - -private: - - struct DownloadInfo { - ~DownloadInfo() { delete m_FileInfo; } - unsigned int m_DownloadID; - QString m_FileName; - QFile m_Output; - QNetworkReply *m_Reply; - QTime m_StartTime; - qint64 m_PreResumeSize; - int m_Progress; - DownloadState m_State; - int m_CurrentUrl; - QStringList m_Urls; - qint64 m_ResumePos; - qint64 m_TotalSize; - QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere - - int m_Tries; - bool m_ReQueried; - - quint32 m_TaskProgressId; - - MOBase::ModRepositoryFileInfo *m_FileInfo; - - bool m_Hidden; - - static DownloadInfo *createNew(const MOBase::ModRepositoryFileInfo *fileInfo, const QStringList &URLs); - static DownloadInfo *createFromMeta(const QString &filePath, bool showHidden); - - /** - * @brief rename the file - * this will change the file name as well as the display name. It will automatically - * append .unfinished to the name if this file is still being downloaded - * @param newName the new name to setName - * @param renameFile if true, the file is assumed to exist and renamed. If the file does not - * yet exist, set this to false - **/ - void setName(QString newName, bool renameFile); - - unsigned int downloadID() { return m_DownloadID; } - - bool isPausedState(); - - QString currentURL(); - private: - static unsigned int s_NextDownloadID; - private: - DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false) {} - }; - -public: - - /** - * @brief constructor - * - * @param nexusInterface interface to use to retrieve information from the relevant nexus page - * @param parent parent object - **/ - explicit DownloadManager(NexusInterface *nexusInterface, QObject *parent); - - ~DownloadManager(); - - /** - * @brief determine if a download is currently in progress - * - * @return true if there is currently a download in progress - **/ - bool downloadsInProgress(); - - /** - * @brief set the output directory to write to - * - * @param outputDirectory the new output directory - **/ - void setOutputDirectory(const QString &outputDirectory); - - /** - * @return current download directory - **/ - QString getOutputDirectory() const { return m_OutputDirectory; } - - /** - * @brief setPreferredServers set the list of preferred servers - */ - void setPreferredServers(const std::map &preferredServers); - - /** - * @brief set the list of supported extensions - * @param extensions list of supported extensions - */ - void setSupportedExtensions(const QStringList &extensions); - - /** - * @brief sets whether hidden files are to be shown after all - */ - void setShowHidden(bool showHidden); - - /** - * @brief download from an already open network connection - * - * @param reply the network reply to download from - * @param fileInfo information about the file, like mod id, file id, version, ... - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again - **/ - bool addDownload(QNetworkReply *reply, const MOBase::ModRepositoryFileInfo *fileInfo); - - /** - * @brief download from an already open network connection - * - * @param reply the network reply to download from - * @param fileName the name to use for the file. This may be overridden by the name in the fileInfo-structure or if the http stream specifies a name - * @param fileInfo information previously retrieved from the nexus network - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again - **/ - bool addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, int modID, int fileID = 0, const MOBase::ModRepositoryFileInfo *fileInfo = new MOBase::ModRepositoryFileInfo()); - - /** - * @brief start a download using a nxm-link - * - * starts a download using a nxm-link. The download manager will first query the nexus - * page for file information. - * @param url a nxm link looking like this: nxm://skyrim/mods/1234/files/4711 - * @todo the game name encoded into the link is currently ignored, all downloads are incorrectly assumed to be for the identified game - **/ - void addNXMDownload(const QString &url); - - /** - * @brief retrieve the total number of downloads, both finished and unfinished including downloads from previous sessions - * - * @return total number of downloads - **/ - int numTotalDownloads() const; - - /** - * @brief retrieve number of pending downloads (nexus downloads for which we don't know the name and url yet) - * @return number of pending downloads - */ - int numPendingDownloads() const; - - /** - * @brief retrieve the info of a pending download - * @param index index of the pending download (index in the range [0, numPendingDownloads()[) - * @return pair of modid, fileid - */ - std::pair getPendingDownload(int index); - - /** - * @brief retrieve the full path to the download specified by index - * - * @param index the index to look up - * @return absolute path of the file - **/ - QString getFilePath(int index) const; - - /** - * @brief retrieve a descriptive name of the download specified by index - * - * @param index index of the file to look up - * @return display name of the file - **/ - QString getDisplayName(int index) const; - - /** - * @brief retrieve the filename of the download specified by index - * - * @param index index of the file to look up - * @return name of the file - **/ - QString getFileName(int index) const; - - /** - * @brief retrieve the file size of the download specified by index - * - * @param index index of the file to look up - * @return size of the file (total size during download) - */ - qint64 getFileSize(int index) const; - - /** - * @brief retrieve the creation time of the download specified by index - * @param index index of the file to look up - * @return size of the file (total size during download) - */ - QDateTime getFileTime(int index) const; - - /** - * @brief retrieve the current progress of the download specified by index - * - * @param index index of the file to look up - * @return progress of the download in percent (integer) - **/ - int getProgress(int index) const; - - /** - * @brief retrieve the current state of the download - * - * retrieve the current state of the download. A download usually goes through - * the following states: - * started -> downloading -> fetching mod info -> fetching file info -> done - * in case of downloads started via nxm-link, file information is fetched first - * - * @param index index of the file to look up - * @return the download state - **/ - DownloadState getState(int index) const; - - /** - * @param index index of the file to look up - * @return true if the nexus information for this download is not complete - **/ - bool isInfoIncomplete(int index) const; - - /** - * @brief retrieve the nexus mod id of the download specified by index - * - * @param index index of the file to look up - * @return the nexus mod id - **/ - int getModID(int index) const; - - /** - * @brief determine if the specified file is supposed to be hidden - * @param index index of the file to look up - * @return true if the specified file is supposed to be hidden - */ - bool isHidden(int index) const; - - /** - * @brief retrieve all nexus info of the download specified by index - * - * @param index index of the file to look up - * @return the nexus mod information - **/ - const MOBase::ModRepositoryFileInfo *getFileInfo(int index) const; - - /** - * @brief mark a download as installed - * - * @param index index of the file to mark installed - */ - void markInstalled(int index); - - /** - * @brief mark a download as uninstalled - * - * @param index index of the file to mark uninstalled - */ - void markUninstalled(int index); - - /** - * @brief refreshes the list of downloads - */ - void refreshList(); - - /** - * @brief Sort function for download servers - * @param LHS - * @param RHS - * @return - */ - static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); - - - virtual int startDownloadURLs(const QStringList &urls); - virtual int startDownloadNexusFile(int modID, int fileID); - virtual QString downloadPath(int id); - - /** - * @brief retrieve a download index from the filename - * @param fileName file to look up - * @return index of that download or -1 if it wasn't found - */ - int indexByName(const QString &fileName) const; - - void pauseAll(); - -signals: - - void aboutToUpdate(); - - /** - * @brief signals that the specified download has changed - * - * @param row the row that changed. This corresponds to the download index - **/ - void update(int row); - - /** - * @brief signals the ui that a message should be displayed - * - * @param message the message to display - **/ - void showMessage(const QString &message); - - /** - * @brief emitted whenever the state of a download changes - * @param row the row that changed - * @param state the new state - */ - void stateChanged(int row, DownloadManager::DownloadState state); - - /** - * @brief emitted whenever a download completes successfully, reporting the download speed for the server used - */ - void downloadSpeed(const QString &serverName, int bytesPerSecond); - - /** - * @brief emitted whenever a new download is added to the list - */ - void downloadAdded(); - -public slots: - - /** - * @brief removes the specified download - * - * @param index index of the download to remove - * @param deleteFile if true, the file will also be deleted from disc, otherwise it is only marked as hidden. - **/ - void removeDownload(int index, bool deleteFile); - - /** - * @brief restores the specified download to view (which was previously hidden - * @param index index of the download to restore - */ - void restoreDownload(int index); - - /** - * @brief cancel the specified download. This will lead to the corresponding file to be deleted - * - * @param index index of the download to cancel - **/ - void cancelDownload(int index); - - void pauseDownload(int index); - - void resumeDownload(int index); - - void queryInfo(int index); - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - -private slots: - - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void downloadReadyRead(); - void downloadFinished(); - void downloadError(QNetworkReply::NetworkError error); - void metaDataChanged(); - void directoryChanged(const QString &dirctory); - -private: - - void createMetaFile(DownloadInfo *info); - QString getDownloadFileName(const QString &baseName) const; - - void startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume); - void resumeDownloadInt(int index); - - /** - * @brief start a download from a url - * - * @param url the url to download from - * @param fileInfo information previously retrieved from the mod page - * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again - **/ - bool addDownload(const QStringList &URLs, int modID, int fileID, const MOBase::ModRepositoryFileInfo *fileInfo); - - // important: the caller has to lock the list-mutex, otherwise the DownloadInfo-pointer might get invalidated at any time - DownloadInfo *findDownload(QObject *reply, int *index = NULL) const; - - void removeFile(int index, bool deleteFile); - - void refreshAlphabeticalTranslation(); - - bool ByName(int LHS, int RHS); - - QString getFileNameFromNetworkReply(QNetworkReply *reply); - - void setState(DownloadInfo *info, DownloadManager::DownloadState state); - - DownloadInfo *downloadInfoByID(unsigned int id); - - QDateTime matchDate(const QString &timeString); - - void removePending(int modID, int fileID); - - static QString getFileTypeString(int fileType); - -private: - - static const int AUTOMATIC_RETRIES = 3; - -private: - - NexusInterface *m_NexusInterface; - - QVector > m_PendingDownloads; - - QVector m_ActiveDownloads; - - QString m_OutputDirectory; - std::map m_PreferredServers; - QStringList m_SupportedExtensions; - std::set m_RequestIDs; - QVector m_AlphabeticalTranslation; - - QFileSystemWatcher m_DirWatcher; - - std::map m_DownloadFails; - - bool m_ShowHidden; - - QRegExp m_DateExpression; - -}; - - - -#endif // DOWNLOADMANAGER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef DOWNLOADMANAGER_H +#define DOWNLOADMANAGER_H + +#include "nexusinterface.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/*! + * \brief manages downloading of files and provides progress information for gui elements + **/ +class DownloadManager : public MOBase::IDownloadManager +{ + Q_OBJECT + +public: + + enum DownloadState { + STATE_STARTED = 0, + STATE_DOWNLOADING, + STATE_CANCELING, + STATE_PAUSING, + STATE_CANCELED, + STATE_PAUSED, + STATE_ERROR, + STATE_FETCHINGMODINFO, + STATE_FETCHINGFILEINFO, + STATE_NOFETCH, + STATE_READY, + STATE_INSTALLED, + STATE_UNINSTALLED + }; + +private: + + struct DownloadInfo { + ~DownloadInfo() { delete m_FileInfo; } + unsigned int m_DownloadID; + QString m_FileName; + QFile m_Output; + QNetworkReply *m_Reply; + QTime m_StartTime; + qint64 m_PreResumeSize; + int m_Progress; + DownloadState m_State; + int m_CurrentUrl; + QStringList m_Urls; + qint64 m_ResumePos; + qint64 m_TotalSize; + QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere + + int m_Tries; + bool m_ReQueried; + + quint32 m_TaskProgressId; + + MOBase::ModRepositoryFileInfo *m_FileInfo; + + bool m_Hidden; + + static DownloadInfo *createNew(const MOBase::ModRepositoryFileInfo *fileInfo, const QStringList &URLs); + static DownloadInfo *createFromMeta(const QString &filePath, bool showHidden); + + /** + * @brief rename the file + * this will change the file name as well as the display name. It will automatically + * append .unfinished to the name if this file is still being downloaded + * @param newName the new name to setName + * @param renameFile if true, the file is assumed to exist and renamed. If the file does not + * yet exist, set this to false + **/ + void setName(QString newName, bool renameFile); + + unsigned int downloadID() { return m_DownloadID; } + + bool isPausedState(); + + QString currentURL(); + private: + static unsigned int s_NextDownloadID; + private: + DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false) {} + }; + +public: + + /** + * @brief constructor + * + * @param nexusInterface interface to use to retrieve information from the relevant nexus page + * @param parent parent object + **/ + explicit DownloadManager(NexusInterface *nexusInterface, QObject *parent); + + ~DownloadManager(); + + /** + * @brief determine if a download is currently in progress + * + * @return true if there is currently a download in progress + **/ + bool downloadsInProgress(); + + /** + * @brief set the output directory to write to + * + * @param outputDirectory the new output directory + **/ + void setOutputDirectory(const QString &outputDirectory); + + /** + * @return current download directory + **/ + QString getOutputDirectory() const { return m_OutputDirectory; } + + /** + * @brief setPreferredServers set the list of preferred servers + */ + void setPreferredServers(const std::map &preferredServers); + + /** + * @brief set the list of supported extensions + * @param extensions list of supported extensions + */ + void setSupportedExtensions(const QStringList &extensions); + + /** + * @brief sets whether hidden files are to be shown after all + */ + void setShowHidden(bool showHidden); + + /** + * @brief download from an already open network connection + * + * @param reply the network reply to download from + * @param fileInfo information about the file, like mod id, file id, version, ... + * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + **/ + bool addDownload(QNetworkReply *reply, const MOBase::ModRepositoryFileInfo *fileInfo); + + /** + * @brief download from an already open network connection + * + * @param reply the network reply to download from + * @param fileName the name to use for the file. This may be overridden by the name in the fileInfo-structure or if the http stream specifies a name + * @param fileInfo information previously retrieved from the nexus network + * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + **/ + bool addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, int modID, int fileID = 0, const MOBase::ModRepositoryFileInfo *fileInfo = new MOBase::ModRepositoryFileInfo()); + + /** + * @brief start a download using a nxm-link + * + * starts a download using a nxm-link. The download manager will first query the nexus + * page for file information. + * @param url a nxm link looking like this: nxm://skyrim/mods/1234/files/4711 + * @todo the game name encoded into the link is currently ignored, all downloads are incorrectly assumed to be for the identified game + **/ + void addNXMDownload(const QString &url); + + /** + * @brief retrieve the total number of downloads, both finished and unfinished including downloads from previous sessions + * + * @return total number of downloads + **/ + int numTotalDownloads() const; + + /** + * @brief retrieve number of pending downloads (nexus downloads for which we don't know the name and url yet) + * @return number of pending downloads + */ + int numPendingDownloads() const; + + /** + * @brief retrieve the info of a pending download + * @param index index of the pending download (index in the range [0, numPendingDownloads()[) + * @return pair of modid, fileid + */ + std::pair getPendingDownload(int index); + + /** + * @brief retrieve the full path to the download specified by index + * + * @param index the index to look up + * @return absolute path of the file + **/ + QString getFilePath(int index) const; + + /** + * @brief retrieve a descriptive name of the download specified by index + * + * @param index index of the file to look up + * @return display name of the file + **/ + QString getDisplayName(int index) const; + + /** + * @brief retrieve the filename of the download specified by index + * + * @param index index of the file to look up + * @return name of the file + **/ + QString getFileName(int index) const; + + /** + * @brief retrieve the file size of the download specified by index + * + * @param index index of the file to look up + * @return size of the file (total size during download) + */ + qint64 getFileSize(int index) const; + + /** + * @brief retrieve the creation time of the download specified by index + * @param index index of the file to look up + * @return size of the file (total size during download) + */ + QDateTime getFileTime(int index) const; + + /** + * @brief retrieve the current progress of the download specified by index + * + * @param index index of the file to look up + * @return progress of the download in percent (integer) + **/ + int getProgress(int index) const; + + /** + * @brief retrieve the current state of the download + * + * retrieve the current state of the download. A download usually goes through + * the following states: + * started -> downloading -> fetching mod info -> fetching file info -> done + * in case of downloads started via nxm-link, file information is fetched first + * + * @param index index of the file to look up + * @return the download state + **/ + DownloadState getState(int index) const; + + /** + * @param index index of the file to look up + * @return true if the nexus information for this download is not complete + **/ + bool isInfoIncomplete(int index) const; + + /** + * @brief retrieve the nexus mod id of the download specified by index + * + * @param index index of the file to look up + * @return the nexus mod id + **/ + int getModID(int index) const; + + /** + * @brief determine if the specified file is supposed to be hidden + * @param index index of the file to look up + * @return true if the specified file is supposed to be hidden + */ + bool isHidden(int index) const; + + /** + * @brief retrieve all nexus info of the download specified by index + * + * @param index index of the file to look up + * @return the nexus mod information + **/ + const MOBase::ModRepositoryFileInfo *getFileInfo(int index) const; + + /** + * @brief mark a download as installed + * + * @param index index of the file to mark installed + */ + void markInstalled(int index); + + /** + * @brief mark a download as uninstalled + * + * @param index index of the file to mark uninstalled + */ + void markUninstalled(int index); + + /** + * @brief refreshes the list of downloads + */ + void refreshList(); + + /** + * @brief Sort function for download servers + * @param LHS + * @param RHS + * @return + */ + static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); + + + virtual int startDownloadURLs(const QStringList &urls); + virtual int startDownloadNexusFile(int modID, int fileID); + virtual QString downloadPath(int id); + + /** + * @brief retrieve a download index from the filename + * @param fileName file to look up + * @return index of that download or -1 if it wasn't found + */ + int indexByName(const QString &fileName) const; + + void pauseAll(); + +signals: + + void aboutToUpdate(); + + /** + * @brief signals that the specified download has changed + * + * @param row the row that changed. This corresponds to the download index + **/ + void update(int row); + + /** + * @brief signals the ui that a message should be displayed + * + * @param message the message to display + **/ + void showMessage(const QString &message); + + /** + * @brief emitted whenever the state of a download changes + * @param row the row that changed + * @param state the new state + */ + void stateChanged(int row, DownloadManager::DownloadState state); + + /** + * @brief emitted whenever a download completes successfully, reporting the download speed for the server used + */ + void downloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * @brief emitted whenever a new download is added to the list + */ + void downloadAdded(); + +public slots: + + /** + * @brief removes the specified download + * + * @param index index of the download to remove + * @param deleteFile if true, the file will also be deleted from disc, otherwise it is only marked as hidden. + **/ + void removeDownload(int index, bool deleteFile); + + /** + * @brief restores the specified download to view (which was previously hidden + * @param index index of the download to restore + */ + void restoreDownload(int index); + + /** + * @brief cancel the specified download. This will lead to the corresponding file to be deleted + * + * @param index index of the download to cancel + **/ + void cancelDownload(int index); + + void pauseDownload(int index); + + void resumeDownload(int index); + + void queryInfo(int index); + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); + +private slots: + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadReadyRead(); + void downloadFinished(); + void downloadError(QNetworkReply::NetworkError error); + void metaDataChanged(); + void directoryChanged(const QString &dirctory); + +private: + + void createMetaFile(DownloadInfo *info); + QString getDownloadFileName(const QString &baseName) const; + + void startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume); + void resumeDownloadInt(int index); + + /** + * @brief start a download from a url + * + * @param url the url to download from + * @param fileInfo information previously retrieved from the mod page + * @return true if the download was started, false if it wasn't. The latter currently only happens if there is a duplicate and the user decides not to download again + **/ + bool addDownload(const QStringList &URLs, int modID, int fileID, const MOBase::ModRepositoryFileInfo *fileInfo); + + // important: the caller has to lock the list-mutex, otherwise the DownloadInfo-pointer might get invalidated at any time + DownloadInfo *findDownload(QObject *reply, int *index = NULL) const; + + void removeFile(int index, bool deleteFile); + + void refreshAlphabeticalTranslation(); + + bool ByName(int LHS, int RHS); + + QString getFileNameFromNetworkReply(QNetworkReply *reply); + + void setState(DownloadInfo *info, DownloadManager::DownloadState state); + + DownloadInfo *downloadInfoByID(unsigned int id); + + QDateTime matchDate(const QString &timeString); + + void removePending(int modID, int fileID); + + static QString getFileTypeString(int fileType); + +private: + + static const int AUTOMATIC_RETRIES = 3; + +private: + + NexusInterface *m_NexusInterface; + + QVector > m_PendingDownloads; + + QVector m_ActiveDownloads; + + QString m_OutputDirectory; + std::map m_PreferredServers; + QStringList m_SupportedExtensions; + std::set m_RequestIDs; + QVector m_AlphabeticalTranslation; + + QFileSystemWatcher m_DirWatcher; + + std::map m_DownloadFails; + + bool m_ShowHidden; + + QRegExp m_DateExpression; + +}; + + + +#endif // DOWNLOADMANAGER_H diff --git a/src/dummybsa.cpp b/src/dummybsa.cpp index 3357dea6..df757139 100644 --- a/src/dummybsa.cpp +++ b/src/dummybsa.cpp @@ -1,244 +1,244 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "dummybsa.h" -#include -#include -#define WIN32_LEAN_AND_MEAN -#include - - -static void writeUlong(unsigned char* buffer, int offset, unsigned long value) -{ - union { - unsigned long ulValue; - unsigned char cValue[4]; - }; - ulValue = value; - memcpy(buffer + offset, cValue, 4); -} - -static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value) -{ - union { - unsigned long long ullValue; - unsigned char cValue[8]; - }; - ullValue = value; - memcpy(buffer + offset, cValue, 8); -} -/* -static unsigned long long genHashInt(const char* pos, const char* end) -{ - unsigned long long hash2 = 0; - for (; pos < end; ++pos) { - hash2 *= 0x1003f; - hash2 += *pos; - } - return hash2; -} - - -static unsigned long long genHash(const char* fileName) -{ - char fileNameLower[MAX_PATH + 1]; - int i = 0; - for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { - fileNameLower[i] = tolower(fileName[i]); - } - fileNameLower[i] = '\0'; - - char* ext = strrchr(fileNameLower, '.'); - if (ext == NULL) { - ext = fileNameLower + strlen(fileNameLower); - } - - int length = ext - fileNameLower; - - unsigned long long hash = 0ULL; - - if (length > 0) { - hash = *(ext - 1) | - ((length > 2 ? *(ext - 2) : 0) << 8) | - (length << 16) | - (fileNameLower[0] << 24); - } - - if (strcmp(ext + 1, "dds") == 0) { - hash |= 0x8080; - } // the rest is not supported currently - - hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32; - hash |= genHashInt(ext, ext + strlen(ext)) << 0x32; - - return hash; -}*/ - - -static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end) -{ - unsigned long hash = 0; - for (; pos < end; ++pos) { - hash *= 0x1003f; - hash += *pos; - } - return hash; -} - - -static unsigned long long genHash(const char* fileName) -{ - char fileNameLower[MAX_PATH + 1]; - int i = 0; - for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { - fileNameLower[i] = static_cast(tolower(fileName[i])); - if (fileNameLower[i] == '\\') { - fileNameLower[i] = '/'; - } - } - fileNameLower[i] = '\0'; - - unsigned char *fileNameLowerU = reinterpret_cast(fileNameLower); - - char* ext = strrchr(fileNameLower, '.'); - if (ext == NULL) { - ext = fileNameLower + strlen(fileNameLower); - } - unsigned char *extU = reinterpret_cast(ext); - - int length = ext - fileNameLower; - - unsigned long long hash = 0ULL; - - if (length > 0) { - hash = *(extU - 1) | - ((length > 2 ? *(ext - 2) : 0) << 8) | - (length << 16) | - (fileNameLowerU[0] << 24); - } - - if (strlen(ext) > 0) { - if (strcmp(ext + 1, "kf") == 0) { - hash |= 0x80; - } else if (strcmp(ext + 1, "nif") == 0) { - hash |= 0x8000; - } else if (strcmp(ext + 1, "dds") == 0) { - hash |= 0x8080; - } else if (strcmp(ext + 1, "wav") == 0) { - hash |= 0x80000000; - } - - unsigned long long temp = static_cast(genHashInt( - fileNameLowerU + 1, extU - 2)); - temp += static_cast(genHashInt( - extU, extU + strlen(ext))); - - hash |= (temp & 0xFFFFFFFF) << 32; - } - return hash; -} - - -DummyBSA::DummyBSA() - : m_Version(MOShared::GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"), - m_TotalFileNameLength(0) -{ -} - - -void DummyBSA::writeHeader(QFile& file) -{ - unsigned char header[] = { - 'B', 'S', 'A', '\0', // magic string - 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later - 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static - 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later - 0x01, 0x00, 0x00, 0x00, // folder count - 0x01, 0x00, 0x00, 0x00, // file count - 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later - 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later - 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later - }; - - writeUlong(header, 4, MOShared::GameInfo::instance().getBSAVersion()); - writeUlong(header, 12, 0x01 | 0x02); // has directories and has files. - writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name - writeUlong(header, 28, m_TotalFileNameLength); // single character file name - - writeUlong(header, 32, 2); // has dds - - file.write(reinterpret_cast(header), sizeof(header)); -} - - -void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName) -{ - unsigned char folderRecord[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash - 0x01, 0x00, 0x00, 0x00, // file count - 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name - }; - // we'd usually have to sort folders be the hash value generated here - writeUlonglong(folderRecord, 0, genHash(folderName.c_str())); - writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly - - file.write(reinterpret_cast(folderRecord), sizeof(folderRecord)); -} - - -void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName) -{ - unsigned char fileRecord[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash - 0xDE, 0xAD, 0xBE, 0xEF, // size - 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data - }; - - // we'd usually have to sort files by the value generated here - writeUlonglong(fileRecord, 0, genHash(fileName.c_str())); - writeUlong( fileRecord, 8, 0); - writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size - - file.write(reinterpret_cast(fileRecord), sizeof(fileRecord)); -} - - -void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName) -{ - file.write(folderName.c_str(), folderName.length() + 1); - - writeFileRecord(file, m_FileName); -} - - -void DummyBSA::write(const QString& fileName) -{ - QFile file(fileName); - file.open(QIODevice::WriteOnly); - - m_TotalFileNameLength = m_FileName.length() + 1; - - writeHeader(file); - writeFolderRecord(file, m_FolderName); - writeFileRecordBlocks(file, m_FolderName); - file.write(m_FileName.c_str() , m_FileName.length() + 1); - char fileSize[] = { 0x00, 0x00, 0x00, 0x00 }; - file.write(fileSize, sizeof(fileSize)); - file.close(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "dummybsa.h" +#include +#include +#define WIN32_LEAN_AND_MEAN +#include + + +static void writeUlong(unsigned char* buffer, int offset, unsigned long value) +{ + union { + unsigned long ulValue; + unsigned char cValue[4]; + }; + ulValue = value; + memcpy(buffer + offset, cValue, 4); +} + +static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value) +{ + union { + unsigned long long ullValue; + unsigned char cValue[8]; + }; + ullValue = value; + memcpy(buffer + offset, cValue, 8); +} +/* +static unsigned long long genHashInt(const char* pos, const char* end) +{ + unsigned long long hash2 = 0; + for (; pos < end; ++pos) { + hash2 *= 0x1003f; + hash2 += *pos; + } + return hash2; +} + + +static unsigned long long genHash(const char* fileName) +{ + char fileNameLower[MAX_PATH + 1]; + int i = 0; + for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { + fileNameLower[i] = tolower(fileName[i]); + } + fileNameLower[i] = '\0'; + + char* ext = strrchr(fileNameLower, '.'); + if (ext == NULL) { + ext = fileNameLower + strlen(fileNameLower); + } + + int length = ext - fileNameLower; + + unsigned long long hash = 0ULL; + + if (length > 0) { + hash = *(ext - 1) | + ((length > 2 ? *(ext - 2) : 0) << 8) | + (length << 16) | + (fileNameLower[0] << 24); + } + + if (strcmp(ext + 1, "dds") == 0) { + hash |= 0x8080; + } // the rest is not supported currently + + hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32; + hash |= genHashInt(ext, ext + strlen(ext)) << 0x32; + + return hash; +}*/ + + +static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end) +{ + unsigned long hash = 0; + for (; pos < end; ++pos) { + hash *= 0x1003f; + hash += *pos; + } + return hash; +} + + +static unsigned long long genHash(const char* fileName) +{ + char fileNameLower[MAX_PATH + 1]; + int i = 0; + for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { + fileNameLower[i] = static_cast(tolower(fileName[i])); + if (fileNameLower[i] == '\\') { + fileNameLower[i] = '/'; + } + } + fileNameLower[i] = '\0'; + + unsigned char *fileNameLowerU = reinterpret_cast(fileNameLower); + + char* ext = strrchr(fileNameLower, '.'); + if (ext == NULL) { + ext = fileNameLower + strlen(fileNameLower); + } + unsigned char *extU = reinterpret_cast(ext); + + int length = ext - fileNameLower; + + unsigned long long hash = 0ULL; + + if (length > 0) { + hash = *(extU - 1) | + ((length > 2 ? *(ext - 2) : 0) << 8) | + (length << 16) | + (fileNameLowerU[0] << 24); + } + + if (strlen(ext) > 0) { + if (strcmp(ext + 1, "kf") == 0) { + hash |= 0x80; + } else if (strcmp(ext + 1, "nif") == 0) { + hash |= 0x8000; + } else if (strcmp(ext + 1, "dds") == 0) { + hash |= 0x8080; + } else if (strcmp(ext + 1, "wav") == 0) { + hash |= 0x80000000; + } + + unsigned long long temp = static_cast(genHashInt( + fileNameLowerU + 1, extU - 2)); + temp += static_cast(genHashInt( + extU, extU + strlen(ext))); + + hash |= (temp & 0xFFFFFFFF) << 32; + } + return hash; +} + + +DummyBSA::DummyBSA() + : m_Version(MOShared::GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"), + m_TotalFileNameLength(0) +{ +} + + +void DummyBSA::writeHeader(QFile& file) +{ + unsigned char header[] = { + 'B', 'S', 'A', '\0', // magic string + 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later + 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static + 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later + 0x01, 0x00, 0x00, 0x00, // folder count + 0x01, 0x00, 0x00, 0x00, // file count + 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later + 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later + 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later + }; + + writeUlong(header, 4, MOShared::GameInfo::instance().getBSAVersion()); + writeUlong(header, 12, 0x01 | 0x02); // has directories and has files. + writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name + writeUlong(header, 28, m_TotalFileNameLength); // single character file name + + writeUlong(header, 32, 2); // has dds + + file.write(reinterpret_cast(header), sizeof(header)); +} + + +void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName) +{ + unsigned char folderRecord[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash + 0x01, 0x00, 0x00, 0x00, // file count + 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name + }; + // we'd usually have to sort folders be the hash value generated here + writeUlonglong(folderRecord, 0, genHash(folderName.c_str())); + writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly + + file.write(reinterpret_cast(folderRecord), sizeof(folderRecord)); +} + + +void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName) +{ + unsigned char fileRecord[] = { + 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash + 0xDE, 0xAD, 0xBE, 0xEF, // size + 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data + }; + + // we'd usually have to sort files by the value generated here + writeUlonglong(fileRecord, 0, genHash(fileName.c_str())); + writeUlong( fileRecord, 8, 0); + writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size + + file.write(reinterpret_cast(fileRecord), sizeof(fileRecord)); +} + + +void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName) +{ + file.write(folderName.c_str(), folderName.length() + 1); + + writeFileRecord(file, m_FileName); +} + + +void DummyBSA::write(const QString& fileName) +{ + QFile file(fileName); + file.open(QIODevice::WriteOnly); + + m_TotalFileNameLength = m_FileName.length() + 1; + + writeHeader(file); + writeFolderRecord(file, m_FolderName); + writeFileRecordBlocks(file, m_FolderName); + file.write(m_FileName.c_str() , m_FileName.length() + 1); + char fileSize[] = { 0x00, 0x00, 0x00, 0x00 }; + file.write(fileSize, sizeof(fileSize)); + file.close(); +} diff --git a/src/dummybsa.h b/src/dummybsa.h index 6fcea82b..0d231393 100644 --- a/src/dummybsa.h +++ b/src/dummybsa.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef DUMMYBSA_H #define DUMMYBSA_H diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 13223b68..02abf30e 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -1,289 +1,289 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "editexecutablesdialog.h" -#include "ui_editexecutablesdialog.h" -#include "filedialogmemory.h" -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - -EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) - : TutorableDialog("EditExecutables", parent), - ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList) -{ - ui->setupUi(this); - - refreshExecutablesWidget(); -} - -EditExecutablesDialog::~EditExecutablesDialog() -{ - delete ui; -} - -ExecutablesList EditExecutablesDialog::getExecutablesList() const -{ - ExecutablesList newList; - for (int i = 0; i < ui->executablesListBox->count(); ++i) { - newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value()); - } - return newList; -} - -void EditExecutablesDialog::refreshExecutablesWidget() -{ - QListWidget *executablesWidget = findChild("executablesListBox"); - - executablesWidget->clear(); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - - for(; current != end; ++current) { - QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); - QVariant temp; - temp.setValue(*current); - newItem->setData(Qt::UserRole, temp); - newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); - executablesWidget->addItem(newItem); - } - - ui->addButton->setEnabled(false); - ui->removeButton->setEnabled(false); -} - - -void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1) -{ - QFileInfo fileInfo(arg1); - ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); -} - -void EditExecutablesDialog::resetInput() -{ - ui->binaryEdit->setText(""); - ui->titleEdit->setText(""); - ui->workingDirEdit->clear(); - ui->argumentsEdit->setText(""); - ui->appIDOverwriteEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->closeCheckBox->setChecked(false); - m_CurrentItem = NULL; -} - - -void EditExecutablesDialog::saveExecutable() -{ - m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), - (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, - ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", - true, false); -} - - -void EditExecutablesDialog::delayedRefresh() -{ - int index = ui->executablesListBox->currentIndex().row(); - resetInput(); - refreshExecutablesWidget(); - ui->executablesListBox->setCurrentRow(index); -} - - -void EditExecutablesDialog::on_addButton_clicked() -{ - saveExecutable(); - - resetInput(); - refreshExecutablesWidget(); -} - -void EditExecutablesDialog::on_browseButton_clicked() -{ - QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, - tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); - - if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { - QString binaryPath; - { // try to find java automatically - std::wstring binaryNameW = ToWString(binaryName); - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(binaryNameW.c_str(), NULL, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty()) { - 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()) { - QMessageBox::information(this, tr("Java (32-bit) required"), - tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " - "from that installation as the binary.")); - } else { - ui->binaryEdit->setText(binaryPath); - } - - ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); - ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); - } else { - ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); - } -} - -void EditExecutablesDialog::on_browseDirButton_clicked() -{ - QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, - tr("Select a directory")); - - ui->workingDirEdit->setText(dirName); -} - -void EditExecutablesDialog::on_removeButton_clicked() -{ -// QLineEdit *binaryEdit = findChild("binaryEdit"); - - if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ExecutablesList.remove(ui->titleEdit->text()); - } - - resetInput(); - refreshExecutablesWidget(); -} - -void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) -{ - QPushButton *addButton = findChild("addButton"); - QPushButton *removeButton = findChild("removeButton"); - - QListWidget *executablesWidget = findChild("executablesListBox"); - - QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); - - addButton->setEnabled(arg1.length() != 0); - - if (existingItems.count() == 0) { - addButton->setText(tr("Add")); - removeButton->setEnabled(false); - } else { - // existing item. is it a custom one? - addButton->setText(tr("Modify")); - removeButton->setEnabled(true); - } -} - - -bool EditExecutablesDialog::executableChanged() -{ - if (m_CurrentItem != NULL) { - const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value(); - - return selectedExecutable.m_Arguments != ui->argumentsEdit->text() - || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() - || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) - || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); - } else { - return false; - } -} - - -void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - if (current == NULL) { - resetInput(); - return; - } - - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - // this invalidates the item passed as a a parameter - saveExecutable(); - - QTimer::singleShot(50, this, SLOT(delayedRefresh())); - return; - } - } - - m_CurrentItem = current; - - const Executable &selectedExecutable = current->data(Qt::UserRole).value(); - - ui->titleEdit->setText(selectedExecutable.m_Title); - ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); - ui->argumentsEdit->setText(selectedExecutable.m_Arguments); - ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); - ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); - if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { - ui->closeCheckBox->setEnabled(false); - ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); - } else { - ui->closeCheckBox->setEnabled(true); - ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); - } - ui->removeButton->setEnabled(selectedExecutable.m_Custom); - ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0); - if (selectedExecutable.m_SteamAppID != 0) { - ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); - } else { - ui->appIDOverwriteEdit->clear(); - } -} - - -void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) -{ - ui->appIDOverwriteEdit->setEnabled(checked); -} - -void EditExecutablesDialog::on_closeButton_clicked() -{ - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - saveExecutable(); - } - } - this->accept(); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "editexecutablesdialog.h" +#include "ui_editexecutablesdialog.h" +#include "filedialogmemory.h" +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + +EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) + : TutorableDialog("EditExecutables", parent), + ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList) +{ + ui->setupUi(this); + + refreshExecutablesWidget(); +} + +EditExecutablesDialog::~EditExecutablesDialog() +{ + delete ui; +} + +ExecutablesList EditExecutablesDialog::getExecutablesList() const +{ + ExecutablesList newList; + for (int i = 0; i < ui->executablesListBox->count(); ++i) { + newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value()); + } + return newList; +} + +void EditExecutablesDialog::refreshExecutablesWidget() +{ + QListWidget *executablesWidget = findChild("executablesListBox"); + + executablesWidget->clear(); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + + for(; current != end; ++current) { + QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); + QVariant temp; + temp.setValue(*current); + newItem->setData(Qt::UserRole, temp); + newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); + executablesWidget->addItem(newItem); + } + + ui->addButton->setEnabled(false); + ui->removeButton->setEnabled(false); +} + + +void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1) +{ + QFileInfo fileInfo(arg1); + ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); +} + +void EditExecutablesDialog::resetInput() +{ + ui->binaryEdit->setText(""); + ui->titleEdit->setText(""); + ui->workingDirEdit->clear(); + ui->argumentsEdit->setText(""); + ui->appIDOverwriteEdit->clear(); + ui->overwriteAppIDBox->setChecked(false); + ui->closeCheckBox->setChecked(false); + m_CurrentItem = NULL; +} + + +void EditExecutablesDialog::saveExecutable() +{ + m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), + ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), + (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, + ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", + true, false); +} + + +void EditExecutablesDialog::delayedRefresh() +{ + int index = ui->executablesListBox->currentIndex().row(); + resetInput(); + refreshExecutablesWidget(); + ui->executablesListBox->setCurrentRow(index); +} + + +void EditExecutablesDialog::on_addButton_clicked() +{ + saveExecutable(); + + resetInput(); + refreshExecutablesWidget(); +} + +void EditExecutablesDialog::on_browseButton_clicked() +{ + QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, + tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); + + if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + QString binaryPath; + { // try to find java automatically + std::wstring binaryNameW = ToWString(binaryName); + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(binaryNameW.c_str(), NULL, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { + qDebug("failed to determine binary type: %lu", ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty()) { + 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()) { + QMessageBox::information(this, tr("Java (32-bit) required"), + tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " + "from that installation as the binary.")); + } else { + ui->binaryEdit->setText(binaryPath); + } + + ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); + ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + } else { + ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); + } +} + +void EditExecutablesDialog::on_browseDirButton_clicked() +{ + QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, + tr("Select a directory")); + + ui->workingDirEdit->setText(dirName); +} + +void EditExecutablesDialog::on_removeButton_clicked() +{ +// QLineEdit *binaryEdit = findChild("binaryEdit"); + + if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ExecutablesList.remove(ui->titleEdit->text()); + } + + resetInput(); + refreshExecutablesWidget(); +} + +void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) +{ + QPushButton *addButton = findChild("addButton"); + QPushButton *removeButton = findChild("removeButton"); + + QListWidget *executablesWidget = findChild("executablesListBox"); + + QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); + + addButton->setEnabled(arg1.length() != 0); + + if (existingItems.count() == 0) { + addButton->setText(tr("Add")); + removeButton->setEnabled(false); + } else { + // existing item. is it a custom one? + addButton->setText(tr("Modify")); + removeButton->setEnabled(true); + } +} + + +bool EditExecutablesDialog::executableChanged() +{ + if (m_CurrentItem != NULL) { + const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value(); + + return selectedExecutable.m_Arguments != ui->argumentsEdit->text() + || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() + || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) + || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) + || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); + } else { + return false; + } +} + + +void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) +{ + if (current == NULL) { + resetInput(); + return; + } + + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return; + } else if (res == QMessageBox::Yes) { + // this invalidates the item passed as a a parameter + saveExecutable(); + + QTimer::singleShot(50, this, SLOT(delayedRefresh())); + return; + } + } + + m_CurrentItem = current; + + const Executable &selectedExecutable = current->data(Qt::UserRole).value(); + + ui->titleEdit->setText(selectedExecutable.m_Title); + ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); + ui->argumentsEdit->setText(selectedExecutable.m_Arguments); + ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); + ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); + if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { + ui->closeCheckBox->setEnabled(false); + ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); + } else { + ui->closeCheckBox->setEnabled(true); + ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); + } + ui->removeButton->setEnabled(selectedExecutable.m_Custom); + ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0); + if (selectedExecutable.m_SteamAppID != 0) { + ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); + } else { + ui->appIDOverwriteEdit->clear(); + } +} + + +void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) +{ + ui->appIDOverwriteEdit->setEnabled(checked); +} + +void EditExecutablesDialog::on_closeButton_clicked() +{ + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return; + } else if (res == QMessageBox::Yes) { + saveExecutable(); + } + } + this->accept(); +} + diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index a90cbc59..d27ccb0a 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -1,98 +1,98 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef EDITEXECUTABLESDIALOG_H -#define EDITEXECUTABLESDIALOG_H - -#include "tutorabledialog.h" -#include -#include -#include "executableslist.h" - -namespace Ui { - class EditExecutablesDialog; -} - -/** - * @brief Dialog to manage the list of executables - **/ -class EditExecutablesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param executablesList current list of executables - * @param parent parent widget - **/ - explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0); - - ~EditExecutablesDialog(); - - /** - * @brief retrieve the updated list of executables - * - * @return updated list of executables - **/ - ExecutablesList getExecutablesList() const; - - void saveExecutable(); -private slots: - - void on_binaryEdit_textChanged(const QString &arg1); - - void on_addButton_clicked(); - - void on_browseButton_clicked(); - - void on_removeButton_clicked(); - - void on_titleEdit_textChanged(const QString &arg1); - - void on_overwriteAppIDBox_toggled(bool checked); - - void on_browseDirButton_clicked(); - - void on_closeButton_clicked(); - - void on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void delayedRefresh(); - -private: - - void resetInput(); - - void refreshExecutablesWidget(); - - bool executableChanged(); - -private: - Ui::EditExecutablesDialog *ui; - - QListWidgetItem *m_CurrentItem; - - ExecutablesList m_ExecutablesList; - -}; - -#endif // EDITEXECUTABLESDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef EDITEXECUTABLESDIALOG_H +#define EDITEXECUTABLESDIALOG_H + +#include "tutorabledialog.h" +#include +#include +#include "executableslist.h" + +namespace Ui { + class EditExecutablesDialog; +} + +/** + * @brief Dialog to manage the list of executables + **/ +class EditExecutablesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param executablesList current list of executables + * @param parent parent widget + **/ + explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0); + + ~EditExecutablesDialog(); + + /** + * @brief retrieve the updated list of executables + * + * @return updated list of executables + **/ + ExecutablesList getExecutablesList() const; + + void saveExecutable(); +private slots: + + void on_binaryEdit_textChanged(const QString &arg1); + + void on_addButton_clicked(); + + void on_browseButton_clicked(); + + void on_removeButton_clicked(); + + void on_titleEdit_textChanged(const QString &arg1); + + void on_overwriteAppIDBox_toggled(bool checked); + + void on_browseDirButton_clicked(); + + void on_closeButton_clicked(); + + void on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + + void delayedRefresh(); + +private: + + void resetInput(); + + void refreshExecutablesWidget(); + + bool executableChanged(); + +private: + Ui::EditExecutablesDialog *ui; + + QListWidgetItem *m_CurrentItem; + + ExecutablesList m_ExecutablesList; + +}; + +#endif // EDITEXECUTABLESDIALOG_H diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 57341323..2c6754d7 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -1,203 +1,203 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "executableslist.h" -#include -#include -#include -#include "utility.h" -#include - - -using namespace MOBase; -using namespace MOShared; - - -QDataStream &operator<<(QDataStream &out, const Executable &obj) -{ - out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO - << obj.m_SteamAppID << obj.m_WorkingDirectory << obj.m_Custom << obj.m_Toolbar; - return out; -} - -QDataStream &operator>>(QDataStream &in, Executable &obj) -{ - QString binaryTemp; - int closeStyleTemp; - in >> obj.m_Title >> binaryTemp >> obj.m_Arguments >> closeStyleTemp - >> obj.m_SteamAppID >> obj.m_WorkingDirectory >> obj.m_Custom >> obj.m_Toolbar; - - obj.m_CloseMO = (CloseMOStyle)closeStyleTemp; - obj.m_BinaryInfo.setFile(binaryTemp); - return in; -} - - -void registerExecutable() -{ - qRegisterMetaType("Executable"); - qRegisterMetaTypeStreamOperators("Executable"); -} - - -ExecutablesList::ExecutablesList() -{ -} - -ExecutablesList::~ExecutablesList() -{ -} - -void ExecutablesList::init() -{ - std::vector executables = GameInfo::instance().getExecutables(); - for (std::vector::const_iterator iter = executables.begin(); iter != executables.end(); ++iter) { - addExecutableInternal(ToQString(iter->title), - QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())).append("/").append(ToQString(iter->binary)), - ToQString(iter->arguments), ToQString(iter->workingDirectory), - iter->closeMO, ToQString(iter->steamAppID)); - } -} - - -void ExecutablesList::getExecutables(std::vector::iterator &begin, std::vector::iterator &end) -{ - begin = m_Executables.begin(); - end = m_Executables.end(); -} - - -void ExecutablesList::getExecutables(std::vector::const_iterator &begin, - std::vector::const_iterator &end) const -{ - begin = m_Executables.begin(); - end = m_Executables.end(); -} - - -const Executable &ExecutablesList::find(const QString &title) const -{ - for (std::vector::const_iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { - return *iter; - } - } - throw std::runtime_error("invalid name"); -} - - -Executable &ExecutablesList::find(const QString &title) -{ - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (QString::compare(iter->m_Title, title, Qt::CaseInsensitive) == 0) { - return *iter; - } - } - throw std::runtime_error("invalid name"); -} - - -Executable *ExecutablesList::findExe(const QString &title) -{ - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { - return &*iter; - } - } - return NULL; -} - - -bool ExecutablesList::titleExists(const QString &title) const -{ - auto test = [&] (const Executable &exe) { return exe.m_Title == title; }; - return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); -} - - -void ExecutablesList::addExecutable(const Executable &executable) -{ - Executable *existingExe = findExe(executable.m_Title); - if (existingExe != NULL) { - *existingExe = executable; - } else { - m_Executables.push_back(executable); - } -} - - -void ExecutablesList::addExecutable(const QString &title, const QString &executableName, const QString &arguments, - const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID, - bool custom, bool toolbar) -{ - QFileInfo file(executableName); - Executable *existingExe = findExe(title); - if (existingExe != NULL) { - existingExe->m_Title = title; - existingExe->m_CloseMO = closeMO; - existingExe->m_BinaryInfo = file; - existingExe->m_Arguments = arguments; - existingExe->m_WorkingDirectory = workingDirectory; - existingExe->m_SteamAppID = steamAppID; - existingExe->m_Custom = custom; - existingExe->m_Toolbar = toolbar; - } else { - Executable newExe; - newExe.m_Title = title; - newExe.m_CloseMO = closeMO; - newExe.m_BinaryInfo = file; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Custom = true; - newExe.m_Toolbar = toolbar; - m_Executables.push_back(newExe); - } -} - - -void ExecutablesList::remove(const QString &title) -{ - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Custom && (iter->m_Title == title)) { - m_Executables.erase(iter); - break; - } - } -} - - -void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, - const QString &arguments, const QString &workingDirectory, - CloseMOStyle closeMO, const QString &steamAppID) -{ - QFileInfo file(executableName); - if (file.exists()) { - Executable newExe; - newExe.m_CloseMO = closeMO; - newExe.m_BinaryInfo = file; - newExe.m_Title = title; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Custom = false; - newExe.m_Toolbar = false; - m_Executables.push_back(newExe); - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "executableslist.h" +#include +#include +#include +#include "utility.h" +#include + + +using namespace MOBase; +using namespace MOShared; + + +QDataStream &operator<<(QDataStream &out, const Executable &obj) +{ + out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO + << obj.m_SteamAppID << obj.m_WorkingDirectory << obj.m_Custom << obj.m_Toolbar; + return out; +} + +QDataStream &operator>>(QDataStream &in, Executable &obj) +{ + QString binaryTemp; + int closeStyleTemp; + in >> obj.m_Title >> binaryTemp >> obj.m_Arguments >> closeStyleTemp + >> obj.m_SteamAppID >> obj.m_WorkingDirectory >> obj.m_Custom >> obj.m_Toolbar; + + obj.m_CloseMO = (CloseMOStyle)closeStyleTemp; + obj.m_BinaryInfo.setFile(binaryTemp); + return in; +} + + +void registerExecutable() +{ + qRegisterMetaType("Executable"); + qRegisterMetaTypeStreamOperators("Executable"); +} + + +ExecutablesList::ExecutablesList() +{ +} + +ExecutablesList::~ExecutablesList() +{ +} + +void ExecutablesList::init() +{ + std::vector executables = GameInfo::instance().getExecutables(); + for (std::vector::const_iterator iter = executables.begin(); iter != executables.end(); ++iter) { + addExecutableInternal(ToQString(iter->title), + QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())).append("/").append(ToQString(iter->binary)), + ToQString(iter->arguments), ToQString(iter->workingDirectory), + iter->closeMO, ToQString(iter->steamAppID)); + } +} + + +void ExecutablesList::getExecutables(std::vector::iterator &begin, std::vector::iterator &end) +{ + begin = m_Executables.begin(); + end = m_Executables.end(); +} + + +void ExecutablesList::getExecutables(std::vector::const_iterator &begin, + std::vector::const_iterator &end) const +{ + begin = m_Executables.begin(); + end = m_Executables.end(); +} + + +const Executable &ExecutablesList::find(const QString &title) const +{ + for (std::vector::const_iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (iter->m_Title == title) { + return *iter; + } + } + throw std::runtime_error("invalid name"); +} + + +Executable &ExecutablesList::find(const QString &title) +{ + for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (QString::compare(iter->m_Title, title, Qt::CaseInsensitive) == 0) { + return *iter; + } + } + throw std::runtime_error("invalid name"); +} + + +Executable *ExecutablesList::findExe(const QString &title) +{ + for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (iter->m_Title == title) { + return &*iter; + } + } + return NULL; +} + + +bool ExecutablesList::titleExists(const QString &title) const +{ + auto test = [&] (const Executable &exe) { return exe.m_Title == title; }; + return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); +} + + +void ExecutablesList::addExecutable(const Executable &executable) +{ + Executable *existingExe = findExe(executable.m_Title); + if (existingExe != NULL) { + *existingExe = executable; + } else { + m_Executables.push_back(executable); + } +} + + +void ExecutablesList::addExecutable(const QString &title, const QString &executableName, const QString &arguments, + const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID, + bool custom, bool toolbar) +{ + QFileInfo file(executableName); + Executable *existingExe = findExe(title); + if (existingExe != NULL) { + existingExe->m_Title = title; + existingExe->m_CloseMO = closeMO; + existingExe->m_BinaryInfo = file; + existingExe->m_Arguments = arguments; + existingExe->m_WorkingDirectory = workingDirectory; + existingExe->m_SteamAppID = steamAppID; + existingExe->m_Custom = custom; + existingExe->m_Toolbar = toolbar; + } else { + Executable newExe; + newExe.m_Title = title; + newExe.m_CloseMO = closeMO; + newExe.m_BinaryInfo = file; + newExe.m_Arguments = arguments; + newExe.m_WorkingDirectory = workingDirectory; + newExe.m_SteamAppID = steamAppID; + newExe.m_Custom = true; + newExe.m_Toolbar = toolbar; + m_Executables.push_back(newExe); + } +} + + +void ExecutablesList::remove(const QString &title) +{ + for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (iter->m_Custom && (iter->m_Title == title)) { + m_Executables.erase(iter); + break; + } + } +} + + +void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, + const QString &arguments, const QString &workingDirectory, + CloseMOStyle closeMO, const QString &steamAppID) +{ + QFileInfo file(executableName); + if (file.exists()) { + Executable newExe; + newExe.m_CloseMO = closeMO; + newExe.m_BinaryInfo = file; + newExe.m_Title = title; + newExe.m_Arguments = arguments; + newExe.m_WorkingDirectory = workingDirectory; + newExe.m_SteamAppID = steamAppID; + newExe.m_Custom = false; + newExe.m_Toolbar = false; + m_Executables.push_back(newExe); + } +} diff --git a/src/executableslist.h b/src/executableslist.h index 4c9e662a..6f7771e5 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -1,155 +1,155 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef EXECUTABLESLIST_H -#define EXECUTABLESLIST_H - - -#include -#include -#include -#include - - -/*! - * @brief Information about an executable - **/ -struct Executable { - QString m_Title; - QFileInfo m_BinaryInfo; - QString m_Arguments; - MOShared::CloseMOStyle m_CloseMO; - QString m_SteamAppID; - QString m_WorkingDirectory; - - bool m_Custom; - bool m_Toolbar; -}; -Q_DECLARE_METATYPE(Executable) - - -void registerExecutable(); - - -/*! - * @brief List of executables configured to by started from MO - **/ -class ExecutablesList { -public: - - /** - * @brief constructor - * - **/ - ExecutablesList(); - - ~ExecutablesList(); - - /** - * @brief initialise the list with the executables preconfigured for this game - **/ - void init(); - - /** - * @brief retrieve an executable by index - * - * @param index index of the executable to look up - * @return reference to the executable - * @exception out_of_range will throw an exception if the index is invalid - **/ - const Executable &get(int index) const { return m_Executables.at(index); } - - /** - * @brief find an executable by its name - * - * @param title the title of the executable to look up - * @return the executable - * @exception runtime_error will throw an exception if the name is not correct - **/ - const Executable &find(const QString &tilte) const; - - /** - * @brief find an executable by its name - * - * @param title the title of the executable to look up - * @return the executable - * @exception runtime_error will throw an exception if the name is not correct - **/ - Executable &find(const QString &tilte); - - /** - * @brief determine if an executable exists - * @param title the title of the executable to look up - * @return true if the executable exists, false otherwise - **/ - bool titleExists(const QString &title) const; - - /** - * @brief add a new executable to the list - * @param executable - */ - void addExecutable(const Executable &executable); - - /** - * @brief add a new executable to the list - * - * @param title name displayed in the UI - * @param executableName the actual filename to execute - * @param arguments arguments to pass to the executable - * @param closeMO if true, MO will be closed when the binary is started - **/ - void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); - - /** - * @brief remove the executable with the specified file name. This needs to be an absolute file path - * - * @param title title of the executable to remove - * @note if the executable name is invalid, nothing happens. There is no way to determine if this was successful - **/ - void remove(const QString &title); - - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::const_iterator &begin, std::vector::const_iterator &end) const; - - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::iterator &begin, std::vector::iterator &end); - -private: - - Executable *findExe(const QString &title); - - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID); - -private: - - std::vector m_Executables; - -}; - -#endif // EXECUTABLESLIST_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef EXECUTABLESLIST_H +#define EXECUTABLESLIST_H + + +#include +#include +#include +#include + + +/*! + * @brief Information about an executable + **/ +struct Executable { + QString m_Title; + QFileInfo m_BinaryInfo; + QString m_Arguments; + MOShared::CloseMOStyle m_CloseMO; + QString m_SteamAppID; + QString m_WorkingDirectory; + + bool m_Custom; + bool m_Toolbar; +}; +Q_DECLARE_METATYPE(Executable) + + +void registerExecutable(); + + +/*! + * @brief List of executables configured to by started from MO + **/ +class ExecutablesList { +public: + + /** + * @brief constructor + * + **/ + ExecutablesList(); + + ~ExecutablesList(); + + /** + * @brief initialise the list with the executables preconfigured for this game + **/ + void init(); + + /** + * @brief retrieve an executable by index + * + * @param index index of the executable to look up + * @return reference to the executable + * @exception out_of_range will throw an exception if the index is invalid + **/ + const Executable &get(int index) const { return m_Executables.at(index); } + + /** + * @brief find an executable by its name + * + * @param title the title of the executable to look up + * @return the executable + * @exception runtime_error will throw an exception if the name is not correct + **/ + const Executable &find(const QString &tilte) const; + + /** + * @brief find an executable by its name + * + * @param title the title of the executable to look up + * @return the executable + * @exception runtime_error will throw an exception if the name is not correct + **/ + Executable &find(const QString &tilte); + + /** + * @brief determine if an executable exists + * @param title the title of the executable to look up + * @return true if the executable exists, false otherwise + **/ + bool titleExists(const QString &title) const; + + /** + * @brief add a new executable to the list + * @param executable + */ + void addExecutable(const Executable &executable); + + /** + * @brief add a new executable to the list + * + * @param title name displayed in the UI + * @param executableName the actual filename to execute + * @param arguments arguments to pass to the executable + * @param closeMO if true, MO will be closed when the binary is started + **/ + void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); + + /** + * @brief remove the executable with the specified file name. This needs to be an absolute file path + * + * @param title title of the executable to remove + * @note if the executable name is invalid, nothing happens. There is no way to determine if this was successful + **/ + void remove(const QString &title); + + /** + * @brief retrieve begin and end iterators of the configured executables + * + * @param begin iterator to the first executable + * @param end iterator one past the last executable + **/ + void getExecutables(std::vector::const_iterator &begin, std::vector::const_iterator &end) const; + + /** + * @brief retrieve begin and end iterators of the configured executables + * + * @param begin iterator to the first executable + * @param end iterator one past the last executable + **/ + void getExecutables(std::vector::iterator &begin, std::vector::iterator &end); + +private: + + Executable *findExe(const QString &title); + + void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID); + +private: + + std::vector m_Executables; + +}; + +#endif // EXECUTABLESLIST_H diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index f614e76d..6b440d0f 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -1,94 +1,94 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "filedialogmemory.h" -#include - - -FileDialogMemory::FileDialogMemory() -{ -} - - -void FileDialogMemory::save(QSettings &settings) -{ - settings.remove("recentDirectories"); - settings.beginWriteArray("recentDirectories"); - int index = 0; - for (std::map::const_iterator iter = instance().m_Cache.begin(); - iter != instance().m_Cache.end(); ++iter) { - settings.setArrayIndex(index++); - settings.setValue("name", iter->first); - settings.setValue("directory", iter->second); - } - settings.endArray(); -} - - -void FileDialogMemory::restore(QSettings &settings) -{ - int size = settings.beginReadArray("recentDirectories"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - QVariant name = settings.value("name"); - QVariant dir = settings.value("directory"); - if (name.isValid() && dir.isValid()) { - instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString())); - } - } - settings.endArray(); -} - - -QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, - QFileDialog::Options options) -{ - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); - - QString result = QFileDialog::getOpenFileName(parent, caption, currentDir.first->second, - filter, selectedFilter, options); - if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); - } - return result; -} - - -QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, QFileDialog::Options options) -{ - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); - - QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options); - if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); - } - return result; -} - - -FileDialogMemory &FileDialogMemory::instance() -{ - static FileDialogMemory instance; - return instance; -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "filedialogmemory.h" +#include + + +FileDialogMemory::FileDialogMemory() +{ +} + + +void FileDialogMemory::save(QSettings &settings) +{ + settings.remove("recentDirectories"); + settings.beginWriteArray("recentDirectories"); + int index = 0; + for (std::map::const_iterator iter = instance().m_Cache.begin(); + iter != instance().m_Cache.end(); ++iter) { + settings.setArrayIndex(index++); + settings.setValue("name", iter->first); + settings.setValue("directory", iter->second); + } + settings.endArray(); +} + + +void FileDialogMemory::restore(QSettings &settings) +{ + int size = settings.beginReadArray("recentDirectories"); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QVariant name = settings.value("name"); + QVariant dir = settings.value("directory"); + if (name.isValid() && dir.isValid()) { + instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString())); + } + } + settings.endArray(); +} + + +QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, + const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, + QFileDialog::Options options) +{ + std::pair::iterator, bool> currentDir = + instance().m_Cache.insert(std::make_pair(dirID, dir)); + + QString result = QFileDialog::getOpenFileName(parent, caption, currentDir.first->second, + filter, selectedFilter, options); + if (!result.isNull()) { + instance().m_Cache[dirID] = QFileInfo(result).path(); + } + return result; +} + + +QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent, + const QString &caption, const QString &dir, QFileDialog::Options options) +{ + std::pair::iterator, bool> currentDir = + instance().m_Cache.insert(std::make_pair(dirID, dir)); + + QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options); + if (!result.isNull()) { + instance().m_Cache[dirID] = QFileInfo(result).path(); + } + return result; +} + + +FileDialogMemory &FileDialogMemory::instance() +{ + static FileDialogMemory instance; + return instance; +} diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index e17d83d6..b2bfdb53 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef FILEDIALOGMEMORY_H #define FILEDIALOGMEMORY_H diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp index 80c46bc8..025ce4e6 100644 --- a/src/gameinfoimpl.cpp +++ b/src/gameinfoimpl.cpp @@ -1,54 +1,54 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "gameinfoimpl.h" -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -GameInfoImpl::GameInfoImpl() -{ -} - -IGameInfo::Type GameInfoImpl::type() const -{ - switch (GameInfo::instance().getType()) { - case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION; - case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3; - case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; - case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; - default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); - } -} - - -QString GameInfoImpl::path() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); -} - -QString GameInfoImpl::binaryName() const -{ - return ToQString(GameInfo::instance().getBinaryName()); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "gameinfoimpl.h" +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +GameInfoImpl::GameInfoImpl() +{ +} + +IGameInfo::Type GameInfoImpl::type() const +{ + switch (GameInfo::instance().getType()) { + case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION; + case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3; + case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; + case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; + default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); + } +} + + +QString GameInfoImpl::path() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); +} + +QString GameInfoImpl::binaryName() const +{ + return ToQString(GameInfo::instance().getBinaryName()); +} diff --git a/src/gameinfoimpl.h b/src/gameinfoimpl.h index 372c4c20..3ed7be6b 100644 --- a/src/gameinfoimpl.h +++ b/src/gameinfoimpl.h @@ -1,39 +1,39 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef GAMEINFOIMPL_H -#define GAMEINFOIMPL_H - - -#include -#include - - -class GameInfoImpl : public MOBase::IGameInfo -{ -public: - GameInfoImpl(); - - virtual Type type() const; - virtual QString path() const; - virtual QString binaryName() const; - -}; - -#endif // GAMEINFOIMPL_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef GAMEINFOIMPL_H +#define GAMEINFOIMPL_H + + +#include +#include + + +class GameInfoImpl : public MOBase::IGameInfo +{ +public: + GameInfoImpl(); + + virtual Type type() const; + virtual QString path() const; + virtual QString binaryName() const; + +}; + +#endif // GAMEINFOIMPL_H diff --git a/src/helper.cpp b/src/helper.cpp index 55b21a4b..d0825d10 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #include "helper.h" #include "report.h" #include "utility.h" diff --git a/src/helper.h b/src/helper.h index ee49a0cd..0144c39f 100644 --- a/src/helper.h +++ b/src/helper.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef HELPER_H #define HELPER_H diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index a0993add..f7cf6977 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -1,70 +1,70 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "icondelegate.h" -#include -#include -#include -#include - - -IconDelegate::IconDelegate(QObject *parent) - : QStyledItemDelegate(parent) -{ -} - - -void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QStyledItemDelegate::paint(painter, option, index); - - QList icons = getIcons(index); - - int x = 4; - painter->save(); - - int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; - iconWidth = std::min(16, iconWidth); - - painter->translate(option.rect.topLeft()); - for (auto iter = icons.begin(); iter != icons.end(); ++iter) { - painter->drawPixmap(x, 2, iconWidth, iconWidth, iter->pixmap(QSize(iconWidth, iconWidth))); - x += iconWidth + 4; - } - - painter->restore(); -} - - -QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const -{ - int count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); - QSize result; - if (index < ModInfo::getNumMods()) { - result = QSize(count * 40, 20); - } else { - result = QSize(1, 20); - } - if (option.rect.width() > 0) { - result.setWidth(std::min(option.rect.width(), result.width())); - } - return result; -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "icondelegate.h" +#include +#include +#include +#include + + +IconDelegate::IconDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + + +void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + QList icons = getIcons(index); + + int x = 4; + painter->save(); + + int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; + iconWidth = std::min(16, iconWidth); + + painter->translate(option.rect.topLeft()); + for (auto iter = icons.begin(); iter != icons.end(); ++iter) { + painter->drawPixmap(x, 2, iconWidth, iconWidth, iter->pixmap(QSize(iconWidth, iconWidth))); + x += iconWidth + 4; + } + + painter->restore(); +} + + +QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +{ + int count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + QSize result; + if (index < ModInfo::getNumMods()) { + result = QSize(count * 40, 20); + } else { + result = QSize(1, 20); + } + if (option.rect.width() > 0) { + result.setWidth(std::min(option.rect.width(), result.width())); + } + return result; +} + diff --git a/src/icondelegate.h b/src/icondelegate.h index cf292443..072343bb 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -1,52 +1,52 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef ICONDELEGATE_H -#define ICONDELEGATE_H - -#include "modinfo.h" -#include -#include - - -class IconDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - - explicit IconDelegate(QObject *parent = 0); - - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; - -signals: - -public slots: - -private: - - virtual QList getIcons(const QModelIndex &index) const = 0; - virtual size_t getNumIcons(const QModelIndex &index) const = 0; - - -private: - -}; - -#endif // ICONDELEGATE_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef ICONDELEGATE_H +#define ICONDELEGATE_H + +#include "modinfo.h" +#include +#include + + +class IconDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + + explicit IconDelegate(QObject *parent = 0); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + +signals: + +public slots: + +private: + + virtual QList getIcons(const QModelIndex &index) const = 0; + virtual size_t getNumIcons(const QModelIndex &index) const = 0; + + +private: + +}; + +#endif // ICONDELEGATE_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index c6ddabce..c865d897 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -1,802 +1,802 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "installationmanager.h" -#include "utility.h" -#include "report.h" -#include "categories.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "queryoverwritedialog.h" -#include "messagedialog.h" -#include "iplugininstallersimple.h" -#include "iplugininstallercustom.h" -#include "nexusinterface.h" -#include "selectiondialog.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -typedef Archive* (*CreateArchiveType)(); - - - -template T resolveFunction(QLibrary &lib, const char *name) -{ - T temp = reinterpret_cast(lib.resolve(name)); - if (temp == NULL) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; -} - - -InstallationManager::InstallationManager(QWidget *parent) - : QObject(parent), m_ParentWidget(parent), - m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) -{ - QLibrary archiveLib("dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - - CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); - - m_CurrentArchive = CreateArchiveFunc(); - if (!m_CurrentArchive->isValid()) { - throw MyException(getErrorString(m_CurrentArchive->getLastError())); - } - - m_InstallationProgress.setWindowFlags(m_InstallationProgress.windowFlags() & (~Qt::WindowContextHelpButtonHint)); -} - - -InstallationManager::~InstallationManager() -{ - delete m_CurrentArchive; -} - - -void InstallationManager::queryPassword(LPSTR password) -{ - QString result = QInputDialog::getText(NULL, tr("Password required"), tr("Password"), QLineEdit::Password); - strncpy(password, result.toLocal8Bit().constData(), MAX_PASSWORD_LENGTH); -} - - -void InstallationManager::mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data) -{ - if (path.length() > 0) { - path.append(L"\\"); - } - - for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - data[iter->getIndex()]->setSkip(false); - std::wstring temp = path.substr().append(ToWString(iter->getName())); - data[iter->getIndex()]->setOutputFileName(temp.c_str()); - } - - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - if ((*iter)->getData().index != -1) { - data[(*iter)->getData().index]->setSkip(false); - std::wstring temp = path.substr().append(ToWString((*iter)->getData().name)); - data[(*iter)->getData().index]->setOutputFileName(temp.c_str()); - } - mapToArchive(*iter, path.substr().append(ToWString((*iter)->getData().name)), data); - } -} - - -void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode) -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - // first disable all files + folders, we will re-enable those present in baseNode - for (size_t i = 0; i < size; ++i) { - data[i]->setSkip(true); - } - - std::wstring currentPath; - - mapToArchive(baseNode, currentPath, data); -} - - -bool InstallationManager::unpackSingleFile(const QString &fileName) -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - QString baseName = QFileInfo(fileName).fileName(); - - bool available = false; - for (size_t i = 0; i < size; ++i) { - if (_wcsicmp(data[i]->getFileName(), ToWString(fileName).c_str()) == 0) { - available = true; - data[i]->setSkip(false); - data[i]->setOutputFileName(ToWString(baseName).c_str()); - m_TempFilesToDelete.insert(baseName); - } else { - data[i]->setSkip(true); - } - } - - if (available) { - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - - bool res = m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), - new MethodCallback(this, &InstallationManager::updateProgress), - new MethodCallback(this, &InstallationManager::dummyProgressFile), - new MethodCallback(this, &InstallationManager::report7ZipError)); - - m_InstallationProgress.hide(); - - return res; - } else { - return false; - } -} - - -QString InstallationManager::extractFile(const QString &fileName) -{ - if (unpackSingleFile(fileName)) { - return QDir::tempPath().append("/").append(QFileInfo(fileName).fileName()); - } else { - return QString(); - } -} - - -QString canonicalize(const QString &name) -{ - QString result(name); - if ((result.startsWith('/')) || - (result.startsWith('\\'))) { - result.remove(0, 1); - } - result.replace('/', '\\'); - - return result; -} - - -QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten) -{ - QStringList files; - - foreach (const QString &file, filesOrig) { - files.append(canonicalize(file)); - } - - QStringList result; - - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - for (size_t i = 0; i < size; ++i) { - if (files.contains(ToQString(data[i]->getFileName()), Qt::CaseInsensitive)) { - const wchar_t *targetFile = data[i]->getFileName(); - if (flatten) { - targetFile = wcsrchr(data[i]->getFileName(), '\\'); - if (targetFile == NULL) { - targetFile = wcsrchr(data[i]->getFileName(), '/'); - } - if (targetFile == NULL) { - qCritical("failed to find backslash in %ls", data[i]->getFileName()); - continue; - } else { - // skip the slash - ++targetFile; - } - } - data[i]->setOutputFileName(targetFile); - - result.append(QDir::tempPath().append("/").append(ToQString(targetFile))); - - data[i]->setSkip(false); - m_TempFilesToDelete.insert(ToQString(targetFile)); - } else { - data[i]->setSkip(true); - } - } - - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - - // unpack only the files we need for the installer - if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), - new MethodCallback(this, &InstallationManager::updateProgress), - new MethodCallback(this, &InstallationManager::dummyProgressFile), - new MethodCallback(this, &InstallationManager::report7ZipError))) { - m_InstallationProgress.hide(); - throw std::runtime_error("extracting failed"); - } - - m_InstallationProgress.hide(); - return result; -} - -IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue &modName, const QString &archiveName) -{ - GuessedValue temp(modName); - bool iniTweaks; - if (install(archiveName, temp, iniTweaks)) { - return IPluginInstaller::RESULT_SUCCESS; - } else { - return IPluginInstaller::RESULT_FAILED; - } -} - - -DirectoryTree *InstallationManager::createFilesTree() -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - QScopedPointer result(new DirectoryTree); - - for (size_t i = 0; i < size; ++i) { - // the files are in a flat list where each file has a a full path relative to the archive root - // to create a tree, we have to iterate over each path component of each. This could be sped up by - // grouping the filenames first, but so far there doesn't seem to be an actual performance problem - DirectoryTree::Node *currentNode = result.data(); - - QString fileName = ToQString(data[i]->getFileName()); - QStringList components = fileName.split("\\"); - - // iterate over all path-components of this filename (including the filename itself) - for (QStringList::iterator componentIter = components.begin(); componentIter != components.end(); ++componentIter) { - if (componentIter->size() == 0) { - // empty string indicates fileName is actually only a directory name and we have - // completely processed it already. - break; - } - - bool exists = false; - // test if this path is already in the tree - for (DirectoryTree::node_iterator nodeIter = currentNode->nodesBegin(); nodeIter != currentNode->nodesEnd(); ++nodeIter) { - if ((*nodeIter)->getData().name == *componentIter) { - currentNode = *nodeIter; - exists = true; - break; - } - } - - if (!exists) { - if (componentIter + 1 == components.end()) { - // last path component. directory or file? - if (data[i]->isDirectory()) { - // this is a bit problematic. archives will often only list directories if they are empty, - // otherwise the dir only appears in the path of a file. In the UI however we allow the user - // to uncheck all files in a directory while keeping the dir checked. Those directories are - // currently not installed. - DirectoryTree::Node *newNode = new DirectoryTree::Node; - newNode->setData(DirectoryTreeInformation(*componentIter, i)); - currentNode->addNode(newNode, false); - currentNode = newNode; - } else { - currentNode->addLeaf(FileTreeInformation(*componentIter, i)); - } - } else { - DirectoryTree::Node *newNode = new DirectoryTree::Node; - newNode->setData(DirectoryTreeInformation(*componentIter, -1)); - currentNode->addNode(newNode, false); - currentNode = newNode; - } - } - } - } - - return result.take(); -} - - -bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle) -{ - // see if there is at least one directory that makes sense on the top level - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || - (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { - qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); - return true; - } - } - - // see if there is a file that makes sense on the top level - for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - if (InstallationTester::isTopLevelSuffix(iter->getName())) { - return true; - } - } - return false; -} - - -DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *dataTree) -{ - DirectoryTree::Node *currentNode = dataTree; - - while (true) { - if (isSimpleArchiveTopLayer(currentNode, false)) { - return currentNode; - } else if ((currentNode->numLeafs() == 0) && - (currentNode->numNodes() == 1)) { - currentNode = *currentNode->nodesBegin(); - } else { - qDebug("not a simple archive"); - return NULL; - } - } -} - - -void InstallationManager::updateProgress(float percentage) -{ - m_InstallationProgress.setValue(static_cast(percentage * 100.0)); - if (m_InstallationProgress.wasCanceled()) { - m_CurrentArchive->cancel(); - m_InstallationProgress.reset(); - } -} - - -void InstallationManager::updateProgressFile(LPCWSTR fileName) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallationProgress.setLabelText(QString::fromWCharArray(fileName)); -#else - m_InstallationProgress.setLabelText(QString::fromUtf16(fileName)); -#endif -} - - -void InstallationManager::report7ZipError(LPCWSTR errorMessage) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallationProgress.setLabelText(QString::fromWCharArray(errorMessage)); -#else - reportError(QString::fromUtf16(errorMessage)); -#endif - m_CurrentArchive->cancel(); -} - - -QString InstallationManager::generateBackupName(const QString &directoryName) const -{ - QString backupName = directoryName + "_backup"; - if (QDir(backupName).exists()) { - int idx = 2; - QString temp = backupName + QString::number(idx); - while (QDir(temp).exists()) { - ++idx; - temp = backupName + QString::number(idx); - } - backupName = temp; - } - return backupName; -} - - -bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const -{ - QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); - - while (QDir(targetDirectory).exists()) { - QueryOverwriteDialog overwriteDialog(m_ParentWidget); - if (overwriteDialog.exec()) { - if (overwriteDialog.backup()) { - QString backupDirectory = generateBackupName(targetDirectory); - if (!copyDir(targetDirectory, backupDirectory, false)) { - reportError(tr("failed to create backup")); - return false; - } - } - if (merge != nullptr) { - *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE); - } - if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { - bool ok = false; - QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), - QLineEdit::Normal, modName, &ok); - if (ok && !name.isEmpty()) { - modName.update(name, GUESS_USER); - if (!ensureValidModName(modName)) { - return false; - } - targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory.mid(0).append("\\").append(modName)); - } - } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) { - // save original settings like categories. Because it makes sense - QString metaFilename = targetDirectory.mid(0).append("/meta.ini"); - QFile settingsFile(metaFilename); - QByteArray originalSettings; - if (settingsFile.open(QIODevice::ReadOnly)) { - originalSettings = settingsFile.readAll(); - settingsFile.close(); - } - - // remove the directory with all content, then recreate it empty - shellDelete(QStringList(targetDirectory)); - if (!QDir().mkdir(targetDirectory)) { - // windows may keep the directory around for a moment, preventing its re-creation. Not sure - // if this still happens with shellDelete - Sleep(100); - QDir().mkdir(targetDirectory); - } - // restore the saved settings - if (settingsFile.open(QIODevice::WriteOnly)) { - settingsFile.write(originalSettings); - settingsFile.close(); - } else { - qCritical("failed to restore original settings: %s", metaFilename.toUtf8().constData()); - } - return true; - } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { - return true; - } - } else { - return false; - } - } - - QDir().mkdir(targetDirectory); - - return true; -} - - -bool InstallationManager::ensureValidModName(GuessedValue &name) const -{ - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"), - tr("The name you entered is invalid, please enter a different one."), - QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return false; - } - } - return true; -} - -bool InstallationManager::doInstall(GuessedValue &modName, int modID, - const QString &version, const QString &newestVersion, int categoryID) -{ - if (!ensureValidModName(modName)) { - return false; - } - - bool merge = false; - // determine target directory - if (!testOverwrite(modName, &merge)) { - return false; - } - - QString targetDirectoryNative = m_ModsDirectory.mid(0).append("\\").append(modName); - QString targetDirectory = QDir::fromNativeSeparators(targetDirectoryNative); - - qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData()); - - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(), - new MethodCallback(this, &InstallationManager::updateProgress), - new MethodCallback(this, &InstallationManager::updateProgressFile), - new MethodCallback(this, &InstallationManager::report7ZipError))) { - m_InstallationProgress.hide(); - if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { - return false; - } else { - throw std::runtime_error("extracting failed"); - } - } - - m_InstallationProgress.hide(); - - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - // overwrite settings only if they are actually are available or haven't been set before - if ((modID != 0) || !settingsFile.contains("modid")) { - settingsFile.setValue("modid", modID); - } - if (!settingsFile.contains("version") || - (!version.isEmpty() && - (!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) { - settingsFile.setValue("version", version); - } - if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { - settingsFile.setValue("newestVersion", newestVersion); - } - // issue #51 used to overwrite the manually set categories - if (!settingsFile.contains("category")) { - settingsFile.setValue("category", QString::number(categoryID)); - } - settingsFile.setValue("installationFile", m_CurrentFile); - - return true; -} - - -bool InstallationManager::wasCancelled() -{ - return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; -} - - -void InstallationManager::postInstallCleanup() const -{ - m_CurrentArchive->close(); - - // directories we may want to remove. sorted from longest to shortest to ensure we remove subdirectories first. - auto longestFirst = [](const QString &LHS, const QString &RHS) -> bool { - if (LHS.size() != RHS.size()) return LHS.size() > RHS.size(); - else return LHS < RHS; - }; - - std::set> directoriesToRemove(longestFirst); - - // clean up temp files - // TODO: this doesn't yet remove directories. Also, the files may be left there if this point isn't reached - foreach (const QString &tempFile, m_TempFilesToDelete) { - QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); - QFile::remove(fileInfo.absoluteFilePath()); - directoriesToRemove.insert(fileInfo.absolutePath()); - } - - // try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok - foreach (const QString &dir, directoriesToRemove) { - QDir().rmdir(dir); - } -} - - -bool InstallationManager::install(const QString &fileName, GuessedValue &modName, bool &hasIniTweaks) -{ - QFileInfo fileInfo(fileName); - if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { - reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix())); - return false; - } - - modName.setFilter(&fixDirectoryName); - - modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); - - // read out meta information from the download if available - int modID = 0; - QString version = ""; - QString newestVersion = ""; - int categoryID = 0; - - QString metaName = fileName.mid(0).append(".meta"); - if (QFile(metaName).exists()) { - QSettings metaFile(metaName, QSettings::IniFormat); - modID = metaFile.value("modID", 0).toInt(); - modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK); - modName.update(metaFile.value("modName", "").toString(), GUESS_META); - - version = metaFile.value("version", "").toString(); - newestVersion = metaFile.value("newestVersion", "").toString(); - unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt()); - categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); - } - - if (version.isEmpty()) { - QDateTime lastMod = fileInfo.lastModified(); - version = "d" + lastMod.toString("yyyy.M.d"); - } - - { // guess the mod name and mod if from the file name if there was no meta information - QString guessedModName; - int guessedModID = modID; - NexusInterface::interpretNexusFileName(QFileInfo(fileName).baseName(), guessedModName, guessedModID, false); - if ((modID == 0) && (guessedModID != -1)) { - modID = guessedModID; - } else if (modID != guessedModID) { - qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); - } - modName.update(guessedModName, GUESS_GOOD); - } - - m_CurrentFile = fileInfo.absoluteFilePath(); - if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { - m_CurrentFile = fileInfo.fileName(); - } - qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(m_CurrentFile)); - - // open the archive and construct the directory tree the installers work on - bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(), - new MethodCallback(this, &InstallationManager::queryPassword)); - if (!archiveOpen) { - qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError()); - } - ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); - - QScopedPointer filesTree(archiveOpen ? createFilesTree() : NULL); - IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; - - std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { - return LHS->priority() > RHS->priority(); - }); - - foreach (IPluginInstaller *installer, m_Installers) { - // don't use inactive installers - if (!installer->isActive()) { - continue; - } - - // try only manual installers if that was requested - if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { - if (!installer->isManualInstaller()) { - continue; - } - } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { - break; - } - - try { - { // simple case - IPluginInstallerSimple *installerSimple = dynamic_cast(installer); - if ((installerSimple != NULL) && - (filesTree != NULL) && (installer->isArchiveSupported(*filesTree))) { - installResult = installerSimple->install(modName, *filesTree, version, modID); - if (installResult == IPluginInstaller::RESULT_SUCCESS) { - mapToArchive(filesTree.data()); - // the simple installer only prepares the installation, the rest works the same for all installers - if (!doInstall(modName, modID, version, newestVersion, categoryID)) { - installResult = IPluginInstaller::RESULT_FAILED; - } - } - } - } - - { // custom case - IPluginInstallerCustom *installerCustom = dynamic_cast(installer); - if ((installerCustom != NULL) && - (((filesTree != NULL) && installer->isArchiveSupported(*filesTree)) || - ((filesTree == NULL) && installerCustom->isArchiveSupported(fileName)))) { - std::set installerExtensions = installerCustom->supportedExtensions(); - if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) { - installResult = installerCustom->install(modName, fileName, version, modID); - } - } - } - } catch (const IncompatibilityException &e) { - qCritical("plugin \"%s\" incompatible: %s", - qPrintable(installer->name()), e.what()); - } - - // act upon the installation result. at this point the files have already been - // extracted to the correct location - switch (installResult) { - case IPluginInstaller::RESULT_CANCELED: - case IPluginInstaller::RESULT_FAILED: { - return false; - } break; - case IPluginInstaller::RESULT_SUCCESS: - case IPluginInstaller::RESULT_SUCCESSCANCEL: { - if (filesTree != NULL) { - DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - return true; - } else { - return false; - } - } break; - } - } - - reportError(tr("None of the available installer plugins were able to handle that archive")); - return false; -} - - - -QString InstallationManager::getErrorString(Archive::Error errorCode) -{ - switch (errorCode) { - case Archive::ERROR_NONE: { - return tr("no error"); - } break; - case Archive::ERROR_LIBRARY_NOT_FOUND: { - return tr("7z.dll not found"); - } break; - case Archive::ERROR_LIBRARY_INVALID: { - return tr("7z.dll isn't valid"); - } break; - case Archive::ERROR_ARCHIVE_NOT_FOUND: { - return tr("archive not found"); - } break; - case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: { - return tr("failed to open archive"); - } break; - case Archive::ERROR_INVALID_ARCHIVE_FORMAT: { - return tr("unsupported archive type"); - } break; - case Archive::ERROR_LIBRARY_ERROR: { - return tr("internal library error"); - } break; - case Archive::ERROR_ARCHIVE_INVALID: { - return tr("archive invalid"); - } break; - default: { - // this probably means the archiver.dll is newer than this - return tr("unknown archive error"); - } break; - } -} - - -void InstallationManager::registerInstaller(IPluginInstaller *installer) -{ - m_Installers.push_back(installer); - installer->setInstallationManager(this); - IPluginInstallerCustom *installerCustom = dynamic_cast(installer); - if (installerCustom != NULL) { - std::set extensions = installerCustom->supportedExtensions(); - m_SupportedExtensions.insert(extensions.begin(), extensions.end()); - } -} - -QStringList InstallationManager::getSupportedExtensions() const -{ - QStringList result; - foreach (const QString &extension, m_SupportedExtensions) { - result.append(extension); - } - return result; -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "installationmanager.h" +#include "utility.h" +#include "report.h" +#include "categories.h" +#include "questionboxmemory.h" +#include "settings.h" +#include "queryoverwritedialog.h" +#include "messagedialog.h" +#include "iplugininstallersimple.h" +#include "iplugininstallercustom.h" +#include "nexusinterface.h" +#include "selectiondialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +typedef Archive* (*CreateArchiveType)(); + + + +template T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(lib.resolve(name)); + if (temp == NULL) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); + } + return temp; +} + + +InstallationManager::InstallationManager(QWidget *parent) + : QObject(parent), m_ParentWidget(parent), + m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) +{ + QLibrary archiveLib("dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); + } + + CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); + + m_CurrentArchive = CreateArchiveFunc(); + if (!m_CurrentArchive->isValid()) { + throw MyException(getErrorString(m_CurrentArchive->getLastError())); + } + + m_InstallationProgress.setWindowFlags(m_InstallationProgress.windowFlags() & (~Qt::WindowContextHelpButtonHint)); +} + + +InstallationManager::~InstallationManager() +{ + delete m_CurrentArchive; +} + + +void InstallationManager::queryPassword(LPSTR password) +{ + QString result = QInputDialog::getText(NULL, tr("Password required"), tr("Password"), QLineEdit::Password); + strncpy(password, result.toLocal8Bit().constData(), MAX_PASSWORD_LENGTH); +} + + +void InstallationManager::mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data) +{ + if (path.length() > 0) { + path.append(L"\\"); + } + + for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { + data[iter->getIndex()]->setSkip(false); + std::wstring temp = path.substr().append(ToWString(iter->getName())); + data[iter->getIndex()]->setOutputFileName(temp.c_str()); + } + + for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { + if ((*iter)->getData().index != -1) { + data[(*iter)->getData().index]->setSkip(false); + std::wstring temp = path.substr().append(ToWString((*iter)->getData().name)); + data[(*iter)->getData().index]->setOutputFileName(temp.c_str()); + } + mapToArchive(*iter, path.substr().append(ToWString((*iter)->getData().name)), data); + } +} + + +void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode) +{ + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + // first disable all files + folders, we will re-enable those present in baseNode + for (size_t i = 0; i < size; ++i) { + data[i]->setSkip(true); + } + + std::wstring currentPath; + + mapToArchive(baseNode, currentPath, data); +} + + +bool InstallationManager::unpackSingleFile(const QString &fileName) +{ + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + QString baseName = QFileInfo(fileName).fileName(); + + bool available = false; + for (size_t i = 0; i < size; ++i) { + if (_wcsicmp(data[i]->getFileName(), ToWString(fileName).c_str()) == 0) { + available = true; + data[i]->setSkip(false); + data[i]->setOutputFileName(ToWString(baseName).c_str()); + m_TempFilesToDelete.insert(baseName); + } else { + data[i]->setSkip(true); + } + } + + if (available) { + m_InstallationProgress.setWindowTitle(tr("Extracting files")); + m_InstallationProgress.setLabelText(QString()); + m_InstallationProgress.setValue(0); + m_InstallationProgress.setWindowModality(Qt::WindowModal); + m_InstallationProgress.show(); + + bool res = m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), + new MethodCallback(this, &InstallationManager::updateProgress), + new MethodCallback(this, &InstallationManager::dummyProgressFile), + new MethodCallback(this, &InstallationManager::report7ZipError)); + + m_InstallationProgress.hide(); + + return res; + } else { + return false; + } +} + + +QString InstallationManager::extractFile(const QString &fileName) +{ + if (unpackSingleFile(fileName)) { + return QDir::tempPath().append("/").append(QFileInfo(fileName).fileName()); + } else { + return QString(); + } +} + + +QString canonicalize(const QString &name) +{ + QString result(name); + if ((result.startsWith('/')) || + (result.startsWith('\\'))) { + result.remove(0, 1); + } + result.replace('/', '\\'); + + return result; +} + + +QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten) +{ + QStringList files; + + foreach (const QString &file, filesOrig) { + files.append(canonicalize(file)); + } + + QStringList result; + + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + for (size_t i = 0; i < size; ++i) { + if (files.contains(ToQString(data[i]->getFileName()), Qt::CaseInsensitive)) { + const wchar_t *targetFile = data[i]->getFileName(); + if (flatten) { + targetFile = wcsrchr(data[i]->getFileName(), '\\'); + if (targetFile == NULL) { + targetFile = wcsrchr(data[i]->getFileName(), '/'); + } + if (targetFile == NULL) { + qCritical("failed to find backslash in %ls", data[i]->getFileName()); + continue; + } else { + // skip the slash + ++targetFile; + } + } + data[i]->setOutputFileName(targetFile); + + result.append(QDir::tempPath().append("/").append(ToQString(targetFile))); + + data[i]->setSkip(false); + m_TempFilesToDelete.insert(ToQString(targetFile)); + } else { + data[i]->setSkip(true); + } + } + + m_InstallationProgress.setWindowTitle(tr("Extracting files")); + m_InstallationProgress.setLabelText(QString()); + m_InstallationProgress.setValue(0); + m_InstallationProgress.setWindowModality(Qt::WindowModal); + m_InstallationProgress.show(); + + // unpack only the files we need for the installer + if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), + new MethodCallback(this, &InstallationManager::updateProgress), + new MethodCallback(this, &InstallationManager::dummyProgressFile), + new MethodCallback(this, &InstallationManager::report7ZipError))) { + m_InstallationProgress.hide(); + throw std::runtime_error("extracting failed"); + } + + m_InstallationProgress.hide(); + return result; +} + +IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue &modName, const QString &archiveName) +{ + GuessedValue temp(modName); + bool iniTweaks; + if (install(archiveName, temp, iniTweaks)) { + return IPluginInstaller::RESULT_SUCCESS; + } else { + return IPluginInstaller::RESULT_FAILED; + } +} + + +DirectoryTree *InstallationManager::createFilesTree() +{ + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + QScopedPointer result(new DirectoryTree); + + for (size_t i = 0; i < size; ++i) { + // the files are in a flat list where each file has a a full path relative to the archive root + // to create a tree, we have to iterate over each path component of each. This could be sped up by + // grouping the filenames first, but so far there doesn't seem to be an actual performance problem + DirectoryTree::Node *currentNode = result.data(); + + QString fileName = ToQString(data[i]->getFileName()); + QStringList components = fileName.split("\\"); + + // iterate over all path-components of this filename (including the filename itself) + for (QStringList::iterator componentIter = components.begin(); componentIter != components.end(); ++componentIter) { + if (componentIter->size() == 0) { + // empty string indicates fileName is actually only a directory name and we have + // completely processed it already. + break; + } + + bool exists = false; + // test if this path is already in the tree + for (DirectoryTree::node_iterator nodeIter = currentNode->nodesBegin(); nodeIter != currentNode->nodesEnd(); ++nodeIter) { + if ((*nodeIter)->getData().name == *componentIter) { + currentNode = *nodeIter; + exists = true; + break; + } + } + + if (!exists) { + if (componentIter + 1 == components.end()) { + // last path component. directory or file? + if (data[i]->isDirectory()) { + // this is a bit problematic. archives will often only list directories if they are empty, + // otherwise the dir only appears in the path of a file. In the UI however we allow the user + // to uncheck all files in a directory while keeping the dir checked. Those directories are + // currently not installed. + DirectoryTree::Node *newNode = new DirectoryTree::Node; + newNode->setData(DirectoryTreeInformation(*componentIter, i)); + currentNode->addNode(newNode, false); + currentNode = newNode; + } else { + currentNode->addLeaf(FileTreeInformation(*componentIter, i)); + } + } else { + DirectoryTree::Node *newNode = new DirectoryTree::Node; + newNode->setData(DirectoryTreeInformation(*componentIter, -1)); + currentNode->addNode(newNode, false); + currentNode = newNode; + } + } + } + } + + return result.take(); +} + + +bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle) +{ + // see if there is at least one directory that makes sense on the top level + for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { + if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || + (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { + qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); + return true; + } + } + + // see if there is a file that makes sense on the top level + for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { + if (InstallationTester::isTopLevelSuffix(iter->getName())) { + return true; + } + } + return false; +} + + +DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *dataTree) +{ + DirectoryTree::Node *currentNode = dataTree; + + while (true) { + if (isSimpleArchiveTopLayer(currentNode, false)) { + return currentNode; + } else if ((currentNode->numLeafs() == 0) && + (currentNode->numNodes() == 1)) { + currentNode = *currentNode->nodesBegin(); + } else { + qDebug("not a simple archive"); + return NULL; + } + } +} + + +void InstallationManager::updateProgress(float percentage) +{ + m_InstallationProgress.setValue(static_cast(percentage * 100.0)); + if (m_InstallationProgress.wasCanceled()) { + m_CurrentArchive->cancel(); + m_InstallationProgress.reset(); + } +} + + +void InstallationManager::updateProgressFile(LPCWSTR fileName) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallationProgress.setLabelText(QString::fromWCharArray(fileName)); +#else + m_InstallationProgress.setLabelText(QString::fromUtf16(fileName)); +#endif +} + + +void InstallationManager::report7ZipError(LPCWSTR errorMessage) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallationProgress.setLabelText(QString::fromWCharArray(errorMessage)); +#else + reportError(QString::fromUtf16(errorMessage)); +#endif + m_CurrentArchive->cancel(); +} + + +QString InstallationManager::generateBackupName(const QString &directoryName) const +{ + QString backupName = directoryName + "_backup"; + if (QDir(backupName).exists()) { + int idx = 2; + QString temp = backupName + QString::number(idx); + while (QDir(temp).exists()) { + ++idx; + temp = backupName + QString::number(idx); + } + backupName = temp; + } + return backupName; +} + + +bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const +{ + QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + + while (QDir(targetDirectory).exists()) { + QueryOverwriteDialog overwriteDialog(m_ParentWidget); + if (overwriteDialog.exec()) { + if (overwriteDialog.backup()) { + QString backupDirectory = generateBackupName(targetDirectory); + if (!copyDir(targetDirectory, backupDirectory, false)) { + reportError(tr("failed to create backup")); + return false; + } + } + if (merge != nullptr) { + *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE); + } + if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { + bool ok = false; + QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), + QLineEdit::Normal, modName, &ok); + if (ok && !name.isEmpty()) { + modName.update(name, GUESS_USER); + if (!ensureValidModName(modName)) { + return false; + } + targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory.mid(0).append("\\").append(modName)); + } + } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) { + // save original settings like categories. Because it makes sense + QString metaFilename = targetDirectory.mid(0).append("/meta.ini"); + QFile settingsFile(metaFilename); + QByteArray originalSettings; + if (settingsFile.open(QIODevice::ReadOnly)) { + originalSettings = settingsFile.readAll(); + settingsFile.close(); + } + + // remove the directory with all content, then recreate it empty + shellDelete(QStringList(targetDirectory)); + if (!QDir().mkdir(targetDirectory)) { + // windows may keep the directory around for a moment, preventing its re-creation. Not sure + // if this still happens with shellDelete + Sleep(100); + QDir().mkdir(targetDirectory); + } + // restore the saved settings + if (settingsFile.open(QIODevice::WriteOnly)) { + settingsFile.write(originalSettings); + settingsFile.close(); + } else { + qCritical("failed to restore original settings: %s", metaFilename.toUtf8().constData()); + } + return true; + } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { + return true; + } + } else { + return false; + } + } + + QDir().mkdir(targetDirectory); + + return true; +} + + +bool InstallationManager::ensureValidModName(GuessedValue &name) const +{ + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"), + tr("The name you entered is invalid, please enter a different one."), + QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return false; + } + } + return true; +} + +bool InstallationManager::doInstall(GuessedValue &modName, int modID, + const QString &version, const QString &newestVersion, int categoryID) +{ + if (!ensureValidModName(modName)) { + return false; + } + + bool merge = false; + // determine target directory + if (!testOverwrite(modName, &merge)) { + return false; + } + + QString targetDirectoryNative = m_ModsDirectory.mid(0).append("\\").append(modName); + QString targetDirectory = QDir::fromNativeSeparators(targetDirectoryNative); + + qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData()); + + m_InstallationProgress.setWindowTitle(tr("Extracting files")); + m_InstallationProgress.setLabelText(QString()); + m_InstallationProgress.setValue(0); + m_InstallationProgress.setWindowModality(Qt::WindowModal); + m_InstallationProgress.show(); + if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(), + new MethodCallback(this, &InstallationManager::updateProgress), + new MethodCallback(this, &InstallationManager::updateProgressFile), + new MethodCallback(this, &InstallationManager::report7ZipError))) { + m_InstallationProgress.hide(); + if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { + return false; + } else { + throw std::runtime_error("extracting failed"); + } + } + + m_InstallationProgress.hide(); + + QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); + + // overwrite settings only if they are actually are available or haven't been set before + if ((modID != 0) || !settingsFile.contains("modid")) { + settingsFile.setValue("modid", modID); + } + if (!settingsFile.contains("version") || + (!version.isEmpty() && + (!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) { + settingsFile.setValue("version", version); + } + if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { + settingsFile.setValue("newestVersion", newestVersion); + } + // issue #51 used to overwrite the manually set categories + if (!settingsFile.contains("category")) { + settingsFile.setValue("category", QString::number(categoryID)); + } + settingsFile.setValue("installationFile", m_CurrentFile); + + return true; +} + + +bool InstallationManager::wasCancelled() +{ + return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; +} + + +void InstallationManager::postInstallCleanup() const +{ + m_CurrentArchive->close(); + + // directories we may want to remove. sorted from longest to shortest to ensure we remove subdirectories first. + auto longestFirst = [](const QString &LHS, const QString &RHS) -> bool { + if (LHS.size() != RHS.size()) return LHS.size() > RHS.size(); + else return LHS < RHS; + }; + + std::set> directoriesToRemove(longestFirst); + + // clean up temp files + // TODO: this doesn't yet remove directories. Also, the files may be left there if this point isn't reached + foreach (const QString &tempFile, m_TempFilesToDelete) { + QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); + QFile::remove(fileInfo.absoluteFilePath()); + directoriesToRemove.insert(fileInfo.absolutePath()); + } + + // try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok + foreach (const QString &dir, directoriesToRemove) { + QDir().rmdir(dir); + } +} + + +bool InstallationManager::install(const QString &fileName, GuessedValue &modName, bool &hasIniTweaks) +{ + QFileInfo fileInfo(fileName); + if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { + reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix())); + return false; + } + + modName.setFilter(&fixDirectoryName); + + modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); + + // read out meta information from the download if available + int modID = 0; + QString version = ""; + QString newestVersion = ""; + int categoryID = 0; + + QString metaName = fileName.mid(0).append(".meta"); + if (QFile(metaName).exists()) { + QSettings metaFile(metaName, QSettings::IniFormat); + modID = metaFile.value("modID", 0).toInt(); + modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK); + modName.update(metaFile.value("modName", "").toString(), GUESS_META); + + version = metaFile.value("version", "").toString(); + newestVersion = metaFile.value("newestVersion", "").toString(); + unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt()); + categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); + } + + if (version.isEmpty()) { + QDateTime lastMod = fileInfo.lastModified(); + version = "d" + lastMod.toString("yyyy.M.d"); + } + + { // guess the mod name and mod if from the file name if there was no meta information + QString guessedModName; + int guessedModID = modID; + NexusInterface::interpretNexusFileName(QFileInfo(fileName).baseName(), guessedModName, guessedModID, false); + if ((modID == 0) && (guessedModID != -1)) { + modID = guessedModID; + } else if (modID != guessedModID) { + qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); + } + modName.update(guessedModName, GUESS_GOOD); + } + + m_CurrentFile = fileInfo.absoluteFilePath(); + if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { + m_CurrentFile = fileInfo.fileName(); + } + qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(m_CurrentFile)); + + // open the archive and construct the directory tree the installers work on + bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(), + new MethodCallback(this, &InstallationManager::queryPassword)); + if (!archiveOpen) { + qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError()); + } + ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); + + QScopedPointer filesTree(archiveOpen ? createFilesTree() : NULL); + IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; + + std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { + return LHS->priority() > RHS->priority(); + }); + + foreach (IPluginInstaller *installer, m_Installers) { + // don't use inactive installers + if (!installer->isActive()) { + continue; + } + + // try only manual installers if that was requested + if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { + if (!installer->isManualInstaller()) { + continue; + } + } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { + break; + } + + try { + { // simple case + IPluginInstallerSimple *installerSimple = dynamic_cast(installer); + if ((installerSimple != NULL) && + (filesTree != NULL) && (installer->isArchiveSupported(*filesTree))) { + installResult = installerSimple->install(modName, *filesTree, version, modID); + if (installResult == IPluginInstaller::RESULT_SUCCESS) { + mapToArchive(filesTree.data()); + // the simple installer only prepares the installation, the rest works the same for all installers + if (!doInstall(modName, modID, version, newestVersion, categoryID)) { + installResult = IPluginInstaller::RESULT_FAILED; + } + } + } + } + + { // custom case + IPluginInstallerCustom *installerCustom = dynamic_cast(installer); + if ((installerCustom != NULL) && + (((filesTree != NULL) && installer->isArchiveSupported(*filesTree)) || + ((filesTree == NULL) && installerCustom->isArchiveSupported(fileName)))) { + std::set installerExtensions = installerCustom->supportedExtensions(); + if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) { + installResult = installerCustom->install(modName, fileName, version, modID); + } + } + } + } catch (const IncompatibilityException &e) { + qCritical("plugin \"%s\" incompatible: %s", + qPrintable(installer->name()), e.what()); + } + + // act upon the installation result. at this point the files have already been + // extracted to the correct location + switch (installResult) { + case IPluginInstaller::RESULT_CANCELED: + case IPluginInstaller::RESULT_FAILED: { + return false; + } break; + case IPluginInstaller::RESULT_SUCCESS: + case IPluginInstaller::RESULT_SUCCESSCANCEL: { + if (filesTree != NULL) { + DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); + hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && + ((*iniTweakNode)->numLeafs() != 0); + return true; + } else { + return false; + } + } break; + } + } + + reportError(tr("None of the available installer plugins were able to handle that archive")); + return false; +} + + + +QString InstallationManager::getErrorString(Archive::Error errorCode) +{ + switch (errorCode) { + case Archive::ERROR_NONE: { + return tr("no error"); + } break; + case Archive::ERROR_LIBRARY_NOT_FOUND: { + return tr("7z.dll not found"); + } break; + case Archive::ERROR_LIBRARY_INVALID: { + return tr("7z.dll isn't valid"); + } break; + case Archive::ERROR_ARCHIVE_NOT_FOUND: { + return tr("archive not found"); + } break; + case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: { + return tr("failed to open archive"); + } break; + case Archive::ERROR_INVALID_ARCHIVE_FORMAT: { + return tr("unsupported archive type"); + } break; + case Archive::ERROR_LIBRARY_ERROR: { + return tr("internal library error"); + } break; + case Archive::ERROR_ARCHIVE_INVALID: { + return tr("archive invalid"); + } break; + default: { + // this probably means the archiver.dll is newer than this + return tr("unknown archive error"); + } break; + } +} + + +void InstallationManager::registerInstaller(IPluginInstaller *installer) +{ + m_Installers.push_back(installer); + installer->setInstallationManager(this); + IPluginInstallerCustom *installerCustom = dynamic_cast(installer); + if (installerCustom != NULL) { + std::set extensions = installerCustom->supportedExtensions(); + m_SupportedExtensions.insert(extensions.begin(), extensions.end()); + } +} + +QStringList InstallationManager::getSupportedExtensions() const +{ + QStringList result; + foreach (const QString &extension, m_SupportedExtensions) { + result.append(extension); + } + return result; +} diff --git a/src/installationmanager.h b/src/installationmanager.h index 3478fecf..d430c065 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -1,216 +1,216 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef INSTALLATIONMANAGER_H -#define INSTALLATIONMANAGER_H - - -#include -#include -#include - -#include -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include - - -/** - * @brief manages the installation of mod archives - * This currently supports two special kind of archives: - * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data directory directly - * - "complex" bain archives: archives with options for the bain system. - * All other archives are managed through the manual "InstallDialog" - * @todo this may be a good place to support plugins - **/ -class InstallationManager : public QObject, public MOBase::IInstallationManager -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param parent parent object. - **/ - explicit InstallationManager(QWidget *parent); - - ~InstallationManager(); - - /** - * @brief update the directory where mods are to be installed - * @param modsDirectory the mod directory - * @note this is called a lot, probably redundantly - */ - void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; } - - /** - * @brief update the directory where downloads are stored - * @param downloadDirectory the download directory - */ - void setDownloadDirectory(const QString &downloadDirectory) { m_DownloadsDirectory = downloadDirectory; } - - /** - * @brief install a mod from an archive - * - * @param fileName absolute file name of the archive to install - * @param modName suggested name of the mod. If this is empty (the default), a name will be guessed based on the filename. The user will always have a chance to rename the mod - * @return true if the archive was installed, false if installation failed or was refused - * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) - **/ - bool install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks); - - /** - * @return true if the installation was canceled - **/ - bool wasCancelled(); - - /** - * @brief retrieve a string describing the specified error code - * - * @param errorCode an error code as returned by the archiving function - * @return the error string - * @todo This function doesn't belong here, it is only public because the SelfUpdater class also uses "Archive" to get to the package.txt file - **/ - static QString getErrorString(Archive::Error errorCode); - - /** - * @brief register an installer-plugin - * @param the installer to register - */ - void registerInstaller(MOBase::IPluginInstaller *installer); - - /** - * @return list of file extensions we can install - */ - QStringList getSupportedExtensions() const; - - /** - * @brief extract the specified file from the currently open archive to a temporary location - * @param (relative) name of the file within the archive - * @return the absolute name of the temporary file - * @note the call will fail with an exception if no archive is open (plugins deriving - * from IPluginInstallerSimple can rely on that, custom installers shouldn't) - * @note the temporary file is automatically cleaned up after the installation - * @note This call can be very slow if the archive is large and "solid" - */ - virtual QString extractFile(const QString &fileName); - - /** - * @brief extract the specified files from the currently open archive to a temporary location - * @param (relative) names of files within the archive - * @return the absolute names of the temporary files - * @note the call will fail with an exception if no archive is open (plugins deriving - * from IPluginInstallerSimple can rely on that, custom installers shouldn't) - * @note the temporary file is automatically cleaned up after the installation - * @note This call can be very slow if the archive is large and "solid" - */ - virtual QStringList extractFiles(const QStringList &files, bool flatten); - - /** - * @brief installs an archive - * @param modName suggested name of the mod - * @param archiveFile path to the archive to install - * @return the installation result - */ - virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName); - - /** - * @brief test if the specified mod name is free. If not, query the user how to proceed - * @param modName current possible names for the mod - * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace - * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable error - */ - virtual bool testOverwrite(MOBase::GuessedValue &modName, bool *merge = NULL) const; - -private: - - void queryPassword(LPSTR password); - void updateProgress(float percentage); - void updateProgressFile(LPCWSTR fileName); - void report7ZipError(LPCWSTR errorMessage); - - void dummyProgressFile(LPCWSTR) {} - - MOBase::DirectoryTree *createFilesTree(); - - // remap all files in the archive to the directory structure represented by baseNode - // files not present in baseNode are disabled - void mapToArchive(const MOBase::DirectoryTree::Node *baseNode); - - // recursive worker function for mapToArchive - void mapToArchive(const MOBase::DirectoryTree::Node *node, std::wstring path, FileData * const *data); - bool unpackSingleFile(const QString &fileName); - - - bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle); - MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree); - - //bool testOverwrite(const QString &modsDirectory, MOBase::GuessedValue &modName, bool *merge = NULL); - - bool doInstall(MOBase::GuessedValue &modName, - int modID, const QString &version, const QString &newestVersion, int categoryID); - - QString generateBackupName(const QString &directoryName) const; - - bool ensureValidModName(MOBase::GuessedValue &name) const; - - void postInstallCleanup() const; - -private: - - struct ByPriority { - bool operator()(MOBase::IPluginInstaller *LHS, MOBase::IPluginInstaller *RHS) const - { - return LHS->priority() > RHS->priority(); - } - }; - - struct CaseInsensitive { - bool operator() (const QString &LHS, const QString &RHS) const - { - return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; - } - }; - -private: - - QWidget *m_ParentWidget; - - QString m_ModsDirectory; - QString m_DownloadsDirectory; - - std::vector m_Installers; - std::set m_SupportedExtensions; - - Archive *m_CurrentArchive; - QString m_CurrentFile; - - QProgressDialog m_InstallationProgress; - - std::set m_TempFilesToDelete; - -}; - - -#endif // INSTALLATIONMANAGER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef INSTALLATIONMANAGER_H +#define INSTALLATIONMANAGER_H + + +#include +#include +#include + +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include + + +/** + * @brief manages the installation of mod archives + * This currently supports two special kind of archives: + * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data directory directly + * - "complex" bain archives: archives with options for the bain system. + * All other archives are managed through the manual "InstallDialog" + * @todo this may be a good place to support plugins + **/ +class InstallationManager : public QObject, public MOBase::IInstallationManager +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param parent parent object. + **/ + explicit InstallationManager(QWidget *parent); + + ~InstallationManager(); + + /** + * @brief update the directory where mods are to be installed + * @param modsDirectory the mod directory + * @note this is called a lot, probably redundantly + */ + void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; } + + /** + * @brief update the directory where downloads are stored + * @param downloadDirectory the download directory + */ + void setDownloadDirectory(const QString &downloadDirectory) { m_DownloadsDirectory = downloadDirectory; } + + /** + * @brief install a mod from an archive + * + * @param fileName absolute file name of the archive to install + * @param modName suggested name of the mod. If this is empty (the default), a name will be guessed based on the filename. The user will always have a chance to rename the mod + * @return true if the archive was installed, false if installation failed or was refused + * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) + **/ + bool install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks); + + /** + * @return true if the installation was canceled + **/ + bool wasCancelled(); + + /** + * @brief retrieve a string describing the specified error code + * + * @param errorCode an error code as returned by the archiving function + * @return the error string + * @todo This function doesn't belong here, it is only public because the SelfUpdater class also uses "Archive" to get to the package.txt file + **/ + static QString getErrorString(Archive::Error errorCode); + + /** + * @brief register an installer-plugin + * @param the installer to register + */ + void registerInstaller(MOBase::IPluginInstaller *installer); + + /** + * @return list of file extensions we can install + */ + QStringList getSupportedExtensions() const; + + /** + * @brief extract the specified file from the currently open archive to a temporary location + * @param (relative) name of the file within the archive + * @return the absolute name of the temporary file + * @note the call will fail with an exception if no archive is open (plugins deriving + * from IPluginInstallerSimple can rely on that, custom installers shouldn't) + * @note the temporary file is automatically cleaned up after the installation + * @note This call can be very slow if the archive is large and "solid" + */ + virtual QString extractFile(const QString &fileName); + + /** + * @brief extract the specified files from the currently open archive to a temporary location + * @param (relative) names of files within the archive + * @return the absolute names of the temporary files + * @note the call will fail with an exception if no archive is open (plugins deriving + * from IPluginInstallerSimple can rely on that, custom installers shouldn't) + * @note the temporary file is automatically cleaned up after the installation + * @note This call can be very slow if the archive is large and "solid" + */ + virtual QStringList extractFiles(const QStringList &files, bool flatten); + + /** + * @brief installs an archive + * @param modName suggested name of the mod + * @param archiveFile path to the archive to install + * @return the installation result + */ + virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName); + + /** + * @brief test if the specified mod name is free. If not, query the user how to proceed + * @param modName current possible names for the mod + * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace + * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable error + */ + virtual bool testOverwrite(MOBase::GuessedValue &modName, bool *merge = NULL) const; + +private: + + void queryPassword(LPSTR password); + void updateProgress(float percentage); + void updateProgressFile(LPCWSTR fileName); + void report7ZipError(LPCWSTR errorMessage); + + void dummyProgressFile(LPCWSTR) {} + + MOBase::DirectoryTree *createFilesTree(); + + // remap all files in the archive to the directory structure represented by baseNode + // files not present in baseNode are disabled + void mapToArchive(const MOBase::DirectoryTree::Node *baseNode); + + // recursive worker function for mapToArchive + void mapToArchive(const MOBase::DirectoryTree::Node *node, std::wstring path, FileData * const *data); + bool unpackSingleFile(const QString &fileName); + + + bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle); + MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree); + + //bool testOverwrite(const QString &modsDirectory, MOBase::GuessedValue &modName, bool *merge = NULL); + + bool doInstall(MOBase::GuessedValue &modName, + int modID, const QString &version, const QString &newestVersion, int categoryID); + + QString generateBackupName(const QString &directoryName) const; + + bool ensureValidModName(MOBase::GuessedValue &name) const; + + void postInstallCleanup() const; + +private: + + struct ByPriority { + bool operator()(MOBase::IPluginInstaller *LHS, MOBase::IPluginInstaller *RHS) const + { + return LHS->priority() > RHS->priority(); + } + }; + + struct CaseInsensitive { + bool operator() (const QString &LHS, const QString &RHS) const + { + return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; + } + }; + +private: + + QWidget *m_ParentWidget; + + QString m_ModsDirectory; + QString m_DownloadsDirectory; + + std::vector m_Installers; + std::set m_SupportedExtensions; + + Archive *m_CurrentArchive; + QString m_CurrentFile; + + QProgressDialog m_InstallationProgress; + + std::set m_TempFilesToDelete; + +}; + + +#endif // INSTALLATIONMANAGER_H diff --git a/src/json.cpp b/src/json.cpp index ca2d728f..a280ba34 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -1,522 +1,522 @@ -/** - * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. - * Copyright (C) 2011 Eeli Reilin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file json.cpp - */ - -#include "json.h" - -namespace QtJson { - static QString sanitizeString(QString str); - static QByteArray join(const QList &list, const QByteArray &sep); - static QVariant parseValue(const QString &json, int &index, bool &success); - static QVariant parseObject(const QString &json, int &index, bool &success); - static QVariant parseArray(const QString &json, int &index, bool &success); - static QVariant parseString(const QString &json, int &index, bool &success); - static QVariant parseNumber(const QString &json, int &index); - static int lastIndexOfNumber(const QString &json, int index); - static void eatWhitespace(const QString &json, int &index); - static int lookAhead(const QString &json, int index); - static int nextToken(const QString &json, int &index); - - template - QByteArray serializeMap(const T &map, bool &success) { - QByteArray str = "{ "; - QList pairs; - for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { - QByteArray serializedValue = serialize(it.value()); - if (serializedValue.isNull()) { - success = false; - break; - } - pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; - } - - str += join(pairs, ", "); - str += " }"; - return str; - } - - - /** - * parse - */ - QVariant parse(const QString &json) { - bool success = true; - return parse(json, success); - } - - /** - * parse - */ - QVariant parse(const QString &json, bool &success) { - success = true; - - // Return an empty QVariant if the JSON data is either null or empty - if (!json.isNull() || !json.isEmpty()) { - QString data = json; - // We'll start from index 0 - int index = 0; - - // Parse the first value - QVariant value = parseValue(data, index, success); - - // Return the parsed value - return value; - } else { - // Return the empty QVariant - return QVariant(); - } - } - - QByteArray serialize(const QVariant &data) { - bool success = true; - return serialize(data, success); - } - - QByteArray serialize(const QVariant &data, bool &success) { - QByteArray str; - success = true; - - if (!data.isValid()) { // invalid or null? - str = "null"; - } else if ((data.type() == QVariant::List) || - (data.type() == QVariant::StringList)) { // variant is a list? - QList values; - const QVariantList list = data.toList(); - Q_FOREACH(const QVariant& v, list) { - QByteArray serializedValue = serialize(v); - if (serializedValue.isNull()) { - success = false; - break; - } - values << serializedValue; - } - - str = "[ " + join( values, ", " ) + " ]"; - } else if (data.type() == QVariant::Hash) { // variant is a hash? - str = serializeMap<>(data.toHash(), success); - } else if (data.type() == QVariant::Map) { // variant is a map? - str = serializeMap<>(data.toMap(), success); - } else if ((data.type() == QVariant::String) || - (data.type() == QVariant::ByteArray)) {// a string or a byte array? - str = sanitizeString(data.toString()).toUtf8(); - } else if (data.type() == QVariant::Double) { // double? - double value = data.toDouble(); - if ((value - value) == 0.0) { - str = QByteArray::number(value, 'g'); - if (!str.contains(".") && ! str.contains("e")) { - str += ".0"; - } - } else { - success = false; - } - } else if (data.type() == QVariant::Bool) { // boolean value? - str = data.toBool() ? "true" : "false"; - } else if (data.type() == QVariant::ULongLong) { // large unsigned number? - str = QByteArray::number(data.value()); - } else if (data.canConvert()) { // any signed number? - str = QByteArray::number(data.value()); - } else if (data.canConvert()) { //TODO: this code is never executed - str = QString::number(data.value()).toUtf8(); - } else if (data.canConvert()) { // can value be converted to string? - // this will catch QDate, QDateTime, QUrl, ... - str = sanitizeString(data.toString()).toUtf8(); - } else { - success = false; - } - - if (success) { - return str; - } else { - return QByteArray(); - } - } - - QString serializeStr(const QVariant &data) { - return QString::fromUtf8(serialize(data)); - } - - QString serializeStr(const QVariant &data, bool &success) { - return QString::fromUtf8(serialize(data, success)); - } - - - /** - * \enum JsonToken - */ - enum JsonToken { - JsonTokenNone = 0, - JsonTokenCurlyOpen = 1, - JsonTokenCurlyClose = 2, - JsonTokenSquaredOpen = 3, - JsonTokenSquaredClose = 4, - JsonTokenColon = 5, - JsonTokenComma = 6, - JsonTokenString = 7, - JsonTokenNumber = 8, - JsonTokenTrue = 9, - JsonTokenFalse = 10, - JsonTokenNull = 11 - }; - - static QString sanitizeString(QString str) { - str.replace(QLatin1String("\\"), QLatin1String("\\\\")); - str.replace(QLatin1String("\""), QLatin1String("\\\"")); - str.replace(QLatin1String("\b"), QLatin1String("\\b")); - str.replace(QLatin1String("\f"), QLatin1String("\\f")); - str.replace(QLatin1String("\n"), QLatin1String("\\n")); - str.replace(QLatin1String("\r"), QLatin1String("\\r")); - str.replace(QLatin1String("\t"), QLatin1String("\\t")); - return QString(QLatin1String("\"%1\"")).arg(str); - } - - static QByteArray join(const QList &list, const QByteArray &sep) { - QByteArray res; - Q_FOREACH(const QByteArray &i, list) { - if (!res.isEmpty()) { - res += sep; - } - res += i; - } - return res; - } - - /** - * parseValue - */ - static QVariant parseValue(const QString &json, int &index, bool &success) { - // Determine what kind of data we should parse by - // checking out the upcoming token - switch(lookAhead(json, index)) { - case JsonTokenString: - return parseString(json, index, success); - case JsonTokenNumber: - return parseNumber(json, index); - case JsonTokenCurlyOpen: - return parseObject(json, index, success); - case JsonTokenSquaredOpen: - return parseArray(json, index, success); - case JsonTokenTrue: - nextToken(json, index); - return QVariant(true); - case JsonTokenFalse: - nextToken(json, index); - return QVariant(false); - case JsonTokenNull: - nextToken(json, index); - return QVariant(); - case JsonTokenNone: - break; - } - - // If there were no tokens, flag the failure and return an empty QVariant - success = false; - return QVariant(); - } - - /** - * parseObject - */ - static QVariant parseObject(const QString &json, int &index, bool &success) { - QVariantMap map; - int token; - - // Get rid of the whitespace and increment index - nextToken(json, index); - - // Loop through all of the key/value pairs of the object - bool done = false; - while (!done) { - // Get the upcoming token - token = lookAhead(json, index); - - if (token == JsonTokenNone) { - success = false; - return QVariantMap(); - } else if (token == JsonTokenComma) { - nextToken(json, index); - } else if (token == JsonTokenCurlyClose) { - nextToken(json, index); - return map; - } else { - // Parse the key/value pair's name - QString name = parseString(json, index, success).toString(); - - if (!success) { - return QVariantMap(); - } - - // Get the next token - token = nextToken(json, index); - - // If the next token is not a colon, flag the failure - // return an empty QVariant - if (token != JsonTokenColon) { - success = false; - return QVariant(QVariantMap()); - } - - // Parse the key/value pair's value - QVariant value = parseValue(json, index, success); - - if (!success) { - return QVariantMap(); - } - - // Assign the value to the key in the map - map[name] = value; - } - } - - // Return the map successfully - return QVariant(map); - } - - /** - * parseArray - */ - static QVariant parseArray(const QString &json, int &index, bool &success) { - QVariantList list; - - nextToken(json, index); - - bool done = false; - while(!done) { - int token = lookAhead(json, index); - - if (token == JsonTokenNone) { - success = false; - return QVariantList(); - } else if (token == JsonTokenComma) { - nextToken(json, index); - } else if (token == JsonTokenSquaredClose) { - nextToken(json, index); - break; - } else { - QVariant value = parseValue(json, index, success); - if (!success) { - return QVariantList(); - } - list.push_back(value); - } - } - - return QVariant(list); - } - - /** - * parseString - */ - static QVariant parseString(const QString &json, int &index, bool &success) { - QString s; - QChar c; - - eatWhitespace(json, index); - - c = json[index++]; - - bool complete = false; - while(!complete) { - if (index == json.size()) { - break; - } - - c = json[index++]; - - if (c == '\"') { - complete = true; - break; - } else if (c == '\\') { - if (index == json.size()) { - break; - } - - c = json[index++]; - - if (c == '\"') { - s.append('\"'); - } else if (c == '\\') { - s.append('\\'); - } else if (c == '/') { - s.append('/'); - } else if (c == 'b') { - s.append('\b'); - } else if (c == 'f') { - s.append('\f'); - } else if (c == 'n') { - s.append('\n'); - } else if (c == 'r') { - s.append('\r'); - } else if (c == 't') { - s.append('\t'); - } else if (c == 'u') { - int remainingLength = json.size() - index; - if (remainingLength >= 4) { - QString unicodeStr = json.mid(index, 4); - - int symbol = unicodeStr.toInt(0, 16); - - s.append(QChar(symbol)); - - index += 4; - } else { - break; - } - } - } else { - s.append(c); - } - } - - if (!complete) { - success = false; - return QVariant(); - } - - return QVariant(s); - } - - /** - * parseNumber - */ - static QVariant parseNumber(const QString &json, int &index) { - eatWhitespace(json, index); - - int lastIndex = lastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - QString numberStr; - - numberStr = json.mid(index, charLength); - - index = lastIndex + 1; - bool ok; - - if (numberStr.contains('.')) { - return QVariant(numberStr.toDouble(NULL)); - } else if (numberStr.startsWith('-')) { - int i = numberStr.toInt(&ok); - if (!ok) { - qlonglong ll = numberStr.toLongLong(&ok); - return ok ? ll : QVariant(numberStr); - } - return i; - } else { - uint u = numberStr.toUInt(&ok); - if (!ok) { - qulonglong ull = numberStr.toULongLong(&ok); - return ok ? ull : QVariant(numberStr); - } - return u; - } - } - - /** - * lastIndexOfNumber - */ - static int lastIndexOfNumber(const QString &json, int index) { - int lastIndex; - - for(lastIndex = index; lastIndex < json.size(); lastIndex++) { - if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { - break; - } - } - - return lastIndex -1; - } - - /** - * eatWhitespace - */ - static void eatWhitespace(const QString &json, int &index) { - for(; index < json.size(); index++) { - if (QString(" \t\n\r").indexOf(json[index]) == -1) { - break; - } - } - } - - /** - * lookAhead - */ - static int lookAhead(const QString &json, int index) { - int saveIndex = index; - return nextToken(json, saveIndex); - } - - /** - * nextToken - */ - static int nextToken(const QString &json, int &index) { - eatWhitespace(json, index); - - if (index == json.size()) { - return JsonTokenNone; - } - - QChar c = json[index]; - index++; - switch(c.toLatin1()) { - case '{': return JsonTokenCurlyOpen; - case '}': return JsonTokenCurlyClose; - case '[': return JsonTokenSquaredOpen; - case ']': return JsonTokenSquaredClose; - case ',': return JsonTokenComma; - case '"': return JsonTokenString; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case '-': return JsonTokenNumber; - case ':': return JsonTokenColon; - } - index--; // ^ WTF? - - int remainingLength = json.size() - index; - - // True - if (remainingLength >= 4) { - if (json[index] == 't' && json[index + 1] == 'r' && - json[index + 2] == 'u' && json[index + 3] == 'e') { - index += 4; - return JsonTokenTrue; - } - } - - // False - if (remainingLength >= 5) { - if (json[index] == 'f' && json[index + 1] == 'a' && - json[index + 2] == 'l' && json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return JsonTokenFalse; - } - } - - // Null - if (remainingLength >= 4) { - if (json[index] == 'n' && json[index + 1] == 'u' && - json[index + 2] == 'l' && json[index + 3] == 'l') { - index += 4; - return JsonTokenNull; - } - } - - return JsonTokenNone; - } -} //end namespace +/** + * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. + * Copyright (C) 2011 Eeli Reilin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file json.cpp + */ + +#include "json.h" + +namespace QtJson { + static QString sanitizeString(QString str); + static QByteArray join(const QList &list, const QByteArray &sep); + static QVariant parseValue(const QString &json, int &index, bool &success); + static QVariant parseObject(const QString &json, int &index, bool &success); + static QVariant parseArray(const QString &json, int &index, bool &success); + static QVariant parseString(const QString &json, int &index, bool &success); + static QVariant parseNumber(const QString &json, int &index); + static int lastIndexOfNumber(const QString &json, int index); + static void eatWhitespace(const QString &json, int &index); + static int lookAhead(const QString &json, int index); + static int nextToken(const QString &json, int &index); + + template + QByteArray serializeMap(const T &map, bool &success) { + QByteArray str = "{ "; + QList pairs; + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + QByteArray serializedValue = serialize(it.value()); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + + str += join(pairs, ", "); + str += " }"; + return str; + } + + + /** + * parse + */ + QVariant parse(const QString &json) { + bool success = true; + return parse(json, success); + } + + /** + * parse + */ + QVariant parse(const QString &json, bool &success) { + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + QString data = json; + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = parseValue(data, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } + } + + QByteArray serialize(const QVariant &data) { + bool success = true; + return serialize(data, success); + } + + QByteArray serialize(const QVariant &data, bool &success) { + QByteArray str; + success = true; + + if (!data.isValid()) { // invalid or null? + str = "null"; + } else if ((data.type() == QVariant::List) || + (data.type() == QVariant::StringList)) { // variant is a list? + QList values; + const QVariantList list = data.toList(); + Q_FOREACH(const QVariant& v, list) { + QByteArray serializedValue = serialize(v); + if (serializedValue.isNull()) { + success = false; + break; + } + values << serializedValue; + } + + str = "[ " + join( values, ", " ) + " ]"; + } else if (data.type() == QVariant::Hash) { // variant is a hash? + str = serializeMap<>(data.toHash(), success); + } else if (data.type() == QVariant::Map) { // variant is a map? + str = serializeMap<>(data.toMap(), success); + } else if ((data.type() == QVariant::String) || + (data.type() == QVariant::ByteArray)) {// a string or a byte array? + str = sanitizeString(data.toString()).toUtf8(); + } else if (data.type() == QVariant::Double) { // double? + double value = data.toDouble(); + if ((value - value) == 0.0) { + str = QByteArray::number(value, 'g'); + if (!str.contains(".") && ! str.contains("e")) { + str += ".0"; + } + } else { + success = false; + } + } else if (data.type() == QVariant::Bool) { // boolean value? + str = data.toBool() ? "true" : "false"; + } else if (data.type() == QVariant::ULongLong) { // large unsigned number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { // any signed number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { //TODO: this code is never executed + str = QString::number(data.value()).toUtf8(); + } else if (data.canConvert()) { // can value be converted to string? + // this will catch QDate, QDateTime, QUrl, ... + str = sanitizeString(data.toString()).toUtf8(); + } else { + success = false; + } + + if (success) { + return str; + } else { + return QByteArray(); + } + } + + QString serializeStr(const QVariant &data) { + return QString::fromUtf8(serialize(data)); + } + + QString serializeStr(const QVariant &data, bool &success) { + return QString::fromUtf8(serialize(data, success)); + } + + + /** + * \enum JsonToken + */ + enum JsonToken { + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 + }; + + static QString sanitizeString(QString str) { + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); + } + + static QByteArray join(const QList &list, const QByteArray &sep) { + QByteArray res; + Q_FOREACH(const QByteArray &i, list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; + } + + /** + * parseValue + */ + static QVariant parseValue(const QString &json, int &index, bool &success) { + // Determine what kind of data we should parse by + // checking out the upcoming token + switch(lookAhead(json, index)) { + case JsonTokenString: + return parseString(json, index, success); + case JsonTokenNumber: + return parseNumber(json, index); + case JsonTokenCurlyOpen: + return parseObject(json, index, success); + case JsonTokenSquaredOpen: + return parseArray(json, index, success); + case JsonTokenTrue: + nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); + } + + /** + * parseObject + */ + static QVariant parseObject(const QString &json, int &index, bool &success) { + QVariantMap map; + int token; + + // Get rid of the whitespace and increment index + nextToken(json, index); + + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantMap(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } + + // Get the next token + token = nextToken(json, index); + + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } + + // Parse the key/value pair's value + QVariant value = parseValue(json, index, success); + + if (!success) { + return QVariantMap(); + } + + // Assign the value to the key in the map + map[name] = value; + } + } + + // Return the map successfully + return QVariant(map); + } + + /** + * parseArray + */ + static QVariant parseArray(const QString &json, int &index, bool &success) { + QVariantList list; + + nextToken(json, index); + + bool done = false; + while(!done) { + int token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantList(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + nextToken(json, index); + break; + } else { + QVariant value = parseValue(json, index, success); + if (!success) { + return QVariantList(); + } + list.push_back(value); + } + } + + return QVariant(list); + } + + /** + * parseString + */ + static QVariant parseString(const QString &json, int &index, bool &success) { + QString s; + QChar c; + + eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while(!complete) { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); + } + } + + if (!complete) { + success = false; + return QVariant(); + } + + return QVariant(s); + } + + /** + * parseNumber + */ + static QVariant parseNumber(const QString &json, int &index) { + eatWhitespace(json, index); + + int lastIndex = lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + bool ok; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + int i = numberStr.toInt(&ok); + if (!ok) { + qlonglong ll = numberStr.toLongLong(&ok); + return ok ? ll : QVariant(numberStr); + } + return i; + } else { + uint u = numberStr.toUInt(&ok); + if (!ok) { + qulonglong ull = numberStr.toULongLong(&ok); + return ok ? ull : QVariant(numberStr); + } + return u; + } + } + + /** + * lastIndexOfNumber + */ + static int lastIndexOfNumber(const QString &json, int index) { + int lastIndex; + + for(lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { + break; + } + } + + return lastIndex -1; + } + + /** + * eatWhitespace + */ + static void eatWhitespace(const QString &json, int &index) { + for(; index < json.size(); index++) { + if (QString(" \t\n\r").indexOf(json[index]) == -1) { + break; + } + } + } + + /** + * lookAhead + */ + static int lookAhead(const QString &json, int index) { + int saveIndex = index; + return nextToken(json, saveIndex); + } + + /** + * nextToken + */ + static int nextToken(const QString &json, int &index) { + eatWhitespace(json, index); + + if (index == json.size()) { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch(c.toLatin1()) { + case '{': return JsonTokenCurlyOpen; + case '}': return JsonTokenCurlyClose; + case '[': return JsonTokenSquaredOpen; + case ']': return JsonTokenSquaredClose; + case ',': return JsonTokenComma; + case '"': return JsonTokenString; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': return JsonTokenNumber; + case ':': return JsonTokenColon; + } + index--; // ^ WTF? + + int remainingLength = json.size() - index; + + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && + json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } + } + + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && + json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } + } + + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && + json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; + } +} //end namespace diff --git a/src/json.h b/src/json.h index a61f6c85..57842e37 100644 --- a/src/json.h +++ b/src/json.h @@ -1,94 +1,94 @@ -/** - * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. - * Copyright (C) 2011 Eeli Reilin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file json.h - */ - -#ifndef JSON_H -#define JSON_H - -#include -#include - - -/** - * \namespace QtJson - * \brief A JSON data parser - * - * Json parses a JSON data into a QVariant hierarchy. - */ -namespace QtJson { - typedef QVariantMap JsonObject; - typedef QVariantList JsonArray; - - /** - * Parse a JSON string - * - * \param json The JSON data - */ - QVariant parse(const QString &json); - - /** - * Parse a JSON string - * - * \param json The JSON data - * \param success The success of the parsing - */ - QVariant parse(const QString &json, bool &success); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * - * \return QByteArray Textual JSON representation in UTF-8 - */ - QByteArray serialize(const QVariant &data); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QByteArray Textual JSON representation in UTF-8 - */ - QByteArray serialize(const QVariant &data, bool &success); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * - * \return QString Textual JSON representation - */ - QString serializeStr(const QVariant &data); - - /** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QString Textual JSON representation - */ - QString serializeStr(const QVariant &data, bool &success); -} - -#endif //JSON_H +/** + * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. + * Copyright (C) 2011 Eeli Reilin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file json.h + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include + + +/** + * \namespace QtJson + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +namespace QtJson { + typedef QVariantMap JsonObject; + typedef QVariantList JsonArray; + + /** + * Parse a JSON string + * + * \param json The JSON data + */ + QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data, bool &success); +} + +#endif //JSON_H diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 426b3763..9d7e4e28 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -1,290 +1,290 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "loadmechanism.h" -#include "utility.h" -#include "util.h" -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -LoadMechanism::LoadMechanism() - : m_SelectedMechanism(LOAD_MODORGANIZER) -{ -} - -void LoadMechanism::writeHintFile(const QDir &targetDirectory) -{ - QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); - QFile hintFile(hintFilePath); - if (hintFile.exists()) { - hintFile.remove(); - } - if (!hintFile.open(QIODevice::WriteOnly)) { - throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); - } - hintFile.write(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str()); - hintFile.close(); -} - - -void LoadMechanism::removeHintFile(QDir &targetDirectory) -{ - targetDirectory.remove("mo_path.txt"); -} - - -bool LoadMechanism::isDirectLoadingSupported() -{ - if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) { - // oblivion can be loaded directly if it's not the steam variant - QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - QDir gameDir(gamePath); - // test if this is the steam version of oblivion - if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) { - return false; - } - } - - // all other games work afaik - return true; -} - - -bool LoadMechanism::isScriptExtenderSupported() -{ - QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) || - (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) { - return true; - } else { - return false; - } -} - - -bool LoadMechanism::isProxyDLLSupported() -{ - QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - return QFile(targetPath).exists(); -} - - -bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS) -{ - QFile fileLHS(fileNameLHS); - if (!fileLHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("%1 not found").arg(fileNameLHS)); - } - QByteArray dataLHS = fileLHS.readAll(); - QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); - - fileLHS.close(); - - QFile fileRHS(fileNameRHS); - if (!fileRHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("%1 not found").arg(fileNameRHS)); - } - QByteArray dataRHS = fileRHS.readAll(); - QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); - - fileRHS.close(); - - return hashLHS == hashRHS; -} - - -void LoadMechanism::deactivateScriptExtender() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString pluginsDirPath = gameDirectory; - pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); - - QDir pluginsDir(pluginsDirPath); - - QString hookDLLName = ToQString(AppConfig::hookDLLName()); - if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) { - // remove dll from SE plugins directory - if (!pluginsDir.remove(hookDLLName)) { - throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName))); - } - } - - removeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what()); - } -} - - -void LoadMechanism::deactivateProxyDLL() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString targetPath = gameDirectory; - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - - QFile targetDLL(targetPath); - if (targetDLL.exists()) { - QString origFile = gameDirectory.mid(0).append(ToQString(AppConfig::proxyDLLOrig())); - // determine if a proxy-dll is installed - // this is a very crude way of making this decision but it should be good enough - if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { - // remove proxy-dll - if (!targetDLL.remove()) { - throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); - } else if (!QFile::rename(origFile, targetPath)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); - } - } - } - - QDir dir(gameDirectory); - removeHintFile(dir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activateScriptExtender() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString pluginsDirPath = gameDirectory; - pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); - - QDir pluginsDir(pluginsDirPath); - - if (!pluginsDir.exists()) { - pluginsDir.mkpath("."); - } - - QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName())); - QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName())); - - QFile dllFile(targetPath); - - if (dllFile.exists()) { - // may be outdated - if (!hashIdentical(targetPath, hookDLLPath)) { - dllFile.remove(); - } - } - - if (!dllFile.exists()) { - // install dll to SE plugins - if (!QFile::copy(hookDLLPath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath)); - } - } - writeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what()); - } -} - - -void LoadMechanism::activateProxyDLL() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString targetPath = gameDirectory; - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - QFile targetDLL(targetPath); - if (!targetDLL.exists()) { - return; - } - - QString sourcePath = QDir::fromNativeSeparators( - ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::proxyDLLSource())); - - // this is a very crude way of making this decision but it should be good enough - if (targetDLL.size() < 24576) { - // determine if a proxy-dll is already installed and if so, if it's the right one - if (!hashIdentical(targetPath, sourcePath)) { - // wrong proxy dll, probably outdated. delete and install the new one - if (!QFile::remove(targetPath)) { - throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } - } // otherwise the proxy-dll is already the right one - } else { - // no proxy dll installed yet. move the original and insert proxy-dll - - QString origFile = gameDirectory; - origFile.append("/").append(ToQString(AppConfig::proxyDLLOrig())); - - if (QFile(origFile).exists()) { - // orig-file exists. this may happen if the steam-api was updated or the user messed with the - // dlls. - if (!QFile::remove(origFile)) { - throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); - } - } - if (!QFile::rename(targetPath, origFile)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } - } - writeHintFile(QDir(gameDirectory)); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activate(EMechanism mechanism) -{ - switch (mechanism) { - case LOAD_MODORGANIZER: { - deactivateProxyDLL(); - deactivateScriptExtender(); - } break; - case LOAD_SCRIPTEXTENDER: { - deactivateProxyDLL(); - activateScriptExtender(); - } break; - case LOAD_PROXYDLL: { - deactivateScriptExtender(); - activateProxyDLL(); - } break; - } -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "loadmechanism.h" +#include "utility.h" +#include "util.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +LoadMechanism::LoadMechanism() + : m_SelectedMechanism(LOAD_MODORGANIZER) +{ +} + +void LoadMechanism::writeHintFile(const QDir &targetDirectory) +{ + QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); + QFile hintFile(hintFilePath); + if (hintFile.exists()) { + hintFile.remove(); + } + if (!hintFile.open(QIODevice::WriteOnly)) { + throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); + } + hintFile.write(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str()); + hintFile.close(); +} + + +void LoadMechanism::removeHintFile(QDir &targetDirectory) +{ + targetDirectory.remove("mo_path.txt"); +} + + +bool LoadMechanism::isDirectLoadingSupported() +{ + if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) { + // oblivion can be loaded directly if it's not the steam variant + QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + QDir gameDir(gamePath); + // test if this is the steam version of oblivion + if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) { + return false; + } + } + + // all other games work afaik + return true; +} + + +bool LoadMechanism::isScriptExtenderSupported() +{ + QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) || + (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) { + return true; + } else { + return false; + } +} + + +bool LoadMechanism::isProxyDLLSupported() +{ + QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + return QFile(targetPath).exists(); +} + + +bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS) +{ + QFile fileLHS(fileNameLHS); + if (!fileLHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameLHS)); + } + QByteArray dataLHS = fileLHS.readAll(); + QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); + + fileLHS.close(); + + QFile fileRHS(fileNameRHS); + if (!fileRHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameRHS)); + } + QByteArray dataRHS = fileRHS.readAll(); + QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); + + fileRHS.close(); + + return hashLHS == hashRHS; +} + + +void LoadMechanism::deactivateScriptExtender() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString pluginsDirPath = gameDirectory; + pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); + + QDir pluginsDir(pluginsDirPath); + + QString hookDLLName = ToQString(AppConfig::hookDLLName()); + if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) { + // remove dll from SE plugins directory + if (!pluginsDir.remove(hookDLLName)) { + throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName))); + } + } + + removeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what()); + } +} + + +void LoadMechanism::deactivateProxyDLL() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString targetPath = gameDirectory; + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + + QFile targetDLL(targetPath); + if (targetDLL.exists()) { + QString origFile = gameDirectory.mid(0).append(ToQString(AppConfig::proxyDLLOrig())); + // determine if a proxy-dll is installed + // this is a very crude way of making this decision but it should be good enough + if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { + // remove proxy-dll + if (!targetDLL.remove()) { + throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); + } else if (!QFile::rename(origFile, targetPath)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); + } + } + } + + QDir dir(gameDirectory); + removeHintFile(dir); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); + } +} + + +void LoadMechanism::activateScriptExtender() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString pluginsDirPath = gameDirectory; + pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); + + QDir pluginsDir(pluginsDirPath); + + if (!pluginsDir.exists()) { + pluginsDir.mkpath("."); + } + + QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName())); + QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName())); + + QFile dllFile(targetPath); + + if (dllFile.exists()) { + // may be outdated + if (!hashIdentical(targetPath, hookDLLPath)) { + dllFile.remove(); + } + } + + if (!dllFile.exists()) { + // install dll to SE plugins + if (!QFile::copy(hookDLLPath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath)); + } + } + writeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what()); + } +} + + +void LoadMechanism::activateProxyDLL() +{ + try { + QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); + + QString targetPath = gameDirectory; + targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); + QFile targetDLL(targetPath); + if (!targetDLL.exists()) { + return; + } + + QString sourcePath = QDir::fromNativeSeparators( + ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::proxyDLLSource())); + + // this is a very crude way of making this decision but it should be good enough + if (targetDLL.size() < 24576) { + // determine if a proxy-dll is already installed and if so, if it's the right one + if (!hashIdentical(targetPath, sourcePath)) { + // wrong proxy dll, probably outdated. delete and install the new one + if (!QFile::remove(targetPath)) { + throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); + } + } // otherwise the proxy-dll is already the right one + } else { + // no proxy dll installed yet. move the original and insert proxy-dll + + QString origFile = gameDirectory; + origFile.append("/").append(ToQString(AppConfig::proxyDLLOrig())); + + if (QFile(origFile).exists()) { + // orig-file exists. this may happen if the steam-api was updated or the user messed with the + // dlls. + if (!QFile::remove(origFile)) { + throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); + } + } + if (!QFile::rename(targetPath, origFile)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); + } + } + writeHintFile(QDir(gameDirectory)); + } catch (const std::exception &e) { + QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what()); + } +} + + +void LoadMechanism::activate(EMechanism mechanism) +{ + switch (mechanism) { + case LOAD_MODORGANIZER: { + deactivateProxyDLL(); + deactivateScriptExtender(); + } break; + case LOAD_SCRIPTEXTENDER: { + deactivateProxyDLL(); + activateScriptExtender(); + } break; + case LOAD_PROXYDLL: { + deactivateScriptExtender(); + activateProxyDLL(); + } break; + } +} + diff --git a/src/loadmechanism.h b/src/loadmechanism.h index 90ed5f4a..43a8dd6c 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef LOADMECHANISM_H #define LOADMECHANISM_H diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 065d9270..c688fff6 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -1,75 +1,75 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "lockeddialog.h" -#include "ui_lockeddialog.h" -#include - - -LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) - : QDialog(parent) - , ui(new Ui::LockedDialog) - , m_UnlockClicked(false) -{ - ui->setupUi(this); - - this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); - - if (parent != NULL) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } - - if (text.length() > 0) { - ui->label->setText(text); - } - if (!unlockButton) { - ui->unlockButton->hide(); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != NULL) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialog::on_unlockButton_clicked() -{ - m_UnlockClicked = true; -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "lockeddialog.h" +#include "ui_lockeddialog.h" +#include + + +LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) + : QDialog(parent) + , ui(new Ui::LockedDialog) + , m_UnlockClicked(false) +{ + ui->setupUi(this); + + this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); + + if (parent != NULL) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } + + if (text.length() > 0) { + ui->label->setText(text); + } + if (!unlockButton) { + ui->unlockButton->hide(); + } +} + +LockedDialog::~LockedDialog() +{ + delete ui; +} + + +void LockedDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + + +void LockedDialog::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != NULL) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialog::on_unlockButton_clicked() +{ + m_UnlockClicked = true; +} diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 0f3b29b0..60af425d 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -1,67 +1,67 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H - -#include - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public QDialog -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); - ~LockedDialog(); - - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockClicked() const { return m_UnlockClicked; } - - void setProcessName(const QString &name); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private slots: - - void on_unlockButton_clicked(); - -private: - Ui::LockedDialog *ui; - bool m_UnlockClicked; -}; - -#endif // LOCKEDDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef LOCKEDDIALOG_H +#define LOCKEDDIALOG_H + +#include + +namespace Ui { + class LockedDialog; +} + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialog : public QDialog +{ + Q_OBJECT + +public: + explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); + ~LockedDialog(); + + /** + * @brief see if the user clicked the unlock-button + * + * @return true if the user clicked the unlock button + **/ + bool unlockClicked() const { return m_UnlockClicked; } + + void setProcessName(const QString &name); + +protected: + + virtual void resizeEvent(QResizeEvent *event); + +private slots: + + void on_unlockButton_clicked(); + +private: + Ui::LockedDialog *ui; + bool m_UnlockClicked; +}; + +#endif // LOCKEDDIALOG_H diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index ef0549b0..17417bf9 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -1,261 +1,261 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "logbuffer.h" -#include "report.h" -#include -#include -#include -#include -#include - -QScopedPointer LogBuffer::s_Instance; -QMutex LogBuffer::s_Mutex; - - -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName) - : QAbstractItemModel(NULL), m_OutFileName(outputFileName), m_ShutDown(false), - m_MinMsgType(minMsgType), m_NumMessages(0) -{ - m_Messages.resize(messageCount); -} - -LogBuffer::~LogBuffer() -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - qInstallMessageHandler(0); -#else - qInstallMsgHandler(0); -#endif -// if (!m_ShutDown) { - write(); -// } -} - - -void LogBuffer::logMessage(QtMsgType type, const QString &message) -{ - if (type >= m_MinMsgType) { - Message msg = { type, QTime::currentTime(), message }; - if (m_NumMessages < m_Messages.size()) { - beginInsertRows(QModelIndex(), m_NumMessages, m_NumMessages + 1); - } - m_Messages.at(m_NumMessages % m_Messages.size()) = msg; - if (m_NumMessages < m_Messages.size()) { - endInsertRows(); - } else { - emit dataChanged(createIndex(0, 0), createIndex(m_Messages.size(), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } - } -} - - -void LogBuffer::write() const -{ - if (m_NumMessages == 0) { - return; - } - - DWORD lastError = ::GetLastError(); - - QFile file(m_OutFileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString())); - return; - } - - unsigned int i = (m_NumMessages > m_Messages.size()) ? m_NumMessages - m_Messages.size() - : 0U; - for (; i < m_NumMessages; ++i) { - file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); - file.write("\r\n"); - } - ::SetLastError(lastError); -} - - -void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outputFileName) -{ - QMutexLocker guard(&s_Mutex); - - if (!s_Instance.isNull()) { - s_Instance.reset(); - } - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - qInstallMessageHandler(LogBuffer::log); -#else - qInstallMsgHandler(LogBuffer::log); -#endif -} - -char LogBuffer::msgTypeID(QtMsgType type) -{ - switch (type) { - case QtDebugMsg: return 'D'; - case QtWarningMsg: return 'W'; - case QtCriticalMsg: return 'C'; - case QtFatalMsg: return 'F'; - default: return '?'; - } -} - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message) -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); - } -// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message)); - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message)); - } else { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qPrintable(message)); - } - fflush(stdout); -} - -#else - -void LogBuffer::log(QtMsgType type, const char *message) -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); - } - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message); - fflush(stdout); -} - -#endif - - -QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogBuffer::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogBuffer::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - else - return std::min(m_NumMessages, m_Messages.size()); -} - -int LogBuffer::columnCount(const QModelIndex&) const -{ - return 2; -} - - -QVariant LogBuffer::data(const QModelIndex &index, int role) const -{ - unsigned offset = m_NumMessages < m_Messages.size() ? 0 - : m_NumMessages - m_Messages.size(); - unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); - switch (role) { - case Qt::DisplayRole: { - if (index.column() == 0) { - return m_Messages.at(msgIndex).time; - } else if (index.column() == 1) { - const QString &msg = m_Messages.at(msgIndex).message; - if (msg.length() < 200) { - return msg; - } else { - return msg.mid(0, 200) + "..."; - } - } - } break; - case Qt::DecorationRole: { - if (index.column() == 1) { - switch (m_Messages.at(msgIndex).type) { - case QtDebugMsg: return QIcon(":/MO/gui/information"); - case QtWarningMsg: return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: return QIcon(":/MO/gui/important"); - case QtFatalMsg: return QIcon(":/MO/gui/problem"); - } - } - } break; - case Qt::UserRole: { - if (index.column() == 1) { - switch (m_Messages.at(msgIndex).type) { - case QtDebugMsg: return "D"; - case QtWarningMsg: return "W"; - case QtCriticalMsg: return "C"; - case QtFatalMsg: return "F"; - } - } - } break; - } - return QVariant(); -} - -void LogBuffer::writeNow() -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->write(); - } -} - - -void LogBuffer::cleanQuit() -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->m_ShutDown = true; - } -} - -void log(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} - - - -QString LogBuffer::Message::toString() const -{ - return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "logbuffer.h" +#include "report.h" +#include +#include +#include +#include +#include + +QScopedPointer LogBuffer::s_Instance; +QMutex LogBuffer::s_Mutex; + + +LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName) + : QAbstractItemModel(NULL), m_OutFileName(outputFileName), m_ShutDown(false), + m_MinMsgType(minMsgType), m_NumMessages(0) +{ + m_Messages.resize(messageCount); +} + +LogBuffer::~LogBuffer() +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + qInstallMessageHandler(0); +#else + qInstallMsgHandler(0); +#endif +// if (!m_ShutDown) { + write(); +// } +} + + +void LogBuffer::logMessage(QtMsgType type, const QString &message) +{ + if (type >= m_MinMsgType) { + Message msg = { type, QTime::currentTime(), message }; + if (m_NumMessages < m_Messages.size()) { + beginInsertRows(QModelIndex(), m_NumMessages, m_NumMessages + 1); + } + m_Messages.at(m_NumMessages % m_Messages.size()) = msg; + if (m_NumMessages < m_Messages.size()) { + endInsertRows(); + } else { + emit dataChanged(createIndex(0, 0), createIndex(m_Messages.size(), 0)); + } + ++m_NumMessages; + if (type >= QtCriticalMsg) { + write(); + } + } +} + + +void LogBuffer::write() const +{ + if (m_NumMessages == 0) { + return; + } + + DWORD lastError = ::GetLastError(); + + QFile file(m_OutFileName); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString())); + return; + } + + unsigned int i = (m_NumMessages > m_Messages.size()) ? m_NumMessages - m_Messages.size() + : 0U; + for (; i < m_NumMessages; ++i) { + file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); + file.write("\r\n"); + } + ::SetLastError(lastError); +} + + +void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outputFileName) +{ + QMutexLocker guard(&s_Mutex); + + if (!s_Instance.isNull()) { + s_Instance.reset(); + } + s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + qInstallMessageHandler(LogBuffer::log); +#else + qInstallMsgHandler(LogBuffer::log); +#endif +} + +char LogBuffer::msgTypeID(QtMsgType type) +{ + switch (type) { + case QtDebugMsg: return 'D'; + case QtWarningMsg: return 'W'; + case QtCriticalMsg: return 'C'; + case QtFatalMsg: return 'F'; + default: return '?'; + } +} + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + +void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->logMessage(type, message); + } +// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message)); + if (type == QtDebugMsg) { + fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message)); + } else { + fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), + context.file, context.line, qPrintable(message)); + } + fflush(stdout); +} + +#else + +void LogBuffer::log(QtMsgType type, const char *message) +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->logMessage(type, message); + } + fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message); + fflush(stdout); +} + +#endif + + +QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + +QModelIndex LogBuffer::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int LogBuffer::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + else + return std::min(m_NumMessages, m_Messages.size()); +} + +int LogBuffer::columnCount(const QModelIndex&) const +{ + return 2; +} + + +QVariant LogBuffer::data(const QModelIndex &index, int role) const +{ + unsigned offset = m_NumMessages < m_Messages.size() ? 0 + : m_NumMessages - m_Messages.size(); + unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); + switch (role) { + case Qt::DisplayRole: { + if (index.column() == 0) { + return m_Messages.at(msgIndex).time; + } else if (index.column() == 1) { + const QString &msg = m_Messages.at(msgIndex).message; + if (msg.length() < 200) { + return msg; + } else { + return msg.mid(0, 200) + "..."; + } + } + } break; + case Qt::DecorationRole: { + if (index.column() == 1) { + switch (m_Messages.at(msgIndex).type) { + case QtDebugMsg: return QIcon(":/MO/gui/information"); + case QtWarningMsg: return QIcon(":/MO/gui/warning"); + case QtCriticalMsg: return QIcon(":/MO/gui/important"); + case QtFatalMsg: return QIcon(":/MO/gui/problem"); + } + } + } break; + case Qt::UserRole: { + if (index.column() == 1) { + switch (m_Messages.at(msgIndex).type) { + case QtDebugMsg: return "D"; + case QtWarningMsg: return "W"; + case QtCriticalMsg: return "C"; + case QtFatalMsg: return "F"; + } + } + } break; + } + return QVariant(); +} + +void LogBuffer::writeNow() +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->write(); + } +} + + +void LogBuffer::cleanQuit() +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->m_ShutDown = true; + } +} + +void log(const char *format, ...) +{ + va_list argList; + va_start(argList, format); + + static const int BUFFERSIZE = 1000; + + char buffer[BUFFERSIZE + 1]; + buffer[BUFFERSIZE] = '\0'; + + vsnprintf(buffer, BUFFERSIZE, format, argList); + + qCritical("%s", buffer); + + va_end(argList); +} + + + +QString LogBuffer::Message::toString() const +{ + return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message); +} diff --git a/src/logbuffer.h b/src/logbuffer.h index caada1d9..f69d33fa 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -1,99 +1,99 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef LOGBUFFER_H -#define LOGBUFFER_H - -#include -#include -#include -#include -#include -#include - - -class LogBuffer : public QAbstractItemModel -{ - Q_OBJECT - -public: - - static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); -#else - static void log(QtMsgType type, const char *message); -#endif - - static void writeNow(); - static void cleanQuit(); - - static LogBuffer *instance() { return s_Instance.data(); } - -public: - - virtual ~LogBuffer(); - - void logMessage(QtMsgType type, const QString &message); - - // QAbstractItemModel interface -public: - QModelIndex index(int row, int column, const QModelIndex &parent) const; - QModelIndex parent(const QModelIndex &child) const; - int rowCount(const QModelIndex &parent) const; - int columnCount(const QModelIndex &parent) const; - QVariant data(const QModelIndex &index, int role) const; - -signals: - -public slots: - -private: - - explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); - LogBuffer(const LogBuffer &reference); // not implemented - LogBuffer &operator=(const LogBuffer &reference); // not implemented - - void write() const; - - static char msgTypeID(QtMsgType type); - -private: - - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; - -private: - - static QScopedPointer s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - unsigned int m_NumMessages; - std::vector m_Messages; - -}; - -#endif // LOGBUFFER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef LOGBUFFER_H +#define LOGBUFFER_H + +#include +#include +#include +#include +#include +#include + + +class LogBuffer : public QAbstractItemModel +{ + Q_OBJECT + +public: + + static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); +#else + static void log(QtMsgType type, const char *message); +#endif + + static void writeNow(); + static void cleanQuit(); + + static LogBuffer *instance() { return s_Instance.data(); } + +public: + + virtual ~LogBuffer(); + + void logMessage(QtMsgType type, const QString &message); + + // QAbstractItemModel interface +public: + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + +signals: + +public slots: + +private: + + explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); + LogBuffer(const LogBuffer &reference); // not implemented + LogBuffer &operator=(const LogBuffer &reference); // not implemented + + void write() const; + + static char msgTypeID(QtMsgType type); + +private: + + struct Message { + QtMsgType type; + QTime time; + QString message; + QString toString() const; + }; + +private: + + static QScopedPointer s_Instance; + static QMutex s_Mutex; + + QString m_OutFileName; + bool m_ShutDown; + QtMsgType m_MinMsgType; + unsigned int m_NumMessages; + std::vector m_Messages; + +}; + +#endif // LOGBUFFER_H diff --git a/src/loghighlighter.cpp b/src/loghighlighter.cpp index 1c95fc2f..f43e5662 100644 --- a/src/loghighlighter.cpp +++ b/src/loghighlighter.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #include "loghighlighter.h" #include diff --git a/src/loghighlighter.h b/src/loghighlighter.h index 127d3228..55c15142 100644 --- a/src/loghighlighter.h +++ b/src/loghighlighter.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef LOGHIGHLIGHTER_H #define LOGHIGHLIGHTER_H diff --git a/src/main.cpp b/src/main.cpp index ecd12345..1ae3e932 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,581 +1,581 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - - -#ifdef LEAK_CHECK_WITH_VLD -#include -#include -#endif // LEAK_CHECK_WITH_VLD - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#include -#include -#include "mainwindow.h" -#include "report.h" -#include "modlist.h" -#include "profile.h" -#include "gameinfo.h" -#include "fallout3info.h" -#include "falloutnvinfo.h" -#include "oblivioninfo.h" -#include "skyriminfo.h" -#include "spawn.h" -#include "executableslist.h" -#include "singleinstance.h" -#include "utility.h" -#include "helper.h" -#include "logbuffer.h" -#include "selectiondialog.h" -#include "moapplication.h" -#include "tutorialmanager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") - - -using namespace MOBase; -using namespace MOShared; - - -// set up required folders (for a first install or after an update or to fix a broken installation) -bool bootstrap() -{ - GameInfo &gameInfo = GameInfo::instance(); - - // remove the temporary backup directory in case we're restarting after an update - QString moDirectory = QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory())); - QString backupDirectory = moDirectory.mid(0).append("/update_backup"); - if (QDir(backupDirectory).exists()) { - shellDelete(QStringList(backupDirectory)); - } - - // cycle logfile - removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name); - - // create organizer directories - QString dirNames[] = { - QDir::fromNativeSeparators(ToQString(gameInfo.getProfilesDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getModsDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) - }; - static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); - - // optimistic run: try to simply create the directories: - for (int i = 0; i < NUM_DIRECTORIES; ++i) { - if (!QDir(dirNames[i]).exists()) { - QDir().mkdir(dirNames[i]); - } - } - - // verify all directories exist and are writable, - // otherwise invoke the helper to create them and make them writable - for (int i = 0; i < NUM_DIRECTORIES; ++i) { - QFileInfo fileInfo(dirNames[i]); - if (!fileInfo.exists() || !fileInfo.isWritable()) { - if (QMessageBox::question(NULL, QObject::tr("Permissions required"), - QObject::tr("The current user account doesn't have the required access rights to run " - "Mod Organizer. The neccessary changes can be made automatically (the MO directory " - "will be made writable for the current user account). You will be asked to run " - "\"helper.exe\" with administrative rights."), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - if (!Helper::init(GameInfo::instance().getOrganizerDirectory())) { - return false; - } - } else { - return false; - } - // no matter which directory didn't exist/wasn't writable, the helper - // should have created them all so we can break the loop - break; - } - } - - // verify the hook-dll exists - QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()); - - if (::GetModuleHandleW(ToWString(dllName).c_str()) != NULL) { - throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)"); - } - - HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str()); - if (dllMod == NULL) { - throw windows_error("hook.dll is missing or invalid"); - } - ::FreeLibrary(dllMod); - - return true; -} - - -void cleanupDir() -{ - // files from previous versions of MO that are no longer - // required (in that location) - QString fileNames[] = { - "ModOrganiser.exe", - "ModOrganizer.log", - "ModOrganizer.log.old", - "7z.dll", - "mo1.dll", - "mo_archive.dll", - "mo_helper.exe", - "msvcp90.dll", - "msvcr90.dll", - "phonon4.dll", - "QtCore4.dll", - "QtGui4.dll", - "QtNetwork4.dll", - "QtXml4.dll", - "QtWebKit4.dll", - "qjpeg4.dll", - "NCC/GamebryoBase.dll", - "plugins/helloWorld.dll", - "plugins/testnexus.py" - }; - - static const int NUM_FILES = sizeof(fileNames) / sizeof(QString); - - qDebug("cleaning up unused files"); - - for (int i = 0; i < NUM_FILES; ++i) { - if (QFile::remove(QApplication::applicationDirPath().append("/").append(fileNames[i]))) { - qDebug("%s removed in cleanup", - QApplication::applicationDirPath().append("/").append(fileNames[i]).toUtf8().constData()); - } - } -} - - -bool isNxmLink(const QString &link) -{ - return link.left(6).toLower() == "nxm://"; -} - -LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) -{ - typedef BOOL (WINAPI *FuncMiniDumpWriteDump)(HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType, - const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam, - const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam, - const PMINIDUMP_CALLBACK_INFORMATION callbackParam); - LONG result = EXCEPTION_CONTINUE_SEARCH; - - HMODULE dbgDLL = ::LoadLibrary(L"dbghelp.dll"); - - static const int errorLen = 200; - char errorBuffer[errorLen + 1]; - memset(errorBuffer, '\0', errorLen + 1); - - if (dbgDLL) { - FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump"); - if (funcDump) { - - if (QMessageBox::question(NULL, QObject::tr("Woops"), - QObject::tr("ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file " - "(%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. " - "Please include a short description of what you were doing when the crash happened").arg(qApp->applicationFilePath().append(".dmp")), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp")); - - HANDLE dumpFile = ::CreateFile(dumpName.c_str(), - GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (dumpFile != INVALID_HANDLE_VALUE) { - _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; - exceptionInfo.ThreadId = ::GetCurrentThreadId(); - exceptionInfo.ExceptionPointers = exceptionPtrs; - exceptionInfo.ClientPointers = NULL; - - BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile, MiniDumpNormal, &exceptionInfo, NULL, NULL); - - ::CloseHandle(dumpFile); - if (success) { - return EXCEPTION_EXECUTE_HANDLER; - } - _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); - } else { - _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); - } - } else { - return result; - } - } else { - _snprintf(errorBuffer, errorLen, "dbghelp.dll outdated"); - } - } else { - _snprintf(errorBuffer, errorLen, "dbghelp.dll not found"); - } - - QMessageBox::critical(NULL, QObject::tr("Woops"), - QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer)); - return result; -} - -void registerMetaTypes() -{ - registerExecutable(); -} - -bool HaveWriteAccess(const std::wstring &path) -{ - bool writable = false; - - const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - - DWORD length = 0; - if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, NULL, NULL, &length) - && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { - std::string tempBuffer; - tempBuffer.reserve(length); - PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); - if (security - && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { - HANDLE token = NULL; - const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; - if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { - if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { - throw std::runtime_error("Unable to get any thread or process token"); - } - } - - HANDLE impersonatedToken = NULL; - if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { - GENERIC_MAPPING mapping = { 0xFFFFFFFF }; - mapping.GenericRead = FILE_GENERIC_READ; - mapping.GenericWrite = FILE_GENERIC_WRITE; - mapping.GenericExecute = FILE_GENERIC_EXECUTE; - mapping.GenericAll = FILE_ALL_ACCESS; - - DWORD genericAccessRights = FILE_GENERIC_WRITE; - ::MapGenericMask(&genericAccessRights, &mapping); - - PRIVILEGE_SET privileges = { 0 }; - DWORD grantedAccess = 0; - DWORD privilegesLength = sizeof(privileges); - BOOL result = 0; - if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { - writable = result != 0; - } - ::CloseHandle(impersonatedToken); - } - - ::CloseHandle(token); - } - } - return writable; -} - - -int main(int argc, char *argv[]) -{ - MOApplication application(argc, argv); - - application.addLibraryPath(application.applicationDirPath() + "/dlls"); - - SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - - if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) { - QStringList arguments = application.arguments(); - arguments.pop_front(); - ::ShellExecuteW( NULL - , L"runas" - , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str() - , ToWString(arguments.join(" ")).c_str() - , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL); - return 1; - } - LogBuffer::init(100, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); - - qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); - qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); - QPixmap pixmap(":/MO/gui/splash"); - QSplashScreen splash(pixmap); - splash.show(); - - { // extend path to include dll directory so plugins don't need a manifest - static const int BUFSIZE = 4096; - - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } - - std::tstring newPath(oldPath.get()); - newPath += TEXT(";"); - newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str(); - newPath += TEXT("\\dlls"); - - ::SetEnvironmentVariable(TEXT("PATH"), newPath.c_str()); - } - - registerMetaTypes(); - - QStringList arguments = application.arguments(); - - bool forcePrimary = false; - if (arguments.contains("update")) { - arguments.removeAll("update"); - forcePrimary = true; - } - - try { - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { - if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending download message"); - instance.sendMessage(arguments.at(1)); - return 0; - } else if (arguments.size() == 1) { - QMessageBox::information(NULL, QObject::tr("Mod Organizer"), QObject::tr("An instance of Mod Organizer is already running")); - return 0; - } - } // we continue for the primary instance OR if MO has been called with parameters - - - // TODO: this should be MAX_PATH_UNICODE! - wchar_t moPath[MAX_PATH]; - memset(moPath, 0, sizeof(TCHAR) * MAX_PATH); - ::GetModuleFileNameW(NULL, moPath, MAX_PATH); - wchar_t *lastBSlash = wcsrchr(moPath, TEXT('\\')); - if (lastBSlash != NULL) { - *lastBSlash = TEXT('\0'); - } - QSettings settings(ToQString(std::wstring(moPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat); - - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - - bool done = false; - while (!done) { - if (!GameInfo::init(moPath, ToWString(QDir::toNativeSeparators(gamePath)))) { - if (!gamePath.isEmpty()) { - reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " - "the game binary and its launcher.").arg(gamePath)); - } - SelectionDialog selection(QObject::tr("Please select the game to manage"), NULL); - - { // add options - QString skyrimPath = ToQString(SkyrimInfo::getRegPathStatic()); - QString falloutNVPath = ToQString(FalloutNVInfo::getRegPathStatic()); - QString fallout3Path = ToQString(Fallout3Info::getRegPathStatic()); - QString oblivionPath = ToQString(OblivionInfo::getRegPathStatic()); - if (skyrimPath.length() != 0) { - selection.addChoice(QString("Skyrim"), skyrimPath, skyrimPath); - } - if (falloutNVPath.length() != 0) { - selection.addChoice(QString("Fallout NV"), falloutNVPath, falloutNVPath); - } - if (fallout3Path.length() != 0) { - selection.addChoice(QString("Fallout 3"), fallout3Path, fallout3Path); - } - if (oblivionPath.length() != 0) { - selection.addChoice(QString("Oblivion"), oblivionPath, oblivionPath); - } - - selection.addChoice(QString("Browse..."), QString(), QString()); - } - - if (selection.exec() == QDialog::Rejected) { - gamePath = ""; - done = true; - } else { - gamePath = QDir::cleanPath(selection.getChoiceData().toString()); - if (gamePath.isEmpty()) { - gamePath = QFileDialog::getExistingDirectory(NULL, QObject::tr("Please select the game to manage"), QString(), - QFileDialog::ShowDirsOnly); - } - } - } else { - done = true; - gamePath = ToQString(GameInfo::instance().getGameDirectory()); - } - } - - if (gamePath.isEmpty()) { - // game not found and user canceled - return -1; - } else if (gamePath.length() != 0) { - // user selected a folder and game was initialised with it - settings.setValue("gamePath", gamePath.toUtf8().constData()); - } - - int edition = 0; - if (settings.contains("game_edition")) { - edition = settings.value("game_edition").toInt(); - } else { - std::vector editions = GameInfo::instance().getSteamVariants(); - if (editions.size() < 2) { - edition = 0; - } else { - SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL); - int index = 0; - for (auto iter = editions.begin(); iter != editions.end(); ++iter) { - selection.addChoice(ToQString(*iter), "", index++); - } - if (selection.exec() == QDialog::Rejected) { - return -1; - } else { - settings.setValue("game_edition", selection.getChoiceData().toInt()); - } - } - } - - qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); - - ExecutablesList executablesList; - - executablesList.init(); - - if (!bootstrap()) { // requires gameinfo to be initialised! - return -1; - } - - cleanupDir(); - - qDebug("setting up configured executables"); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - CloseMOStyle closeMO = settings.value("closeOnStart").toBool() ? DEFAULT_CLOSE : DEFAULT_STAY; - executablesList.addExecutable(settings.value("title").toString(), - settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - closeMO, - settings.value("steamAppID", "").toString(), - settings.value("custom", true).toBool(), - settings.value("toolbar", false).toBool()); - } - - settings.endArray(); - - qDebug("initializing tutorials"); - TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/")); - - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { - // disable invalid stylesheet - settings.setValue("Settings/style", ""); - } - - int res = 1; - { // scope to control lifetime of mainwindow - // set up main window and its data structures - MainWindow mainWindow(argv[0], settings); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); - - mainWindow.setExecutablesList(executablesList); - mainWindow.readSettings(); - - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); - - { // see if there is a profile on the command line - int profileIndex = arguments.indexOf("-p", 1); - if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); - selectedProfileName = arguments.at(profileIndex + 1); - } - arguments.removeAt(profileIndex); - arguments.removeAt(profileIndex); - } - if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); - } else { - qDebug("configured profile: %s", qPrintable(selectedProfileName)); - } - - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) { - 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 { - mainWindow.startApplication(exeName, arguments, QString(), selectedProfileName); - } catch (const std::exception &e) { - reportError(QObject::tr("failed to start application: %1").arg(e.what())); - } - - return 0; - } - - mainWindow.createFirstProfile(); - - if (selectedProfileName.length() != 0) { - if (!mainWindow.setCurrentProfile(selectedProfileName)) { - mainWindow.setCurrentProfile(1); - qWarning("failed to set profile: %s", - selectedProfileName.toUtf8().constData()); - } - } else { - mainWindow.setCurrentProfile(1); - } - - qDebug("displaying main window"); - mainWindow.show(); - - if ((arguments.size() > 1) && - (isNxmLink(arguments.at(1)))) { - qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - mainWindow.externalMessage(arguments.at(1)); - } - splash.finish(&mainWindow); - res = application.exec(); - } - return res; - } catch (const std::exception &e) { - reportError(e.what()); - return 1; - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + + +#ifdef LEAK_CHECK_WITH_VLD +#include +#include +#endif // LEAK_CHECK_WITH_VLD + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#include +#include "mainwindow.h" +#include "report.h" +#include "modlist.h" +#include "profile.h" +#include "gameinfo.h" +#include "fallout3info.h" +#include "falloutnvinfo.h" +#include "oblivioninfo.h" +#include "skyriminfo.h" +#include "spawn.h" +#include "executableslist.h" +#include "singleinstance.h" +#include "utility.h" +#include "helper.h" +#include "logbuffer.h" +#include "selectiondialog.h" +#include "moapplication.h" +#include "tutorialmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") + + +using namespace MOBase; +using namespace MOShared; + + +// set up required folders (for a first install or after an update or to fix a broken installation) +bool bootstrap() +{ + GameInfo &gameInfo = GameInfo::instance(); + + // remove the temporary backup directory in case we're restarting after an update + QString moDirectory = QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory())); + QString backupDirectory = moDirectory.mid(0).append("/update_backup"); + if (QDir(backupDirectory).exists()) { + shellDelete(QStringList(backupDirectory)); + } + + // cycle logfile + removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name); + + // create organizer directories + QString dirNames[] = { + QDir::fromNativeSeparators(ToQString(gameInfo.getProfilesDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getModsDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), + QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) + }; + static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); + + // optimistic run: try to simply create the directories: + for (int i = 0; i < NUM_DIRECTORIES; ++i) { + if (!QDir(dirNames[i]).exists()) { + QDir().mkdir(dirNames[i]); + } + } + + // verify all directories exist and are writable, + // otherwise invoke the helper to create them and make them writable + for (int i = 0; i < NUM_DIRECTORIES; ++i) { + QFileInfo fileInfo(dirNames[i]); + if (!fileInfo.exists() || !fileInfo.isWritable()) { + if (QMessageBox::question(NULL, QObject::tr("Permissions required"), + QObject::tr("The current user account doesn't have the required access rights to run " + "Mod Organizer. The neccessary changes can be made automatically (the MO directory " + "will be made writable for the current user account). You will be asked to run " + "\"helper.exe\" with administrative rights."), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { + if (!Helper::init(GameInfo::instance().getOrganizerDirectory())) { + return false; + } + } else { + return false; + } + // no matter which directory didn't exist/wasn't writable, the helper + // should have created them all so we can break the loop + break; + } + } + + // verify the hook-dll exists + QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()); + + if (::GetModuleHandleW(ToWString(dllName).c_str()) != NULL) { + throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)"); + } + + HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str()); + if (dllMod == NULL) { + throw windows_error("hook.dll is missing or invalid"); + } + ::FreeLibrary(dllMod); + + return true; +} + + +void cleanupDir() +{ + // files from previous versions of MO that are no longer + // required (in that location) + QString fileNames[] = { + "ModOrganiser.exe", + "ModOrganizer.log", + "ModOrganizer.log.old", + "7z.dll", + "mo1.dll", + "mo_archive.dll", + "mo_helper.exe", + "msvcp90.dll", + "msvcr90.dll", + "phonon4.dll", + "QtCore4.dll", + "QtGui4.dll", + "QtNetwork4.dll", + "QtXml4.dll", + "QtWebKit4.dll", + "qjpeg4.dll", + "NCC/GamebryoBase.dll", + "plugins/helloWorld.dll", + "plugins/testnexus.py" + }; + + static const int NUM_FILES = sizeof(fileNames) / sizeof(QString); + + qDebug("cleaning up unused files"); + + for (int i = 0; i < NUM_FILES; ++i) { + if (QFile::remove(QApplication::applicationDirPath().append("/").append(fileNames[i]))) { + qDebug("%s removed in cleanup", + QApplication::applicationDirPath().append("/").append(fileNames[i]).toUtf8().constData()); + } + } +} + + +bool isNxmLink(const QString &link) +{ + return link.left(6).toLower() == "nxm://"; +} + +LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) +{ + typedef BOOL (WINAPI *FuncMiniDumpWriteDump)(HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType, + const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam, + const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam, + const PMINIDUMP_CALLBACK_INFORMATION callbackParam); + LONG result = EXCEPTION_CONTINUE_SEARCH; + + HMODULE dbgDLL = ::LoadLibrary(L"dbghelp.dll"); + + static const int errorLen = 200; + char errorBuffer[errorLen + 1]; + memset(errorBuffer, '\0', errorLen + 1); + + if (dbgDLL) { + FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump"); + if (funcDump) { + + if (QMessageBox::question(NULL, QObject::tr("Woops"), + QObject::tr("ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file " + "(%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. " + "Please include a short description of what you were doing when the crash happened").arg(qApp->applicationFilePath().append(".dmp")), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp")); + + HANDLE dumpFile = ::CreateFile(dumpName.c_str(), + GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (dumpFile != INVALID_HANDLE_VALUE) { + _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; + exceptionInfo.ThreadId = ::GetCurrentThreadId(); + exceptionInfo.ExceptionPointers = exceptionPtrs; + exceptionInfo.ClientPointers = NULL; + + BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile, MiniDumpNormal, &exceptionInfo, NULL, NULL); + + ::CloseHandle(dumpFile); + if (success) { + return EXCEPTION_EXECUTE_HANDLER; + } + _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)", + dumpName.c_str(), ::GetLastError()); + } else { + _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)", + dumpName.c_str(), ::GetLastError()); + } + } else { + return result; + } + } else { + _snprintf(errorBuffer, errorLen, "dbghelp.dll outdated"); + } + } else { + _snprintf(errorBuffer, errorLen, "dbghelp.dll not found"); + } + + QMessageBox::critical(NULL, QObject::tr("Woops"), + QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer)); + return result; +} + +void registerMetaTypes() +{ + registerExecutable(); +} + +bool HaveWriteAccess(const std::wstring &path) +{ + bool writable = false; + + const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + + DWORD length = 0; + if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, NULL, NULL, &length) + && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + std::string tempBuffer; + tempBuffer.reserve(length); + PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); + if (security + && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { + HANDLE token = NULL; + const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; + if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { + if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { + throw std::runtime_error("Unable to get any thread or process token"); + } + } + + HANDLE impersonatedToken = NULL; + if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { + GENERIC_MAPPING mapping = { 0xFFFFFFFF }; + mapping.GenericRead = FILE_GENERIC_READ; + mapping.GenericWrite = FILE_GENERIC_WRITE; + mapping.GenericExecute = FILE_GENERIC_EXECUTE; + mapping.GenericAll = FILE_ALL_ACCESS; + + DWORD genericAccessRights = FILE_GENERIC_WRITE; + ::MapGenericMask(&genericAccessRights, &mapping); + + PRIVILEGE_SET privileges = { 0 }; + DWORD grantedAccess = 0; + DWORD privilegesLength = sizeof(privileges); + BOOL result = 0; + if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { + writable = result != 0; + } + ::CloseHandle(impersonatedToken); + } + + ::CloseHandle(token); + } + } + return writable; +} + + +int main(int argc, char *argv[]) +{ + MOApplication application(argc, argv); + + application.addLibraryPath(application.applicationDirPath() + "/dlls"); + + SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + + if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) { + QStringList arguments = application.arguments(); + arguments.pop_front(); + ::ShellExecuteW( NULL + , L"runas" + , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str() + , ToWString(arguments.join(" ")).c_str() + , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL); + return 1; + } + LogBuffer::init(100, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); + + qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); + qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); + QPixmap pixmap(":/MO/gui/splash"); + QSplashScreen splash(pixmap); + splash.show(); + + { // extend path to include dll directory so plugins don't need a manifest + static const int BUFSIZE = 4096; + + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); + DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); + if (offset > BUFSIZE) { + oldPath.reset(new TCHAR[offset]); + ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + } + + std::tstring newPath(oldPath.get()); + newPath += TEXT(";"); + newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str(); + newPath += TEXT("\\dlls"); + + ::SetEnvironmentVariable(TEXT("PATH"), newPath.c_str()); + } + + registerMetaTypes(); + + QStringList arguments = application.arguments(); + + bool forcePrimary = false; + if (arguments.contains("update")) { + arguments.removeAll("update"); + forcePrimary = true; + } + + try { + SingleInstance instance(forcePrimary); + if (!instance.primaryInstance()) { + if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { + qDebug("not primary instance, sending download message"); + instance.sendMessage(arguments.at(1)); + return 0; + } else if (arguments.size() == 1) { + QMessageBox::information(NULL, QObject::tr("Mod Organizer"), QObject::tr("An instance of Mod Organizer is already running")); + return 0; + } + } // we continue for the primary instance OR if MO has been called with parameters + + + // TODO: this should be MAX_PATH_UNICODE! + wchar_t moPath[MAX_PATH]; + memset(moPath, 0, sizeof(TCHAR) * MAX_PATH); + ::GetModuleFileNameW(NULL, moPath, MAX_PATH); + wchar_t *lastBSlash = wcsrchr(moPath, TEXT('\\')); + if (lastBSlash != NULL) { + *lastBSlash = TEXT('\0'); + } + QSettings settings(ToQString(std::wstring(moPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat); + + QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + + bool done = false; + while (!done) { + if (!GameInfo::init(moPath, ToWString(QDir::toNativeSeparators(gamePath)))) { + if (!gamePath.isEmpty()) { + reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " + "the game binary and its launcher.").arg(gamePath)); + } + SelectionDialog selection(QObject::tr("Please select the game to manage"), NULL); + + { // add options + QString skyrimPath = ToQString(SkyrimInfo::getRegPathStatic()); + QString falloutNVPath = ToQString(FalloutNVInfo::getRegPathStatic()); + QString fallout3Path = ToQString(Fallout3Info::getRegPathStatic()); + QString oblivionPath = ToQString(OblivionInfo::getRegPathStatic()); + if (skyrimPath.length() != 0) { + selection.addChoice(QString("Skyrim"), skyrimPath, skyrimPath); + } + if (falloutNVPath.length() != 0) { + selection.addChoice(QString("Fallout NV"), falloutNVPath, falloutNVPath); + } + if (fallout3Path.length() != 0) { + selection.addChoice(QString("Fallout 3"), fallout3Path, fallout3Path); + } + if (oblivionPath.length() != 0) { + selection.addChoice(QString("Oblivion"), oblivionPath, oblivionPath); + } + + selection.addChoice(QString("Browse..."), QString(), QString()); + } + + if (selection.exec() == QDialog::Rejected) { + gamePath = ""; + done = true; + } else { + gamePath = QDir::cleanPath(selection.getChoiceData().toString()); + if (gamePath.isEmpty()) { + gamePath = QFileDialog::getExistingDirectory(NULL, QObject::tr("Please select the game to manage"), QString(), + QFileDialog::ShowDirsOnly); + } + } + } else { + done = true; + gamePath = ToQString(GameInfo::instance().getGameDirectory()); + } + } + + if (gamePath.isEmpty()) { + // game not found and user canceled + return -1; + } else if (gamePath.length() != 0) { + // user selected a folder and game was initialised with it + settings.setValue("gamePath", gamePath.toUtf8().constData()); + } + + int edition = 0; + if (settings.contains("game_edition")) { + edition = settings.value("game_edition").toInt(); + } else { + std::vector editions = GameInfo::instance().getSteamVariants(); + if (editions.size() < 2) { + edition = 0; + } else { + SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL); + int index = 0; + for (auto iter = editions.begin(); iter != editions.end(); ++iter) { + selection.addChoice(ToQString(*iter), "", index++); + } + if (selection.exec() == QDialog::Rejected) { + return -1; + } else { + settings.setValue("game_edition", selection.getChoiceData().toInt()); + } + } + } + + qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); + + ExecutablesList executablesList; + + executablesList.init(); + + if (!bootstrap()) { // requires gameinfo to be initialised! + return -1; + } + + cleanupDir(); + + qDebug("setting up configured executables"); + + int numCustomExecutables = settings.beginReadArray("customExecutables"); + for (int i = 0; i < numCustomExecutables; ++i) { + settings.setArrayIndex(i); + CloseMOStyle closeMO = settings.value("closeOnStart").toBool() ? DEFAULT_CLOSE : DEFAULT_STAY; + executablesList.addExecutable(settings.value("title").toString(), + settings.value("binary").toString(), + settings.value("arguments").toString(), + settings.value("workingDirectory", "").toString(), + closeMO, + settings.value("steamAppID", "").toString(), + settings.value("custom", true).toBool(), + settings.value("toolbar", false).toBool()); + } + + settings.endArray(); + + qDebug("initializing tutorials"); + TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/")); + + if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + // disable invalid stylesheet + settings.setValue("Settings/style", ""); + } + + int res = 1; + { // scope to control lifetime of mainwindow + // set up main window and its data structures + MainWindow mainWindow(argv[0], settings); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); + + mainWindow.setExecutablesList(executablesList); + mainWindow.readSettings(); + + QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + + { // see if there is a profile on the command line + int profileIndex = arguments.indexOf("-p", 1); + if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { + qDebug("profile overwritten on command line"); + selectedProfileName = arguments.at(profileIndex + 1); + } + arguments.removeAt(profileIndex); + arguments.removeAt(profileIndex); + } + if (selectedProfileName.isEmpty()) { + qDebug("no configured profile"); + } else { + qDebug("configured profile: %s", qPrintable(selectedProfileName)); + } + + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) { + 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 { + mainWindow.startApplication(exeName, arguments, QString(), selectedProfileName); + } catch (const std::exception &e) { + reportError(QObject::tr("failed to start application: %1").arg(e.what())); + } + + return 0; + } + + mainWindow.createFirstProfile(); + + if (selectedProfileName.length() != 0) { + if (!mainWindow.setCurrentProfile(selectedProfileName)) { + mainWindow.setCurrentProfile(1); + qWarning("failed to set profile: %s", + selectedProfileName.toUtf8().constData()); + } + } else { + mainWindow.setCurrentProfile(1); + } + + qDebug("displaying main window"); + mainWindow.show(); + + if ((arguments.size() > 1) && + (isNxmLink(arguments.at(1)))) { + qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); + mainWindow.externalMessage(arguments.at(1)); + } + splash.finish(&mainWindow); + res = application.exec(); + } + return res; + } catch (const std::exception &e) { + reportError(e.what()); + return 1; + } +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cf283b49..c3182321 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5601 +1,5601 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include -#include "spawn.h" -#include "report.h" -#include "modlist.h" -#include "modlistsortproxy.h" -#include "qtgroupingproxy.h" -#include "profile.h" -#include "pluginlist.h" -#include "profilesdialog.h" -#include "editexecutablesdialog.h" -#include "categories.h" -#include "categoriesdialog.h" -#include "utility.h" -#include "modinfodialog.h" -#include "overwriteinfodialog.h" -#include "activatemodsdialog.h" -#include "downloadlist.h" -#include "downloadlistwidget.h" -#include "downloadlistwidgetcompact.h" -#include "messagedialog.h" -#include "installationmanager.h" -#include "lockeddialog.h" -#include "syncoverwritedialog.h" -#include "logbuffer.h" -#include "downloadlistsortproxy.h" -#include "motddialog.h" -#include "filedialogmemory.h" -#include "questionboxmemory.h" -#include "tutorialmanager.h" -#include "modflagicondelegate.h" -#include "pluginflagicondelegate.h" -#include "credentialsdialog.h" -#include "selectiondialog.h" -#include "csvbuilder.h" -#include "gameinfoimpl.h" -#include "savetextasdialog.h" -#include "problemsdialog.h" -#include "previewdialog.h" -#include "browserdialog.h" -#include "aboutdialog.h" -#include "safewritefile.h" -#include "organizerproxy.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) -#include -#else -#include -#endif -#include -#include -#include -#include -#include -#include - -#ifdef TEST_MODELS -#include "modeltest.h" -#endif // TEST_MODELS - -#pragma warning( disable : 4428 ) - -using namespace MOBase; -using namespace MOShared; - - - - -static bool isOnline() -{ - QList interfaces = QNetworkInterface::allInterfaces(); - - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) { - if ( (iter->flags() & QNetworkInterface::IsUp) && - (iter->flags() & QNetworkInterface::IsRunning) && - !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; - } - qDebug("interface %s seems to be up (address: %s)", - qPrintable(iter->humanReadableName()), - qPrintable(addresses[0].ip().toString())); - connected = true; - } - } - - return connected; -} - - -MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), - m_ExeName(exeName), m_OldProfileIndex(-1), - m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), - m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), - m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), - m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), - m_CurrentProfile(NULL), m_AskForNexusPW(false), - m_ArchivesInit(false), m_DirectoryUpdate(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL), - m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled(), m_DidUpdateMasterList(false) -{ - ui->setupUi(this); - this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); - - languageChange(m_Settings.language()); - - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); - int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value - ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), ui->logList, SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); - - m_RefreshProgress = new QProgressBar(statusBar()); - m_RefreshProgress->setTextVisible(true); - m_RefreshProgress->setRange(0, 100); - m_RefreshProgress->setValue(0); - statusBar()->addWidget(m_RefreshProgress, 1000); - statusBar()->clearMessage(); - - ui->actionEndorseMO->setVisible(false); - - MOBase::QuestionBoxMemory::init(initSettings.fileName()); - - updateProblemsButton(); - - updateToolBar(); - - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - - // set up mod list - m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); - m_ModListSortProxy->setSourceModel(&m_ModList); - -#ifdef TEST_MODELS - new ModelTest(&m_ModList, this); - new ModelTest(m_ModListSortProxy, this); -#endif //TEST_MODELS - - ui->modList->setModel(m_ModListSortProxy); - - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); - //ui->modList->setAcceptDrops(true); - ui->modList->header()->installEventFilter(&m_ModList); - if (initSettings.contains("mod_list_state")) { - ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); - } else { - // hide these columns by default - ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); - ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - } - - ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden - ui->modList->installEventFilter(&m_ModList); - - // set up plugin list - m_PluginListSortProxy = new PluginListSortProxy(this); - m_PluginListSortProxy->setSourceModel(&m_PluginList); - - ui->espList->setModel(m_PluginListSortProxy); - ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); - ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); - if (initSettings.contains("plugin_list_state")) { - ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); - } - ui->espList->installEventFilter(&m_PluginList); - - ui->bsaList->setLocalMoveOnly(true); - - ui->splitter->setStretchFactor(0, 3); - ui->splitter->setStretchFactor(1, 2); - - resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state")); - - QMenu *linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); - ui->linkButton->setMenu(linkMenu); - - ui->listOptionsBtn->setMenu(modListContextMenu()); - - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - - updateDownloadListDelegate(); - - ui->savegameList->installEventFilter(this); - ui->savegameList->setMouseTracking(true); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int))); - connect(&m_DownloadManager, SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom())); - - connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - - connect(&m_ModList, SIGNAL(modorder_changed()), this, SLOT(modorder_changed())); - connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(&m_ModList, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), this, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); - connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex))); - connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); - connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); - - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); - - connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved())); - - connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); - - connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); - connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString))); - - connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); - - connect(&m_Settings, SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_Settings, SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); - - connect(&m_Updater, SIGNAL(restart()), this, SLOT(close())); - connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); - connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - -// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); - - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); - connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); - connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); - connect(NexusInterface::instance(), SIGNAL(needLogin()), this, SLOT(nexusLogin())); - - connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); - connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); - connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - - connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); - - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); - - m_CheckBSATimer.setSingleShot(true); - connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); - - m_UpdateProblemsTimer.setSingleShot(true); - connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton())); - - m_SaveMetaTimer.setSingleShot(false); - connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); - m_SaveMetaTimer.start(5000); - - m_DirectoryRefresher.moveToThread(&m_RefresherThread); - m_RefresherThread.start(); - - m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); - - fixCategories(); - - if (isOnline() && !m_Settings.offlineMode()) { - m_Updater.testForUpdate(); - } else { - qDebug("user doesn't seem to be connected to the internet"); - } - - m_StartTime = QTime::currentTime(); - - m_Tutorial.expose("modList", &m_ModList); - m_Tutorial.expose("espList", &m_PluginList); - - // before we start loading plugins we, add the dll path to the dll search order - ::SetDllDirectoryW(ToWString(QDir::toNativeSeparators(qApp->applicationDirPath() + "/dlls")).c_str()); - loadPlugins(); -} - - -MainWindow::~MainWindow() -{ - m_RefresherThread.exit(); - m_RefresherThread.wait(); - m_IntegratedBrowser.close(); - delete ui; - delete m_GameInfo; - delete m_DirectoryStructure; -} - - -void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) -{ - if (!modListCustom) { - // resize mod list to fit content -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); -#else - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setResizeMode(ModList::COL_NAME, QHeaderView::Stretch); -#endif - } - - if (!pluginListCustom) { - // resize plugin list to fit content -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch); -#else - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setResizeMode(i, QHeaderView::ResizeToContents); - } - ui->espList->header()->setResizeMode(0, QHeaderView::Stretch); -#endif - } -} - - -void MainWindow::allowListResize() -{ - // allow resize on mod list -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); - } - ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); -#else - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setResizeMode(i, QHeaderView::Interactive); - } - ui->modList->header()->setResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); -#endif - - - // allow resize on plugin list -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); - } - ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); -#else - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setResizeMode(i, QHeaderView::Interactive); - } - ui->espList->header()->setResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); -#endif - -} - -void MainWindow::updateStyle(const QString&) -{ - // no effect? - ensurePolished(); -} - -void MainWindow::resizeEvent(QResizeEvent *event) -{ - m_Tutorial.resize(event->size()); - QMainWindow::resizeEvent(event); -} - - -static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) -{ - QModelIndex result = idx; - const QAbstractItemModel *model = idx.model(); - while (model != targetModel) { - if (model == NULL) { - return QModelIndex(); - } - const QAbstractProxyModel *proxyModel = qobject_cast(model); - if (proxyModel == NULL) { - return QModelIndex(); - } - result = proxyModel->mapToSource(result); - model = proxyModel->sourceModel(); - } - return result; -} - - -void MainWindow::actionToToolButton(QAction *&sourceAction) -{ - QToolButton *button = new QToolButton(ui->toolBar); - button->setObjectName(sourceAction->objectName()); - button->setIcon(sourceAction->icon()); - button->setText(sourceAction->text()); - button->setPopupMode(QToolButton::InstantPopup); - button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); - button->setToolTip(sourceAction->toolTip()); - button->setShortcut(sourceAction->shortcut()); - QMenu *buttonMenu = new QMenu(sourceAction->text(), button); - button->setMenu(buttonMenu); - QAction *newAction = ui->toolBar->insertWidget(sourceAction, button); - newAction->setObjectName(sourceAction->objectName()); - newAction->setIcon(sourceAction->icon()); - newAction->setText(sourceAction->text()); - newAction->setToolTip(sourceAction->toolTip()); - newAction->setShortcut(sourceAction->shortcut()); - ui->toolBar->removeAction(sourceAction); - sourceAction->deleteLater(); - sourceAction = newAction; -} - - -void MainWindow::updateToolBar() -{ - foreach (QAction *action, ui->toolBar->actions()) { - if (action->objectName().startsWith("custom__")) { - ui->toolBar->removeAction(action); - } - } - - QWidget *spacer = new QWidget(ui->toolBar); - spacer->setObjectName("custom__spacer"); - spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); - QToolButton *toolBtn = qobject_cast(widget); - - if (toolBtn->menu() == NULL) { - actionToToolButton(ui->actionTool); - } - - actionToToolButton(ui->actionHelp); - createHelpWidget(); - - foreach (QAction *action, ui->toolBar->actions()) { - if (action->isSeparator()) { - // insert spacers - ui->toolBar->insertWidget(action, spacer); - - std::vector::iterator begin, end; - m_ExecutablesList.getExecutables(begin, end); - for (auto iter = begin; iter != end; ++iter) { - if (iter->m_Toolbar) { - QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), - iter->m_Title, - ui->toolBar); - QVariant temp; - temp.setValue(*iter); - exeAction->setData(temp); - exeAction->setObjectName(QString("custom__") + iter->m_Title); - if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); - } - ui->toolBar->insertAction(action, exeAction); - } - } - } - } -} - - -void MainWindow::scheduleUpdateButton() -{ - if (!m_UpdateProblemsTimer.isActive()) { - m_UpdateProblemsTimer.start(1000); - } -} - - -void MainWindow::updateProblemsButton() -{ - int numProblems = checkForProblems(); - if (numProblems > 0) { - ui->actionProblems->setEnabled(true); - ui->actionProblems->setIconText(tr("Problems")); - ui->actionProblems->setToolTip(tr("There are potential problems with your setup")); - - QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); - { - QPainter painter(&mergedIcon); - std::string badgeName = std::string(":/MO/gui/badge_") + (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); - painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); - } - ui->actionProblems->setIcon(QIcon(mergedIcon)); - } else { - ui->actionProblems->setEnabled(false); - ui->actionProblems->setIconText(tr("No Problems")); - ui->actionProblems->setToolTip(tr("Everything seems to be in order")); - ui->actionProblems->setIcon(QIcon(":/MO/gui/warning")); - } -} - - -bool MainWindow::errorReported(QString &logFile) -{ - QDir dir(ToQString(GameInfo::instance().getLogDir())); - QFileInfoList files = dir.entryInfoList(QStringList("ModOrganizer_??_??_??_??_??.log"), - QDir::Files, QDir::Name | QDir::Reversed); - - if (files.count() > 0) { - logFile = files.at(0).absoluteFilePath(); - QFile file(logFile); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - char buffer[1024]; - int line = 0; - while (!file.atEnd()) { - file.readLine(buffer, 1024); - if (strncmp(buffer, "ERROR", 5) == 0) { - return true; - } - - // prevent this function from taking forever - if (line++ >= 50000) { - break; - } - } - } - } - - return false; -} - - -int MainWindow::checkForProblems() -{ - int numProblems = 0; - foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) { - numProblems += diagnose->activeProblems().size(); - } - return numProblems; -} - -void MainWindow::about() -{ - AboutDialog dialog(m_Updater.getVersion().displayString(), this); - dialog.exec(); -} - - -void MainWindow::createHelpWidget() -{ - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); - QMenu *buttonMenu = toolBtn->menu(); - if (buttonMenu == NULL) { - return; - } - buttonMenu->clear(); - - QAction *helpAction = new QAction(tr("Help on UI"), buttonMenu); - connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); - buttonMenu->addAction(helpAction); - - QAction *wikiAction = new QAction(tr("Documentation Wiki"), buttonMenu); - connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); - buttonMenu->addAction(wikiAction); - - QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); - connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); - buttonMenu->addAction(issueAction); - - QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); - - typedef std::vector > ActionList; - - ActionList tutorials; - - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); - while (dirIter.hasNext()) { - dirIter.next(); - QString fileName = dirIter.fileName(); - - QFile file(dirIter.filePath()); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; - continue; - } - QString firstLine = QString::fromUtf8(file.readLine()); - if (firstLine.startsWith("//TL")) { - QStringList params = firstLine.mid(4).trimmed().split('#'); - if (params.size() != 2) { - qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; - continue; - } - QAction *tutAction = new QAction(params.at(0), tutorialMenu); - tutAction->setData(fileName); - tutorials.push_back(std::make_pair(params.at(1).toInt(), tutAction)); - } - } - - std::sort(tutorials.begin(), tutorials.end(), - [](const ActionList::value_type &LHS, const ActionList::value_type &RHS) { - return LHS.first < RHS.first; } ); - - for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { - connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); - tutorialMenu->addAction(iter->second); - } - - buttonMenu->addMenu(tutorialMenu); - buttonMenu->addAction(tr("About"), this, SLOT(about())); - buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); -} - - -bool MainWindow::saveArchiveList() -{ - if (m_ArchivesInit) { - SafeWriteFile archiveFile(m_CurrentProfile->getArchivesFileName()); - for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); - for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem *item = tlItem->child(j); - if (item->checkState(0) == Qt::Checked) { - // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini - if (ui->manageArchivesBox->isChecked() - || item->data(0, Qt::UserRole).toBool()) { - archiveFile->write(item->text(0).toUtf8().append("\r\n")); - } - } - } - } - if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); - return true; - } - } else { - qWarning("archive list not initialised"); - } - return false; -} - -void MainWindow::savePluginList() -{ - m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName(), - m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); - m_PluginList.saveLoadOrder(*m_DirectoryStructure); -} - -void MainWindow::modFilterActive(bool filterActive) -{ - if (filterActive) { - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else if (ui->groupCombo->currentIndex() != 0) { - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); - } else { - ui->modList->setStyleSheet(""); - } -} - -void MainWindow::espFilterChanged(const QString &filter) -{ - if (!filter.isEmpty()) { - ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else { - ui->espList->setStyleSheet(""); - } -} - -void MainWindow::downloadFilterChanged(const QString &filter) -{ - if (!filter.isEmpty()) { - ui->downloadView->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else { - ui->downloadView->setStyleSheet(""); - } -} - -void MainWindow::expandModList(const QModelIndex &index) -{ - QAbstractItemModel *model = ui->modList->model(); -#pragma message("why is this so complicated? mapping the index doesn't work, probably a bug in QtGroupingProxy?") - for (int i = 0; i < model->rowCount(); ++i) { - QModelIndex targetIdx = model->index(i, 0); - if (model->data(targetIdx).toString() == index.data().toString()) { - ui->modList->expand(targetIdx); - break; - } - } -} - -bool MainWindow::saveCurrentLists() -{ - if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); - return false; - } - - try { - savePluginList(); - saveArchiveList(); - } catch (const std::exception &e) { - reportError(tr("failed to save load order: %1").arg(e.what())); - } - - return true; -} - - -bool MainWindow::addProfile() -{ - QComboBox *profileBox = findChild("profileBox"); - bool okClicked = false; - - QString name = QInputDialog::getText(this, tr("Name"), - tr("Please enter a name for the new profile"), - QLineEdit::Normal, QString(), &okClicked); - if (okClicked && (name.size() > 0)) { - try { - profileBox->addItem(name); - profileBox->setCurrentIndex(profileBox->count() - 1); - return true; - } catch (const std::exception& e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - return false; - } - } else { - return false; - } -} - - -void MainWindow::hookUpWindowTutorials() -{ - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); - while (dirIter.hasNext()) { - dirIter.next(); - QString fileName = dirIter.fileName(); - QFile file(dirIter.filePath()); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; - continue; - } - QString firstLine = QString::fromUtf8(file.readLine()); - if (firstLine.startsWith("//WIN")) { - QString windowName = firstLine.mid(6).trimmed(); - if (!m_Settings.directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { - TutorialManager::instance().activateTutorial(windowName, fileName); - } - } - } -} - - -void MainWindow::showEvent(QShowEvent *event) -{ - refreshFilters(); - - QMainWindow::showEvent(event); - m_Tutorial.registerControl(); - - hookUpWindowTutorials(); - - if (m_Settings.directInterface().value("first_start", true).toBool()) { - QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); - if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { - if (QMessageBox::question(this, tr("Show tutorial?"), - tr("You are starting Mod Organizer for the first time. " - "Do you want to show a tutorial of its basic features? If you choose " - "no you can always start the tutorial from the \"Help\"-menu."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); - } - } else { - qCritical() << firstStepsTutorial << " missing"; - QPoint pos = ui->toolBar->mapToGlobal(QPoint()); - pos.rx() += ui->toolBar->width() / 2; - pos.ry() += ui->toolBar->height(); - QWhatsThis::showText(pos, - QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); - } - - m_Settings.directInterface().setValue("first_start", false); - } - - // this has no visible impact when called before the ui is visible - int grouping = m_Settings.directInterface().value("group_state").toInt(); - ui->groupCombo->setCurrentIndex(grouping); - - allowListResize(); - - m_Settings.registerAsNXMHandler(false); -} - - -void MainWindow::closeEvent(QCloseEvent* event) -{ - if (m_DownloadManager.downloadsInProgress()) { - if (QMessageBox::question(this, tr("Downloads in progress"), - tr("There are still downloads in progress, do you really want to quit?"), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { - event->ignore(); - return; - } else { - m_DownloadManager.pauseAll(); - } - } - - setCursor(Qt::WaitCursor); - - m_IntegratedBrowser.close(); - - storeSettings(); - - // profile has to be cleaned up before the modinfo-buffer is cleared - delete m_CurrentProfile; - m_CurrentProfile = NULL; - - ModInfo::clear(); - LogBuffer::cleanQuit(); - m_ModList.setProfile(NULL); -} - - -void MainWindow::createFirstProfile() -{ - if (!refreshProfiles(false)) { - qDebug("creating default profile"); - Profile newProf("Default", false); - refreshProfiles(false); - } -} - - -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_IntegratedBrowser.restoreGeometry(geometry); -} - - -SaveGameGamebryo *MainWindow::getSaveGame(const QString &name) -{ - return new SaveGameGamebryo(this, name); -} - - -SaveGameGamebryo *MainWindow::getSaveGame(QListWidgetItem *item) -{ - try { - SaveGameGamebryo *saveGame = getSaveGame(item->data(Qt::UserRole).toString()); - saveGame->setParent(item->listWidget()); - return saveGame; - } catch (const std::exception &e) { - reportError(tr("failed to read savegame: %1").arg(e.what())); - return NULL; - } -} - - -void MainWindow::displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos) -{ - if (m_CurrentSaveView == NULL) { - m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, &m_PluginList, this); - } else { - m_CurrentSaveView->setSave(save); - } - - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); - - if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { - pos.rx() -= (m_CurrentSaveView->width() + 2); - } else { - pos.rx() += 5; - } - - if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) { - pos.ry() -= (m_CurrentSaveView->height() + 10); - } else { - pos.ry() += 20; - } - m_CurrentSaveView->move(pos); - - m_CurrentSaveView->show(); - ui->savegameList->activateWindow(); - connect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); -} - - -void MainWindow::saveSelectionChanged(QListWidgetItem *newItem) -{ - if (newItem == NULL) { - hideSaveGameInfo(); - } else if ((m_CurrentSaveView == NULL) || (newItem != m_CurrentSaveView->property("displayItem").value())) { - const SaveGameGamebryo *save = getSaveGame(newItem); - if (save != NULL) { - displaySaveGameInfo(save, QCursor::pos()); - m_CurrentSaveView->setProperty("displayItem", qVariantFromValue((void*)newItem)); - } - } -} - - - -void MainWindow::hideSaveGameInfo() -{ - if (m_CurrentSaveView != NULL) { - disconnect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); - m_CurrentSaveView->deleteLater(); - m_CurrentSaveView = NULL; - } -} - -bool MainWindow::eventFilter(QObject *object, QEvent *event) -{ - if ((object == ui->savegameList) && - ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { - hideSaveGameInfo(); - } - return false; -} - - -bool MainWindow::testForSteam() -{ - DWORD processIDs[1024]; - DWORD bytesReturned; - if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) { - qWarning("failed to determine if steam is running"); - return true; - } - - TCHAR processName[MAX_PATH]; - for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { - memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); - if (processIDs[i] != 0) { - HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); - - if (process != NULL) { - HMODULE module; - DWORD ignore; - - // first module in a process is always the binary - if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) { - GetModuleBaseName(process, module, processName, MAX_PATH); - if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) || - (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { - return true; - } - } - } - } - } - - return false; -} - - -bool MainWindow::verifyPlugin(IPlugin *plugin) -{ - if (plugin == NULL) { - return false; - } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) { - qWarning("plugin failed to initialize"); - return false; - } - return true; -} - - -void MainWindow::toolPluginInvoke() -{ - QAction *triggeredAction = qobject_cast(sender()); - IPluginTool *plugin = (IPluginTool*)triggeredAction->data().value(); - try { - plugin->display(); - } catch (const std::exception &e) { - reportError(tr("Plugin \"%1\" failed: %2").arg(plugin->name()).arg(e.what())); - } catch (...) { - reportError(tr("Plugin \"%1\" failed").arg(plugin->name())); - } -} - - -void MainWindow::requestDownload(const QUrl &url, QNetworkReply *reply) -{ - QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - if (browserBtn->menu() != NULL) { - // go through modpage plugins, find one to handle the download. - QList browserActions = browserBtn->menu()->actions(); - foreach (QAction *action, browserActions) { - // the nexus action doesn't have a plugin connected currently - if (action->data().isValid()) { - IPluginModPage *plugin = qobject_cast(qvariant_cast(action->data())); - if (plugin == NULL) { - qCritical("invalid mod page. This is a bug"); - continue; - } - ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); - if (plugin->handlesDownload(url, reply->url(), *fileInfo)) { - fileInfo->repository = plugin->name(); - m_DownloadManager.addDownload(reply, fileInfo); - return; - } - } - } - } - - // no mod found that could handle the download. Is it a nexus mod? - if (url.host() == "www.nexusmods.com") { - int modID = 0; - int fileID = 0; - QRegExp modExp("mods/(\\d+)"); - if (modExp.indexIn(url.toString()) != -1) { - modID = modExp.cap(1).toInt(); - } - QRegExp fileExp("fid=(\\d+)"); - if (fileExp.indexIn(reply->url().toString()) != -1) { - fileID = fileExp.cap(1).toInt(); - } - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID)); - } else { - if (QMessageBox::question(this, tr("Download?"), - tr("A download has been started but no installed page plugin recognizes it.\n" - "If you download anyway no information (i.e. version) will be associated with the download.\n" - "Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); - } - } -} - - -void MainWindow::modPagePluginInvoke() -{ - QAction *triggeredAction = qobject_cast(sender()); - IPluginModPage *plugin = qobject_cast(triggeredAction->data().value()); - if (plugin->useIntegratedBrowser()) { - m_IntegratedBrowser.setWindowTitle(plugin->displayName()); - m_IntegratedBrowser.openUrl(plugin->pageURL()); - } else { - ::ShellExecuteW(NULL, L"open", ToWString(plugin->pageURL().toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); - } -} - -void MainWindow::registerPluginTool(IPluginTool *tool) -{ - QAction *action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); - action->setToolTip(tool->tooltip()); - tool->setParentWidget(this); - action->setData(qVariantFromValue((void*)tool)); - connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); - toolBtn->menu()->addAction(action); -} - - -void MainWindow::registerModPage(IPluginModPage *modPage) -{ - QToolButton *browserBtn = NULL; - // turn the browser action into a drop-down menu if necessary - if (ui->actionNexus->menu() == NULL) { - QAction *nexusAction = ui->actionNexus; - // TODO: use a different icon for nexus! - ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); - ui->toolBar->insertAction(nexusAction, ui->actionNexus); - ui->toolBar->removeAction(nexusAction); - actionToToolButton(ui->actionNexus); - - browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - browserBtn->menu()->addAction(nexusAction); - } else { - browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - } - - QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); - modPage->setParentWidget(this); - action->setData(qVariantFromValue(reinterpret_cast(modPage))); - - connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); - QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - toolBtn->menu()->addAction(action); -} - - -bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) -{ - { // generic treatment for all plugins - IPlugin *pluginObj = qobject_cast(plugin); - if (pluginObj == NULL) { - qDebug("not an IPlugin"); - return false; - } - plugin->setProperty("filename", fileName); - m_Settings.registerPlugin(pluginObj); - installTranslator(QFileInfo(fileName).baseName()); - } - - { // diagnosis plugins - IPluginDiagnose *diagnose = qobject_cast(plugin); - if (diagnose != NULL) { - m_DiagnosisPlugins.push_back(diagnose); - diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }); - } - } - { // mod page plugin - IPluginModPage *modPage = qobject_cast(plugin); - if (verifyPlugin(modPage)) { - registerModPage(modPage); - return true; - } - } - { // tool plugins - IPluginTool *tool = qobject_cast(plugin); - if (verifyPlugin(tool)) { - registerPluginTool(tool); - return true; - } - } - { // installer plugins - IPluginInstaller *installer = qobject_cast(plugin); - if (verifyPlugin(installer)) { - installer->setParentWidget(this); - m_InstallationManager.registerInstaller(installer); - return true; - } - } - { // preview plugins - IPluginPreview *preview = qobject_cast(plugin); - if (verifyPlugin(preview)) { - m_PreviewGenerator.registerPlugin(preview); - } - } - { // proxy plugins - IPluginProxy *proxy = qobject_cast(plugin); - if (verifyPlugin(proxy)) { - proxy->setParentWidget(this); - QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - foreach (const QString &pluginName, pluginNames) { - try { - QObject *proxiedPlugin = proxy->instantiate(pluginName); - if (proxiedPlugin != NULL) { - if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); - } else { - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } catch (const std::exception &e) { - reportError(tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what())); - } - } - return true; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin *dummy = qobject_cast(plugin); - if (verifyPlugin(dummy)) { - return true; - } - } - - qDebug("no matching plugin interface"); - - return false; -} - - -void MainWindow::loadPlugins() -{ - m_DiagnosisPlugins.clear(); - - m_Settings.clearPlugins(); - - foreach (QObject *plugin, QPluginLoader::staticInstances()) { - registerPlugin(plugin, ""); - } - - QFile loadCheck(QCoreApplication::applicationDirPath() + "/plugin_loadcheck.tmp"); - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - if (QMessageBox::question(this, tr("Plugin error"), - tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)").arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Settings.addBlacklistPlugin(fileName); - } - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - - QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); - QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - if (m_Settings.pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); - continue; - } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - QString pluginName = iter.filePath(); - if (QLibrary::isLibrary(pluginName)) { - QPluginLoader pluginLoader(pluginName); - if (pluginLoader.instance() == NULL) { - m_UnloadedPlugins.push_back(pluginName); - qCritical("failed to load plugin %s: %s", - qPrintable(pluginName), qPrintable(pluginLoader.errorString())); - } else { - if (registerPlugin(pluginLoader.instance(), pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); - } else { - m_UnloadedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } - } - - // remove the load check file on success - loadCheck.remove(); - - m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); - - m_DiagnosisPlugins.push_back(this); -} - - -void MainWindow::startSteam() -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - QString temp = QString("\"%1\"").arg(exe); - if (!QProcess::startDetached(temp)) { - reportError(tr("Failed to start \"%1\"").arg(temp)); - } else { - QMessageBox::information(this, tr("Waiting"), tr("Please press OK once you're logged into steam.")); - } - } -} - - -HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID) -{ - storeSettings(); - - if (!binary.exists()) { - reportError(tr("Executable \"%1\" not found").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); - } - - if ((GameInfo::instance().requiresSteam()) && - (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { - if (!testForSteam()) { - if (QuestionBoxMemory::query(this->isVisible() ? this : NULL, - "steamQuery", tr("Start Steam?"), - tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { - startSteam(); - } - } - } - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); - } else { - qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); - return INVALID_HANDLE_VALUE; - } -} - - -void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) -{ - LockedDialog *dialog = new LockedDialog(this); - dialog->show(); - ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); }); - - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID); - if (processHandle != INVALID_HANDLE_VALUE) { - if (closeAfterStart) { - close(); - } else { - this->setEnabled(false); - // re-enable the locked dialog because what'd be the point otherwise? - dialog->setEnabled(true); - - QCoreApplication::processEvents(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - - bool isJobHandle = true; - - DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { - if (isJobHandle) { - if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - break; - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - - res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - } - ::CloseHandle(processHandle); - - this->setEnabled(true); - refreshDirectoryStructure(); - if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - } - } -} - - -void MainWindow::startExeAction() -{ - QAction *action = qobject_cast(sender()); - if (action != NULL) { - Executable selectedExecutable = action->data().value(); - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); - } else { - qCritical("not an action?"); - } -} - - -void MainWindow::setExecutablesList(const ExecutablesList &executablesList) -{ - m_ExecutablesList = executablesList; - refreshExecutablesList(); - updateToolBar(); -} - -void MainWindow::setExecutableIndex(int index) -{ - QComboBox *executableBox = findChild("executablesListBox"); - - if ((index != 0) && (executableBox->count() > index)) { - executableBox->setCurrentIndex(index); - } else { - executableBox->setCurrentIndex(1); - } -} - -void MainWindow::activateSelectedProfile() -{ - QString profileName = ui->profileBox->currentText(); - qDebug("activate profile \"%s\"", qPrintable(profileName)); - QString profileDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) - .append("/").append(profileName); - delete m_CurrentProfile; - m_CurrentProfile = new Profile(QDir(profileDir)); - m_ModList.setProfile(m_CurrentProfile); - - m_ModListSortProxy->setProfile(m_CurrentProfile); - - connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); - - refreshSaveList(); - refreshModList(); -} - -void MainWindow::on_profileBox_currentIndexChanged(int index) -{ - if (ui->profileBox->isEnabled()) { - int previousIndex = m_OldProfileIndex; - m_OldProfileIndex = index; - - if ((previousIndex != -1) && - (m_CurrentProfile != NULL) && - m_CurrentProfile->exists()) { - saveCurrentLists(); - } - - // ensure the new index is valid - if (index < 0 || index >= ui->profileBox->count()) { - qDebug("invalid profile index, using last profile"); - ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); - } - - if (ui->profileBox->currentIndex() == 0) { - ProfilesDialog(m_GamePath).exec(); - while (!refreshProfiles()) { - ProfilesDialog(m_GamePath).exec(); - } - ui->profileBox->setCurrentIndex(previousIndex); - } else { - activateSelectedProfile(); - } - } -} - - -void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly) -{ - { - std::vector files = directoryEntry.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - if (conflictsOnly && (current->getAlternatives().size() == 0)) { - continue; - } - - QString fileName = ToQString(current->getName()); - QStringList columns(fileName); - bool isArchive = false; - int originID = current->getOrigin(isArchive); - QString source = ToQString(m_DirectoryStructure->getOriginByID(originID).getName()); - std::wstring archive = current->getArchive(); - if (archive.length() != 0) { - source.append(" (").append(ToQString(archive)).append(")"); - } - columns.append(source); - QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); - if (isArchive) { - QFont font = fileChild->font(0); - font.setItalic(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { - QFont font = fileChild->font(0); - font.setStrikeOut(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } - fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath())); - fileChild->setData(0, Qt::UserRole + 1, isArchive); - fileChild->setData(1, Qt::UserRole, source); - fileChild->setData(1, Qt::UserRole + 1, originID); - - std::vector alternatives = current->getAlternatives(); - - if (!alternatives.empty()) { - std::wostringstream altString; - altString << ToWString(tr("Also in:
    ")); - for (std::vector::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << " , "; - } - altString << "" << m_DirectoryStructure->getOriginByID(*altIter).getName() << ""; - } - fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); - fileChild->setForeground(1, QBrush(Qt::red)); - } else { - fileChild->setToolTip(1, tr("No conflict")); - } - subTree->addChild(fileChild); - } - } - - std::wostringstream temp; - temp << directorySoFar << "\\" << directoryEntry.getName(); - { - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - QString pathName = ToQString((*current)->getName()); - QStringList columns(pathName); - columns.append(""); - if (!(*current)->isEmpty()) { - QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); - if (conflictsOnly) { - updateTo(directoryChild, temp.str(), **current, conflictsOnly); - if (directoryChild->childCount() != 0) { - subTree->addChild(directoryChild); - } else { - delete directoryChild; - } - } else { - QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); - onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); - onDemandLoad->setData(0, Qt::UserRole + 1, ToQString(temp.str())); - onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); - directoryChild->addChild(onDemandLoad); - subTree->addChild(directoryChild); - } - } - } - } - - subTree->sortChildren(0, Qt::AscendingOrder); -} - -void MainWindow::delayedRemove() -{ - foreach (QTreeWidgetItem *item, m_RemoveWidget) { - item->removeChild(item->child(0)); - } - m_RemoveWidget.clear(); -} - -void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) -{ - if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { - // read the data we need from the sub-item, then dispose of it - QTreeWidgetItem *onDemandDataItem = item->child(0); - std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); - bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(virtualPath); - if (dir != NULL) { - updateTo(item, path, *dir, conflictsOnly); - } else { - qWarning("failed to update view of %ls", path.c_str()); - } - m_RemoveWidget.push_back(item); - QTimer::singleShot(5, this, SLOT(delayedRemove())); - } -} - - -bool MainWindow::refreshProfiles(bool selectProfile) -{ - QComboBox* profileBox = findChild("profileBox"); - - QString currentProfileName = profileBox->currentText(); - - profileBox->blockSignals(true); - profileBox->clear(); - profileBox->addItem(QObject::tr("")); - - QDir profilesDir(ToQString(GameInfo::instance().getProfilesDir())); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - - QDirIterator profileIter(profilesDir); - - int newIndex = profileIter.hasNext() ? 1 : 0; - int currentIndex = 0; - while (profileIter.hasNext()) { - profileIter.next(); - ++currentIndex; - try { - profileBox->addItem(profileIter.fileName()); - if (currentProfileName == profileIter.fileName()) { - newIndex = currentIndex; - } - } catch (const std::runtime_error& error) { - reportError(QObject::tr("failed to parse profile %1: %2").arg(profileIter.fileName()).arg(error.what())); - } - } - - // now select one of the profiles, preferably the one that was selected before - profileBox->blockSignals(false); - - if (selectProfile) { - if (profileBox->count() > 1) { - if (currentProfileName.length() != 0) { - if ((newIndex != 0) && (profileBox->count() > newIndex)) { - profileBox->setCurrentIndex(newIndex); - } else { - profileBox->setCurrentIndex(1); - } - } - return true; - } else { - return false; - } - } else { - return profileBox->count() > 1; - } -} - -std::set MainWindow::enabledArchives() -{ - std::set result; - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); - } - archiveFile.close(); - } - return result; -} - -void MainWindow::refreshDirectoryStructure() -{ - m_DirectoryUpdate = true; - std::vector > activeModList = m_CurrentProfile->getActiveMods(); - - m_DirectoryRefresher.setMods(activeModList, enabledArchives()); - - statusBar()->show(); - m_RefreshProgress->setRange(0, 100); - - QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); -} - -#if QT_VERSION >= 0x050000 -extern QPixmap qt_pixmapFromWinHICON(HICON icon); -#else -#define qt_pixmapFromWinHICON(icon) QPixmap::fromWinHICON(icon) -#endif - -QIcon MainWindow::iconForExecutable(const QString &filePath) -{ - HICON winIcon; - UINT res = ::ExtractIconExW(ToWString(filePath).c_str(), 0, &winIcon, NULL, 1); - if (res == 1) { - QIcon result = QIcon(qt_pixmapFromWinHICON(winIcon)); - ::DestroyIcon(winIcon); - return result; - } else { - return QIcon(":/MO/gui/executable"); - } -} - -void MainWindow::refreshExecutablesList() -{ - QComboBox* executablesList = findChild("executablesListBox"); - executablesList->setEnabled(false); - executablesList->clear(); - executablesList->addItem(tr("")); - - QAbstractItemModel *model = executablesList->model(); - - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - for(int i = 0; current != end; ++current, ++i) { - QVariant temp; - temp.setValue(*current); - QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); - executablesList->addItem(icon, current->m_Title, temp); - model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); - } - - setExecutableIndex(1); - executablesList->setEnabled(true); -} - - -void MainWindow::refreshDataTree() -{ - QCheckBox *conflictsBox = findChild("conflictsCheckBox"); - QTreeWidget *tree = findChild("dataTree"); - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - updateTo(subTree, L"", *m_DirectoryStructure, conflictsBox->isChecked()); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); - tree->header()->resizeSection(0, 200); -} - - -void MainWindow::refreshSavesIfOpen() -{ - if (ui->tabWidget->currentIndex() == 3) { - refreshSaveList(); - } -} - - -void MainWindow::refreshSaveList() -{ - ui->savegameList->clear(); - - QDir savesDir; - if (m_CurrentProfile->localSavesEnabled()) { - savesDir.setPath(m_CurrentProfile->getPath() + "/saves"); - } else { - wchar_t path[MAX_PATH]; - ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves", - path, MAX_PATH, - (ToWString(m_CurrentProfile->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str()); - savesDir.setPath(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getDocumentsDir() + L"\\" + path))); - } - - if (m_SavesWatcher.directories().length() > 0) { - m_SavesWatcher.removePaths(m_SavesWatcher.directories()); - } - m_SavesWatcher.addPath(savesDir.absolutePath()); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); - - foreach (const QFileInfo &file, files) { - QListWidgetItem *item = new QListWidgetItem(file.fileName()); - item->setData(Qt::UserRole, file.absoluteFilePath()); - ui->savegameList->addItem(item); - } -} - - -void MainWindow::refreshLists() -{ - if ((m_CurrentProfile != NULL) && m_DirectoryStructure->isPopulated()) { - refreshESPList(); - refreshBSAList(); - } // no point in refreshing lists if no files have been added to the directory tree -} - - -void MainWindow::refreshESPList() -{ - m_CurrentProfile->writeModlist(); - - // clear list - try { - m_PluginList.refresh(m_CurrentProfile->getName(), - *m_DirectoryStructure, - m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName()); - } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); - } -} - -void MainWindow::refreshModList(bool saveChanges) -{ - // don't lose changes! - if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); - } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - - m_CurrentProfile->refreshModStatus(); - - m_ModList.notifyChange(-1); - - refreshDirectoryStructure(); -} - - -static bool BySortValue(const std::pair &LHS, const std::pair &RHS) -{ - return LHS.first < RHS.first; -} - - -template -QStringList toStringList(InputIterator current, InputIterator end) -{ - QStringList result; - for (; current != end; ++current) { - result.append(*current); - } - return result; -} - - -void MainWindow::refreshBSAList() -{ - m_ArchivesInit = false; - ui->bsaList->clear(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); -#else - ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents); -#endif - - m_DefaultArchives.clear(); - - wchar_t buffer[256]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName())); - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives = ToQString(buffer).split(','); - } else { - std::vector vanillaBSAs = GameInfo::instance().getVanillaBSAs(); - for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) { - m_DefaultArchives.append(ToQString(*iter)); - } - } - - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives.append(ToQString(buffer).split(',')); - } - - for (int i = 0; i < m_DefaultArchives.count(); ++i) { - m_DefaultArchives[i] = m_DefaultArchives[i].trimmed(); - } - - m_ActiveArchives.clear(); - - auto iter = enabledArchives(); - m_ActiveArchives = toStringList(iter.begin(), iter.end()); - if (m_ActiveArchives.isEmpty()) { - m_ActiveArchives = m_DefaultArchives; - } - - std::vector > items; - - std::vector files = m_DirectoryStructure->getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - - QString filename = ToQString(current->getName().c_str()); - QString extension = filename.right(3).toLower(); - - if (extension == "bsa") { - int index = m_ActiveArchives.indexOf(filename); - if (index == -1) { - index = 0xFFFF; - } - QString basename = filename.left(filename.indexOf(".")); - QStringList strings(filename); - bool isArchive = false; - int origin = current->getOrigin(isArchive); - strings.append(ToQString(m_DirectoryStructure->getOriginByID(origin).getName())); - QTreeWidgetItem *newItem = new QTreeWidgetItem(strings); - newItem->setData(0, Qt::UserRole, index); - newItem->setData(1, Qt::UserRole, origin); - newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, false); - if (m_Settings.forceEnableCoreFiles() - && m_DefaultArchives.contains(filename)) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - newItem->setData(0, Qt::UserRole, true); - } else if ((m_PluginList.state(basename + ".esp") == IPluginList::STATE_ACTIVE) - || (m_PluginList.state(basename + ".esm") == IPluginList::STATE_ACTIVE)) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - } else { - if (ui->manageArchivesBox->isChecked()) { - newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked); - } else { - newItem->setCheckState(0, Qt::Unchecked); - newItem->setDisabled(true); - } - } - - if (index < 0) index = 0; - - UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); - items.push_back(std::make_pair(sortValue, newItem)); - } - } - - std::sort(items.begin(), items.end(), BySortValue); - - for (std::vector >::iterator iter = items.begin(); iter != items.end(); ++iter) { - int originID = iter->second->data(1, Qt::UserRole).toInt(); - FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); - QList items = ui->bsaList->findItems(ToQString(origin.getName()), Qt::MatchFixedString); - QTreeWidgetItem *subItem = NULL; - if (items.length() > 0) { - subItem = items.at(0); - } else { - subItem = new QTreeWidgetItem(QStringList(ToQString(origin.getName()))); - subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); - ui->bsaList->addTopLevelItem(subItem); - } - subItem->addChild(iter->second); - subItem->setExpanded(true); - } - - checkBSAList(); - m_ArchivesInit = true; -} - - -void MainWindow::checkBSAList() -{ - ui->bsaList->blockSignals(true); - - bool warning = false; - - for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - bool modWarning = false; - QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); - for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem *item = tlItem->child(j); - QString filename = item->text(0); - item->setIcon(0, QIcon()); - item->setToolTip(0, QString()); - - if (item->checkState(0) == Qt::Unchecked) { - if (m_DefaultArchives.contains(filename)) { - item->setIcon(0, QIcon(":/MO/gui/warning")); - item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); - modWarning = true; - } - } - } - if (modWarning) { - ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); - warning = true; - } - } - - if (warning) { - ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning")); - } else { - ui->tabWidget->setTabIcon(1, QIcon()); - } - - ui->bsaList->blockSignals(false); -} - - -void MainWindow::saveModMetas() -{ - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->saveMeta(); - } - } - - -void MainWindow::fixCategories() -{ - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - std::set categories = modInfo->getCategories(); - for (std::set::iterator iter = categories.begin(); - iter != categories.end(); ++iter) { - if (!m_CategoryFactory.categoryExists(*iter)) { - modInfo->setCategory(*iter, false); - } - } - } -} - - -void MainWindow::setupNetworkProxy(bool activate) -{ - QNetworkProxyFactory::setUseSystemConfiguration(activate); -/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); - query.setProtocolTag("http"); - QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); - if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { - qDebug("Using proxy: %s", qPrintable(proxies.at(0).hostName())); - QNetworkProxy::setApplicationProxy(proxies[0]); - } else { - qDebug("Not using proxy"); - }*/ -} - - -void MainWindow::activateProxy(bool activate) -{ - QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); - busyDialog.setWindowFlags(busyDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - busyDialog.setWindowModality(Qt::WindowModal); - busyDialog.show(); - QFuture future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate); - while (!future.isFinished()) { - QCoreApplication::processEvents(); - ::Sleep(100); - } - busyDialog.hide(); -} - -void MainWindow::readSettings() -{ - QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat); - - if (settings.contains("window_geometry")) { - restoreGeometry(settings.value("window_geometry").toByteArray()); - } - - if (settings.contains("window_split")) { - ui->splitter->restoreState(settings.value("window_split").toByteArray()); - } - - if (settings.contains("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); - } - - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); - - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); - } - - ui->manageArchivesBox->blockSignals(true); - ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool()); - ui->manageArchivesBox->blockSignals(false); -} - - -void MainWindow::storeSettings() -{ - if (m_CurrentProfile == NULL) { - return; - } - m_CurrentProfile->writeModlist(); - m_CurrentProfile->createTweakedIniFile(); - saveCurrentLists(); - m_Settings.setupLoadMechanism(); - - QString iniFile = ToQString(GameInfo::instance().getIniFilename()); - shellCopy(iniFile, iniFile + ".new", true, this); - - QSettings::Status result = QSettings::NoError; - { - QSettings settings(iniFile + ".new", QSettings::IniFormat); - if (m_CurrentProfile != NULL) { - settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); - } else { - settings.remove("selected_profile"); - } - - settings.setValue("mod_list_state", ui->modList->header()->saveState()); - settings.setValue("plugin_list_state", ui->espList->header()->saveState()); - - settings.setValue("group_state", ui->groupCombo->currentIndex()); - - settings.setValue("ask_for_nexuspw", m_AskForNexusPW); - - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); - - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked()); - - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - int count = 0; - for (; current != end; ++current) { - const Executable &item = *current; - if (item.m_Custom || item.m_Toolbar) { - settings.setArrayIndex(count++); - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("title", item.m_Title); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); - settings.setValue("steamAppID", item.m_SteamAppID); - settings.setValue("custom", item.m_Custom); - settings.setValue("toolbar", item.m_Toolbar); - } - } - settings.endArray(); - - QComboBox *executableBox = findChild("executablesListBox"); - settings.setValue("selected_executable", executableBox->currentIndex()); - - FileDialogMemory::save(settings); - - settings.sync(); - result = settings.status(); - } - if (result == QSettings::NoError) { - if (!shellRename(iniFile + ".new", iniFile, true, this)) { - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError()))); - } - } else { - QString reason = result == QSettings::AccessError ? tr("File is write protected") - : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(reason)); - } -} - - -void MainWindow::on_btnRefreshData_clicked() -{ - if (!m_DirectoryUpdate) { - refreshDirectoryStructure(); - } else { - qDebug("directory update"); - } -} - -void MainWindow::on_tabWidget_currentChanged(int index) -{ - if (index == 0) { - refreshESPList(); - } else if (index == 1) { - refreshBSAList(); - } else if (index == 2) { - refreshDataTree(); - } else if (index == 3) { - refreshSaveList(); - } else if (index == 4) { - ui->downloadView->scrollToBottom(); - } -} - -std::vector MainWindow::activeProblems() const -{ - std::vector problems; - if (m_UnloadedPlugins.size() != 0) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - if (m_PluginList.enabledCount() > 255) { - problems.push_back(PROBLEM_TOOMANYPLUGINS); - } - return problems; -} - -QString MainWindow::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("Too many esps and esms enabled"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString MainWindow::fullDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
      "; - foreach (const QString &plugin, m_UnloadedPlugins) { - result += "
    • " + plugin + "
    • "; - } - result += "
        "; - return result; - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or " - "merge some plugins into one. You can find a guide here: http://wiki.step-project.com/Guide:Merging_Plugins"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool MainWindow::hasGuidedFix(unsigned int) const -{ - return false; -} - -void MainWindow::startGuidedFix(unsigned int) const -{ -} - -void MainWindow::installMod() -{ - try { - QStringList extensions = m_InstallationManager.getSupportedExtensions(); - for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { - *iter = "*." + *iter; - } - - QString fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), - tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); - - if (fileName.length() == 0) { - return; - } else { - installMod(fileName); - } - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -IModInterface *MainWindow::installMod(const QString &fileName) -{ - if (m_CurrentProfile == NULL) { - return NULL; - } - - bool hasIniTweaks = false; - GuessedValue modName; - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); - } - m_ModInstalled(modName); - return modInfo.data(); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } - return NULL; -} - -IModInterface *MainWindow::getMod(const QString &name) -{ - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - return NULL; - } else { - return ModInfo::getByIndex(index).data(); - } -} - -IModInterface *MainWindow::createMod(GuessedValue &name) -{ - if (!m_InstallationManager.testOverwrite(name)) { - return NULL; - } - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - - QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); - - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - settingsFile.setValue("modid", 0); - settingsFile.setValue("version", ""); - settingsFile.setValue("newestVersion", ""); - settingsFile.setValue("category", 0); - settingsFile.setValue("installationFile", ""); - return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); -} - -bool MainWindow::removeMod(IModInterface *mod) -{ - unsigned int index = ModInfo::getIndex(mod->name()); - if (index == UINT_MAX) { - return mod->remove(); - } else { - return ModInfo::removeMod(index); - } -} - -QList MainWindow::findFileInfos(const QString &path, const std::function &filter) const -{ - QList result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - IOrganizer::FileInfo info; - info.filePath = ToQString(file->getFullPath()); - bool fromArchive = false; - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); - info.archive = fromArchive ? ToQString(file->getArchive()) : ""; - foreach (int idx, file->getAlternatives()) { - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); - } - - if (filter(info)) { - result.append(info); - } - } - } - return result; -} - -void MainWindow::on_startButton_clicked() -{ - QComboBox* executablesList = findChild("executablesListBox"); - - Executable selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); - - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); -} - - -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, - LPCSTR linkFileName, LPCWSTR description, - LPCWSTR currentDirectory) -{ - HRESULT result = E_INVALIDARG; - if ((targetFileName != NULL) && (wcslen(targetFileName) > 0) && - (arguments != NULL) && - (linkFileName != NULL) && (strlen(linkFileName) > 0) && - (description != NULL) && - (currentDirectory != NULL)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - if (!SUCCEEDED(result)) return result; - - result = shellLink->SetPath(targetFileName); - if (!SUCCEEDED(result)) { - qCritical("failed to set target path %ls", targetFileName); - shellLink->Release(); - return result; - } - result = shellLink->SetArguments(arguments); - if (!SUCCEEDED(result)) { - qCritical("failed to set arguments: %ls", arguments); - shellLink->Release(); - return result; - } - - if (wcslen(description) > 0) { - result = shellLink->SetDescription(description); - if (!SUCCEEDED(result)) { - qCritical("failed to set description: %ls", description); - shellLink->Release(); - return result; - } - } - - if (wcslen(currentDirectory) > 0) { - result = shellLink->SetWorkingDirectory(currentDirectory); - if (!SUCCEEDED(result)) { - qCritical("failed to set working directory: %ls", currentDirectory); - shellLink->Release(); - return result; - } - } - - IPersistFile* persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH); - result = persistFile->Save(linkFileNameW, TRUE); - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - - -bool MainWindow::modifyExecutablesDialog() -{ - bool result = false; - try { - EditExecutablesDialog dialog(m_ExecutablesList); - if (dialog.exec() == QDialog::Accepted) { - m_ExecutablesList = dialog.getExecutablesList(); - result = true; - } - refreshExecutablesList(); - } catch (const std::exception &e) { - reportError(e.what()); - } - return result; -} - -void MainWindow::on_executablesListBox_currentIndexChanged(int index) -{ - QComboBox* executablesList = findChild("executablesListBox"); - - int previousIndex = m_OldExecutableIndex; - m_OldExecutableIndex = index; - - if (executablesList->isEnabled()) { - - if (executablesList->itemData(index).isNull()) { - if (modifyExecutablesDialog()) { - setExecutableIndex(previousIndex); -// executablesList->setCurrentIndex(previousIndex); - } - } else { - setExecutableIndex(index); - } - } -} - -void MainWindow::helpTriggered() -{ - QWhatsThis::enterWhatsThisMode(); -} - -void MainWindow::wikiTriggered() -{ -// ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg/wiki/Modorganizer%3AMainPage", NULL, NULL, SW_SHOWNORMAL); - ::ShellExecuteW(NULL, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::issueTriggered() -{ - ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg", NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::tutorialTriggered() -{ - QAction *tutorialAction = qobject_cast(sender()); - if (tutorialAction != NULL) { - if (QMessageBox::question(this, tr("Start Tutorial?"), - tr("You're about to start a tutorial. For technical reasons it's not possible to end " - "the tutorial early. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - TutorialManager::instance().activateTutorial("MainWindow", tutorialAction->data().toString()); - } - } -} - - -void MainWindow::on_actionInstallMod_triggered() -{ - installMod(); -} - -void MainWindow::on_actionAdd_Profile_triggered() -{ - bool repeat = true; - while (repeat) { - ProfilesDialog profilesDialog(m_GamePath); - profilesDialog.exec(); - if (refreshProfiles() && !profilesDialog.failed()) { - repeat = false; - } - } -// addProfile(); -} - -void MainWindow::on_actionModify_Executables_triggered() -{ - if (modifyExecutablesDialog()) { - setExecutableIndex(m_OldExecutableIndex); - } -} - - -void MainWindow::setModListSorting(int index) -{ - Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; - int column = index >> 1; - ui->modList->header()->setSortIndicator(column, order); -} - - -void MainWindow::setESPListSorting(int index) -{ - switch (index) { - case 0: { - ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder); - } break; - case 1: { - ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder); - } break; - case 2: { - ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder); - } break; - case 3: { - ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder); - } break; - } -} - - -bool MainWindow::queryLogin(QString &username, QString &password) -{ - CredentialsDialog dialog(this); - int res = dialog.exec(); - if (dialog.neverAsk()) { - m_AskForNexusPW = false; - } - if (res == QDialog::Accepted) { - username = dialog.username(); - password = dialog.password(); - if (dialog.store()) { - m_Settings.setNexusLogin(username, password); - } - return true; - } else { - return false; - } -} - - -bool MainWindow::setCurrentProfile(int index) -{ - QComboBox *profilesBox = findChild("profileBox"); - if (index >= profilesBox->count()) { - return false; - } else { - profilesBox->setCurrentIndex(index); - return true; - } -} - -bool MainWindow::setCurrentProfile(const QString &name) -{ - QComboBox *profilesBox = findChild("profileBox"); - for (int i = 0; i < profilesBox->count(); ++i) { - if (QString::compare(profilesBox->itemText(i), name, Qt::CaseInsensitive) == 0) { - profilesBox->setCurrentIndex(i); - return true; - } - } - // profile not valid - profilesBox->setCurrentIndex(1); - return false; -} - - -void MainWindow::refresher_progress(int percent) -{ - m_RefreshProgress->setValue(percent); -} - - -void MainWindow::directory_refreshed() -{ - DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); - Q_ASSERT(newStructure != m_DirectoryStructure); - if (newStructure != NULL) { - std::swap(m_DirectoryStructure, newStructure); - delete newStructure; - refreshDataTree(); - } else { - // TODO: don't know why this happens, this slot seems to get called twice with only one emit - return; - } - m_DirectoryUpdate = false; - if (m_CurrentProfile != NULL) { - refreshLists(); - } - - // some problem-reports may rely on the virtual directory tree so they need to be updated - // now - updateProblemsButton(); - - for (int i = 0; i < m_ModList.rowCount(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->clearCaches(); - } - statusBar()->hide(); -} - -void MainWindow::externalMessage(const QString &message) -{ - if (message.left(6).toLower() == "nxm://") { - MessageDialog::showMessage(tr("Download started"), this); - downloadRequestedNXM(message); - } -} - -void MainWindow::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) -{ - // add files of the bsa to the directory structure - m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->stealFiles() - ); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - // need to refresh plugin list now so we can activate esps - refreshESPList(); - // activate all esps of the specified mod so the bsas get activated along with it - updateModActiveState(index, true); - // now we need to refresh the bsa list and save it so there is no confusion about what archives are avaiable and active - refreshBSAList(); - saveArchiveList(); - m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); - - // finally also add files from bsas to the directory structure - m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->archives() - ); -} - -void MainWindow::modStatusChanged(unsigned int index) -{ - try { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (m_CurrentProfile->modEnabled(index)) { - updateModInDirectoryStructure(index, modInfo); - } else { - updateModActiveState(index, false); - refreshESPList(); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - } - } - modInfo->clearCaches(); - - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - int priority = m_CurrentProfile->getModPriority(i); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); - } - } - m_DirectoryStructure->getFileRegister()->sortOrigins(); - - refreshLists(); - } catch (const std::exception& e) { - reportError(tr("failed to update mod list: %1").arg(e.what())); - } -} - - -void MainWindow::removeOrigin(const QString &name) -{ - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); - origin.enable(false); - refreshLists(); -} - - -void MainWindow::modorder_changed() -{ - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - int priority = m_CurrentProfile->getModPriority(i); - if (m_CurrentProfile->modEnabled(i)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); - } - } - refreshBSAList(); - m_CurrentProfile->writeModlist(); - saveArchiveList(); - m_DirectoryStructure->getFileRegister()->sortOrigins(); -} - -void MainWindow::procError(QProcess::ProcessError error) -{ - reportError(tr("failed to spawn notepad.exe: %1").arg(error)); - this->sender()->deleteLater(); -} - -void MainWindow::procFinished(int, QProcess::ExitStatus) -{ - this->sender()->deleteLater(); -} - -void MainWindow::profileRefresh() -{ - // have to refresh mods twice (again in refreshModList), otherwise the refresh isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - m_CurrentProfile->refreshModStatus(); - - refreshModList(); -} - -void MainWindow::showMessage(const QString &message) -{ - MessageDialog::showMessage(message, this); -} - -void MainWindow::showError(const QString &message) -{ - reportError(message); -} - -void MainWindow::installMod_clicked() -{ - installMod(); -} - -void MainWindow::renameModInList(QFile &modList, const QString &oldName, const QString &newName) -{ - //TODO this code needs to be merged with ModList::readFrom - if (!modList.open(QIODevice::ReadWrite)) { - reportError(tr("failed to open %1").arg(modList.fileName())); - return; - } - - QBuffer outBuffer; - outBuffer.open(QIODevice::WriteOnly); - - while (!modList.atEnd()) { - QByteArray line = modList.readLine(); - - if (line.length() == 0) { - // ignore empty lines - qWarning("mod list contained invalid data: empty line"); - continue; - } - - char spec = line.at(0); - if (spec == '#') { - // don't touch comments - outBuffer.write(line); - continue; - } - - QString modName = QString::fromUtf8(line).mid(1).trimmed(); - - if (modName.isEmpty()) { - // file broken? - qWarning("mod list contained invalid data: missing mod name"); - continue; - } - - outBuffer.write(QByteArray(1, spec)); - if (modName == oldName) { - modName = newName; - } - outBuffer.write(modName.toUtf8().constData()); - outBuffer.write("\r\n"); - } - - modList.resize(0); - modList.write(outBuffer.buffer()); - modList.close(); -} - - -void MainWindow::modRenamed(const QString &oldName, const QString &newName) -{ - // fix the profiles directly on disc - for (int i = 0; i < ui->profileBox->count(); ++i) { - QString profileName = ui->profileBox->itemText(i); - - //TODO this functionality should be in the Profile class - QString modlistName = QString("%1/%2/modlist.txt") - .arg(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))) - .arg(profileName); - - QFile modList(modlistName); - if (modList.exists()) { - qDebug("rewrite modlist %s", QDir::toNativeSeparators(modlistName).toUtf8().constData()); - renameModInList(modList, oldName, newName); - } - } - - // immediately refresh the active profile because the data in memory is invalid - m_CurrentProfile->refreshModStatus(); - - // also fix the directory structure - try { - if (m_DirectoryStructure->originExists(ToWString(oldName))) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(oldName)); - origin.setName(ToWString(newName)); - } else { - - } - } catch (const std::exception &e) { - reportError(tr("failed to change origin name: %1").arg(e.what())); - } -} - - -void MainWindow::modlistChanged(int) -{ - m_ModListSortProxy->invalidate(); -} - - -void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) -{ - const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath)); - if (filePtr.get() != NULL) { - try { - if (m_DirectoryStructure->originExists(ToWString(newOriginName))) { - FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName)); - - QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; - WIN32_FIND_DATAW findData; - ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); - - filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); - } - if (m_DirectoryStructure->originExists(ToWString(oldOriginName))) { - FilesOrigin &oldOrigin = m_DirectoryStructure->getOriginByName(ToWString(oldOriginName)); - filePtr->removeOrigin(oldOrigin.getID()); - } - } catch (const std::exception &e) { - reportError(tr("failed to move \"%1\" from mod \"%2\" to \"%3\": %4").arg(filePath).arg(oldOriginName).arg(newOriginName).arg(e.what())); - } - } else { - // this is probably not an error, the specified path is likely a directory - } -} - - -QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID) -{ - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); - item->setData(0, Qt::UserRole, categoryID); - if (root != NULL) { - root->addChild(item); - } else { - ui->categoriesList->addTopLevelItem(item); - } - return item; -} - - -void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) -{ - for (unsigned i = 1; i < m_CategoryFactory.numCategories(); ++i) { - if ((m_CategoryFactory.getParentID(i) == targetID)) { - int categoryID = m_CategoryFactory.getCategoryID(i); - if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { - QTreeWidgetItem *item = addFilterItem(root, m_CategoryFactory.getCategoryName(i), categoryID); - if (m_CategoryFactory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); - } - } - } - } -} - - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - ui->modList->setCurrentIndex(QModelIndex()); - - QStringList selectedItems; - foreach (QTreeWidgetItem *item, ui->categoriesList->selectedItems()) { - selectedItems.append(item->text(0)); - } - - ui->categoriesList->clear(); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT); - addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED); - - std::set categoriesUsed; - for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); - BOOST_FOREACH (int categoryID, modInfo->getCategories()) { - int currentID = categoryID; - // also add parents so they show up in the tree - while (currentID != 0) { - categoriesUsed.insert(currentID); - currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); - } - } - } - - addCategoryFilters(NULL, categoriesUsed, 0); - - foreach (const QString &item, selectedItems) { - QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); - if (matches.size() > 0) { - matches.at(0)->setSelected(true); - } - } - - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); -} - - -void MainWindow::renameMod_clicked() -{ - try { - ui->modList->edit(ui->modList->currentIndex()); - } catch (const std::exception &e) { - reportError(tr("failed to rename mod: %1").arg(e.what())); - } -} - - -void MainWindow::restoreBackup_clicked() -{ - QRegExp backupRegEx("(.*)_backup[0-9]*$"); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - if (backupRegEx.indexIn(modInfo->name()) != -1) { - QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_Settings.getModDirectory())); - if (!modDir.exists(regName) || - (QMessageBox::question(this, tr("Overwrite?"), - tr("This will replace the existing mod \"%1\". Continue?").arg(regName), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { - reportError(tr("failed to remove mod \"%1\"").arg(regName)); - } else { - QString destinationPath = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + "/" + regName; - if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); - } - refreshModList(); - } - } - } -} - -void MainWindow::updateModActiveState(int index, bool active) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - - QDir dir(modInfo->absolutePath()); - foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) { - m_PluginList.enableESP(esm, active); - } - int enabled = 0; - QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files); - foreach (const QString &esp, esps) { - if (active != m_PluginList.isEnabled(esp)) { - m_PluginList.enableESP(esp, active); - ++enabled; - } - } - if (active && (enabled > 1)) { - MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), this); - } - m_PluginList.refreshLoadOrder(); - // immediately save affected lists - savePluginList(); -// refreshBSAList(); -} - -void MainWindow::modlistChanged(const QModelIndex&, int) -{ - m_CurrentProfile->writeModlist(); -} - -void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) -{ - ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - if (m_ModListSortProxy != NULL) { - m_ModListSortProxy->invalidate(); - } - ui->modList->verticalScrollBar()->repaint(); -} - -void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) -{ - ui->modList->verticalScrollBar()->repaint(); -} - -void MainWindow::removeMod_clicked() -{ - try { - QItemSelectionModel *selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - QString mods; - QStringList modNames; - foreach (QModelIndex idx, selection->selectedRows()) { -// QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name(); - QString name = idx.data().toString(); - if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { - continue; - } - mods += "
      • " + name + "
      • "; - modNames.append(name); - } - if (QMessageBox::question(this, tr("Confirm"), - tr("Remove the following mods?
          %1
        ").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - // use mod names instead of indexes because those become invalid during the removal - foreach (QString name, modNames) { - m_ModList.removeRowForce(ModInfo::getIndex(name)); - } - } - } else { - m_ModList.removeRow(m_ContextRow, QModelIndex()); - } - } catch (const std::exception &e) { - reportError(tr("failed to remove mod: %1").arg(e.what())); - } -} - - -void MainWindow::modRemoved(const QString &fileName) -{ - if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { - int index = m_DownloadManager.indexByName(fileName); - if (index >= 0) { - m_DownloadManager.markUninstalled(index); - } - } -} - - -void MainWindow::reinstallMod_clicked() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - QString installationFile = modInfo->getInstallationFile(); - if (installationFile.length() != 0) { - QString fullInstallationFile; - QFileInfo fileInfo(installationFile); - if (fileInfo.isAbsolute()) { - if (fileInfo.exists()) { - fullInstallationFile = installationFile; - } else { - fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(fileInfo.fileName()); - } - } else { - fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(installationFile); - } - if (QFile::exists(fullInstallationFile)) { - installMod(fullInstallationFile); - } else { - QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists")); - } - } else { - QMessageBox::information(this, tr("Failed"), - tr("Mods installed with old versions of MO can't be reinstalled in this way.")); - } -} - - -void MainWindow::resumeDownload(int downloadIndex) -{ - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_DownloadManager.resumeDownload(downloadIndex); - } else { - QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); - } - } -} - - -void MainWindow::endorseMod(ModInfo::Ptr mod) -{ - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - mod->endorse(true); - } else { - QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } -} - - -void MainWindow::endorse_clicked() -{ - endorseMod(ModInfo::getByIndex(m_ContextRow)); -} - -void MainWindow::dontendorse_clicked() -{ - ModInfo::getByIndex(m_ContextRow)->setNeverEndorse(); -} - - -void MainWindow::unendorse_clicked() -{ - QString username, password; - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - ModInfo::getByIndex(m_ContextRow)->endorse(false); - } else { - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::unendorse_clicked)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } -} - - -void MainWindow::overwriteClosed(int) -{ - OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); - if (dialog != NULL) { - m_ModList.modInfoChanged(dialog->modInfo()); - dialog->deleteLater(); - } -} - - -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) -{ - m_ModList.modInfoAboutToChange(modInfo); - std::vector flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - QDialog *dialog = this->findChild("__overwriteDialog"); - try { - if (dialog == NULL) { - dialog = new OverwriteInfoDialog(modInfo, this); - dialog->setObjectName("__overwriteDialog"); - } else { - qobject_cast(dialog)->setModInfo(modInfo); - } - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); - } catch (const std::exception &e) { - reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); - } - } else { - modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); - connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); - connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); - connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenNext()), this, SLOT(modOpenNext()), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenPrev()), this, SLOT(modOpenPrev()), Qt::QueuedConnection); - connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); - - dialog.openTab(tab); - dialog.restoreTabState(m_Settings.directInterface().value("mod_info_tabs").toByteArray()); - dialog.exec(); - m_Settings.directInterface().setValue("mod_info_tabs", dialog.saveTabState()); - - modInfo->saveMeta(); - emit modInfoDisplayed(); - m_ModList.modInfoChanged(modInfo); - } - - if (m_CurrentProfile->modEnabled(index)) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - m_DirectoryRefresher.addModToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->stealFiles() - , modInfo->archives()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - refreshLists(); - } - } -} - - -void MainWindow::modOpenNext() -{ - QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); - - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { - // skip overwrite and backups - modOpenNext(); - } else { - displayModInformation(m_ContextRow); - } -} - -void MainWindow::modOpenPrev() -{ - QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } - - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { - // skip overwrite and backups - modOpenPrev(); - } else { - displayModInformation(m_ContextRow); - } -} - -void MainWindow::displayModInformation(const QString &modName, int tab) -{ - unsigned int index = ModInfo::getIndex(modName); - if (index == UINT_MAX) { - qCritical("failed to resolve mod name %s", modName.toUtf8().constData()); - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - displayModInformation(modInfo, index, tab); -} - - -void MainWindow::displayModInformation(int row, int tab) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - displayModInformation(modInfo, row, tab); -} - - -void MainWindow::ignoreMissingData_clicked() -{ - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - QDir(info->absolutePath()).mkdir("textures"); - info->testValid(); - connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), &m_ModList, SIGNAL(dataChanged(QModelIndex,QModelIndex))); - - emit modListDataChanged(m_ModList.index(m_ContextRow, 0), m_ModList.index(m_ContextRow, m_ModList.columnCount() - 1)); -} - - -void MainWindow::visitOnNexus_clicked() -{ - int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); - if (modID > 0) { - nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID)); - } else { - MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); - } -} - -void MainWindow::openExplorer_clicked() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - ::ShellExecuteW(NULL, L"explore", ToWString(modInfo->absolutePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::information_clicked() -{ - try { - displayModInformation(m_ContextRow); - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::syncOverwrite() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, this); - if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); - modInfo->testValid(); - refreshDirectoryStructure(); - } - -} - -void MainWindow::createModFromOverwrite() -{ - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will move all files from overwrite into a new, regular mod.\n" - "Please enter a name:"), QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (getMod(name) != NULL) { - reportError(tr("A mod with this name already exists")); - return; - } - - IModInterface *newMod = createMod(name); - if (newMod == NULL) { - return; - } - - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(m_ContextRow); - shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this); - - refreshModList(); -} - -void MainWindow::cancelModListEditor() -{ - ui->modList->setEnabled(false); - ui->modList->setEnabled(true); -} - -void MainWindow::on_modList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - QModelIndex sourceIdx = mapToModel(&m_ModList, index); - if (!sourceIdx.isValid()) { - return; - } - - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); -// displayModInformation(m_ModListSortProxy->mapToSource(index).row()); - displayModInformation(sourceIdx.row()); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - const std::set &categories = modInfo->getCategories(); - - bool childEnabled = false; - - for (unsigned i = 1; i < m_CategoryFactory.numCategories(); ++i) { - if (m_CategoryFactory.getParentID(i) == targetID) { - QMenu *targetMenu = menu; - if (m_CategoryFactory.hasChildren(i)) { - targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - } - - int id = m_CategoryFactory.getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); - bool enabled = categories.find(id) != categories.end(); - checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - if (enabled) { - childEnabled = true; - } - checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); - - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); - checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); - - if (m_CategoryFactory.hasChildren(i)) { - if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { - targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); - } - } - } - } - return childEnabled; -} - -void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); - foreach (QAction* action, menu->actions()) { - if (action->menu() != NULL) { - replaceCategoriesFromMenu(action->menu(), modRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); - } - } - } -} - -void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) -{ - if (referenceRow != -1 && referenceRow != modRow) { - ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); - foreach (QAction* action, menu->actions()) { - if (action->menu() != NULL) { - addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); - } else { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); - int categoryId = widgetAction->data().toInt(); - bool checkedBefore = editedModInfo->categorySet(categoryId); - bool checkedAfter = checkbox->isChecked(); - - if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod - ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); - currentModInfo->setCategory(categoryId, checkedAfter); - } - } - } - } - } else { - replaceCategoriesFromMenu(menu, modRow); - } -} - -void MainWindow::addRemoveCategories_MenuHandler() { - QMenu *menu = qobject_cast(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - QModelIndexList selectedTemp = ui->modList->selectionModel()->selectedRows(); - QList selected; - foreach (const QModelIndex &idx, selectedTemp) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - foreach (const QPersistentModelIndex &idx, selected) { - qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString())); - QModelIndex modIdx = mapToModel(&m_ModList, idx); - if (modIdx.row() != m_ContextIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); - } - } - replaceCategoriesFromMenu(menu, m_ContextIdx.row()); - - m_ModList.notifyChange(-1); - - foreach (const QPersistentModelIndex &idx, selected) { - ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_ModList.notifyChange(m_ContextRow); - } - - refreshFilters(); -} - -void MainWindow::replaceCategories_MenuHandler() { - QMenu *menu = qobject_cast(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); - - if (selected.size() > 0) { - QStringList selectedMods; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); - selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row()); - } - - m_ModList.notifyChange(-1); - - // find mods by their name because indices are invalidated - QAbstractItemModel *model = ui->modList->model(); - Q_FOREACH(const QString &mod, selectedMods) { - QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, - Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); - if (matches.size() > 0) { - ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } - } else { - //For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_ModList.notifyChange(m_ContextRow); - } - - refreshFilters(); -} - -void MainWindow::savePrimaryCategory() -{ - QMenu *menu = qobject_cast(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - foreach (QAction* action, menu->actions()) { - QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - QRadioButton *btn = qobject_cast(widgetAction->defaultWidget()); - if (btn->isChecked()) { - QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); - for (int i = 0; i < selected.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(selected.at(i)).row()); - modInfo->setPrimaryCategory(widgetAction->data().toInt()); - } - break; - } - } - } -} - -void MainWindow::checkModsForUpdates() -{ - statusBar()->show(); - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_ModsToUpdate = ModInfo::checkAllForUpdate(this); - m_RefreshProgress->setRange(0, m_ModsToUpdate); - } else { - QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { // otherwise there will be no endorsement info - m_ModsToUpdate = ModInfo::checkAllForUpdate(this); - } - } -} - -void MainWindow::changeVersioningScheme() { - if (QMessageBox::question(this, tr("Continue?"), - tr("The versioning scheme decides which version is considered newer than another.\n" - "This function will guess the versioning scheme under the assumption that the installed version is outdated."), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - - bool success = false; - - static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; - - for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { - VersionInfo verOld(info->getVersion().canonicalString(), schemes[i]); - VersionInfo verNew(info->getNewestVersion().canonicalString(), schemes[i]); - if (verOld < verNew) { - info->setVersion(verOld); - info->setNewestVersion(verNew); - success = true; - } - } - if (!success) { - QMessageBox::information(this, tr("Sorry"), - tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->getNewestVersion().canonicalString()).arg(info->getVersion().canonicalString()), - QMessageBox::Ok); - } - } -} - -void MainWindow::ignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(true); -} - -void MainWindow::unignoreUpdate() -{ - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(false); -} - -void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info) -{ - const std::set &categories = info->getCategories(); - foreach (int categoryID, categories) { - int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); - QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); - try { - QRadioButton *categoryBox = new QRadioButton(m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"), - primaryCategoryMenu); - categoryBox->setChecked(categoryID == info->getPrimaryCategory()); - action->setDefaultWidget(categoryBox); - } catch (const std::exception &e) { - qCritical("failed to create category checkbox: %s", e.what()); - } - - action->setData(categoryID); - primaryCategoryMenu->addAction(action); - } -} - -void MainWindow::addPrimaryCategoryCandidates() -{ - QMenu *menu = qobject_cast(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - addPrimaryCategoryCandidates(menu, modInfo); -} - -void MainWindow::enableVisibleMods() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->enableAllVisible(); - } -} - -void MainWindow::disableVisibleMods() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->disableAllVisible(); - } -} - -void MainWindow::exportModListCSV() -{ - SelectionDialog selection(tr("Choose what to export")); - - selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); - selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); - selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); - - if (selection.exec() == QDialog::Accepted) { - unsigned int numMods = ModInfo::getNumMods(); - - try { - QBuffer buffer; - buffer.open(QIODevice::ReadWrite); - CSVBuilder builder(&buffer); - builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); - std::vector > fields; - fields.push_back(std::make_pair(QString("mod_id"), CSVBuilder::TYPE_INTEGER)); - fields.push_back(std::make_pair(QString("mod_installed_name"), CSVBuilder::TYPE_STRING)); - fields.push_back(std::make_pair(QString("mod_version"), CSVBuilder::TYPE_STRING)); - fields.push_back(std::make_pair(QString("file_installed_name"), CSVBuilder::TYPE_STRING)); -// fields.push_back(std::make_pair(QString("file_category"), CSVBuilder::TYPE_INTEGER)); - builder.setFields(fields); - - builder.writeHeader(); - - for (unsigned int i = 0; i < numMods; ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - bool enabled = m_CurrentProfile->modEnabled(i); - if ((selection.getChoiceData().toInt() == 1) && !enabled) { - continue; - } else if ((selection.getChoiceData().toInt() == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { - continue; - } - std::vector flags = info->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { - builder.setRowField("mod_id", info->getNexusID()); - builder.setRowField("mod_installed_name", info->name()); - builder.setRowField("mod_version", info->getVersion().canonicalString()); - builder.setRowField("file_installed_name", info->getInstallationFile()); - builder.writeRow(); - } - } - - SaveTextAsDialog saveDialog(this); - saveDialog.setText(buffer.data()); - saveDialog.exec(); - } catch (const std::exception &e) { - reportError(tr("export failed: %1").arg(e.what())); - } - } -} - -void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) -{ - QPushButton *pushBtn = new QPushButton(subMenu->title()); - pushBtn->setMenu(subMenu); - QWidgetAction *action = new QWidgetAction(menu); - action->setDefaultWidget(pushBtn); - menu->addAction(action); -} - -QMenu *MainWindow::modListContextMenu() -{ - QMenu *menu = new QMenu(this); - menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); - - menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); - menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - - menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); - - menu->addAction(tr("Refresh"), this, SLOT(profileRefresh())); - - menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); - return menu; -} - -void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) -{ - try { - QTreeView *modList = findChild("modList"); - - m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos)); - m_ContextRow = m_ContextIdx.row(); - - QMenu *menu = NULL; - QMenu *allMods = modListContextMenu(); - if (m_ContextRow == -1) { - // no selection - menu = allMods; - } else { - menu = new QMenu(this); - allMods->setTitle(tr("All Mods")); - menu->addMenu(allMods); - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - if (QDir(info->absolutePath()).count() > 2) { - menu->addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite())); - menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); - } - } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); - menu->addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); - } 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")); - populateMenuCategories(addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); - addMenuAsPushButton(menu, addRemoveCategoriesMenu); - - 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())); - connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory())); - addMenuAsPushButton(menu, primaryCategoryMenu); - - menu->addSeparator(); - if (info->downgradeAvailable()) { - menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); - } - if (info->updateAvailable() || info->downgradeAvailable()) { - if (info->updateIgnored()) { - menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); - } else { - menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); - } - } - 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())); - switch (info->endorsedState()) { - case ModInfo::ENDORSED_TRUE: { - menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); - } break; - case ModInfo::ENDORSED_FALSE: { - menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - menu->addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); - } break; - case ModInfo::ENDORSED_NEVER: { - menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - } break; - default: { - QAction *action = new QAction(tr("Endorsement state unknown"), menu); - action->setEnabled(false); - menu->addAction(action); - } break; - } - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); - } - - menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); - } - - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction *infoAction = menu->addAction(tr("Information..."), this, SLOT(information_clicked())); - menu->setDefaultAction(infoAction); - } - } - - menu->exec(modList->mapToGlobal(pos)); - } catch (const std::exception &e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); - } -} - - -void MainWindow::on_categoriesList_itemSelectionChanged() -{ - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); - std::vector categories; - foreach (const QModelIndex &index, indices) { - int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } - - m_ModListSortProxy->setCategoryFilter(categories); - ui->clickBlankLabel->setEnabled(categories.size() > 0); - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); -} - - -void MainWindow::deleteSavegame_clicked() -{ - QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes(); - - QString savesMsgLabel; - QStringList deleteFiles; - - foreach (const QModelIndex &idx, selectedIndexes) { - QString name = idx.data().toString(); - SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); - - savesMsgLabel += "
      • " + QFileInfo(name).completeBaseName() + "
      • "; - - deleteFiles << save->saveFiles(); - } - - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %n save(s)?
          %1

        Removed saves will be sent to the Recycle Bin.", "", selectedIndexes.count()) - .arg(savesMsgLabel), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - shellDelete(deleteFiles, true); // recycle bin delete. - } -} - - -void MainWindow::fixMods_clicked() -{ - QListWidgetItem *selectedItem = ui->savegameList->currentItem(); - - if (selectedItem == NULL) - return; - - // if required, parse the save game - if (selectedItem->data(Qt::UserRole).isNull()) { - QVariant temp; - SaveGameGamebryo *save = getSaveGame(selectedItem->data(Qt::UserRole).toString()); - save->setParent(selectedItem->listWidget()); - temp.setValue(save); - selectedItem->setData(Qt::UserRole, temp); - } - - const SaveGameGamebryo *save = getSaveGame(selectedItem); - - // collect the list of missing plugins - std::map > missingPlugins; - - for (int i = 0; i < save->numPlugins(); ++i) { - const QString &pluginName = save->plugin(i); - if (!m_PluginList.isEnabled(pluginName)) { - missingPlugins[pluginName] = std::vector(); - } - } - - // figure out, for each esp/esm, which mod, if any, contains it - QStringList espFilter("*.esp"); - espFilter.append("*.esm"); - - // search in data - { - QDir dataDir(m_GamePath + "/data"); - QStringList esps = dataDir.entryList(espFilter); - foreach (const QString &esp, esps) { - std::map >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back(""); - } - } - } - - // search in mods - for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) { - int modIndex = m_CurrentProfile->modIndexByPriority(i); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - QStringList esps = QDir(modInfo->absolutePath()).entryList(espFilter); - foreach (const QString &esp, esps) { - std::map >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back(modInfo->name()); - } - } - } - - // search in overwrite - { - QDir overwriteDir(ToQString(GameInfo::instance().getOverwriteDir())); - QStringList esps = overwriteDir.entryList(espFilter); - foreach (const QString &esp, esps) { - std::map >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back(""); - } - } - } - - - ActivateModsDialog dialog(missingPlugins, this); - if (dialog.exec() == QDialog::Accepted) { - // activate the required mods, then enable all esps - std::set modsToActivate = dialog.getModsToActivate(); - for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { - if ((*iter != "") && (*iter != "")) { - unsigned int modIndex = ModInfo::getIndex(*iter); - m_CurrentProfile->setModEnabled(modIndex, true); - } - } - - m_CurrentProfile->writeModlist(); - refreshLists(); - - std::set espsToActivate = dialog.getESPsToActivate(); - for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { - m_PluginList.enableESP(*iter); - } - saveCurrentLists(); - } -} - - -void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selection = ui->savegameList->selectionModel(); - - if (!selection->hasSelection()) - return; - - QMenu menu; - - if (!(selection->selectedIndexes().count() > 1)) - menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); - - QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count()); - - menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); - - menu.exec(ui->savegameList->mapToGlobal(pos)); -} - -void MainWindow::linkToolbar() -{ - const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value(); - Executable &exe = m_ExecutablesList.find(selectedExecutable.m_Title); - exe.m_Toolbar = !exe.m_Toolbar; - ui->linkButton->menu()->actions().at(2)->setIcon(exe.m_Toolbar ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); - updateToolBar(); -} - -void MainWindow::linkDesktop() -{ - QComboBox* executablesList = findChild("executablesListBox"); - - const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); - QString linkName = getDesktopDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; - - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo exeInfo(qApp->arguments().at(0)); - // create link - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) - .arg(selectedExecutable.m_Arguments)); - std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); - if (CreateShortcut(targetFile.c_str() - , parameter.c_str() - , linkName.toUtf8().constData() - , description.c_str() - , currentDirectory.c_str()) != E_INVALIDARG) { - ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} - -void MainWindow::linkMenu() -{ - QComboBox* executablesList = findChild("executablesListBox"); - - const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); - QString linkName = getStartMenuDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; - - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo exeInfo(qApp->arguments().at(0)); - // create link - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) - .arg(selectedExecutable.m_Arguments)); - std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); - - if (CreateShortcut(targetFile.c_str(), parameter.c_str(), - linkName.toUtf8().constData(), - description.c_str(), currentDirectory.c_str()) != E_INVALIDARG) { - ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} - -void MainWindow::downloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); -} - - -void MainWindow::on_actionSettings_triggered() -{ - QString oldModDirectory(m_Settings.getModDirectory()); - QString oldCacheDirectory(m_Settings.getCacheDirectory()); - bool oldDisplayForeign(m_Settings.displayForeign()); - bool proxy = m_Settings.useProxy(); - m_Settings.query(this); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - fixCategories(); - refreshFilters(); - if (QDir::fromNativeSeparators(m_DownloadManager.getOutputDirectory()) != QDir::fromNativeSeparators(m_Settings.getDownloadDirectory())) { - if (m_DownloadManager.downloadsInProgress()) { - MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this); - } else { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - } - } - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - if ((m_Settings.getModDirectory() != oldModDirectory) - || (m_Settings.displayForeign() != oldDisplayForeign)) { - profileRefresh(); - } - - if (m_Settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - } - - if (proxy != m_Settings.useProxy()) { - activateProxy(m_Settings.useProxy()); - } - - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - updateDownloadListDelegate(); -} - - -void MainWindow::on_actionNexus_triggered() -{ - ::ShellExecuteW(NULL, L"open", GameInfo::instance().getNexusPage(false).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - - -void MainWindow::nexusLinkActivated(const QString &link) -{ - ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL); - ui->tabWidget->setCurrentIndex(4); -} - - -void MainWindow::linkClicked(const QString &url) -{ - ::ShellExecuteW(NULL, L"open", ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - - -bool MainWindow::nexusLogin() -{ - QString username, password; - - NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); - - if (!accessManager->loginAttempted() - && !accessManager->loggedIn() - && (m_Settings.getNexusLogin(username, password) - || (m_AskForNexusPW - && queryLogin(username, password)))) { - accessManager->login(username, password); - return true; - } else { - return false; - } -} - - -void MainWindow::downloadRequestedNXM(const QString &url) -{ - qDebug("download requested: %s", qPrintable(url)); - if (nexusLogin()) { - m_PendingDownloads.append(url); - } else { - m_DownloadManager.addNXMDownload(url); - } -} - - -void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName) -{ - try { - if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { - MessageDialog::showMessage(tr("Download started"), this); - } - } catch (const std::exception &e) { - MessageDialog::showMessage(tr("Download failed"), this); - qCritical("exception starting download: %s", e.what()); - } -} - - -void MainWindow::installTranslator(const QString &name) -{ -/* if (m_CurrentLanguage == "en_US") { - return; - }*/ - QTranslator *translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; - if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage != "en_US") { - qWarning("localization file %s not found", qPrintable(fileName)); - } // we don't actually expect localization files for english - } - qApp->installTranslator(translator); - m_Translators.push_back(translator); -} - - -void MainWindow::languageChange(const QString &newLanguage) -{ - foreach (QTranslator *trans, m_Translators) { - qApp->removeTranslator(trans); - } - m_Translators.clear(); - - m_CurrentLanguage = newLanguage; - - installTranslator("qt"); - installTranslator(ToQString(AppConfig::translationPrefix())); - ui->retranslateUi(this); - qDebug("loaded language %s", qPrintable(newLanguage)); - - ui->profileBox->setItemText(0, QObject::tr("")); - - createHelpWidget(); - - updateDownloadListDelegate(); - updateProblemsButton(); - - ui->listOptionsBtn->setMenu(modListContextMenu()); -} - - -void MainWindow::installDownload(int index) -{ - try { - QString fileName = m_DownloadManager.getFilePath(index); - int modID = m_DownloadManager.getModID(index); - GuessedValue modName; - - // see if there already are mods with the specified mod id - if (modID != 0) { - std::vector modInfo = ModInfo::getByModID(modID); - for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { - std::vector flags = (*iter)->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) { - modName.update((*iter)->name(), GUESS_PRESET); - (*iter)->saveMeta(); - } - } - } - - m_CurrentProfile->writeModlistNow(); - - bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); - } - - m_ModInstalled(modName); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - m_DownloadManager.markInstalled(index); - - emit modInstalled(); - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } - } catch (const std::exception &e) { - reportError(e.what()); - } -} - - -void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) -{ - { // list files -// std::set::const_iterator current, end; -// directoryEntry.getFiles(current, end); -// for (; current != end; ++current) { - - std::vector files = directoryEntry.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - bool isArchive = false; - int origin = current->getOrigin(isArchive); - if (isArchive) { - // TODO: don't list files from archives. maybe make this an option? - continue; - } - QString fullName = directory; - fullName.append("\\").append(ToQString(current->getName())); - file.write(fullName.toUtf8()); - - file.write("\t("); - file.write(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()).toUtf8()); - file.write(")\r\n"); - } - } - - { // recurse into subdirectories - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - writeDataToFile(file, directory.mid(0).append("\\").append(ToQString((*current)->getName())), **current); - } - } -} - - -void MainWindow::writeDataToFile() -{ - QString fileName = QFileDialog::getSaveFileName(this); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write to file %1").arg(fileName)); - } - - writeDataToFile(file, "data", *m_DirectoryStructure); - file.close(); - - MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); -} - - -int MainWindow::getBinaryExecuteInfo(const QFileInfo &targetInfo, - QFileInfo &binaryInfo, QString &arguments) -{ - QString extension = targetInfo.completeSuffix(); - if ((extension == "exe") || - (extension == "cmd") || - (extension == "com") || - (extension == "bat")) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - return 1; - } else if (extension == "jar") { - // 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(), NULL, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(targetPathW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty() && (extension == "jar")) { - 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 MainWindow::addAsExecutable() -{ - if (m_ContextItem != NULL) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - QString name = QInputDialog::getText(this, tr("Enter Name"), - tr("Please enter a name for the executable"), QLineEdit::Normal, - targetInfo.baseName()); - if (!name.isEmpty()) { - m_ExecutablesList.addExecutable(name, binaryInfo.absoluteFilePath(), - arguments, targetInfo.absolutePath(), - DEFAULT_STAY, QString(), - true, false); - refreshExecutablesList(); - } - } break; - case 2: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - } break; - default: { - // nop - } break; - } - } -} - - -void MainWindow::originModified(int originID) -{ - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); - origin.enable(false); - m_DirectoryStructure->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); -} - - -void MainWindow::hideFile() -{ - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName + ModInfo::s_HiddenExt; - - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } - - if (QFile::rename(oldName, newName)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTree(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); - } -} - - -void MainWindow::unhideFile() -{ - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } - if (QFile::rename(oldName, newName)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTree(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); - } -} - -void MainWindow::previewDataFile() -{ - QString fileName = QDir::fromNativeSeparators(m_ContextItem->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 - - // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory - int offset = m_Settings.getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); - - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL); - - if (file.get() == NULL) { - reportError(tr("file not found: %1").arg(fileName)); - return; - } - - // set up preview dialog - PreviewDialog preview(fileName); - auto addFunc = [&] (int originId) { - FilesOrigin &origin = m_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_PreviewGenerator.genPreview(filePath); - if (wid == NULL) { - reportError(tr("failed to generate preview for %1").arg(filePath)); - } else { - preview.addVariant(ToQString(origin.getName()), wid); - } - } - }; - - addFunc(file->getOrigin()); - foreach (int i, file->getAlternatives()) { - addFunc(i); - } - 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 MainWindow::openDataFile() -{ - if (m_ContextItem != NULL) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - spawnBinaryDirect(binaryInfo, arguments, m_CurrentProfile->getName(), targetInfo.absolutePath(), ""); - } break; - case 2: { - ::ShellExecuteW(NULL, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } - } -} - - -void MainWindow::updateAvailable() -{ - QToolBar *toolBar = findChild("toolBar"); - foreach (QAction *action, toolBar->actions()) { - if (action->text() == tr("Update")) { - action->setEnabled(true); - action->setToolTip(tr("Update available")); - break; - } - } -} - - -void MainWindow::motdReceived(const QString &motd) -{ - // don't show motd after 5 seconds, may be annoying. Hopefully the user's - // internet connection is faster next time - if (m_StartTime.secsTo(QTime::currentTime()) < 5) { - uint hash = qHash(motd); - if (hash != m_Settings.getMotDHash()) { - MotDDialog dialog(motd); - dialog.exec(); - m_Settings.setMotDHash(hash); - } - } - - ui->actionEndorseMO->setVisible(false); -} - - -void MainWindow::notEndorsedYet() -{ - ui->actionEndorseMO->setVisible(true); -} - - -void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidget *dataTree = findChild("dataTree"); - m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); - - QMenu menu; - if ((m_ContextItem != NULL) && (m_ContextItem->childCount() == 0)) { - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); - menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); - - QString fileName = m_ContextItem->text(0); - if (m_PreviewGenerator.previewSupported(QFileInfo(fileName).completeSuffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); - } - - // offer to hide/unhide file, but not for files from archives - if (!m_ContextItem->data(0, Qt::UserRole + 1).toBool()) { - if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideFile())); - } - } - - menu.addSeparator(); - } - menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); - menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); - - menu.exec(dataTree->mapToGlobal(pos)); -} - -void MainWindow::on_conflictsCheckBox_toggled(bool) -{ - refreshDataTree(); -} - - -void MainWindow::on_actionUpdate_triggered() -{ - if (nexusLogin()) { - m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); }); - } else { - m_Updater.startUpdate(); - } -} - - -void MainWindow::on_actionEndorseMO_triggered() -{ - if (QMessageBox::question(this, tr("Endorse Mod Organizer"), - tr("Do you want to endorse Mod Organizer on %1 now?").arg(ToQString(GameInfo::instance().getNexusPage())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant(), QString()); - } -} - - -void MainWindow::updateDownloadListDelegate() -{ - if (m_Settings.compactDownloads()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); - } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); - } - - DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); - sortProxy->setSourceModel(new DownloadList(&m_DownloadManager, ui->downloadView)); - connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString))); - connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); - - ui->downloadView->setModel(sortProxy); - ui->downloadView->sortByColumn(1, Qt::AscendingOrder); - ui->downloadView->header()->resizeSections(QHeaderView::Fixed); - - connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), this, SLOT(installDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), &m_DownloadManager, SLOT(queryInfo(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), &m_DownloadManager, SLOT(removeDownload(int, bool))); - connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); -} - - -void MainWindow::modDetailsUpdated(bool) -{ - --m_ModsToUpdate; - if (m_ModsToUpdate == 0) { - statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } -// m_RefreshProgress->setVisible(false); - } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); - } -} - -void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int) -{ - m_ModsToUpdate -= modIDs.size(); - QVariantList resultList = resultData.toList(); - for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { - QVariantMap result = iter->toMap(); - if (result["id"].toInt() == GameInfo::instance().getNexusModID()) { - if (!result["voted_by_user"].toBool()) { - ui->actionEndorseMO->setVisible(true); - } - } else { - std::vector info = ModInfo::getByModID(result["id"].toInt()); - for (auto iter = info.begin(); iter != info.end(); ++iter) { - (*iter)->setNewestVersion(result["version"].toString()); - (*iter)->setNexusDescription(result["description"].toString()); - if (NexusInterface::instance()->getAccessManager()->loggedIn() && - result.contains("voted_by_user")) { - // don't use endorsement info if we're not logged in or if the response doesn't contain it - (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); - } - } - } - } - - if (m_ModsToUpdate <= 0) { - statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } - } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); - } -} - -void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) -{ - if (resultData.toBool()) { - ui->actionEndorseMO->setVisible(false); - QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - } - - if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), - this, SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); - } -} - -void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) -{ - QVariantList serverList = resultData.toList(); - - QList servers; - foreach (const QVariant &server, serverList) { - QVariantMap serverInfo = server.toMap(); - ServerInfo info; - info.name = serverInfo["Name"].toString(); - info.premium = serverInfo["IsPremium"].toBool(); - info.lastSeen = QDate::currentDate(); - info.preferred = 0; - // other keys: ConnectedUsers, Country, URI - servers.append(info); - } - m_Settings.updateServers(servers); -} - - -void MainWindow::nxmRequestFailed(int modID, int, QVariant, int, const QString &errorString) -{ - if (modID == -1) { - // must be the update-check that failed - m_ModsToUpdate = 0; - statusBar()->hide(); - } - MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); -} - - -void MainWindow::loginSuccessful(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - foreach (auto task, m_PostLoginTasks) { - task(this); - } - - m_PostLoginTasks.clear(); - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginSuccessfulUpdate(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - m_Updater.startUpdate(); -} - - -void MainWindow::loginFailed(const QString &message) -{ - if (!m_PendingDownloads.isEmpty()) { - MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), this); - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - } else { - MessageDialog::showMessage(tr("login failed: %1").arg(message), this); - m_PostLoginTasks.clear(); - statusBar()->hide(); - } - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginFailedUpdate(const QString &message) -{ - MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), this); -} - - -void MainWindow::windowTutorialFinished(const QString &windowName) -{ - m_Settings.directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); -} - - -BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, - QProgressDialog &progress) -{ - QDir().mkdir(destination); - BSA::EErrorCode result = BSA::ERROR_NONE; - QString errorFile; - - for (unsigned int i = 0; i < folder->getNumFiles(); ++i) { - BSA::File::Ptr file = folder->getFile(i); - BSA::EErrorCode res = archive.extract(file, destination.toUtf8().constData()); - if (res != BSA::ERROR_NONE) { - reportError(tr("failed to read %1: %2").arg(file->getName().c_str()).arg(res)); - result = res; - } - progress.setLabelText(file->getName().c_str()); - progress.setValue(progress.value() + 1); - QCoreApplication::processEvents(); - if (progress.wasCanceled()) { - result = BSA::ERROR_CANCELED; - } - } - - if (result != BSA::ERROR_NONE) { - if (QMessageBox::critical(this, tr("Error"), tr("failed to extract %1 (errorcode %2)").arg(errorFile).arg(result), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { - return result; - } - } - - for (unsigned int i = 0; i < folder->getNumSubFolders(); ++i) { - BSA::Folder::Ptr subFolder = folder->getSubFolder(i); - BSA::EErrorCode res = extractBSA(archive, subFolder, - destination.mid(0).append("/").append(subFolder->getName().c_str()), progress); - if (res != BSA::ERROR_NONE) { - return res; - } - } - return BSA::ERROR_NONE; -} - - -bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std::string fileName) -{ - progress.setLabelText(fileName.c_str()); - progress.setValue(percentage); - QCoreApplication::processEvents(); - return !progress.wasCanceled(); -} - - -void MainWindow::extractBSATriggered() -{ - QTreeWidgetItem *item = m_ContextItem; - - QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); - if (!targetFolder.isEmpty()) { - BSA::Archive archive; - QString originPath = QDir::fromNativeSeparators(ToQString(m_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; - } - - 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.")); - } - } -} - - -void MainWindow::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - // display a list of all headers as checkboxes - QAbstractItemModel *model = ui->modList->header()->model(); - for (int i = 1; i < model->columnCount(); ++i) { - QString columnName = model->headerData(i, Qt::Horizontal).toString(); - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(columnName); - checkBox->setChecked(!ui->modList->header()->isSectionHidden(i)); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - - // view/hide columns depending on check-state - int i = 1; - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); - if (checkBox != NULL) { - ui->modList->header()->setSectionHidden(i, !checkBox->isChecked()); - } - } - ++i; - } -} - - -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::bsaList_itemMoved() -{ - saveArchiveList(); - m_CheckBSATimer.start(500); -} - - -void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) -{ - saveArchiveList(); - m_CheckBSATimer.start(500); -} - -void MainWindow::on_actionProblems_triggered() -{ -// QString problemDescription; -// checkForProblems(problemDescription); -// QMessageBox::information(this, tr("Problems"), problemDescription); - ProblemsDialog problems(m_DiagnosisPlugins, this); - if (problems.hasProblems()) { - problems.exec(); - updateProblemsButton(); - } -} - -void MainWindow::setCategoryListVisible(bool visible) -{ - if (visible) { - ui->categoriesGroup->show(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); - } else { - ui->categoriesGroup->hide(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); - } -} - -void MainWindow::on_displayCategoriesBtn_toggled(bool checked) -{ - setCategoryListVisible(checked); -} - -void MainWindow::editCategories() -{ - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - -void MainWindow::deselectFilters() -{ - ui->categoriesList->clearSelection(); -} - -void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); - menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); - - menu.exec(ui->categoriesList->mapToGlobal(pos)); -} - - -void MainWindow::updateESPLock(bool locked) -{ - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - if (currentSelection.count() == 0) { - // this path is probably useless - m_PluginList.lockESPIndex(m_ContextRow, locked); - } else { - Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - m_PluginList.lockESPIndex(mapToModel(&m_PluginList, idx).row(), locked); - } - } -} - - -void MainWindow::lockESPIndex() -{ - updateESPLock(true); -} - -void MainWindow::unlockESPIndex() -{ - updateESPLock(false); -} - - -void MainWindow::removeFromToolbar() -{ - try { - Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); - exe.m_Toolbar = false; - } catch (const std::runtime_error&) { - qDebug("executable doesn't exist any more"); - } - - updateToolBar(); -} - - -void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) -{ - QAction *action = ui->toolBar->actionAt(point); - if (action != NULL) { - if (action->objectName().startsWith("custom_")) { - m_ContextAction = action; - QMenu menu; - menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); - menu.exec(ui->toolBar->mapToGlobal(point)); - } - } -} - -void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) -{ - m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); - - QMenu menu; - menu.addAction(tr("Enable all"), &m_PluginList, SLOT(enableAll())); - menu.addAction(tr("Disable all"), &m_PluginList, SLOT(disableAll())); - - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - bool hasLocked = false; - bool hasUnlocked = false; - Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - int row = m_PluginListSortProxy->mapToSource(idx).row(); - if (m_PluginList.isEnabled(row)) { - if (m_PluginList.isESPLocked(row)) { - hasLocked = true; - } else { - hasUnlocked = true; - } - } - } - - menu.addSeparator(); - - if (hasLocked) { - menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); - } - if (hasUnlocked) { - menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); - } - - try { - menu.exec(ui->espList->mapToGlobal(pos)); - } catch (const std::exception &e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); - } -} - -void MainWindow::on_groupCombo_currentIndexChanged(int index) -{ - if (m_ModListSortProxy == NULL) { - return; - } - QAbstractProxyModel *newModel = NULL; - switch (index) { - case 1: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, - 0, Qt::UserRole + 2); - } break; - case 2: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, - QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, - Qt::UserRole + 2); - } break; - default: { - newModel = NULL; - } break; - } - - if (newModel != NULL) { -#ifdef TEST_MODELS - new ModelTest(newModel, this); -#endif // TEST_MODELS - m_ModListSortProxy->setSourceModel(newModel); - connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); - connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); - connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); - } else { - m_ModListSortProxy->setSourceModel(&m_ModList); - } - modFilterActive(m_ModListSortProxy->isFilterActive()); -} - -void MainWindow::on_linkButton_pressed() -{ - const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value(); - - QIcon addIcon(":/MO/gui/link"); - QIcon removeIcon(":/MO/gui/remove"); - - QFileInfo linkDesktopFile(QDir::fromNativeSeparators(getDesktopDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); - QFileInfo linkMenuFile(QDir::fromNativeSeparators(getStartMenuDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); - - ui->linkButton->menu()->actions().at(0)->setIcon(selectedExecutable.m_Toolbar ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(1)->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(2)->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); -} - -void MainWindow::on_showHiddenBox_toggled(bool checked) -{ - m_DownloadManager.setShowHidden(checked); -} - - -void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = NULL; - - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - qCritical("failed to create stdout reroute"); - } - - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - qCritical("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; - } -} - -std::string MainWindow::readFromPipe(HANDLE stdOutRead) -{ - static const int chunkSize = 128; - std::string result; - - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; - - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; -} - -void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog) -{ - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - - foreach (const std::string &line, lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t reportidx = line.find("[report]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (reportidx != std::string::npos) { - reportURL = line.substr(reportidx + 9); - } else if (erroridx != std::string::npos) { - 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::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else { - qDebug("%s", line.c_str()); - } - } - } - } -} - - -HANDLE MainWindow::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) -{ - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString profileName = profile; - if (profile.length() == 0) { - if (m_CurrentProfile != NULL) { - profileName = m_CurrentProfile->getName(); - } else { - throw MyException(tr("No profile set")); - } - } - QString steamAppID; - if (executable.contains('\\') || executable.contains('/')) { - // file path - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); - } - if (cwd.length() == 0) { - currentDirectory = binary.absolutePath(); - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.find(executable); - steamAppID = exe.m_SteamAppID; - if (arguments == "") { - arguments = exe.m_Arguments; - } - binary = exe.m_BinaryInfo; - if (cwd.length() == 0) { - currentDirectory = exe.m_WorkingDirectory; - } - } catch (const std::runtime_error&) { - qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); - binary = QFileInfo(executable); - } - } - - return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); -} - -void MainWindow::on_bossButton_clicked() -{ - std::string reportURL; - std::string errorMessages; - - m_CurrentProfile->writeModlistNow(); - - bool success = false; - - try { - this->setEnabled(false); - ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("LOOT working")); - dialog.setMaximum(0); - dialog.show(); - - QStringList parameters; - parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) - << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); - - if (!m_DidUpdateMasterList) { - parameters << "--updateMasterlist"; - m_DidUpdateMasterList = true; - } - - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), - parameters.join(" "), - m_CurrentProfile->getName(), - m_Settings.logLevel(), - qApp->applicationDirPath() + "/loot", - true, - stdOutWrite); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_PluginList.clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - bool isJobHandle = true; - - if (loot != INVALID_HANDLE_VALUE) { - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - qDebug("no more processes in job"); - break; - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - if (dialog.wasCanceled()) { - if (isJobHandle) { - ::TerminateJobObject(loot, 1); - } else { - ::TerminateProcess(loot, 1); - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, reportURL, errorMessages, dialog); - - res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, reportURL, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(loot, &exitCode); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - } - } else { - reportError(tr("failed to start loot")); - } - } catch (const std::exception &e) { - reportError(tr("failed to run loot: %1").arg(e.what())); - } - if (errorMessages.length() > 0) { - QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); - warn->setModal(false); - warn->show(); - } - - if (success) { - if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - if (QFile::exists(report)) { - m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report)); - } else { - qWarning("report file missing"); - } - } - refreshESPList(); - - if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - } -} - - -const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; -const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; -const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; - - -bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) -{ - QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); - if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { - QFileInfo fileInfo(filePath); - removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); - return true; - } else { - return false; - } -} - -void MainWindow::on_saveButton_clicked() -{ - savePluginList(); - QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getPluginsFileName(), now) - && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) - && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { - MessageDialog::showMessage(tr("Backup of load order created"), this); - } -} - -QString MainWindow::queryRestore(const QString &filePath) -{ - QFileInfo pluginFileInfo(filePath); - QString pattern = pluginFileInfo.fileName() + ".*"; - QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); - - SelectionDialog dialog(tr("Choose backup to restore"), this); - QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); - QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); - foreach(const QFileInfo &info, files) { - if (exp.exactMatch(info.fileName())) { - QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); - dialog.addChoice(time.toString(), "", exp.cap(1)); - } else if (exp2.exactMatch(info.fileName())) { - dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); - } - } - - if (dialog.numChoices() == 0) { - QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); - return QString(); - } - - if (dialog.exec() == QDialog::Accepted) { - return dialog.getChoiceData().toString(); - } else { - return QString(); - } -} - -void MainWindow::on_restoreButton_clicked() -{ - QString pluginName = m_CurrentProfile->getPluginsFileName(); - QString choice = queryRestore(pluginName); - if (!choice.isEmpty()) { - QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); - QString lockedName = m_CurrentProfile->getLockedOrderFileName(); - if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || - !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || - !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); - } - refreshESPList(); - } -} - -void MainWindow::on_saveModsButton_clicked() -{ - m_CurrentProfile->writeModlistNow(true); - QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { - MessageDialog::showMessage(tr("Backup of modlist created"), this); - } -} -void MainWindow::on_restoreModsButton_clicked() -{ - QString modlistName = m_CurrentProfile->getModlistFileName(); - QString choice = queryRestore(modlistName); - if (!choice.isEmpty()) { - if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical(this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); - } - refreshModList(false); - } -} - -void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() -{ - QStringList lines; - QAbstractItemModel *model = ui->logList->model(); - for (int i = 0; i < model->rowCount(); ++i) { - lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) - .arg(model->index(i, 1).data(Qt::UserRole).toString()) - .arg(model->index(i, 1).data().toString())); - } - QApplication::clipboard()->setText(lines.join("\n")); -} - -void MainWindow::on_categoriesAndBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); - } -} - -void MainWindow::on_categoriesOrBtn_toggled(bool checked) -{ - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); - } -} - -void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) -{ - QToolTip::showText(QCursor::pos(), - ui->managedArchiveLabel->toolTip()); -} - -void MainWindow::on_manageArchivesBox_toggled(bool) -{ - refreshBSAList(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include +#include "spawn.h" +#include "report.h" +#include "modlist.h" +#include "modlistsortproxy.h" +#include "qtgroupingproxy.h" +#include "profile.h" +#include "pluginlist.h" +#include "profilesdialog.h" +#include "editexecutablesdialog.h" +#include "categories.h" +#include "categoriesdialog.h" +#include "utility.h" +#include "modinfodialog.h" +#include "overwriteinfodialog.h" +#include "activatemodsdialog.h" +#include "downloadlist.h" +#include "downloadlistwidget.h" +#include "downloadlistwidgetcompact.h" +#include "messagedialog.h" +#include "installationmanager.h" +#include "lockeddialog.h" +#include "syncoverwritedialog.h" +#include "logbuffer.h" +#include "downloadlistsortproxy.h" +#include "motddialog.h" +#include "filedialogmemory.h" +#include "questionboxmemory.h" +#include "tutorialmanager.h" +#include "modflagicondelegate.h" +#include "pluginflagicondelegate.h" +#include "credentialsdialog.h" +#include "selectiondialog.h" +#include "csvbuilder.h" +#include "gameinfoimpl.h" +#include "savetextasdialog.h" +#include "problemsdialog.h" +#include "previewdialog.h" +#include "browserdialog.h" +#include "aboutdialog.h" +#include "safewritefile.h" +#include "organizerproxy.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +#ifdef TEST_MODELS +#include "modeltest.h" +#endif // TEST_MODELS + +#pragma warning( disable : 4428 ) + +using namespace MOBase; +using namespace MOShared; + + + + +static bool isOnline() +{ + QList interfaces = QNetworkInterface::allInterfaces(); + + bool connected = false; + for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) { + if ( (iter->flags() & QNetworkInterface::IsUp) && + (iter->flags() & QNetworkInterface::IsRunning) && + !(iter->flags() & QNetworkInterface::IsLoopBack)) { + auto addresses = iter->addressEntries(); + if (addresses.count() == 0) { + continue; + } + qDebug("interface %s seems to be up (address: %s)", + qPrintable(iter->humanReadableName()), + qPrintable(addresses[0].ip().toString())); + connected = true; + } + } + + return connected; +} + + +MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), + m_ExeName(exeName), m_OldProfileIndex(-1), + m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), + m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), + m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), + m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), + m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), + m_CurrentProfile(NULL), m_AskForNexusPW(false), + m_ArchivesInit(false), m_DirectoryUpdate(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL), + m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled(), m_DidUpdateMasterList(false) +{ + ui->setupUi(this); + this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); + + languageChange(m_Settings.language()); + + ui->logList->setModel(LogBuffer::instance()); + ui->logList->setColumnWidth(0, 100); + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value + ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); + connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), ui->logList, SLOT(scrollToBottom())); + connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), ui->logList, SLOT(scrollToBottom())); + + m_RefreshProgress = new QProgressBar(statusBar()); + m_RefreshProgress->setTextVisible(true); + m_RefreshProgress->setRange(0, 100); + m_RefreshProgress->setValue(0); + statusBar()->addWidget(m_RefreshProgress, 1000); + statusBar()->clearMessage(); + + ui->actionEndorseMO->setVisible(false); + + MOBase::QuestionBoxMemory::init(initSettings.fileName()); + + updateProblemsButton(); + + updateToolBar(); + + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + + // set up mod list + m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); + m_ModListSortProxy->setSourceModel(&m_ModList); + +#ifdef TEST_MODELS + new ModelTest(&m_ModList, this); + new ModelTest(m_ModListSortProxy, this); +#endif //TEST_MODELS + + ui->modList->setModel(m_ModListSortProxy); + + ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); + //ui->modList->setAcceptDrops(true); + ui->modList->header()->installEventFilter(&m_ModList); + if (initSettings.contains("mod_list_state")) { + ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); + } else { + // hide these columns by default + ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); + ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + } + + ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden + ui->modList->installEventFilter(&m_ModList); + + // set up plugin list + m_PluginListSortProxy = new PluginListSortProxy(this); + m_PluginListSortProxy->setSourceModel(&m_PluginList); + + ui->espList->setModel(m_PluginListSortProxy); + ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); + if (initSettings.contains("plugin_list_state")) { + ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); + } + ui->espList->installEventFilter(&m_PluginList); + + ui->bsaList->setLocalMoveOnly(true); + + ui->splitter->setStretchFactor(0, 3); + ui->splitter->setStretchFactor(1, 2); + + resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state")); + + QMenu *linkMenu = new QMenu(this); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar())); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + ui->linkButton->setMenu(linkMenu); + + ui->listOptionsBtn->setMenu(modListContextMenu()); + + m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + + NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + + updateDownloadListDelegate(); + + ui->savegameList->installEventFilter(this); + ui->savegameList->setMouseTracking(true); + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); + connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int))); + connect(&m_DownloadManager, SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom())); + + connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); + + connect(&m_ModList, SIGNAL(modorder_changed()), this, SLOT(modorder_changed())); + connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); + connect(&m_ModList, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); + connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString))); + connect(&m_ModList, SIGNAL(modUninstalled(QString)), this, SLOT(modRemoved(QString))); + connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); + connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); + connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); + connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex))); + connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); + connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); + + connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); + connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); + connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); + + connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved())); + + connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); + + connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); + connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); + connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString))); + + connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); + + connect(&m_Settings, SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); + connect(&m_Settings, SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); + + connect(&m_Updater, SIGNAL(restart()), this, SLOT(close())); + connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); + connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); + +// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); + + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); + connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); + connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); + connect(NexusInterface::instance(), SIGNAL(needLogin()), this, SLOT(nexusLogin())); + + connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); + connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); + connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); + + connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); + + connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + + m_CheckBSATimer.setSingleShot(true); + connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); + + m_UpdateProblemsTimer.setSingleShot(true); + connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton())); + + m_SaveMetaTimer.setSingleShot(false); + connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); + m_SaveMetaTimer.start(5000); + + m_DirectoryRefresher.moveToThread(&m_RefresherThread); + m_RefresherThread.start(); + + m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); + setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); + FileDialogMemory::restore(initSettings); + + fixCategories(); + + if (isOnline() && !m_Settings.offlineMode()) { + m_Updater.testForUpdate(); + } else { + qDebug("user doesn't seem to be connected to the internet"); + } + + m_StartTime = QTime::currentTime(); + + m_Tutorial.expose("modList", &m_ModList); + m_Tutorial.expose("espList", &m_PluginList); + + // before we start loading plugins we, add the dll path to the dll search order + ::SetDllDirectoryW(ToWString(QDir::toNativeSeparators(qApp->applicationDirPath() + "/dlls")).c_str()); + loadPlugins(); +} + + +MainWindow::~MainWindow() +{ + m_RefresherThread.exit(); + m_RefresherThread.wait(); + m_IntegratedBrowser.close(); + delete ui; + delete m_GameInfo; + delete m_DirectoryStructure; +} + + +void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) +{ + if (!modListCustom) { + // resize mod list to fit content +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); +#else + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setResizeMode(i, QHeaderView::ResizeToContents); + } + ui->modList->header()->setResizeMode(ModList::COL_NAME, QHeaderView::Stretch); +#endif + } + + if (!pluginListCustom) { + // resize plugin list to fit content +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + for (int i = 0; i < ui->espList->header()->count(); ++i) { + ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch); +#else + for (int i = 0; i < ui->espList->header()->count(); ++i) { + ui->espList->header()->setResizeMode(i, QHeaderView::ResizeToContents); + } + ui->espList->header()->setResizeMode(0, QHeaderView::Stretch); +#endif + } +} + + +void MainWindow::allowListResize() +{ + // allow resize on mod list +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + } + ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); +#else + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setResizeMode(i, QHeaderView::Interactive); + } + ui->modList->header()->setResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); +#endif + + + // allow resize on plugin list +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + for (int i = 0; i < ui->espList->header()->count(); ++i) { + ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + } + ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); +#else + for (int i = 0; i < ui->espList->header()->count(); ++i) { + ui->espList->header()->setResizeMode(i, QHeaderView::Interactive); + } + ui->espList->header()->setResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); +#endif + +} + +void MainWindow::updateStyle(const QString&) +{ + // no effect? + ensurePolished(); +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + m_Tutorial.resize(event->size()); + QMainWindow::resizeEvent(event); +} + + +static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) +{ + QModelIndex result = idx; + const QAbstractItemModel *model = idx.model(); + while (model != targetModel) { + if (model == NULL) { + return QModelIndex(); + } + const QAbstractProxyModel *proxyModel = qobject_cast(model); + if (proxyModel == NULL) { + return QModelIndex(); + } + result = proxyModel->mapToSource(result); + model = proxyModel->sourceModel(); + } + return result; +} + + +void MainWindow::actionToToolButton(QAction *&sourceAction) +{ + QToolButton *button = new QToolButton(ui->toolBar); + button->setObjectName(sourceAction->objectName()); + button->setIcon(sourceAction->icon()); + button->setText(sourceAction->text()); + button->setPopupMode(QToolButton::InstantPopup); + button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); + button->setToolTip(sourceAction->toolTip()); + button->setShortcut(sourceAction->shortcut()); + QMenu *buttonMenu = new QMenu(sourceAction->text(), button); + button->setMenu(buttonMenu); + QAction *newAction = ui->toolBar->insertWidget(sourceAction, button); + newAction->setObjectName(sourceAction->objectName()); + newAction->setIcon(sourceAction->icon()); + newAction->setText(sourceAction->text()); + newAction->setToolTip(sourceAction->toolTip()); + newAction->setShortcut(sourceAction->shortcut()); + ui->toolBar->removeAction(sourceAction); + sourceAction->deleteLater(); + sourceAction = newAction; +} + + +void MainWindow::updateToolBar() +{ + foreach (QAction *action, ui->toolBar->actions()) { + if (action->objectName().startsWith("custom__")) { + ui->toolBar->removeAction(action); + } + } + + QWidget *spacer = new QWidget(ui->toolBar); + spacer->setObjectName("custom__spacer"); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); + QToolButton *toolBtn = qobject_cast(widget); + + if (toolBtn->menu() == NULL) { + actionToToolButton(ui->actionTool); + } + + actionToToolButton(ui->actionHelp); + createHelpWidget(); + + foreach (QAction *action, ui->toolBar->actions()) { + if (action->isSeparator()) { + // insert spacers + ui->toolBar->insertWidget(action, spacer); + + std::vector::iterator begin, end; + m_ExecutablesList.getExecutables(begin, end); + for (auto iter = begin; iter != end; ++iter) { + if (iter->m_Toolbar) { + QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), + iter->m_Title, + ui->toolBar); + QVariant temp; + temp.setValue(*iter); + exeAction->setData(temp); + exeAction->setObjectName(QString("custom__") + iter->m_Title); + if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { + qDebug("failed to connect trigger?"); + } + ui->toolBar->insertAction(action, exeAction); + } + } + } + } +} + + +void MainWindow::scheduleUpdateButton() +{ + if (!m_UpdateProblemsTimer.isActive()) { + m_UpdateProblemsTimer.start(1000); + } +} + + +void MainWindow::updateProblemsButton() +{ + int numProblems = checkForProblems(); + if (numProblems > 0) { + ui->actionProblems->setEnabled(true); + ui->actionProblems->setIconText(tr("Problems")); + ui->actionProblems->setToolTip(tr("There are potential problems with your setup")); + + QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); + { + QPainter painter(&mergedIcon); + std::string badgeName = std::string(":/MO/gui/badge_") + (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); + painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); + } + ui->actionProblems->setIcon(QIcon(mergedIcon)); + } else { + ui->actionProblems->setEnabled(false); + ui->actionProblems->setIconText(tr("No Problems")); + ui->actionProblems->setToolTip(tr("Everything seems to be in order")); + ui->actionProblems->setIcon(QIcon(":/MO/gui/warning")); + } +} + + +bool MainWindow::errorReported(QString &logFile) +{ + QDir dir(ToQString(GameInfo::instance().getLogDir())); + QFileInfoList files = dir.entryInfoList(QStringList("ModOrganizer_??_??_??_??_??.log"), + QDir::Files, QDir::Name | QDir::Reversed); + + if (files.count() > 0) { + logFile = files.at(0).absoluteFilePath(); + QFile file(logFile); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + char buffer[1024]; + int line = 0; + while (!file.atEnd()) { + file.readLine(buffer, 1024); + if (strncmp(buffer, "ERROR", 5) == 0) { + return true; + } + + // prevent this function from taking forever + if (line++ >= 50000) { + break; + } + } + } + } + + return false; +} + + +int MainWindow::checkForProblems() +{ + int numProblems = 0; + foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) { + numProblems += diagnose->activeProblems().size(); + } + return numProblems; +} + +void MainWindow::about() +{ + AboutDialog dialog(m_Updater.getVersion().displayString(), this); + dialog.exec(); +} + + +void MainWindow::createHelpWidget() +{ + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); + QMenu *buttonMenu = toolBtn->menu(); + if (buttonMenu == NULL) { + return; + } + buttonMenu->clear(); + + QAction *helpAction = new QAction(tr("Help on UI"), buttonMenu); + connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); + buttonMenu->addAction(helpAction); + + QAction *wikiAction = new QAction(tr("Documentation Wiki"), buttonMenu); + connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); + buttonMenu->addAction(wikiAction); + + QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); + connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); + buttonMenu->addAction(issueAction); + + QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); + + typedef std::vector > ActionList; + + ActionList tutorials; + + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + while (dirIter.hasNext()) { + dirIter.next(); + QString fileName = dirIter.fileName(); + + QFile file(dirIter.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open " << fileName; + continue; + } + QString firstLine = QString::fromUtf8(file.readLine()); + if (firstLine.startsWith("//TL")) { + QStringList params = firstLine.mid(4).trimmed().split('#'); + if (params.size() != 2) { + qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; + continue; + } + QAction *tutAction = new QAction(params.at(0), tutorialMenu); + tutAction->setData(fileName); + tutorials.push_back(std::make_pair(params.at(1).toInt(), tutAction)); + } + } + + std::sort(tutorials.begin(), tutorials.end(), + [](const ActionList::value_type &LHS, const ActionList::value_type &RHS) { + return LHS.first < RHS.first; } ); + + for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { + connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); + tutorialMenu->addAction(iter->second); + } + + buttonMenu->addMenu(tutorialMenu); + buttonMenu->addAction(tr("About"), this, SLOT(about())); + buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); +} + + +bool MainWindow::saveArchiveList() +{ + if (m_ArchivesInit) { + SafeWriteFile archiveFile(m_CurrentProfile->getArchivesFileName()); + for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { + QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem *item = tlItem->child(j); + if (item->checkState(0) == Qt::Checked) { + // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini + if (ui->manageArchivesBox->isChecked() + || item->data(0, Qt::UserRole).toBool()) { + archiveFile->write(item->text(0).toUtf8().append("\r\n")); + } + } + } + } + if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); + return true; + } + } else { + qWarning("archive list not initialised"); + } + return false; +} + +void MainWindow::savePluginList() +{ + m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(), + m_CurrentProfile->getLoadOrderFileName(), + m_CurrentProfile->getLockedOrderFileName(), + m_CurrentProfile->getDeleterFileName(), + m_Settings.hideUncheckedPlugins()); + m_PluginList.saveLoadOrder(*m_DirectoryStructure); +} + +void MainWindow::modFilterActive(bool filterActive) +{ + if (filterActive) { + ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else if (ui->groupCombo->currentIndex() != 0) { + ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); + } else { + ui->modList->setStyleSheet(""); + } +} + +void MainWindow::espFilterChanged(const QString &filter) +{ + if (!filter.isEmpty()) { + ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else { + ui->espList->setStyleSheet(""); + } +} + +void MainWindow::downloadFilterChanged(const QString &filter) +{ + if (!filter.isEmpty()) { + ui->downloadView->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else { + ui->downloadView->setStyleSheet(""); + } +} + +void MainWindow::expandModList(const QModelIndex &index) +{ + QAbstractItemModel *model = ui->modList->model(); +#pragma message("why is this so complicated? mapping the index doesn't work, probably a bug in QtGroupingProxy?") + for (int i = 0; i < model->rowCount(); ++i) { + QModelIndex targetIdx = model->index(i, 0); + if (model->data(targetIdx).toString() == index.data().toString()) { + ui->modList->expand(targetIdx); + break; + } + } +} + +bool MainWindow::saveCurrentLists() +{ + if (m_DirectoryUpdate) { + qWarning("not saving lists during directory update"); + return false; + } + + try { + savePluginList(); + saveArchiveList(); + } catch (const std::exception &e) { + reportError(tr("failed to save load order: %1").arg(e.what())); + } + + return true; +} + + +bool MainWindow::addProfile() +{ + QComboBox *profileBox = findChild("profileBox"); + bool okClicked = false; + + QString name = QInputDialog::getText(this, tr("Name"), + tr("Please enter a name for the new profile"), + QLineEdit::Normal, QString(), &okClicked); + if (okClicked && (name.size() > 0)) { + try { + profileBox->addItem(name); + profileBox->setCurrentIndex(profileBox->count() - 1); + return true; + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + return false; + } + } else { + return false; + } +} + + +void MainWindow::hookUpWindowTutorials() +{ + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + while (dirIter.hasNext()) { + dirIter.next(); + QString fileName = dirIter.fileName(); + QFile file(dirIter.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open " << fileName; + continue; + } + QString firstLine = QString::fromUtf8(file.readLine()); + if (firstLine.startsWith("//WIN")) { + QString windowName = firstLine.mid(6).trimmed(); + if (!m_Settings.directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { + TutorialManager::instance().activateTutorial(windowName, fileName); + } + } + } +} + + +void MainWindow::showEvent(QShowEvent *event) +{ + refreshFilters(); + + QMainWindow::showEvent(event); + m_Tutorial.registerControl(); + + hookUpWindowTutorials(); + + if (m_Settings.directInterface().value("first_start", true).toBool()) { + QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); + if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { + if (QMessageBox::question(this, tr("Show tutorial?"), + tr("You are starting Mod Organizer for the first time. " + "Do you want to show a tutorial of its basic features? If you choose " + "no you can always start the tutorial from the \"Help\"-menu."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); + } + } else { + qCritical() << firstStepsTutorial << " missing"; + QPoint pos = ui->toolBar->mapToGlobal(QPoint()); + pos.rx() += ui->toolBar->width() / 2; + pos.ry() += ui->toolBar->height(); + QWhatsThis::showText(pos, + QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); + } + + m_Settings.directInterface().setValue("first_start", false); + } + + // this has no visible impact when called before the ui is visible + int grouping = m_Settings.directInterface().value("group_state").toInt(); + ui->groupCombo->setCurrentIndex(grouping); + + allowListResize(); + + m_Settings.registerAsNXMHandler(false); +} + + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (m_DownloadManager.downloadsInProgress()) { + if (QMessageBox::question(this, tr("Downloads in progress"), + tr("There are still downloads in progress, do you really want to quit?"), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { + event->ignore(); + return; + } else { + m_DownloadManager.pauseAll(); + } + } + + setCursor(Qt::WaitCursor); + + m_IntegratedBrowser.close(); + + storeSettings(); + + // profile has to be cleaned up before the modinfo-buffer is cleared + delete m_CurrentProfile; + m_CurrentProfile = NULL; + + ModInfo::clear(); + LogBuffer::cleanQuit(); + m_ModList.setProfile(NULL); +} + + +void MainWindow::createFirstProfile() +{ + if (!refreshProfiles(false)) { + qDebug("creating default profile"); + Profile newProf("Default", false); + refreshProfiles(false); + } +} + + +void MainWindow::setBrowserGeometry(const QByteArray &geometry) +{ + m_IntegratedBrowser.restoreGeometry(geometry); +} + + +SaveGameGamebryo *MainWindow::getSaveGame(const QString &name) +{ + return new SaveGameGamebryo(this, name); +} + + +SaveGameGamebryo *MainWindow::getSaveGame(QListWidgetItem *item) +{ + try { + SaveGameGamebryo *saveGame = getSaveGame(item->data(Qt::UserRole).toString()); + saveGame->setParent(item->listWidget()); + return saveGame; + } catch (const std::exception &e) { + reportError(tr("failed to read savegame: %1").arg(e.what())); + return NULL; + } +} + + +void MainWindow::displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos) +{ + if (m_CurrentSaveView == NULL) { + m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, &m_PluginList, this); + } else { + m_CurrentSaveView->setSave(save); + } + + QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); + + if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { + pos.rx() -= (m_CurrentSaveView->width() + 2); + } else { + pos.rx() += 5; + } + + if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) { + pos.ry() -= (m_CurrentSaveView->height() + 10); + } else { + pos.ry() += 20; + } + m_CurrentSaveView->move(pos); + + m_CurrentSaveView->show(); + ui->savegameList->activateWindow(); + connect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); +} + + +void MainWindow::saveSelectionChanged(QListWidgetItem *newItem) +{ + if (newItem == NULL) { + hideSaveGameInfo(); + } else if ((m_CurrentSaveView == NULL) || (newItem != m_CurrentSaveView->property("displayItem").value())) { + const SaveGameGamebryo *save = getSaveGame(newItem); + if (save != NULL) { + displaySaveGameInfo(save, QCursor::pos()); + m_CurrentSaveView->setProperty("displayItem", qVariantFromValue((void*)newItem)); + } + } +} + + + +void MainWindow::hideSaveGameInfo() +{ + if (m_CurrentSaveView != NULL) { + disconnect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); + m_CurrentSaveView->deleteLater(); + m_CurrentSaveView = NULL; + } +} + +bool MainWindow::eventFilter(QObject *object, QEvent *event) +{ + if ((object == ui->savegameList) && + ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { + hideSaveGameInfo(); + } + return false; +} + + +bool MainWindow::testForSteam() +{ + DWORD processIDs[1024]; + DWORD bytesReturned; + if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) { + qWarning("failed to determine if steam is running"); + return true; + } + + TCHAR processName[MAX_PATH]; + for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { + memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); + if (processIDs[i] != 0) { + HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); + + if (process != NULL) { + HMODULE module; + DWORD ignore; + + // first module in a process is always the binary + if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) { + GetModuleBaseName(process, module, processName, MAX_PATH); + if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) || + (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { + return true; + } + } + } + } + } + + return false; +} + + +bool MainWindow::verifyPlugin(IPlugin *plugin) +{ + if (plugin == NULL) { + return false; + } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) { + qWarning("plugin failed to initialize"); + return false; + } + return true; +} + + +void MainWindow::toolPluginInvoke() +{ + QAction *triggeredAction = qobject_cast(sender()); + IPluginTool *plugin = (IPluginTool*)triggeredAction->data().value(); + try { + plugin->display(); + } catch (const std::exception &e) { + reportError(tr("Plugin \"%1\" failed: %2").arg(plugin->name()).arg(e.what())); + } catch (...) { + reportError(tr("Plugin \"%1\" failed").arg(plugin->name())); + } +} + + +void MainWindow::requestDownload(const QUrl &url, QNetworkReply *reply) +{ + QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + if (browserBtn->menu() != NULL) { + // go through modpage plugins, find one to handle the download. + QList browserActions = browserBtn->menu()->actions(); + foreach (QAction *action, browserActions) { + // the nexus action doesn't have a plugin connected currently + if (action->data().isValid()) { + IPluginModPage *plugin = qobject_cast(qvariant_cast(action->data())); + if (plugin == NULL) { + qCritical("invalid mod page. This is a bug"); + continue; + } + ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); + if (plugin->handlesDownload(url, reply->url(), *fileInfo)) { + fileInfo->repository = plugin->name(); + m_DownloadManager.addDownload(reply, fileInfo); + return; + } + } + } + } + + // no mod found that could handle the download. Is it a nexus mod? + if (url.host() == "www.nexusmods.com") { + int modID = 0; + int fileID = 0; + QRegExp modExp("mods/(\\d+)"); + if (modExp.indexIn(url.toString()) != -1) { + modID = modExp.cap(1).toInt(); + } + QRegExp fileExp("fid=(\\d+)"); + if (fileExp.indexIn(reply->url().toString()) != -1) { + fileID = fileExp.cap(1).toInt(); + } + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID)); + } else { + if (QMessageBox::question(this, tr("Download?"), + tr("A download has been started but no installed page plugin recognizes it.\n" + "If you download anyway no information (i.e. version) will be associated with the download.\n" + "Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); + } + } +} + + +void MainWindow::modPagePluginInvoke() +{ + QAction *triggeredAction = qobject_cast(sender()); + IPluginModPage *plugin = qobject_cast(triggeredAction->data().value()); + if (plugin->useIntegratedBrowser()) { + m_IntegratedBrowser.setWindowTitle(plugin->displayName()); + m_IntegratedBrowser.openUrl(plugin->pageURL()); + } else { + ::ShellExecuteW(NULL, L"open", ToWString(plugin->pageURL().toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); + } +} + +void MainWindow::registerPluginTool(IPluginTool *tool) +{ + QAction *action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); + action->setToolTip(tool->tooltip()); + tool->setParentWidget(this); + action->setData(qVariantFromValue((void*)tool)); + connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); + toolBtn->menu()->addAction(action); +} + + +void MainWindow::registerModPage(IPluginModPage *modPage) +{ + QToolButton *browserBtn = NULL; + // turn the browser action into a drop-down menu if necessary + if (ui->actionNexus->menu() == NULL) { + QAction *nexusAction = ui->actionNexus; + // TODO: use a different icon for nexus! + ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); + ui->toolBar->insertAction(nexusAction, ui->actionNexus); + ui->toolBar->removeAction(nexusAction); + actionToToolButton(ui->actionNexus); + + browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + browserBtn->menu()->addAction(nexusAction); + } else { + browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + } + + QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); + modPage->setParentWidget(this); + action->setData(qVariantFromValue(reinterpret_cast(modPage))); + + connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + toolBtn->menu()->addAction(action); +} + + +bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) +{ + { // generic treatment for all plugins + IPlugin *pluginObj = qobject_cast(plugin); + if (pluginObj == NULL) { + qDebug("not an IPlugin"); + return false; + } + plugin->setProperty("filename", fileName); + m_Settings.registerPlugin(pluginObj); + installTranslator(QFileInfo(fileName).baseName()); + } + + { // diagnosis plugins + IPluginDiagnose *diagnose = qobject_cast(plugin); + if (diagnose != NULL) { + m_DiagnosisPlugins.push_back(diagnose); + diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }); + } + } + { // mod page plugin + IPluginModPage *modPage = qobject_cast(plugin); + if (verifyPlugin(modPage)) { + registerModPage(modPage); + return true; + } + } + { // tool plugins + IPluginTool *tool = qobject_cast(plugin); + if (verifyPlugin(tool)) { + registerPluginTool(tool); + return true; + } + } + { // installer plugins + IPluginInstaller *installer = qobject_cast(plugin); + if (verifyPlugin(installer)) { + installer->setParentWidget(this); + m_InstallationManager.registerInstaller(installer); + return true; + } + } + { // preview plugins + IPluginPreview *preview = qobject_cast(plugin); + if (verifyPlugin(preview)) { + m_PreviewGenerator.registerPlugin(preview); + } + } + { // proxy plugins + IPluginProxy *proxy = qobject_cast(plugin); + if (verifyPlugin(proxy)) { + proxy->setParentWidget(this); + QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); + foreach (const QString &pluginName, pluginNames) { + try { + QObject *proxiedPlugin = proxy->instantiate(pluginName); + if (proxiedPlugin != NULL) { + if (registerPlugin(proxiedPlugin, pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); + } else { + qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); + } + } + } catch (const std::exception &e) { + reportError(tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what())); + } + } + return true; + } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin *dummy = qobject_cast(plugin); + if (verifyPlugin(dummy)) { + return true; + } + } + + qDebug("no matching plugin interface"); + + return false; +} + + +void MainWindow::loadPlugins() +{ + m_DiagnosisPlugins.clear(); + + m_Settings.clearPlugins(); + + foreach (QObject *plugin, QPluginLoader::staticInstances()) { + registerPlugin(plugin, ""); + } + + QFile loadCheck(QCoreApplication::applicationDirPath() + "/plugin_loadcheck.tmp"); + if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { + // oh, there was a failed plugin load last time. Find out which plugin was loaded last + QString fileName; + while (!loadCheck.atEnd()) { + fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); + } + if (QMessageBox::question(this, tr("Plugin error"), + tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Settings.addBlacklistPlugin(fileName); + } + loadCheck.close(); + } + + loadCheck.open(QIODevice::WriteOnly); + + QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); + qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); + QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); + + while (iter.hasNext()) { + iter.next(); + if (m_Settings.pluginBlacklisted(iter.fileName())) { + qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); + continue; + } + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + QString pluginName = iter.filePath(); + if (QLibrary::isLibrary(pluginName)) { + QPluginLoader pluginLoader(pluginName); + if (pluginLoader.instance() == NULL) { + m_UnloadedPlugins.push_back(pluginName); + qCritical("failed to load plugin %s: %s", + qPrintable(pluginName), qPrintable(pluginLoader.errorString())); + } else { + if (registerPlugin(pluginLoader.instance(), pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); + } else { + m_UnloadedPlugins.push_back(pluginName); + qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); + } + } + } + } + + // remove the load check file on success + loadCheck.remove(); + + m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); + + m_DiagnosisPlugins.push_back(this); +} + + +void MainWindow::startSteam() +{ + QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", + QSettings::NativeFormat); + QString exe = steamSettings.value("SteamExe", "").toString(); + if (!exe.isEmpty()) { + QString temp = QString("\"%1\"").arg(exe); + if (!QProcess::startDetached(temp)) { + reportError(tr("Failed to start \"%1\"").arg(temp)); + } else { + QMessageBox::information(this, tr("Waiting"), tr("Please press OK once you're logged into steam.")); + } + } +} + + +HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID) +{ + storeSettings(); + + if (!binary.exists()) { + reportError(tr("Executable \"%1\" not found").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + + if (!steamAppID.isEmpty()) { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + } else { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); + } + + if ((GameInfo::instance().requiresSteam()) && + (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + if (!testForSteam()) { + if (QuestionBoxMemory::query(this->isVisible() ? this : NULL, + "steamQuery", tr("Start Steam?"), + tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { + startSteam(); + } + } + } + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (m_AboutToRun(binary.absoluteFilePath())) { + return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); + } else { + qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); + return INVALID_HANDLE_VALUE; + } +} + + +void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) +{ + LockedDialog *dialog = new LockedDialog(this); + dialog->show(); + ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); }); + + HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID); + if (processHandle != INVALID_HANDLE_VALUE) { + if (closeAfterStart) { + close(); + } else { + this->setEnabled(false); + // re-enable the locked dialog because what'd be the point otherwise? + dialog->setEnabled(true); + + QCoreApplication::processEvents(); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + + bool isJobHandle = true; + + DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + break; + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + + res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + } + ::CloseHandle(processHandle); + + this->setEnabled(true); + refreshDirectoryStructure(); + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + } + } +} + + +void MainWindow::startExeAction() +{ + QAction *action = qobject_cast(sender()); + if (action != NULL) { + Executable selectedExecutable = action->data().value(); + spawnBinary(selectedExecutable.m_BinaryInfo, + selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_CloseMO == DEFAULT_CLOSE, + selectedExecutable.m_SteamAppID); + } else { + qCritical("not an action?"); + } +} + + +void MainWindow::setExecutablesList(const ExecutablesList &executablesList) +{ + m_ExecutablesList = executablesList; + refreshExecutablesList(); + updateToolBar(); +} + +void MainWindow::setExecutableIndex(int index) +{ + QComboBox *executableBox = findChild("executablesListBox"); + + if ((index != 0) && (executableBox->count() > index)) { + executableBox->setCurrentIndex(index); + } else { + executableBox->setCurrentIndex(1); + } +} + +void MainWindow::activateSelectedProfile() +{ + QString profileName = ui->profileBox->currentText(); + qDebug("activate profile \"%s\"", qPrintable(profileName)); + QString profileDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + .append("/").append(profileName); + delete m_CurrentProfile; + m_CurrentProfile = new Profile(QDir(profileDir)); + m_ModList.setProfile(m_CurrentProfile); + + m_ModListSortProxy->setProfile(m_CurrentProfile); + + connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); + + refreshSaveList(); + refreshModList(); +} + +void MainWindow::on_profileBox_currentIndexChanged(int index) +{ + if (ui->profileBox->isEnabled()) { + int previousIndex = m_OldProfileIndex; + m_OldProfileIndex = index; + + if ((previousIndex != -1) && + (m_CurrentProfile != NULL) && + m_CurrentProfile->exists()) { + saveCurrentLists(); + } + + // ensure the new index is valid + if (index < 0 || index >= ui->profileBox->count()) { + qDebug("invalid profile index, using last profile"); + ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); + } + + if (ui->profileBox->currentIndex() == 0) { + ProfilesDialog(m_GamePath).exec(); + while (!refreshProfiles()) { + ProfilesDialog(m_GamePath).exec(); + } + ui->profileBox->setCurrentIndex(previousIndex); + } else { + activateSelectedProfile(); + } + } +} + + +void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly) +{ + { + std::vector files = directoryEntry.getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + FileEntry::Ptr current = *iter; + if (conflictsOnly && (current->getAlternatives().size() == 0)) { + continue; + } + + QString fileName = ToQString(current->getName()); + QStringList columns(fileName); + bool isArchive = false; + int originID = current->getOrigin(isArchive); + QString source = ToQString(m_DirectoryStructure->getOriginByID(originID).getName()); + std::wstring archive = current->getArchive(); + if (archive.length() != 0) { + source.append(" (").append(ToQString(archive)).append(")"); + } + columns.append(source); + QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); + if (isArchive) { + QFont font = fileChild->font(0); + font.setItalic(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { + QFont font = fileChild->font(0); + font.setStrikeOut(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } + fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath())); + fileChild->setData(0, Qt::UserRole + 1, isArchive); + fileChild->setData(1, Qt::UserRole, source); + fileChild->setData(1, Qt::UserRole + 1, originID); + + std::vector alternatives = current->getAlternatives(); + + if (!alternatives.empty()) { + std::wostringstream altString; + altString << ToWString(tr("Also in:
        ")); + for (std::vector::iterator altIter = alternatives.begin(); + altIter != alternatives.end(); ++altIter) { + if (altIter != alternatives.begin()) { + altString << " , "; + } + altString << "" << m_DirectoryStructure->getOriginByID(*altIter).getName() << ""; + } + fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); + fileChild->setForeground(1, QBrush(Qt::red)); + } else { + fileChild->setToolTip(1, tr("No conflict")); + } + subTree->addChild(fileChild); + } + } + + std::wostringstream temp; + temp << directorySoFar << "\\" << directoryEntry.getName(); + { + std::vector::const_iterator current, end; + directoryEntry.getSubDirectories(current, end); + for (; current != end; ++current) { + QString pathName = ToQString((*current)->getName()); + QStringList columns(pathName); + columns.append(""); + if (!(*current)->isEmpty()) { + QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); + if (conflictsOnly) { + updateTo(directoryChild, temp.str(), **current, conflictsOnly); + if (directoryChild->childCount() != 0) { + subTree->addChild(directoryChild); + } else { + delete directoryChild; + } + } else { + QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); + onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); + onDemandLoad->setData(0, Qt::UserRole + 1, ToQString(temp.str())); + onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); + directoryChild->addChild(onDemandLoad); + subTree->addChild(directoryChild); + } + } + } + } + + subTree->sortChildren(0, Qt::AscendingOrder); +} + +void MainWindow::delayedRemove() +{ + foreach (QTreeWidgetItem *item, m_RemoveWidget) { + item->removeChild(item->child(0)); + } + m_RemoveWidget.clear(); +} + +void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) +{ + if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { + // read the data we need from the sub-item, then dispose of it + QTreeWidgetItem *onDemandDataItem = item->child(0); + std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); + bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); + + std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(virtualPath); + if (dir != NULL) { + updateTo(item, path, *dir, conflictsOnly); + } else { + qWarning("failed to update view of %ls", path.c_str()); + } + m_RemoveWidget.push_back(item); + QTimer::singleShot(5, this, SLOT(delayedRemove())); + } +} + + +bool MainWindow::refreshProfiles(bool selectProfile) +{ + QComboBox* profileBox = findChild("profileBox"); + + QString currentProfileName = profileBox->currentText(); + + profileBox->blockSignals(true); + profileBox->clear(); + profileBox->addItem(QObject::tr("")); + + QDir profilesDir(ToQString(GameInfo::instance().getProfilesDir())); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + + QDirIterator profileIter(profilesDir); + + int newIndex = profileIter.hasNext() ? 1 : 0; + int currentIndex = 0; + while (profileIter.hasNext()) { + profileIter.next(); + ++currentIndex; + try { + profileBox->addItem(profileIter.fileName()); + if (currentProfileName == profileIter.fileName()) { + newIndex = currentIndex; + } + } catch (const std::runtime_error& error) { + reportError(QObject::tr("failed to parse profile %1: %2").arg(profileIter.fileName()).arg(error.what())); + } + } + + // now select one of the profiles, preferably the one that was selected before + profileBox->blockSignals(false); + + if (selectProfile) { + if (profileBox->count() > 1) { + if (currentProfileName.length() != 0) { + if ((newIndex != 0) && (profileBox->count() > newIndex)) { + profileBox->setCurrentIndex(newIndex); + } else { + profileBox->setCurrentIndex(1); + } + } + return true; + } else { + return false; + } + } else { + return profileBox->count() > 1; + } +} + +std::set MainWindow::enabledArchives() +{ + std::set result; + QFile archiveFile(m_CurrentProfile->getArchivesFileName()); + if (archiveFile.open(QIODevice::ReadOnly)) { + while (!archiveFile.atEnd()) { + result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); + } + archiveFile.close(); + } + return result; +} + +void MainWindow::refreshDirectoryStructure() +{ + m_DirectoryUpdate = true; + std::vector > activeModList = m_CurrentProfile->getActiveMods(); + + m_DirectoryRefresher.setMods(activeModList, enabledArchives()); + + statusBar()->show(); + m_RefreshProgress->setRange(0, 100); + + QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); +} + +#if QT_VERSION >= 0x050000 +extern QPixmap qt_pixmapFromWinHICON(HICON icon); +#else +#define qt_pixmapFromWinHICON(icon) QPixmap::fromWinHICON(icon) +#endif + +QIcon MainWindow::iconForExecutable(const QString &filePath) +{ + HICON winIcon; + UINT res = ::ExtractIconExW(ToWString(filePath).c_str(), 0, &winIcon, NULL, 1); + if (res == 1) { + QIcon result = QIcon(qt_pixmapFromWinHICON(winIcon)); + ::DestroyIcon(winIcon); + return result; + } else { + return QIcon(":/MO/gui/executable"); + } +} + +void MainWindow::refreshExecutablesList() +{ + QComboBox* executablesList = findChild("executablesListBox"); + executablesList->setEnabled(false); + executablesList->clear(); + executablesList->addItem(tr("")); + + QAbstractItemModel *model = executablesList->model(); + + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + for(int i = 0; current != end; ++current, ++i) { + QVariant temp; + temp.setValue(*current); + QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); + executablesList->addItem(icon, current->m_Title, temp); + model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); + } + + setExecutableIndex(1); + executablesList->setEnabled(true); +} + + +void MainWindow::refreshDataTree() +{ + QCheckBox *conflictsBox = findChild("conflictsCheckBox"); + QTreeWidget *tree = findChild("dataTree"); + tree->clear(); + QStringList columns("data"); + columns.append(""); + QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); + updateTo(subTree, L"", *m_DirectoryStructure, conflictsBox->isChecked()); + tree->insertTopLevelItem(0, subTree); + subTree->setExpanded(true); + tree->header()->resizeSection(0, 200); +} + + +void MainWindow::refreshSavesIfOpen() +{ + if (ui->tabWidget->currentIndex() == 3) { + refreshSaveList(); + } +} + + +void MainWindow::refreshSaveList() +{ + ui->savegameList->clear(); + + QDir savesDir; + if (m_CurrentProfile->localSavesEnabled()) { + savesDir.setPath(m_CurrentProfile->getPath() + "/saves"); + } else { + wchar_t path[MAX_PATH]; + ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves", + path, MAX_PATH, + (ToWString(m_CurrentProfile->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str()); + savesDir.setPath(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getDocumentsDir() + L"\\" + path))); + } + + if (m_SavesWatcher.directories().length() > 0) { + m_SavesWatcher.removePaths(m_SavesWatcher.directories()); + } + m_SavesWatcher.addPath(savesDir.absolutePath()); + + QStringList filters; + filters << ToQString(GameInfo::instance().getSaveGameExtension()); + savesDir.setNameFilters(filters); + + QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); + + foreach (const QFileInfo &file, files) { + QListWidgetItem *item = new QListWidgetItem(file.fileName()); + item->setData(Qt::UserRole, file.absoluteFilePath()); + ui->savegameList->addItem(item); + } +} + + +void MainWindow::refreshLists() +{ + if ((m_CurrentProfile != NULL) && m_DirectoryStructure->isPopulated()) { + refreshESPList(); + refreshBSAList(); + } // no point in refreshing lists if no files have been added to the directory tree +} + + +void MainWindow::refreshESPList() +{ + m_CurrentProfile->writeModlist(); + + // clear list + try { + m_PluginList.refresh(m_CurrentProfile->getName(), + *m_DirectoryStructure, + m_CurrentProfile->getPluginsFileName(), + m_CurrentProfile->getLoadOrderFileName(), + m_CurrentProfile->getLockedOrderFileName()); + } catch (const std::exception &e) { + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); + } +} + +void MainWindow::refreshModList(bool saveChanges) +{ + // don't lose changes! + if (saveChanges) { + m_CurrentProfile->writeModlistNow(true); + } + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + + m_CurrentProfile->refreshModStatus(); + + m_ModList.notifyChange(-1); + + refreshDirectoryStructure(); +} + + +static bool BySortValue(const std::pair &LHS, const std::pair &RHS) +{ + return LHS.first < RHS.first; +} + + +template +QStringList toStringList(InputIterator current, InputIterator end) +{ + QStringList result; + for (; current != end; ++current) { + result.append(*current); + } + return result; +} + + +void MainWindow::refreshBSAList() +{ + m_ArchivesInit = false; + ui->bsaList->clear(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); +#else + ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents); +#endif + + m_DefaultArchives.clear(); + + wchar_t buffer[256]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName())); + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 256, iniFileName.c_str()) != 0) { + m_DefaultArchives = ToQString(buffer).split(','); + } else { + std::vector vanillaBSAs = GameInfo::instance().getVanillaBSAs(); + for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) { + m_DefaultArchives.append(ToQString(*iter)); + } + } + + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(), + L"", buffer, 256, iniFileName.c_str()) != 0) { + m_DefaultArchives.append(ToQString(buffer).split(',')); + } + + for (int i = 0; i < m_DefaultArchives.count(); ++i) { + m_DefaultArchives[i] = m_DefaultArchives[i].trimmed(); + } + + m_ActiveArchives.clear(); + + auto iter = enabledArchives(); + m_ActiveArchives = toStringList(iter.begin(), iter.end()); + if (m_ActiveArchives.isEmpty()) { + m_ActiveArchives = m_DefaultArchives; + } + + std::vector > items; + + std::vector files = m_DirectoryStructure->getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + FileEntry::Ptr current = *iter; + + QString filename = ToQString(current->getName().c_str()); + QString extension = filename.right(3).toLower(); + + if (extension == "bsa") { + int index = m_ActiveArchives.indexOf(filename); + if (index == -1) { + index = 0xFFFF; + } + QString basename = filename.left(filename.indexOf(".")); + QStringList strings(filename); + bool isArchive = false; + int origin = current->getOrigin(isArchive); + strings.append(ToQString(m_DirectoryStructure->getOriginByID(origin).getName())); + QTreeWidgetItem *newItem = new QTreeWidgetItem(strings); + newItem->setData(0, Qt::UserRole, index); + newItem->setData(1, Qt::UserRole, origin); + newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); + newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); + newItem->setData(0, Qt::UserRole, false); + if (m_Settings.forceEnableCoreFiles() + && m_DefaultArchives.contains(filename)) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + newItem->setData(0, Qt::UserRole, true); + } else if ((m_PluginList.state(basename + ".esp") == IPluginList::STATE_ACTIVE) + || (m_PluginList.state(basename + ".esm") == IPluginList::STATE_ACTIVE)) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + } else { + if (ui->manageArchivesBox->isChecked()) { + newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked); + } else { + newItem->setCheckState(0, Qt::Unchecked); + newItem->setDisabled(true); + } + } + + if (index < 0) index = 0; + + UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); + items.push_back(std::make_pair(sortValue, newItem)); + } + } + + std::sort(items.begin(), items.end(), BySortValue); + + for (std::vector >::iterator iter = items.begin(); iter != items.end(); ++iter) { + int originID = iter->second->data(1, Qt::UserRole).toInt(); + FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); + QList items = ui->bsaList->findItems(ToQString(origin.getName()), Qt::MatchFixedString); + QTreeWidgetItem *subItem = NULL; + if (items.length() > 0) { + subItem = items.at(0); + } else { + subItem = new QTreeWidgetItem(QStringList(ToQString(origin.getName()))); + subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); + ui->bsaList->addTopLevelItem(subItem); + } + subItem->addChild(iter->second); + subItem->setExpanded(true); + } + + checkBSAList(); + m_ArchivesInit = true; +} + + +void MainWindow::checkBSAList() +{ + ui->bsaList->blockSignals(true); + + bool warning = false; + + for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { + bool modWarning = false; + QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem *item = tlItem->child(j); + QString filename = item->text(0); + item->setIcon(0, QIcon()); + item->setToolTip(0, QString()); + + if (item->checkState(0) == Qt::Unchecked) { + if (m_DefaultArchives.contains(filename)) { + item->setIcon(0, QIcon(":/MO/gui/warning")); + item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); + modWarning = true; + } + } + } + if (modWarning) { + ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); + warning = true; + } + } + + if (warning) { + ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning")); + } else { + ui->tabWidget->setTabIcon(1, QIcon()); + } + + ui->bsaList->blockSignals(false); +} + + +void MainWindow::saveModMetas() +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->saveMeta(); + } + } + + +void MainWindow::fixCategories() +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + std::set categories = modInfo->getCategories(); + for (std::set::iterator iter = categories.begin(); + iter != categories.end(); ++iter) { + if (!m_CategoryFactory.categoryExists(*iter)) { + modInfo->setCategory(*iter, false); + } + } + } +} + + +void MainWindow::setupNetworkProxy(bool activate) +{ + QNetworkProxyFactory::setUseSystemConfiguration(activate); +/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); + query.setProtocolTag("http"); + QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); + if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { + qDebug("Using proxy: %s", qPrintable(proxies.at(0).hostName())); + QNetworkProxy::setApplicationProxy(proxies[0]); + } else { + qDebug("Not using proxy"); + }*/ +} + + +void MainWindow::activateProxy(bool activate) +{ + QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); + busyDialog.setWindowFlags(busyDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + busyDialog.setWindowModality(Qt::WindowModal); + busyDialog.show(); + QFuture future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + ::Sleep(100); + } + busyDialog.hide(); +} + +void MainWindow::readSettings() +{ + QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat); + + if (settings.contains("window_geometry")) { + restoreGeometry(settings.value("window_geometry").toByteArray()); + } + + if (settings.contains("window_split")) { + ui->splitter->restoreState(settings.value("window_split").toByteArray()); + } + + if (settings.contains("log_split")) { + ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); + } + + bool filtersVisible = settings.value("filters_visible", false).toBool(); + setCategoryListVisible(filtersVisible); + ui->displayCategoriesBtn->setChecked(filtersVisible); + + int selectedExecutable = settings.value("selected_executable").toInt(); + setExecutableIndex(selectedExecutable); + + if (settings.value("Settings/use_proxy", false).toBool()) { + activateProxy(true); + } + + ui->manageArchivesBox->blockSignals(true); + ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool()); + ui->manageArchivesBox->blockSignals(false); +} + + +void MainWindow::storeSettings() +{ + if (m_CurrentProfile == NULL) { + return; + } + m_CurrentProfile->writeModlist(); + m_CurrentProfile->createTweakedIniFile(); + saveCurrentLists(); + m_Settings.setupLoadMechanism(); + + QString iniFile = ToQString(GameInfo::instance().getIniFilename()); + shellCopy(iniFile, iniFile + ".new", true, this); + + QSettings::Status result = QSettings::NoError; + { + QSettings settings(iniFile + ".new", QSettings::IniFormat); + if (m_CurrentProfile != NULL) { + settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); + } else { + settings.remove("selected_profile"); + } + + settings.setValue("mod_list_state", ui->modList->header()->saveState()); + settings.setValue("plugin_list_state", ui->espList->header()->saveState()); + + settings.setValue("group_state", ui->groupCombo->currentIndex()); + + settings.setValue("ask_for_nexuspw", m_AskForNexusPW); + + settings.setValue("window_geometry", saveGeometry()); + settings.setValue("window_split", ui->splitter->saveState()); + settings.setValue("log_split", ui->topLevelSplitter->saveState()); + + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); + + settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked()); + + settings.remove("customExecutables"); + settings.beginWriteArray("customExecutables"); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + int count = 0; + for (; current != end; ++current) { + const Executable &item = *current; + if (item.m_Custom || item.m_Toolbar) { + settings.setArrayIndex(count++); + settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); + settings.setValue("title", item.m_Title); + settings.setValue("arguments", item.m_Arguments); + settings.setValue("workingDirectory", item.m_WorkingDirectory); + settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); + settings.setValue("steamAppID", item.m_SteamAppID); + settings.setValue("custom", item.m_Custom); + settings.setValue("toolbar", item.m_Toolbar); + } + } + settings.endArray(); + + QComboBox *executableBox = findChild("executablesListBox"); + settings.setValue("selected_executable", executableBox->currentIndex()); + + FileDialogMemory::save(settings); + + settings.sync(); + result = settings.status(); + } + if (result == QSettings::NoError) { + if (!shellRename(iniFile + ".new", iniFile, true, this)) { + QMessageBox::critical(this, tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError()))); + } + } else { + QString reason = result == QSettings::AccessError ? tr("File is write protected") + : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") + : tr("Unknown error %1").arg(result); + QMessageBox::critical(this, tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(reason)); + } +} + + +void MainWindow::on_btnRefreshData_clicked() +{ + if (!m_DirectoryUpdate) { + refreshDirectoryStructure(); + } else { + qDebug("directory update"); + } +} + +void MainWindow::on_tabWidget_currentChanged(int index) +{ + if (index == 0) { + refreshESPList(); + } else if (index == 1) { + refreshBSAList(); + } else if (index == 2) { + refreshDataTree(); + } else if (index == 3) { + refreshSaveList(); + } else if (index == 4) { + ui->downloadView->scrollToBottom(); + } +} + +std::vector MainWindow::activeProblems() const +{ + std::vector problems; + if (m_UnloadedPlugins.size() != 0) { + problems.push_back(PROBLEM_PLUGINSNOTLOADED); + } + if (m_PluginList.enabledCount() > 255) { + problems.push_back(PROBLEM_TOOMANYPLUGINS); + } + return problems; +} + +QString MainWindow::shortDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + return tr("Some plugins could not be loaded"); + } break; + case PROBLEM_TOOMANYPLUGINS: { + return tr("Too many esps and esms enabled"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +QString MainWindow::fullDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
          "; + foreach (const QString &plugin, m_UnloadedPlugins) { + result += "
        • " + plugin + "
        • "; + } + result += "
            "; + return result; + } break; + case PROBLEM_TOOMANYPLUGINS: { + return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or " + "merge some plugins into one. You can find a guide here: http://wiki.step-project.com/Guide:Merging_Plugins"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +bool MainWindow::hasGuidedFix(unsigned int) const +{ + return false; +} + +void MainWindow::startGuidedFix(unsigned int) const +{ +} + +void MainWindow::installMod() +{ + try { + QStringList extensions = m_InstallationManager.getSupportedExtensions(); + for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { + *iter = "*." + *iter; + } + + QString fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), + tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); + + if (fileName.length() == 0) { + return; + } else { + installMod(fileName); + } + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +IModInterface *MainWindow::installMod(const QString &fileName) +{ + if (m_CurrentProfile == NULL) { + return NULL; + } + + bool hasIniTweaks = false; + GuessedValue modName; + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), this); + refreshModList(); + + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); + if (posList.count() == 1) { + ui->modList->scrollTo(posList.at(0)); + } + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (hasIniTweaks && + (QMessageBox::question(this, tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); + } + m_ModInstalled(modName); + return modInfo.data(); + } else { + reportError(tr("mod \"%1\" not found").arg(modName)); + } + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); + } + return NULL; +} + +IModInterface *MainWindow::getMod(const QString &name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + return NULL; + } else { + return ModInfo::getByIndex(index).data(); + } +} + +IModInterface *MainWindow::createMod(GuessedValue &name) +{ + if (!m_InstallationManager.testOverwrite(name)) { + return NULL; + } + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + + QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); + + QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); + + settingsFile.setValue("modid", 0); + settingsFile.setValue("version", ""); + settingsFile.setValue("newestVersion", ""); + settingsFile.setValue("category", 0); + settingsFile.setValue("installationFile", ""); + return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); +} + +bool MainWindow::removeMod(IModInterface *mod) +{ + unsigned int index = ModInfo::getIndex(mod->name()); + if (index == UINT_MAX) { + return mod->remove(); + } else { + return ModInfo::removeMod(index); + } +} + +QList MainWindow::findFileInfos(const QString &path, const std::function &filter) const +{ + QList result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != NULL) { + std::vector files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + IOrganizer::FileInfo info; + info.filePath = ToQString(file->getFullPath()); + bool fromArchive = false; + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); + info.archive = fromArchive ? ToQString(file->getArchive()) : ""; + foreach (int idx, file->getAlternatives()) { + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); + } + + if (filter(info)) { + result.append(info); + } + } + } + return result; +} + +void MainWindow::on_startButton_clicked() +{ + QComboBox* executablesList = findChild("executablesListBox"); + + Executable selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); + + spawnBinary(selectedExecutable.m_BinaryInfo, + selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_CloseMO == DEFAULT_CLOSE, + selectedExecutable.m_SteamAppID); +} + + +static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, + LPCSTR linkFileName, LPCWSTR description, + LPCWSTR currentDirectory) +{ + HRESULT result = E_INVALIDARG; + if ((targetFileName != NULL) && (wcslen(targetFileName) > 0) && + (arguments != NULL) && + (linkFileName != NULL) && (strlen(linkFileName) > 0) && + (description != NULL) && + (currentDirectory != NULL)) { + + IShellLink* shellLink; + result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLink, (LPVOID*)&shellLink); + + if (!SUCCEEDED(result)) { + qCritical("failed to create IShellLink instance"); + return result; + } + if (!SUCCEEDED(result)) return result; + + result = shellLink->SetPath(targetFileName); + if (!SUCCEEDED(result)) { + qCritical("failed to set target path %ls", targetFileName); + shellLink->Release(); + return result; + } + result = shellLink->SetArguments(arguments); + if (!SUCCEEDED(result)) { + qCritical("failed to set arguments: %ls", arguments); + shellLink->Release(); + return result; + } + + if (wcslen(description) > 0) { + result = shellLink->SetDescription(description); + if (!SUCCEEDED(result)) { + qCritical("failed to set description: %ls", description); + shellLink->Release(); + return result; + } + } + + if (wcslen(currentDirectory) > 0) { + result = shellLink->SetWorkingDirectory(currentDirectory); + if (!SUCCEEDED(result)) { + qCritical("failed to set working directory: %ls", currentDirectory); + shellLink->Release(); + return result; + } + } + + IPersistFile* persistFile; + result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(result)) { + wchar_t linkFileNameW[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH); + result = persistFile->Save(linkFileNameW, TRUE); + persistFile->Release(); + } else { + qCritical("failed to create IPersistFile instance"); + } + + shellLink->Release(); + } + return result; +} + + +bool MainWindow::modifyExecutablesDialog() +{ + bool result = false; + try { + EditExecutablesDialog dialog(m_ExecutablesList); + if (dialog.exec() == QDialog::Accepted) { + m_ExecutablesList = dialog.getExecutablesList(); + result = true; + } + refreshExecutablesList(); + } catch (const std::exception &e) { + reportError(e.what()); + } + return result; +} + +void MainWindow::on_executablesListBox_currentIndexChanged(int index) +{ + QComboBox* executablesList = findChild("executablesListBox"); + + int previousIndex = m_OldExecutableIndex; + m_OldExecutableIndex = index; + + if (executablesList->isEnabled()) { + + if (executablesList->itemData(index).isNull()) { + if (modifyExecutablesDialog()) { + setExecutableIndex(previousIndex); +// executablesList->setCurrentIndex(previousIndex); + } + } else { + setExecutableIndex(index); + } + } +} + +void MainWindow::helpTriggered() +{ + QWhatsThis::enterWhatsThisMode(); +} + +void MainWindow::wikiTriggered() +{ +// ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg/wiki/Modorganizer%3AMainPage", NULL, NULL, SW_SHOWNORMAL); + ::ShellExecuteW(NULL, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", NULL, NULL, SW_SHOWNORMAL); +} + +void MainWindow::issueTriggered() +{ + ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg", NULL, NULL, SW_SHOWNORMAL); +} + +void MainWindow::tutorialTriggered() +{ + QAction *tutorialAction = qobject_cast(sender()); + if (tutorialAction != NULL) { + if (QMessageBox::question(this, tr("Start Tutorial?"), + tr("You're about to start a tutorial. For technical reasons it's not possible to end " + "the tutorial early. Continue?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + TutorialManager::instance().activateTutorial("MainWindow", tutorialAction->data().toString()); + } + } +} + + +void MainWindow::on_actionInstallMod_triggered() +{ + installMod(); +} + +void MainWindow::on_actionAdd_Profile_triggered() +{ + bool repeat = true; + while (repeat) { + ProfilesDialog profilesDialog(m_GamePath); + profilesDialog.exec(); + if (refreshProfiles() && !profilesDialog.failed()) { + repeat = false; + } + } +// addProfile(); +} + +void MainWindow::on_actionModify_Executables_triggered() +{ + if (modifyExecutablesDialog()) { + setExecutableIndex(m_OldExecutableIndex); + } +} + + +void MainWindow::setModListSorting(int index) +{ + Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; + int column = index >> 1; + ui->modList->header()->setSortIndicator(column, order); +} + + +void MainWindow::setESPListSorting(int index) +{ + switch (index) { + case 0: { + ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder); + } break; + case 1: { + ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder); + } break; + case 2: { + ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder); + } break; + case 3: { + ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder); + } break; + } +} + + +bool MainWindow::queryLogin(QString &username, QString &password) +{ + CredentialsDialog dialog(this); + int res = dialog.exec(); + if (dialog.neverAsk()) { + m_AskForNexusPW = false; + } + if (res == QDialog::Accepted) { + username = dialog.username(); + password = dialog.password(); + if (dialog.store()) { + m_Settings.setNexusLogin(username, password); + } + return true; + } else { + return false; + } +} + + +bool MainWindow::setCurrentProfile(int index) +{ + QComboBox *profilesBox = findChild("profileBox"); + if (index >= profilesBox->count()) { + return false; + } else { + profilesBox->setCurrentIndex(index); + return true; + } +} + +bool MainWindow::setCurrentProfile(const QString &name) +{ + QComboBox *profilesBox = findChild("profileBox"); + for (int i = 0; i < profilesBox->count(); ++i) { + if (QString::compare(profilesBox->itemText(i), name, Qt::CaseInsensitive) == 0) { + profilesBox->setCurrentIndex(i); + return true; + } + } + // profile not valid + profilesBox->setCurrentIndex(1); + return false; +} + + +void MainWindow::refresher_progress(int percent) +{ + m_RefreshProgress->setValue(percent); +} + + +void MainWindow::directory_refreshed() +{ + DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); + Q_ASSERT(newStructure != m_DirectoryStructure); + if (newStructure != NULL) { + std::swap(m_DirectoryStructure, newStructure); + delete newStructure; + refreshDataTree(); + } else { + // TODO: don't know why this happens, this slot seems to get called twice with only one emit + return; + } + m_DirectoryUpdate = false; + if (m_CurrentProfile != NULL) { + refreshLists(); + } + + // some problem-reports may rely on the virtual directory tree so they need to be updated + // now + updateProblemsButton(); + + for (int i = 0; i < m_ModList.rowCount(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->clearCaches(); + } + statusBar()->hide(); +} + +void MainWindow::externalMessage(const QString &message) +{ + if (message.left(6).toLower() == "nxm://") { + MessageDialog::showMessage(tr("Download started"), this); + downloadRequestedNXM(message); + } +} + +void MainWindow::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) +{ + // add files of the bsa to the directory structure + m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath() + , modInfo->stealFiles() + ); + DirectoryRefresher::cleanStructure(m_DirectoryStructure); + // need to refresh plugin list now so we can activate esps + refreshESPList(); + // activate all esps of the specified mod so the bsas get activated along with it + updateModActiveState(index, true); + // now we need to refresh the bsa list and save it so there is no confusion about what archives are avaiable and active + refreshBSAList(); + saveArchiveList(); + m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); + + // finally also add files from bsas to the directory structure + m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath() + , modInfo->archives() + ); +} + +void MainWindow::modStatusChanged(unsigned int index) +{ + try { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (m_CurrentProfile->modEnabled(index)) { + updateModInDirectoryStructure(index, modInfo); + } else { + updateModActiveState(index, false); + refreshESPList(); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + } + } + modInfo->clearCaches(); + + for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + int priority = m_CurrentProfile->getModPriority(i); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + // priorities in the directory structure are one higher because data is 0 + m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); + } + } + m_DirectoryStructure->getFileRegister()->sortOrigins(); + + refreshLists(); + } catch (const std::exception& e) { + reportError(tr("failed to update mod list: %1").arg(e.what())); + } +} + + +void MainWindow::removeOrigin(const QString &name) +{ + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); + origin.enable(false); + refreshLists(); +} + + +void MainWindow::modorder_changed() +{ + for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { + int priority = m_CurrentProfile->getModPriority(i); + if (m_CurrentProfile->modEnabled(i)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + // priorities in the directory structure are one higher because data is 0 + m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); + } + } + refreshBSAList(); + m_CurrentProfile->writeModlist(); + saveArchiveList(); + m_DirectoryStructure->getFileRegister()->sortOrigins(); +} + +void MainWindow::procError(QProcess::ProcessError error) +{ + reportError(tr("failed to spawn notepad.exe: %1").arg(error)); + this->sender()->deleteLater(); +} + +void MainWindow::procFinished(int, QProcess::ExitStatus) +{ + this->sender()->deleteLater(); +} + +void MainWindow::profileRefresh() +{ + // have to refresh mods twice (again in refreshModList), otherwise the refresh isn't complete. Not sure why + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + m_CurrentProfile->refreshModStatus(); + + refreshModList(); +} + +void MainWindow::showMessage(const QString &message) +{ + MessageDialog::showMessage(message, this); +} + +void MainWindow::showError(const QString &message) +{ + reportError(message); +} + +void MainWindow::installMod_clicked() +{ + installMod(); +} + +void MainWindow::renameModInList(QFile &modList, const QString &oldName, const QString &newName) +{ + //TODO this code needs to be merged with ModList::readFrom + if (!modList.open(QIODevice::ReadWrite)) { + reportError(tr("failed to open %1").arg(modList.fileName())); + return; + } + + QBuffer outBuffer; + outBuffer.open(QIODevice::WriteOnly); + + while (!modList.atEnd()) { + QByteArray line = modList.readLine(); + + if (line.length() == 0) { + // ignore empty lines + qWarning("mod list contained invalid data: empty line"); + continue; + } + + char spec = line.at(0); + if (spec == '#') { + // don't touch comments + outBuffer.write(line); + continue; + } + + QString modName = QString::fromUtf8(line).mid(1).trimmed(); + + if (modName.isEmpty()) { + // file broken? + qWarning("mod list contained invalid data: missing mod name"); + continue; + } + + outBuffer.write(QByteArray(1, spec)); + if (modName == oldName) { + modName = newName; + } + outBuffer.write(modName.toUtf8().constData()); + outBuffer.write("\r\n"); + } + + modList.resize(0); + modList.write(outBuffer.buffer()); + modList.close(); +} + + +void MainWindow::modRenamed(const QString &oldName, const QString &newName) +{ + // fix the profiles directly on disc + for (int i = 0; i < ui->profileBox->count(); ++i) { + QString profileName = ui->profileBox->itemText(i); + + //TODO this functionality should be in the Profile class + QString modlistName = QString("%1/%2/modlist.txt") + .arg(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))) + .arg(profileName); + + QFile modList(modlistName); + if (modList.exists()) { + qDebug("rewrite modlist %s", QDir::toNativeSeparators(modlistName).toUtf8().constData()); + renameModInList(modList, oldName, newName); + } + } + + // immediately refresh the active profile because the data in memory is invalid + m_CurrentProfile->refreshModStatus(); + + // also fix the directory structure + try { + if (m_DirectoryStructure->originExists(ToWString(oldName))) { + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(oldName)); + origin.setName(ToWString(newName)); + } else { + + } + } catch (const std::exception &e) { + reportError(tr("failed to change origin name: %1").arg(e.what())); + } +} + + +void MainWindow::modlistChanged(int) +{ + m_ModListSortProxy->invalidate(); +} + + +void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) +{ + const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath)); + if (filePtr.get() != NULL) { + try { + if (m_DirectoryStructure->originExists(ToWString(newOriginName))) { + FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName)); + + QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; + WIN32_FIND_DATAW findData; + ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); + + filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); + } + if (m_DirectoryStructure->originExists(ToWString(oldOriginName))) { + FilesOrigin &oldOrigin = m_DirectoryStructure->getOriginByName(ToWString(oldOriginName)); + filePtr->removeOrigin(oldOrigin.getID()); + } + } catch (const std::exception &e) { + reportError(tr("failed to move \"%1\" from mod \"%2\" to \"%3\": %4").arg(filePath).arg(oldOriginName).arg(newOriginName).arg(e.what())); + } + } else { + // this is probably not an error, the specified path is likely a directory + } +} + + +QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::UserRole, categoryID); + if (root != NULL) { + root->addChild(item); + } else { + ui->categoriesList->addTopLevelItem(item); + } + return item; +} + + +void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +{ + for (unsigned i = 1; i < m_CategoryFactory.numCategories(); ++i) { + if ((m_CategoryFactory.getParentID(i) == targetID)) { + int categoryID = m_CategoryFactory.getCategoryID(i); + if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { + QTreeWidgetItem *item = addFilterItem(root, m_CategoryFactory.getCategoryName(i), categoryID); + if (m_CategoryFactory.hasChildren(i)) { + addCategoryFilters(item, categoriesUsed, categoryID); + } + } + } + } +} + + +void MainWindow::refreshFilters() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + ui->modList->setCurrentIndex(QModelIndex()); + + QStringList selectedItems; + foreach (QTreeWidgetItem *item, ui->categoriesList->selectedItems()) { + selectedItems.append(item->text(0)); + } + + ui->categoriesList->clear(); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT); + addFilterItem(NULL, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED); + + std::set categoriesUsed; + for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); + BOOST_FOREACH (int categoryID, modInfo->getCategories()) { + int currentID = categoryID; + // also add parents so they show up in the tree + while (currentID != 0) { + categoriesUsed.insert(currentID); + currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); + } + } + } + + addCategoryFilters(NULL, categoriesUsed, 0); + + foreach (const QString &item, selectedItems) { + QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { + matches.at(0)->setSelected(true); + } + } + + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); +} + + +void MainWindow::renameMod_clicked() +{ + try { + ui->modList->edit(ui->modList->currentIndex()); + } catch (const std::exception &e) { + reportError(tr("failed to rename mod: %1").arg(e.what())); + } +} + + +void MainWindow::restoreBackup_clicked() +{ + QRegExp backupRegEx("(.*)_backup[0-9]*$"); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + if (backupRegEx.indexIn(modInfo->name()) != -1) { + QString regName = backupRegEx.cap(1); + QDir modDir(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + if (!modDir.exists(regName) || + (QMessageBox::question(this, tr("Overwrite?"), + tr("This will replace the existing mod \"%1\". Continue?").arg(regName), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { + reportError(tr("failed to remove mod \"%1\"").arg(regName)); + } else { + QString destinationPath = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + "/" + regName; + if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); + } + refreshModList(); + } + } + } +} + +void MainWindow::updateModActiveState(int index, bool active) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + + QDir dir(modInfo->absolutePath()); + foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) { + m_PluginList.enableESP(esm, active); + } + int enabled = 0; + QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files); + foreach (const QString &esp, esps) { + if (active != m_PluginList.isEnabled(esp)) { + m_PluginList.enableESP(esp, active); + ++enabled; + } + } + if (active && (enabled > 1)) { + MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), this); + } + m_PluginList.refreshLoadOrder(); + // immediately save affected lists + savePluginList(); +// refreshBSAList(); +} + +void MainWindow::modlistChanged(const QModelIndex&, int) +{ + m_CurrentProfile->writeModlist(); +} + +void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) +{ + ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + if (m_ModListSortProxy != NULL) { + m_ModListSortProxy->invalidate(); + } + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) +{ + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::removeMod_clicked() +{ + try { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + QString mods; + QStringList modNames; + foreach (QModelIndex idx, selection->selectedRows()) { +// QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name(); + QString name = idx.data().toString(); + if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { + continue; + } + mods += "
          • " + name + "
          • "; + modNames.append(name); + } + if (QMessageBox::question(this, tr("Confirm"), + tr("Remove the following mods?
              %1
            ").arg(mods), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + // use mod names instead of indexes because those become invalid during the removal + foreach (QString name, modNames) { + m_ModList.removeRowForce(ModInfo::getIndex(name)); + } + } + } else { + m_ModList.removeRow(m_ContextRow, QModelIndex()); + } + } catch (const std::exception &e) { + reportError(tr("failed to remove mod: %1").arg(e.what())); + } +} + + +void MainWindow::modRemoved(const QString &fileName) +{ + if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { + int index = m_DownloadManager.indexByName(fileName); + if (index >= 0) { + m_DownloadManager.markUninstalled(index); + } + } +} + + +void MainWindow::reinstallMod_clicked() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + QString installationFile = modInfo->getInstallationFile(); + if (installationFile.length() != 0) { + QString fullInstallationFile; + QFileInfo fileInfo(installationFile); + if (fileInfo.isAbsolute()) { + if (fileInfo.exists()) { + fullInstallationFile = installationFile; + } else { + fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(fileInfo.fileName()); + } + } else { + fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(installationFile); + } + if (QFile::exists(fullInstallationFile)) { + installMod(fullInstallationFile); + } else { + QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists")); + } + } else { + QMessageBox::information(this, tr("Failed"), + tr("Mods installed with old versions of MO can't be reinstalled in this way.")); + } +} + + +void MainWindow::resumeDownload(int downloadIndex) +{ + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + m_DownloadManager.resumeDownload(downloadIndex); + } else { + QString username, password; + if (m_Settings.getNexusLogin(username, password)) { + m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); + } + } +} + + +void MainWindow::endorseMod(ModInfo::Ptr mod) +{ + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + mod->endorse(true); + } else { + QString username, password; + if (m_Settings.getNexusLogin(username, password)) { + m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod)); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); + } + } +} + + +void MainWindow::endorse_clicked() +{ + endorseMod(ModInfo::getByIndex(m_ContextRow)); +} + +void MainWindow::dontendorse_clicked() +{ + ModInfo::getByIndex(m_ContextRow)->setNeverEndorse(); +} + + +void MainWindow::unendorse_clicked() +{ + QString username, password; + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + ModInfo::getByIndex(m_ContextRow)->endorse(false); + } else { + if (m_Settings.getNexusLogin(username, password)) { + m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::unendorse_clicked)); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); + } + } +} + + +void MainWindow::overwriteClosed(int) +{ + OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); + if (dialog != NULL) { + m_ModList.modInfoChanged(dialog->modInfo()); + dialog->deleteLater(); + } +} + + +void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) +{ + m_ModList.modInfoAboutToChange(modInfo); + std::vector flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + QDialog *dialog = this->findChild("__overwriteDialog"); + try { + if (dialog == NULL) { + dialog = new OverwriteInfoDialog(modInfo, this); + dialog->setObjectName("__overwriteDialog"); + } else { + qobject_cast(dialog)->setModInfo(modInfo); + } + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + } catch (const std::exception &e) { + reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); + } + } else { + modInfo->saveMeta(); + ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); + connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); + connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); + connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); + connect(&dialog, SIGNAL(modOpenNext()), this, SLOT(modOpenNext()), Qt::QueuedConnection); + connect(&dialog, SIGNAL(modOpenPrev()), this, SLOT(modOpenPrev()), Qt::QueuedConnection); + connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); + + dialog.openTab(tab); + dialog.restoreTabState(m_Settings.directInterface().value("mod_info_tabs").toByteArray()); + dialog.exec(); + m_Settings.directInterface().setValue("mod_info_tabs", dialog.saveTabState()); + + modInfo->saveMeta(); + emit modInfoDisplayed(); + m_ModList.modInfoChanged(modInfo); + } + + if (m_CurrentProfile->modEnabled(index)) { + FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + m_DirectoryRefresher.addModToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath() + , modInfo->stealFiles() + , modInfo->archives()); + DirectoryRefresher::cleanStructure(m_DirectoryStructure); + refreshLists(); + } + } +} + + +void MainWindow::modOpenNext() +{ + QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); + index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); + + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + std::vector flags = mod->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { + // skip overwrite and backups + modOpenNext(); + } else { + displayModInformation(m_ContextRow); + } +} + +void MainWindow::modOpenPrev() +{ + QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); + int row = index.row() - 1; + if (row == -1) { + row = m_ModListSortProxy->rowCount() - 1; + } + + m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + std::vector flags = mod->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { + // skip overwrite and backups + modOpenPrev(); + } else { + displayModInformation(m_ContextRow); + } +} + +void MainWindow::displayModInformation(const QString &modName, int tab) +{ + unsigned int index = ModInfo::getIndex(modName); + if (index == UINT_MAX) { + qCritical("failed to resolve mod name %s", modName.toUtf8().constData()); + return; + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + displayModInformation(modInfo, index, tab); +} + + +void MainWindow::displayModInformation(int row, int tab) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + displayModInformation(modInfo, row, tab); +} + + +void MainWindow::ignoreMissingData_clicked() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + QDir(info->absolutePath()).mkdir("textures"); + info->testValid(); + connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), &m_ModList, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + emit modListDataChanged(m_ModList.index(m_ContextRow, 0), m_ModList.index(m_ContextRow, m_ModList.columnCount() - 1)); +} + + +void MainWindow::visitOnNexus_clicked() +{ + int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); + if (modID > 0) { + nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID)); + } else { + MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); + } +} + +void MainWindow::openExplorer_clicked() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + + ::ShellExecuteW(NULL, L"explore", ToWString(modInfo->absolutePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); +} + +void MainWindow::information_clicked() +{ + try { + displayModInformation(m_ContextRow); + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +void MainWindow::syncOverwrite() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, this); + if (syncDialog.exec() == QDialog::Accepted) { + syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + modInfo->testValid(); + refreshDirectoryStructure(); + } + +} + +void MainWindow::createModFromOverwrite() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(this, tr("Create Mod..."), + tr("This will move all files from overwrite into a new, regular mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } + + if (getMod(name) != NULL) { + reportError(tr("A mod with this name already exists")); + return; + } + + IModInterface *newMod = createMod(name); + if (newMod == NULL) { + return; + } + + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(m_ContextRow); + shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this); + + refreshModList(); +} + +void MainWindow::cancelModListEditor() +{ + ui->modList->setEnabled(false); + ui->modList->setEnabled(true); +} + +void MainWindow::on_modList_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } + QModelIndex sourceIdx = mapToModel(&m_ModList, index); + if (!sourceIdx.isValid()) { + return; + } + + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); +// displayModInformation(m_ModListSortProxy->mapToSource(index).row()); + displayModInformation(sourceIdx.row()); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + const std::set &categories = modInfo->getCategories(); + + bool childEnabled = false; + + for (unsigned i = 1; i < m_CategoryFactory.numCategories(); ++i) { + if (m_CategoryFactory.getParentID(i) == targetID) { + QMenu *targetMenu = menu; + if (m_CategoryFactory.hasChildren(i)) { + targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); + } + + int id = m_CategoryFactory.getCategoryID(i); + QScopedPointer checkBox(new QCheckBox(targetMenu)); + bool enabled = categories.find(id) != categories.end(); + checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); + if (enabled) { + childEnabled = true; + } + checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); + + QScopedPointer checkableAction(new QWidgetAction(targetMenu)); + checkableAction->setDefaultWidget(checkBox.take()); + checkableAction->setData(id); + targetMenu->addAction(checkableAction.take()); + + if (m_CategoryFactory.hasChildren(i)) { + if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { + targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); + } + } + } + } + return childEnabled; +} + +void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); + foreach (QAction* action, menu->actions()) { + if (action->menu() != NULL) { + replaceCategoriesFromMenu(action->menu(), modRow); + } else { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); + } + } + } +} + +void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) +{ + if (referenceRow != -1 && referenceRow != modRow) { + ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); + foreach (QAction* action, menu->actions()) { + if (action->menu() != NULL) { + addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); + } else { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + int categoryId = widgetAction->data().toInt(); + bool checkedBefore = editedModInfo->categorySet(categoryId); + bool checkedAfter = checkbox->isChecked(); + + if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod + ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); + currentModInfo->setCategory(categoryId, checkedAfter); + } + } + } + } + } else { + replaceCategoriesFromMenu(menu, modRow); + } +} + +void MainWindow::addRemoveCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == NULL) { + qCritical("not a menu?"); + return; + } + + QModelIndexList selectedTemp = ui->modList->selectionModel()->selectedRows(); + QList selected; + foreach (const QModelIndex &idx, selectedTemp) { + selected.append(QPersistentModelIndex(idx)); + } + + if (selected.size() > 0) { + foreach (const QPersistentModelIndex &idx, selected) { + qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString())); + QModelIndex modIdx = mapToModel(&m_ModList, idx); + if (modIdx.row() != m_ContextIdx.row()) { + addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); + } + } + replaceCategoriesFromMenu(menu, m_ContextIdx.row()); + + m_ModList.notifyChange(-1); + + foreach (const QPersistentModelIndex &idx, selected) { + ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_ModList.notifyChange(m_ContextRow); + } + + refreshFilters(); +} + +void MainWindow::replaceCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == NULL) { + qCritical("not a menu?"); + return; + } + + QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); + + if (selected.size() > 0) { + QStringList selectedMods; + for (int i = 0; i < selected.size(); ++i) { + QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); + selectedMods.append(temp.data().toString()); + replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row()); + } + + m_ModList.notifyChange(-1); + + // find mods by their name because indices are invalidated + QAbstractItemModel *model = ui->modList->model(); + Q_FOREACH(const QString &mod, selectedMods) { + QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, + Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); + if (matches.size() > 0) { + ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_ModList.notifyChange(m_ContextRow); + } + + refreshFilters(); +} + +void MainWindow::savePrimaryCategory() +{ + QMenu *menu = qobject_cast(sender()); + if (menu == NULL) { + qCritical("not a menu?"); + return; + } + + foreach (QAction* action, menu->actions()) { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + QRadioButton *btn = qobject_cast(widgetAction->defaultWidget()); + if (btn->isChecked()) { + QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); + for (int i = 0; i < selected.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(selected.at(i)).row()); + modInfo->setPrimaryCategory(widgetAction->data().toInt()); + } + break; + } + } + } +} + +void MainWindow::checkModsForUpdates() +{ + statusBar()->show(); + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + m_ModsToUpdate = ModInfo::checkAllForUpdate(this); + m_RefreshProgress->setRange(0, m_ModsToUpdate); + } else { + QString username, password; + if (m_Settings.getNexusLogin(username, password)) { + m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates)); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { // otherwise there will be no endorsement info + m_ModsToUpdate = ModInfo::checkAllForUpdate(this); + } + } +} + +void MainWindow::changeVersioningScheme() { + if (QMessageBox::question(this, tr("Continue?"), + tr("The versioning scheme decides which version is considered newer than another.\n" + "This function will guess the versioning scheme under the assumption that the installed version is outdated."), + QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { + + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + + bool success = false; + + static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + + for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { + VersionInfo verOld(info->getVersion().canonicalString(), schemes[i]); + VersionInfo verNew(info->getNewestVersion().canonicalString(), schemes[i]); + if (verOld < verNew) { + info->setVersion(verOld); + info->setNewestVersion(verNew); + success = true; + } + } + if (!success) { + QMessageBox::information(this, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->getNewestVersion().canonicalString()).arg(info->getVersion().canonicalString()), + QMessageBox::Ok); + } + } +} + +void MainWindow::ignoreUpdate() { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(true); +} + +void MainWindow::unignoreUpdate() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(false); +} + +void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info) +{ + const std::set &categories = info->getCategories(); + foreach (int categoryID, categories) { + int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); + QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); + try { + QRadioButton *categoryBox = new QRadioButton(m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"), + primaryCategoryMenu); + categoryBox->setChecked(categoryID == info->getPrimaryCategory()); + action->setDefaultWidget(categoryBox); + } catch (const std::exception &e) { + qCritical("failed to create category checkbox: %s", e.what()); + } + + action->setData(categoryID); + primaryCategoryMenu->addAction(action); + } +} + +void MainWindow::addPrimaryCategoryCandidates() +{ + QMenu *menu = qobject_cast(sender()); + if (menu == NULL) { + qCritical("not a menu?"); + return; + } + menu->clear(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + + addPrimaryCategoryCandidates(menu, modInfo); +} + +void MainWindow::enableVisibleMods() +{ + if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->enableAllVisible(); + } +} + +void MainWindow::disableVisibleMods() +{ + if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->disableAllVisible(); + } +} + +void MainWindow::exportModListCSV() +{ + SelectionDialog selection(tr("Choose what to export")); + + selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); + selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); + selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); + + if (selection.exec() == QDialog::Accepted) { + unsigned int numMods = ModInfo::getNumMods(); + + try { + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + CSVBuilder builder(&buffer); + builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); + std::vector > fields; + fields.push_back(std::make_pair(QString("mod_id"), CSVBuilder::TYPE_INTEGER)); + fields.push_back(std::make_pair(QString("mod_installed_name"), CSVBuilder::TYPE_STRING)); + fields.push_back(std::make_pair(QString("mod_version"), CSVBuilder::TYPE_STRING)); + fields.push_back(std::make_pair(QString("file_installed_name"), CSVBuilder::TYPE_STRING)); +// fields.push_back(std::make_pair(QString("file_category"), CSVBuilder::TYPE_INTEGER)); + builder.setFields(fields); + + builder.writeHeader(); + + for (unsigned int i = 0; i < numMods; ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + bool enabled = m_CurrentProfile->modEnabled(i); + if ((selection.getChoiceData().toInt() == 1) && !enabled) { + continue; + } else if ((selection.getChoiceData().toInt() == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { + continue; + } + std::vector flags = info->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end()) && + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end())) { + builder.setRowField("mod_id", info->getNexusID()); + builder.setRowField("mod_installed_name", info->name()); + builder.setRowField("mod_version", info->getVersion().canonicalString()); + builder.setRowField("file_installed_name", info->getInstallationFile()); + builder.writeRow(); + } + } + + SaveTextAsDialog saveDialog(this); + saveDialog.setText(buffer.data()); + saveDialog.exec(); + } catch (const std::exception &e) { + reportError(tr("export failed: %1").arg(e.what())); + } + } +} + +void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) +{ + QPushButton *pushBtn = new QPushButton(subMenu->title()); + pushBtn->setMenu(subMenu); + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(pushBtn); + menu->addAction(action); +} + +QMenu *MainWindow::modListContextMenu() +{ + QMenu *menu = new QMenu(this); + menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); + + menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); + menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); + + menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); + + menu->addAction(tr("Refresh"), this, SLOT(profileRefresh())); + + menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); + return menu; +} + +void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) +{ + try { + QTreeView *modList = findChild("modList"); + + m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos)); + m_ContextRow = m_ContextIdx.row(); + + QMenu *menu = NULL; + QMenu *allMods = modListContextMenu(); + if (m_ContextRow == -1) { + // no selection + menu = allMods; + } else { + menu = new QMenu(this); + allMods->setTitle(tr("All Mods")); + menu->addMenu(allMods); + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + std::vector flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + if (QDir(info->absolutePath()).count() > 2) { + menu->addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite())); + menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); + } + } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { + menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); + menu->addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); + } 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")); + populateMenuCategories(addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); + addMenuAsPushButton(menu, addRemoveCategoriesMenu); + + 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())); + connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory())); + addMenuAsPushButton(menu, primaryCategoryMenu); + + menu->addSeparator(); + if (info->downgradeAvailable()) { + menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); + } + if (info->updateAvailable() || info->downgradeAvailable()) { + if (info->updateIgnored()) { + menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); + } else { + menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + } + } + 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())); + switch (info->endorsedState()) { + case ModInfo::ENDORSED_TRUE: { + menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); + } break; + case ModInfo::ENDORSED_FALSE: { + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + menu->addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); + } break; + case ModInfo::ENDORSED_NEVER: { + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + } break; + default: { + QAction *action = new QAction(tr("Endorsement state unknown"), menu); + action->setEnabled(false); + menu->addAction(action); + } break; + } + std::vector flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); + } + + menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); + menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); + } + + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { + QAction *infoAction = menu->addAction(tr("Information..."), this, SLOT(information_clicked())); + menu->setDefaultAction(infoAction); + } + } + + menu->exec(modList->mapToGlobal(pos)); + } catch (const std::exception &e) { + reportError(tr("Exception: ").arg(e.what())); + } catch (...) { + reportError(tr("Unknown exception")); + } +} + + +void MainWindow::on_categoriesList_itemSelectionChanged() +{ + QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + std::vector categories; + foreach (const QModelIndex &index, indices) { + int categoryId = index.data(Qt::UserRole).toInt(); + if (categoryId != CategoryFactory::CATEGORY_NONE) { + categories.push_back(categoryId); + } + } + + m_ModListSortProxy->setCategoryFilter(categories); + ui->clickBlankLabel->setEnabled(categories.size() > 0); + if (indices.count() == 0) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else if (indices.count() > 1) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else { + ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); + } + ui->modList->reset(); +} + + +void MainWindow::deleteSavegame_clicked() +{ + QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes(); + + QString savesMsgLabel; + QStringList deleteFiles; + + foreach (const QModelIndex &idx, selectedIndexes) { + QString name = idx.data().toString(); + SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); + + savesMsgLabel += "
          • " + QFileInfo(name).completeBaseName() + "
          • "; + + deleteFiles << save->saveFiles(); + } + + if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %n save(s)?
              %1

            Removed saves will be sent to the Recycle Bin.", "", selectedIndexes.count()) + .arg(savesMsgLabel), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + shellDelete(deleteFiles, true); // recycle bin delete. + } +} + + +void MainWindow::fixMods_clicked() +{ + QListWidgetItem *selectedItem = ui->savegameList->currentItem(); + + if (selectedItem == NULL) + return; + + // if required, parse the save game + if (selectedItem->data(Qt::UserRole).isNull()) { + QVariant temp; + SaveGameGamebryo *save = getSaveGame(selectedItem->data(Qt::UserRole).toString()); + save->setParent(selectedItem->listWidget()); + temp.setValue(save); + selectedItem->setData(Qt::UserRole, temp); + } + + const SaveGameGamebryo *save = getSaveGame(selectedItem); + + // collect the list of missing plugins + std::map > missingPlugins; + + for (int i = 0; i < save->numPlugins(); ++i) { + const QString &pluginName = save->plugin(i); + if (!m_PluginList.isEnabled(pluginName)) { + missingPlugins[pluginName] = std::vector(); + } + } + + // figure out, for each esp/esm, which mod, if any, contains it + QStringList espFilter("*.esp"); + espFilter.append("*.esm"); + + // search in data + { + QDir dataDir(m_GamePath + "/data"); + QStringList esps = dataDir.entryList(espFilter); + foreach (const QString &esp, esps) { + std::map >::iterator iter = missingPlugins.find(esp); + if (iter != missingPlugins.end()) { + iter->second.push_back(""); + } + } + } + + // search in mods + for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) { + int modIndex = m_CurrentProfile->modIndexByPriority(i); + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + QStringList esps = QDir(modInfo->absolutePath()).entryList(espFilter); + foreach (const QString &esp, esps) { + std::map >::iterator iter = missingPlugins.find(esp); + if (iter != missingPlugins.end()) { + iter->second.push_back(modInfo->name()); + } + } + } + + // search in overwrite + { + QDir overwriteDir(ToQString(GameInfo::instance().getOverwriteDir())); + QStringList esps = overwriteDir.entryList(espFilter); + foreach (const QString &esp, esps) { + std::map >::iterator iter = missingPlugins.find(esp); + if (iter != missingPlugins.end()) { + iter->second.push_back(""); + } + } + } + + + ActivateModsDialog dialog(missingPlugins, this); + if (dialog.exec() == QDialog::Accepted) { + // activate the required mods, then enable all esps + std::set modsToActivate = dialog.getModsToActivate(); + for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { + if ((*iter != "") && (*iter != "")) { + unsigned int modIndex = ModInfo::getIndex(*iter); + m_CurrentProfile->setModEnabled(modIndex, true); + } + } + + m_CurrentProfile->writeModlist(); + refreshLists(); + + std::set espsToActivate = dialog.getESPsToActivate(); + for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { + m_PluginList.enableESP(*iter); + } + saveCurrentLists(); + } +} + + +void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selection = ui->savegameList->selectionModel(); + + if (!selection->hasSelection()) + return; + + QMenu menu; + + if (!(selection->selectedIndexes().count() > 1)) + menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); + + QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count()); + + menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); + + menu.exec(ui->savegameList->mapToGlobal(pos)); +} + +void MainWindow::linkToolbar() +{ + const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value(); + Executable &exe = m_ExecutablesList.find(selectedExecutable.m_Title); + exe.m_Toolbar = !exe.m_Toolbar; + ui->linkButton->menu()->actions().at(2)->setIcon(exe.m_Toolbar ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); + updateToolBar(); +} + +void MainWindow::linkDesktop() +{ + QComboBox* executablesList = findChild("executablesListBox"); + + const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); + QString linkName = getDesktopDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; + + if (QFile::exists(linkName)) { + if (QFile::remove(linkName)) { + ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/link")); + } else { + reportError(tr("failed to remove %1").arg(linkName)); + } + } else { + QFileInfo exeInfo(qApp->arguments().at(0)); + // create link + std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); + std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) + .arg(selectedExecutable.m_Arguments)); + std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); + std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); + if (CreateShortcut(targetFile.c_str() + , parameter.c_str() + , linkName.toUtf8().constData() + , description.c_str() + , currentDirectory.c_str()) != E_INVALIDARG) { + ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/remove")); + } else { + reportError(tr("failed to create %1").arg(linkName)); + } + } +} + +void MainWindow::linkMenu() +{ + QComboBox* executablesList = findChild("executablesListBox"); + + const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); + QString linkName = getStartMenuDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; + + if (QFile::exists(linkName)) { + if (QFile::remove(linkName)) { + ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/link")); + } else { + reportError(tr("failed to remove %1").arg(linkName)); + } + } else { + QFileInfo exeInfo(qApp->arguments().at(0)); + // create link + std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); + std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) + .arg(selectedExecutable.m_Arguments)); + std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); + std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); + + if (CreateShortcut(targetFile.c_str(), parameter.c_str(), + linkName.toUtf8().constData(), + description.c_str(), currentDirectory.c_str()) != E_INVALIDARG) { + ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/remove")); + } else { + reportError(tr("failed to create %1").arg(linkName)); + } + } +} + +void MainWindow::downloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.setDownloadSpeed(serverName, bytesPerSecond); +} + + +void MainWindow::on_actionSettings_triggered() +{ + QString oldModDirectory(m_Settings.getModDirectory()); + QString oldCacheDirectory(m_Settings.getCacheDirectory()); + bool oldDisplayForeign(m_Settings.displayForeign()); + bool proxy = m_Settings.useProxy(); + m_Settings.query(this); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + fixCategories(); + refreshFilters(); + if (QDir::fromNativeSeparators(m_DownloadManager.getOutputDirectory()) != QDir::fromNativeSeparators(m_Settings.getDownloadDirectory())) { + if (m_DownloadManager.downloadsInProgress()) { + MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this); + } else { + m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + } + } + m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + + if ((m_Settings.getModDirectory() != oldModDirectory) + || (m_Settings.displayForeign() != oldDisplayForeign)) { + profileRefresh(); + } + + if (m_Settings.getCacheDirectory() != oldCacheDirectory) { + NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); + } + + if (proxy != m_Settings.useProxy()) { + activateProxy(m_Settings.useProxy()); + } + + NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + + updateDownloadListDelegate(); +} + + +void MainWindow::on_actionNexus_triggered() +{ + ::ShellExecuteW(NULL, L"open", GameInfo::instance().getNexusPage(false).c_str(), NULL, NULL, SW_SHOWNORMAL); +} + + +void MainWindow::nexusLinkActivated(const QString &link) +{ + ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL); + ui->tabWidget->setCurrentIndex(4); +} + + +void MainWindow::linkClicked(const QString &url) +{ + ::ShellExecuteW(NULL, L"open", ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL); +} + + +bool MainWindow::nexusLogin() +{ + QString username, password; + + NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); + + if (!accessManager->loginAttempted() + && !accessManager->loggedIn() + && (m_Settings.getNexusLogin(username, password) + || (m_AskForNexusPW + && queryLogin(username, password)))) { + accessManager->login(username, password); + return true; + } else { + return false; + } +} + + +void MainWindow::downloadRequestedNXM(const QString &url) +{ + qDebug("download requested: %s", qPrintable(url)); + if (nexusLogin()) { + m_PendingDownloads.append(url); + } else { + m_DownloadManager.addNXMDownload(url); + } +} + + +void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName) +{ + try { + if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { + MessageDialog::showMessage(tr("Download started"), this); + } + } catch (const std::exception &e) { + MessageDialog::showMessage(tr("Download failed"), this); + qCritical("exception starting download: %s", e.what()); + } +} + + +void MainWindow::installTranslator(const QString &name) +{ +/* if (m_CurrentLanguage == "en_US") { + return; + }*/ + QTranslator *translator = new QTranslator(this); + QString fileName = name + "_" + m_CurrentLanguage; + if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { + if (m_CurrentLanguage != "en_US") { + qWarning("localization file %s not found", qPrintable(fileName)); + } // we don't actually expect localization files for english + } + qApp->installTranslator(translator); + m_Translators.push_back(translator); +} + + +void MainWindow::languageChange(const QString &newLanguage) +{ + foreach (QTranslator *trans, m_Translators) { + qApp->removeTranslator(trans); + } + m_Translators.clear(); + + m_CurrentLanguage = newLanguage; + + installTranslator("qt"); + installTranslator(ToQString(AppConfig::translationPrefix())); + ui->retranslateUi(this); + qDebug("loaded language %s", qPrintable(newLanguage)); + + ui->profileBox->setItemText(0, QObject::tr("")); + + createHelpWidget(); + + updateDownloadListDelegate(); + updateProblemsButton(); + + ui->listOptionsBtn->setMenu(modListContextMenu()); +} + + +void MainWindow::installDownload(int index) +{ + try { + QString fileName = m_DownloadManager.getFilePath(index); + int modID = m_DownloadManager.getModID(index); + GuessedValue modName; + + // see if there already are mods with the specified mod id + if (modID != 0) { + std::vector modInfo = ModInfo::getByModID(modID); + for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { + std::vector flags = (*iter)->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) { + modName.update((*iter)->name(), GUESS_PRESET); + (*iter)->saveMeta(); + } + } + } + + m_CurrentProfile->writeModlistNow(); + + bool hasIniTweaks = false; + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), this); + refreshModList(); + + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); + if (posList.count() == 1) { + ui->modList->scrollTo(posList.at(0)); + } + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + + if (hasIniTweaks && + (QMessageBox::question(this, tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); + } + + m_ModInstalled(modName); + } else { + reportError(tr("mod \"%1\" not found").arg(modName)); + } + m_DownloadManager.markInstalled(index); + + emit modInstalled(); + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); + } + } catch (const std::exception &e) { + reportError(e.what()); + } +} + + +void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) +{ + { // list files +// std::set::const_iterator current, end; +// directoryEntry.getFiles(current, end); +// for (; current != end; ++current) { + + std::vector files = directoryEntry.getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + FileEntry::Ptr current = *iter; + bool isArchive = false; + int origin = current->getOrigin(isArchive); + if (isArchive) { + // TODO: don't list files from archives. maybe make this an option? + continue; + } + QString fullName = directory; + fullName.append("\\").append(ToQString(current->getName())); + file.write(fullName.toUtf8()); + + file.write("\t("); + file.write(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()).toUtf8()); + file.write(")\r\n"); + } + } + + { // recurse into subdirectories + std::vector::const_iterator current, end; + directoryEntry.getSubDirectories(current, end); + for (; current != end; ++current) { + writeDataToFile(file, directory.mid(0).append("\\").append(ToQString((*current)->getName())), **current); + } + } +} + + +void MainWindow::writeDataToFile() +{ + QString fileName = QFileDialog::getSaveFileName(this); + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to write to file %1").arg(fileName)); + } + + writeDataToFile(file, "data", *m_DirectoryStructure); + file.close(); + + MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); +} + + +int MainWindow::getBinaryExecuteInfo(const QFileInfo &targetInfo, + QFileInfo &binaryInfo, QString &arguments) +{ + QString extension = targetInfo.completeSuffix(); + if ((extension == "exe") || + (extension == "cmd") || + (extension == "com") || + (extension == "bat")) { + binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); + arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + return 1; + } else if (extension == "jar") { + // 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(), NULL, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(targetPathW.c_str(), &binaryType)) { + qDebug("failed to determine binary type: %lu", ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty() && (extension == "jar")) { + 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 MainWindow::addAsExecutable() +{ + if (m_ContextItem != NULL) { + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + QString name = QInputDialog::getText(this, tr("Enter Name"), + tr("Please enter a name for the executable"), QLineEdit::Normal, + targetInfo.baseName()); + if (!name.isEmpty()) { + m_ExecutablesList.addExecutable(name, binaryInfo.absoluteFilePath(), + arguments, targetInfo.absolutePath(), + DEFAULT_STAY, QString(), + true, false); + refreshExecutablesList(); + } + } break; + case 2: { + QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); + } break; + default: { + // nop + } break; + } + } +} + + +void MainWindow::originModified(int originID) +{ + FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); + origin.enable(false); + m_DirectoryStructure->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); + DirectoryRefresher::cleanStructure(m_DirectoryStructure); +} + + +void MainWindow::hideFile() +{ + QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); + QString newName = oldName + ModInfo::s_HiddenExt; + + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return; + } + } else { + return; + } + } + + if (QFile::rename(oldName, newName)) { + originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); + refreshDataTree(); + } else { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); + } +} + + +void MainWindow::unhideFile() +{ + QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return; + } + } else { + return; + } + } + if (QFile::rename(oldName, newName)) { + originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); + refreshDataTree(); + } else { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); + } +} + +void MainWindow::previewDataFile() +{ + QString fileName = QDir::fromNativeSeparators(m_ContextItem->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 + + // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory + int offset = m_Settings.getModDirectory().size() + 1; + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); + + const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL); + + if (file.get() == NULL) { + reportError(tr("file not found: %1").arg(fileName)); + return; + } + + // set up preview dialog + PreviewDialog preview(fileName); + auto addFunc = [&] (int originId) { + FilesOrigin &origin = m_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_PreviewGenerator.genPreview(filePath); + if (wid == NULL) { + reportError(tr("failed to generate preview for %1").arg(filePath)); + } else { + preview.addVariant(ToQString(origin.getName()), wid); + } + } + }; + + addFunc(file->getOrigin()); + foreach (int i, file->getAlternatives()) { + addFunc(i); + } + 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 MainWindow::openDataFile() +{ + if (m_ContextItem != NULL) { + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + spawnBinaryDirect(binaryInfo, arguments, m_CurrentProfile->getName(), targetInfo.absolutePath(), ""); + } break; + case 2: { + ::ShellExecuteW(NULL, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); + } break; + default: { + // nop + } break; + } + } +} + + +void MainWindow::updateAvailable() +{ + QToolBar *toolBar = findChild("toolBar"); + foreach (QAction *action, toolBar->actions()) { + if (action->text() == tr("Update")) { + action->setEnabled(true); + action->setToolTip(tr("Update available")); + break; + } + } +} + + +void MainWindow::motdReceived(const QString &motd) +{ + // don't show motd after 5 seconds, may be annoying. Hopefully the user's + // internet connection is faster next time + if (m_StartTime.secsTo(QTime::currentTime()) < 5) { + uint hash = qHash(motd); + if (hash != m_Settings.getMotDHash()) { + MotDDialog dialog(motd); + dialog.exec(); + m_Settings.setMotDHash(hash); + } + } + + ui->actionEndorseMO->setVisible(false); +} + + +void MainWindow::notEndorsedYet() +{ + ui->actionEndorseMO->setVisible(true); +} + + +void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) +{ + QTreeWidget *dataTree = findChild("dataTree"); + m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); + + QMenu menu; + if ((m_ContextItem != NULL) && (m_ContextItem->childCount() == 0)) { + menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); + + QString fileName = m_ContextItem->text(0); + if (m_PreviewGenerator.previewSupported(QFileInfo(fileName).completeSuffix())) { + menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + } + + // offer to hide/unhide file, but not for files from archives + if (!m_ContextItem->data(0, Qt::UserRole + 1).toBool()) { + if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); + } else { + menu.addAction(tr("Hide"), this, SLOT(hideFile())); + } + } + + menu.addSeparator(); + } + menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); + menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); + + menu.exec(dataTree->mapToGlobal(pos)); +} + +void MainWindow::on_conflictsCheckBox_toggled(bool) +{ + refreshDataTree(); +} + + +void MainWindow::on_actionUpdate_triggered() +{ + if (nexusLogin()) { + m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); }); + } else { + m_Updater.startUpdate(); + } +} + + +void MainWindow::on_actionEndorseMO_triggered() +{ + if (QMessageBox::question(this, tr("Endorse Mod Organizer"), + tr("Do you want to endorse Mod Organizer on %1 now?").arg(ToQString(GameInfo::instance().getNexusPage())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant(), QString()); + } +} + + +void MainWindow::updateDownloadListDelegate() +{ + if (m_Settings.compactDownloads()) { + ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(), + ui->downloadView, ui->downloadView)); + } else { + ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(), + ui->downloadView, ui->downloadView)); + } + + DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); + sortProxy->setSourceModel(new DownloadList(&m_DownloadManager, ui->downloadView)); + connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString))); + connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); + + ui->downloadView->setModel(sortProxy); + ui->downloadView->sortByColumn(1, Qt::AscendingOrder); + ui->downloadView->header()->resizeSections(QHeaderView::Fixed); + + connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), this, SLOT(installDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), &m_DownloadManager, SLOT(queryInfo(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), &m_DownloadManager, SLOT(removeDownload(int, bool))); + connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); +} + + +void MainWindow::modDetailsUpdated(bool) +{ + --m_ModsToUpdate; + if (m_ModsToUpdate == 0) { + statusBar()->hide(); + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } +// m_RefreshProgress->setVisible(false); + } else { + m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); + } +} + +void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int) +{ + m_ModsToUpdate -= modIDs.size(); + QVariantList resultList = resultData.toList(); + for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { + QVariantMap result = iter->toMap(); + if (result["id"].toInt() == GameInfo::instance().getNexusModID()) { + if (!result["voted_by_user"].toBool()) { + ui->actionEndorseMO->setVisible(true); + } + } else { + std::vector info = ModInfo::getByModID(result["id"].toInt()); + for (auto iter = info.begin(); iter != info.end(); ++iter) { + (*iter)->setNewestVersion(result["version"].toString()); + (*iter)->setNexusDescription(result["description"].toString()); + if (NexusInterface::instance()->getAccessManager()->loggedIn() && + result.contains("voted_by_user")) { + // don't use endorsement info if we're not logged in or if the response doesn't contain it + (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); + } + } + } + } + + if (m_ModsToUpdate <= 0) { + statusBar()->hide(); + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } + } else { + m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); + } +} + +void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) +{ + if (resultData.toBool()) { + ui->actionEndorseMO->setVisible(false); + QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); + } + + if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), + this, SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { + qCritical("failed to disconnect endorsement slot"); + } +} + +void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) +{ + QVariantList serverList = resultData.toList(); + + QList servers; + foreach (const QVariant &server, serverList) { + QVariantMap serverInfo = server.toMap(); + ServerInfo info; + info.name = serverInfo["Name"].toString(); + info.premium = serverInfo["IsPremium"].toBool(); + info.lastSeen = QDate::currentDate(); + info.preferred = 0; + // other keys: ConnectedUsers, Country, URI + servers.append(info); + } + m_Settings.updateServers(servers); +} + + +void MainWindow::nxmRequestFailed(int modID, int, QVariant, int, const QString &errorString) +{ + if (modID == -1) { + // must be the update-check that failed + m_ModsToUpdate = 0; + statusBar()->hide(); + } + MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); +} + + +void MainWindow::loginSuccessful(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), this); + } + foreach (QString url, m_PendingDownloads) { + downloadRequestedNXM(url); + } + m_PendingDownloads.clear(); + foreach (auto task, m_PostLoginTasks) { + task(this); + } + + m_PostLoginTasks.clear(); + NexusInterface::instance()->loginCompleted(); +} + + +void MainWindow::loginSuccessfulUpdate(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), this); + } + m_Updater.startUpdate(); +} + + +void MainWindow::loginFailed(const QString &message) +{ + if (!m_PendingDownloads.isEmpty()) { + MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), this); + foreach (QString url, m_PendingDownloads) { + downloadRequestedNXM(url); + } + m_PendingDownloads.clear(); + } else { + MessageDialog::showMessage(tr("login failed: %1").arg(message), this); + m_PostLoginTasks.clear(); + statusBar()->hide(); + } + NexusInterface::instance()->loginCompleted(); +} + + +void MainWindow::loginFailedUpdate(const QString &message) +{ + MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), this); +} + + +void MainWindow::windowTutorialFinished(const QString &windowName) +{ + m_Settings.directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); +} + + +BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, + QProgressDialog &progress) +{ + QDir().mkdir(destination); + BSA::EErrorCode result = BSA::ERROR_NONE; + QString errorFile; + + for (unsigned int i = 0; i < folder->getNumFiles(); ++i) { + BSA::File::Ptr file = folder->getFile(i); + BSA::EErrorCode res = archive.extract(file, destination.toUtf8().constData()); + if (res != BSA::ERROR_NONE) { + reportError(tr("failed to read %1: %2").arg(file->getName().c_str()).arg(res)); + result = res; + } + progress.setLabelText(file->getName().c_str()); + progress.setValue(progress.value() + 1); + QCoreApplication::processEvents(); + if (progress.wasCanceled()) { + result = BSA::ERROR_CANCELED; + } + } + + if (result != BSA::ERROR_NONE) { + if (QMessageBox::critical(this, tr("Error"), tr("failed to extract %1 (errorcode %2)").arg(errorFile).arg(result), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return result; + } + } + + for (unsigned int i = 0; i < folder->getNumSubFolders(); ++i) { + BSA::Folder::Ptr subFolder = folder->getSubFolder(i); + BSA::EErrorCode res = extractBSA(archive, subFolder, + destination.mid(0).append("/").append(subFolder->getName().c_str()), progress); + if (res != BSA::ERROR_NONE) { + return res; + } + } + return BSA::ERROR_NONE; +} + + +bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std::string fileName) +{ + progress.setLabelText(fileName.c_str()); + progress.setValue(percentage); + QCoreApplication::processEvents(); + return !progress.wasCanceled(); +} + + +void MainWindow::extractBSATriggered() +{ + QTreeWidgetItem *item = m_ContextItem; + + QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); + if (!targetFolder.isEmpty()) { + BSA::Archive archive; + QString originPath = QDir::fromNativeSeparators(ToQString(m_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; + } + + 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.")); + } + } +} + + +void MainWindow::displayColumnSelection(const QPoint &pos) +{ + QMenu menu; + + // display a list of all headers as checkboxes + QAbstractItemModel *model = ui->modList->header()->model(); + for (int i = 1; i < model->columnCount(); ++i) { + QString columnName = model->headerData(i, Qt::Horizontal).toString(); + QCheckBox *checkBox = new QCheckBox(&menu); + checkBox->setText(columnName); + checkBox->setChecked(!ui->modList->header()->isSectionHidden(i)); + QWidgetAction *checkableAction = new QWidgetAction(&menu); + checkableAction->setDefaultWidget(checkBox); + menu.addAction(checkableAction); + } + menu.exec(pos); + + // view/hide columns depending on check-state + int i = 1; + foreach (const QAction *action, menu.actions()) { + const QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); + if (checkBox != NULL) { + ui->modList->header()->setSectionHidden(i, !checkBox->isChecked()); + } + } + ++i; + } +} + + +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::bsaList_itemMoved() +{ + saveArchiveList(); + m_CheckBSATimer.start(500); +} + + +void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) +{ + saveArchiveList(); + m_CheckBSATimer.start(500); +} + +void MainWindow::on_actionProblems_triggered() +{ +// QString problemDescription; +// checkForProblems(problemDescription); +// QMessageBox::information(this, tr("Problems"), problemDescription); + ProblemsDialog problems(m_DiagnosisPlugins, this); + if (problems.hasProblems()) { + problems.exec(); + updateProblemsButton(); + } +} + +void MainWindow::setCategoryListVisible(bool visible) +{ + if (visible) { + ui->categoriesGroup->show(); + ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); + } else { + ui->categoriesGroup->hide(); + ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); + } +} + +void MainWindow::on_displayCategoriesBtn_toggled(bool checked) +{ + setCategoryListVisible(checked); +} + +void MainWindow::editCategories() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void MainWindow::deselectFilters() +{ + ui->categoriesList->clearSelection(); +} + +void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); + menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); + + menu.exec(ui->categoriesList->mapToGlobal(pos)); +} + + +void MainWindow::updateESPLock(bool locked) +{ + QItemSelection currentSelection = ui->espList->selectionModel()->selection(); + if (currentSelection.count() == 0) { + // this path is probably useless + m_PluginList.lockESPIndex(m_ContextRow, locked); + } else { + Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { + m_PluginList.lockESPIndex(mapToModel(&m_PluginList, idx).row(), locked); + } + } +} + + +void MainWindow::lockESPIndex() +{ + updateESPLock(true); +} + +void MainWindow::unlockESPIndex() +{ + updateESPLock(false); +} + + +void MainWindow::removeFromToolbar() +{ + try { + Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); + exe.m_Toolbar = false; + } catch (const std::runtime_error&) { + qDebug("executable doesn't exist any more"); + } + + updateToolBar(); +} + + +void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) +{ + QAction *action = ui->toolBar->actionAt(point); + if (action != NULL) { + if (action->objectName().startsWith("custom_")) { + m_ContextAction = action; + QMenu menu; + menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); + menu.exec(ui->toolBar->mapToGlobal(point)); + } + } +} + +void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) +{ + m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); + + QMenu menu; + menu.addAction(tr("Enable all"), &m_PluginList, SLOT(enableAll())); + menu.addAction(tr("Disable all"), &m_PluginList, SLOT(disableAll())); + + QItemSelection currentSelection = ui->espList->selectionModel()->selection(); + bool hasLocked = false; + bool hasUnlocked = false; + Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { + int row = m_PluginListSortProxy->mapToSource(idx).row(); + if (m_PluginList.isEnabled(row)) { + if (m_PluginList.isESPLocked(row)) { + hasLocked = true; + } else { + hasUnlocked = true; + } + } + } + + menu.addSeparator(); + + if (hasLocked) { + menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); + } + if (hasUnlocked) { + menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); + } + + try { + menu.exec(ui->espList->mapToGlobal(pos)); + } catch (const std::exception &e) { + reportError(tr("Exception: ").arg(e.what())); + } catch (...) { + reportError(tr("Unknown exception")); + } +} + +void MainWindow::on_groupCombo_currentIndexChanged(int index) +{ + if (m_ModListSortProxy == NULL) { + return; + } + QAbstractProxyModel *newModel = NULL; + switch (index) { + case 1: { + newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, + 0, Qt::UserRole + 2); + } break; + case 2: { + newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, + QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, + Qt::UserRole + 2); + } break; + default: { + newModel = NULL; + } break; + } + + if (newModel != NULL) { +#ifdef TEST_MODELS + new ModelTest(newModel, this); +#endif // TEST_MODELS + m_ModListSortProxy->setSourceModel(newModel); + connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); + connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); + connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); + } else { + m_ModListSortProxy->setSourceModel(&m_ModList); + } + modFilterActive(m_ModListSortProxy->isFilterActive()); +} + +void MainWindow::on_linkButton_pressed() +{ + const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value(); + + QIcon addIcon(":/MO/gui/link"); + QIcon removeIcon(":/MO/gui/remove"); + + QFileInfo linkDesktopFile(QDir::fromNativeSeparators(getDesktopDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); + QFileInfo linkMenuFile(QDir::fromNativeSeparators(getStartMenuDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); + + ui->linkButton->menu()->actions().at(0)->setIcon(selectedExecutable.m_Toolbar ? removeIcon : addIcon); + ui->linkButton->menu()->actions().at(1)->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); + ui->linkButton->menu()->actions().at(2)->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); +} + +void MainWindow::on_showHiddenBox_toggled(bool checked) +{ + m_DownloadManager.setShowHidden(checked); +} + + +void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = NULL; + + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + qCritical("failed to create stdout reroute"); + } + + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + qCritical("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } +} + +std::string MainWindow::readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; + + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; + + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { + break; + } + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } + } + } + return result; +} + +void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog) +{ + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + + std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + + foreach (const std::string &line, lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t reportidx = line.find("[report]"); + size_t erroridx = line.find("[error]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (reportidx != std::string::npos) { + reportURL = line.substr(reportidx + 9); + } else if (erroridx != std::string::npos) { + 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::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + m_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else { + qDebug("%s", line.c_str()); + } + } + } + } +} + + +HANDLE MainWindow::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) +{ + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString profileName = profile; + if (profile.length() == 0) { + if (m_CurrentProfile != NULL) { + profileName = m_CurrentProfile->getName(); + } else { + throw MyException(tr("No profile set")); + } + } + QString steamAppID; + if (executable.contains('\\') || executable.contains('/')) { + // file path + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); + } + if (cwd.length() == 0) { + currentDirectory = binary.absolutePath(); + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.find(executable); + steamAppID = exe.m_SteamAppID; + if (arguments == "") { + arguments = exe.m_Arguments; + } + binary = exe.m_BinaryInfo; + if (cwd.length() == 0) { + currentDirectory = exe.m_WorkingDirectory; + } + } catch (const std::runtime_error&) { + qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); + binary = QFileInfo(executable); + } + } + + return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); +} + +void MainWindow::on_bossButton_clicked() +{ + std::string reportURL; + std::string errorMessages; + + m_CurrentProfile->writeModlistNow(); + + bool success = false; + + try { + this->setEnabled(false); + ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); + QProgressDialog dialog(this); + dialog.setLabelText(tr("LOOT working")); + dialog.setMaximum(0); + dialog.show(); + + QStringList parameters; + parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) + << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); + + if (!m_DidUpdateMasterList) { + parameters << "--updateMasterlist"; + m_DidUpdateMasterList = true; + } + + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + parameters.join(" "), + m_CurrentProfile->getName(), + m_Settings.logLevel(), + qApp->applicationDirPath() + "/loot", + true, + stdOutWrite); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + m_PluginList.clearAdditionalInformation(); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + bool isJobHandle = true; + + if (loot != INVALID_HANDLE_VALUE) { + DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (isJobHandle) { + if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + qDebug("no more processes in job"); + break; + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + if (dialog.wasCanceled()) { + if (isJobHandle) { + ::TerminateJobObject(loot, 1); + } else { + ::TerminateProcess(loot, 1); + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + std::string lootOut = readFromPipe(stdOutRead); + processLOOTOut(lootOut, reportURL, errorMessages, dialog); + + res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); + } + + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + processLOOTOut(remainder, reportURL, errorMessages, dialog); + } + DWORD exitCode = 0UL; + ::GetExitCodeProcess(loot, &exitCode); + if (exitCode != 0UL) { + reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); + return; + } else { + success = true; + } + } else { + reportError(tr("failed to start loot")); + } + } catch (const std::exception &e) { + reportError(tr("failed to run loot: %1").arg(e.what())); + } + if (errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); + warn->setModal(false); + warn->show(); + } + + if (success) { + if (reportURL.length() > 0) { + m_IntegratedBrowser.setWindowTitle("LOOT Report"); + QString report(reportURL.c_str()); + if (QFile::exists(report)) { + m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report)); + } else { + qWarning("report file missing"); + } + } + refreshESPList(); + + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + } + } +} + + +const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; +const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; +const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; + + +bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +{ + QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); + if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { + QFileInfo fileInfo(filePath); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + return true; + } else { + return false; + } +} + +void MainWindow::on_saveButton_clicked() +{ + savePluginList(); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getPluginsFileName(), now) + && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) + && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { + MessageDialog::showMessage(tr("Backup of load order created"), this); + } +} + +QString MainWindow::queryRestore(const QString &filePath) +{ + QFileInfo pluginFileInfo(filePath); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + + SelectionDialog dialog(tr("Choose backup to restore"), this); + QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); + QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); + foreach(const QFileInfo &info, files) { + if (exp.exactMatch(info.fileName())) { + QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); + dialog.addChoice(time.toString(), "", exp.cap(1)); + } else if (exp2.exactMatch(info.fileName())) { + dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); + } + } + + if (dialog.numChoices() == 0) { + QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + return QString(); + } + + if (dialog.exec() == QDialog::Accepted) { + return dialog.getChoiceData().toString(); + } else { + return QString(); + } +} + +void MainWindow::on_restoreButton_clicked() +{ + QString pluginName = m_CurrentProfile->getPluginsFileName(); + QString choice = queryRestore(pluginName); + if (!choice.isEmpty()) { + QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); + QString lockedName = m_CurrentProfile->getLockedOrderFileName(); + if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || + !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshESPList(); + } +} + +void MainWindow::on_saveModsButton_clicked() +{ + m_CurrentProfile->writeModlistNow(true); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { + MessageDialog::showMessage(tr("Backup of modlist created"), this); + } +} +void MainWindow::on_restoreModsButton_clicked() +{ + QString modlistName = m_CurrentProfile->getModlistFileName(); + QString choice = queryRestore(modlistName); + if (!choice.isEmpty()) { + if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + refreshModList(false); + } +} + +void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() +{ + QStringList lines; + QAbstractItemModel *model = ui->logList->model(); + for (int i = 0; i < model->rowCount(); ++i) { + lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) + .arg(model->index(i, 1).data(Qt::UserRole).toString()) + .arg(model->index(i, 1).data().toString())); + } + QApplication::clipboard()->setText(lines.join("\n")); +} + +void MainWindow::on_categoriesAndBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); + } +} + +void MainWindow::on_categoriesOrBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); + } +} + +void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) +{ + QToolTip::showText(QCursor::pos(), + ui->managedArchiveLabel->toolTip()); +} + +void MainWindow::on_manageArchivesBox_toggled(bool) +{ + refreshBSAList(); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 89e883f9..62503062 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,615 +1,615 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "executableslist.h" -#include "modlist.h" -#include "pluginlist.h" -#define WIN32_LEAN_AND_MEAN -#include -#include -#include "directoryrefresher.h" -#include -#include -#include -#include -#include "settings.h" -#include "downloadmanager.h" -#include "installationmanager.h" -#include "selfupdater.h" -#include "savegamegamebyro.h" -#include "modlistsortproxy.h" -#include "pluginlistsortproxy.h" -#include "tutorialcontrol.h" -#include "savegameinfowidgetgamebryo.h" -#include "previewgenerator.h" -#include "browserdialog.h" -#include -#include -#include - -namespace Ui { - class MainWindow; -} - -class QToolButton; -class ModListSortProxy; -class ModListGroupCategoriesProxy; - - -class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose -{ - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - - friend class OrganizerProxy; - -private: - - struct SignalCombinerAnd - { - typedef bool result_type; - template - bool operator()(InputIterator first, InputIterator last) const - { - while (first != last) { - if (!(*first)) { - return false; - } - ++first; - } - return true; - } - }; - - typedef boost::signals2::signal SignalAboutToRunApplication; - typedef boost::signals2::signal SignalModInstalled; - -public: - explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0); - ~MainWindow(); - - void readSettings(); - - bool addProfile(); - void refreshBSAList(); - void refreshDataTree(); - void refreshSaveList(); - - void setExecutablesList(const ExecutablesList &executablesList); - - void setModListSorting(int index); - void setESPListSorting(int index); - - bool setCurrentProfile(int index); - bool setCurrentProfile(const QString &name); - - void createFirstProfile(); - -/* void spawnProgram(const QString &fileName, const QString &argumentsArg, - const QString &profileName, const QDir ¤tDirectory);*/ - - void loadPlugins(); - - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; - - void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - - bool saveArchiveList(); - - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog); - - HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); - - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - - void waitForProcessOrJob(HANDLE processHandle); -public slots: - - void refreshLists(); - - void displayColumnSelection(const QPoint &pos); - - void externalMessage(const QString &message); - void modorder_changed(); - void refresher_progress(int percent); - void directory_refreshed(); - - void toolPluginInvoke(); - void modPagePluginInvoke(); - -signals: - - /** - * @brief emitted after a mod has been installed - * @node this is currently only used for tutorials - */ - void modInstalled(); - - /** - * @brief emitted after the information dialog has been closed - */ - void modInfoDisplayed(); - - /** - * @brief emitted when the selected style changes - */ - void styleChanged(const QString &styleFile); - - - void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - -protected: - - virtual void showEvent(QShowEvent *event); - virtual void closeEvent(QCloseEvent *event); - virtual bool eventFilter(QObject *obj, QEvent *event); - virtual void resizeEvent(QResizeEvent *event); - -private: - - void refreshESPList(); - void refreshModList(bool saveChanges = true); - - void actionToToolButton(QAction *&sourceAction); - bool verifyPlugin(MOBase::IPlugin *plugin); - void registerPluginTool(MOBase::IPluginTool *tool); - void registerModPage(MOBase::IPluginModPage *modPage); - bool registerPlugin(QObject *pluginObj, const QString &fileName); - - void updateToolBar(); - void activateSelectedProfile(); - - void setExecutableIndex(int index); - - bool testForSteam(); - void startSteam(); - - HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); - void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); - - void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); - void refreshDirectoryStructure(); - bool refreshProfiles(bool selectProfile = true); - void refreshExecutablesList(); - void installMod(); - MOBase::IModInterface *installMod(const QString &fileName); - MOBase::IModInterface *getMod(const QString &name); - MOBase::IModInterface *createMod(MOBase::GuessedValue &name); - bool removeMod(MOBase::IModInterface *mod); - - QList findFileInfos(const QString &path, const std::function &filter) const; - - bool modifyExecutablesDialog(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); - void displayModInformation(int row, int tab = 0); - void testExtractBSA(int modIndex); - - void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - - void renameModInList(QFile &modList, const QString &oldName, const QString &newName); - - void refreshFilters(); - - /** - * Sets category selections from menu; for multiple mods, this will only apply - * the changes made in the menu (which is the delta between the current menu selection and the reference mod) - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - * @param referenceRow row of the reference mod - */ - void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); - - /** - * Sets category selections from menu; for multiple mods, this will completely - * replace the current set of categories on each selected with those selected in the menu - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - */ - void replaceCategoriesFromMenu(QMenu *menu, int modRow); - - bool populateMenuCategories(QMenu *menu, int targetID); - - void updateDownloadListDelegate(); - - // remove invalid category-references from mods - void fixCategories(); - - void storeSettings(); - - bool queryLogin(QString &username, QString &password); - - void createHelpWidget(); - - bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); - - int checkForProblems(); - - int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); - QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID); - void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - - void setCategoryListVisible(bool visible); - - SaveGameGamebryo *getSaveGame(const QString &name); - SaveGameGamebryo *getSaveGame(QListWidgetItem *item); - - void displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos); - - HANDLE nextChildProcess(); - - bool errorReported(QString &logFile); - - QIcon iconForExecutable(const QString &filePath); - - void updateESPLock(bool locked); - - static void setupNetworkProxy(bool activate); - void activateProxy(bool activate); - void installTranslator(const QString &name); - void setBrowserGeometry(const QByteArray &geometry); - - bool createBackup(const QString &filePath, const QDateTime &time); - QString queryRestore(const QString &filePath); - - QMenu *modListContextMenu(); - - std::set enabledArchives(); - - void scheduleUpdateButton(); - - void updateModActiveState(int index, bool active); - -private: - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; - - static const char *PATTERN_BACKUP_GLOB; - static const char *PATTERN_BACKUP_REGEX; - static const char *PATTERN_BACKUP_DATE; - -private: - - Ui::MainWindow *ui; - - MOBase::TutorialControl m_Tutorial; - - QString m_ExeName; - - int m_OldProfileIndex; - - QThread m_RefresherThread; - DirectoryRefresher m_DirectoryRefresher; - MOShared::DirectoryEntry *m_DirectoryStructure; - std::vector m_ModNameList; // the mod-list to go with the directory structure - QProgressBar *m_RefreshProgress; - bool m_Refreshing; - - ModList m_ModList; - QAbstractItemModel *m_ModListGroupingProxy; - ModListSortProxy *m_ModListSortProxy; - - PluginList m_PluginList; - PluginListSortProxy *m_PluginListSortProxy; - - ExecutablesList m_ExecutablesList; - int m_OldExecutableIndex; - - QString m_GamePath; - - int m_ContextRow; - QPersistentModelIndex m_ContextIdx; - QTreeWidgetItem *m_ContextItem; - QAction *m_ContextAction; - - //int m_SelectedSaveGame; - - Settings m_Settings; - - DownloadManager m_DownloadManager; - InstallationManager m_InstallationManager; - - SelfUpdater m_Updater; - - CategoryFactory &m_CategoryFactory; - - Profile *m_CurrentProfile; - - int m_ModsToUpdate; - - QStringList m_PendingDownloads; - QList > m_PostLoginTasks; - bool m_AskForNexusPW; - bool m_LoginAttempted; - - QStringList m_DefaultArchives; - QStringList m_ActiveArchives; - bool m_DirectoryUpdate; - bool m_ArchivesInit; - QTimer m_CheckBSATimer; - QTimer m_SaveMetaTimer; - QTimer m_UpdateProblemsTimer; - - QTime m_StartTime; - SaveGameInfoWidget *m_CurrentSaveView; - - MOBase::IGameInfo *m_GameInfo; - - std::vector m_DiagnosisPlugins; - std::vector m_ModPages; - std::vector m_UnloadedPlugins; - - QFile m_PluginsCheck; - - SignalAboutToRunApplication m_AboutToRun; - SignalModInstalled m_ModInstalled; - - QString m_CurrentLanguage; - std::vector m_Translators; - - PreviewGenerator m_PreviewGenerator; - BrowserDialog m_IntegratedBrowser; - - QFileSystemWatcher m_SavesWatcher; - - std::vector m_RemoveWidget; - - QByteArray m_ArchiveListHash; - - bool m_DidUpdateMasterList; - -private slots: - - void showMessage(const QString &message); - void showError(const QString &message); - - // main window actions - void helpTriggered(); - void issueTriggered(); - void wikiTriggered(); - void tutorialTriggered(); - void extractBSATriggered(); - - // modlist context menu - void installMod_clicked(); - void restoreBackup_clicked(); - void renameMod_clicked(); - void removeMod_clicked(); - void reinstallMod_clicked(); - void endorse_clicked(); - void dontendorse_clicked(); - void unendorse_clicked(); - void ignoreMissingData_clicked(); - void visitOnNexus_clicked(); - void openExplorer_clicked(); - void information_clicked(); - // savegame context menu - void deleteSavegame_clicked(); - void fixMods_clicked(); - // data-tree context menu - void writeDataToFile(); - void openDataFile(); - void addAsExecutable(); - void previewDataFile(); - void hideFile(); - void unhideFile(); - - void linkToolbar(); - void linkDesktop(); - void linkMenu(); - - void languageChange(const QString &newLanguage); - void modStatusChanged(unsigned int index); - void saveSelectionChanged(QListWidgetItem *newItem); - - bool saveCurrentLists(); - - void windowTutorialFinished(const QString &windowName); - - BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); - - void syncOverwrite(); - - void createModFromOverwrite(); - - void removeOrigin(const QString &name); - - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - - // nexus related - void checkModsForUpdates(); - void nexusLinkActivated(const QString &link); - - void linkClicked(const QString &url); - - bool nexusLogin(); - - void loginSuccessful(bool necessary); - void loginSuccessfulUpdate(bool necessary); - void loginFailed(const QString &message); - void loginFailedUpdate(const QString &message); - - void downloadRequestedNXM(const QString &url); - void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName); - - void installDownload(int index); - void updateAvailable(); - - void motdReceived(const QString &motd); - void notEndorsedYet(); - - void originModified(int originID); - - void addRemoveCategories_MenuHandler(); - void replaceCategories_MenuHandler(); - - void savePrimaryCategory(); - void addPrimaryCategoryCandidates(); - - void modDetailsUpdated(bool success); - void modlistChanged(int row); - - void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); - void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - - void editCategories(); - void deselectFilters(); - - void displayModInformation(const QString &modName, int tab); - void modOpenNext(); - void modOpenPrev(); - - void modRenamed(const QString &oldName, const QString &newName); - void modRemoved(const QString &fileName); - - void hideSaveGameInfo(); - - void hookUpWindowTutorials(); - - void resumeDownload(int downloadIndex); - void endorseMod(ModInfo::Ptr mod); - void cancelModListEditor(); - - void lockESPIndex(); - void unlockESPIndex(); - - void enableVisibleMods(); - void disableVisibleMods(); - void exportModListCSV(); - - void startExeAction(); - - void checkBSAList(); - - void updateProblemsButton(); - - void saveModMetas(); - - void updateStyle(const QString &style); - - void modlistChanged(const QModelIndex &index, int role); - void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - - void savePluginList(); - - void modFilterActive(bool active); - void espFilterChanged(const QString &filter); - void downloadFilterChanged(const QString &filter); - - void expandModList(const QModelIndex &index); - - /** - * @brief resize columns in mod list and plugin list to content - */ - void resizeLists(bool modListCustom, bool pluginListCustom); - - /** - * @brief allow columns in mod list and plugin list to be resized - */ - void allowListResize(); - - void downloadSpeed(const QString &serverName, int bytesPerSecond); - - void toolBar_customContextMenuRequested(const QPoint &point); - void removeFromToolbar(); - void overwriteClosed(int); - - void changeVersioningScheme(); - void ignoreUpdate(); - void unignoreUpdate(); - - void refreshSavesIfOpen(); - void expandDataTreeItem(QTreeWidgetItem *item); - void about(); - void delayedRemove(); - - void requestDownload(const QUrl &url, QNetworkReply *reply); - void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); - void modListSortIndicatorChanged(int column, Qt::SortOrder order); - -private slots: // ui slots - void profileRefresh(); - // actions - void on_actionAdd_Profile_triggered(); - void on_actionInstallMod_triggered(); - void on_actionModify_Executables_triggered(); - void on_actionNexus_triggered(); - void on_actionProblems_triggered(); - void on_actionSettings_triggered(); - void on_actionUpdate_triggered(); - void on_actionEndorseMO_triggered(); - - void on_bsaList_customContextMenuRequested(const QPoint &pos); - void bsaList_itemMoved(); - void on_btnRefreshData_clicked(); - void on_categoriesList_customContextMenuRequested(const QPoint &pos); - void on_conflictsCheckBox_toggled(bool checked); - void on_dataTree_customContextMenuRequested(const QPoint &pos); - void on_executablesListBox_currentIndexChanged(int index); - void on_modList_customContextMenuRequested(const QPoint &pos); - void on_modList_doubleClicked(const QModelIndex &index); - void on_profileBox_currentIndexChanged(int index); - void on_savegameList_customContextMenuRequested(const QPoint &pos); - void on_startButton_clicked(); - void on_tabWidget_currentChanged(int index); - - void on_espList_customContextMenuRequested(const QPoint &pos); - void on_displayCategoriesBtn_toggled(bool checked); - void on_groupCombo_currentIndexChanged(int index); - void on_categoriesList_itemSelectionChanged(); - void on_linkButton_pressed(); - void on_showHiddenBox_toggled(bool checked); - void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); - void on_bossButton_clicked(); - - void on_saveButton_clicked(); - void on_restoreButton_clicked(); - void on_restoreModsButton_clicked(); - void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); - void on_categoriesAndBtn_toggled(bool checked); - void on_categoriesOrBtn_toggled(bool checked); - void on_managedArchiveLabel_linkHovered(const QString &link); - void on_manageArchivesBox_toggled(bool checked); -}; - - - -#endif // MAINWINDOW_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "executableslist.h" +#include "modlist.h" +#include "pluginlist.h" +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "directoryrefresher.h" +#include +#include +#include +#include +#include "settings.h" +#include "downloadmanager.h" +#include "installationmanager.h" +#include "selfupdater.h" +#include "savegamegamebyro.h" +#include "modlistsortproxy.h" +#include "pluginlistsortproxy.h" +#include "tutorialcontrol.h" +#include "savegameinfowidgetgamebryo.h" +#include "previewgenerator.h" +#include "browserdialog.h" +#include +#include +#include + +namespace Ui { + class MainWindow; +} + +class QToolButton; +class ModListSortProxy; +class ModListGroupCategoriesProxy; + + +class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) + + friend class OrganizerProxy; + +private: + + struct SignalCombinerAnd + { + typedef bool result_type; + template + bool operator()(InputIterator first, InputIterator last) const + { + while (first != last) { + if (!(*first)) { + return false; + } + ++first; + } + return true; + } + }; + + typedef boost::signals2::signal SignalAboutToRunApplication; + typedef boost::signals2::signal SignalModInstalled; + +public: + explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0); + ~MainWindow(); + + void readSettings(); + + bool addProfile(); + void refreshBSAList(); + void refreshDataTree(); + void refreshSaveList(); + + void setExecutablesList(const ExecutablesList &executablesList); + + void setModListSorting(int index); + void setESPListSorting(int index); + + bool setCurrentProfile(int index); + bool setCurrentProfile(const QString &name); + + void createFirstProfile(); + +/* void spawnProgram(const QString &fileName, const QString &argumentsArg, + const QString &profileName, const QDir ¤tDirectory);*/ + + void loadPlugins(); + + virtual std::vector activeProblems() const; + virtual QString shortDescription(unsigned int key) const; + virtual QString fullDescription(unsigned int key) const; + virtual bool hasGuidedFix(unsigned int key) const; + virtual void startGuidedFix(unsigned int key) const; + + void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); + + bool saveArchiveList(); + + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog); + + HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); + + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + + void waitForProcessOrJob(HANDLE processHandle); +public slots: + + void refreshLists(); + + void displayColumnSelection(const QPoint &pos); + + void externalMessage(const QString &message); + void modorder_changed(); + void refresher_progress(int percent); + void directory_refreshed(); + + void toolPluginInvoke(); + void modPagePluginInvoke(); + +signals: + + /** + * @brief emitted after a mod has been installed + * @node this is currently only used for tutorials + */ + void modInstalled(); + + /** + * @brief emitted after the information dialog has been closed + */ + void modInfoDisplayed(); + + /** + * @brief emitted when the selected style changes + */ + void styleChanged(const QString &styleFile); + + + void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); + +protected: + + virtual void showEvent(QShowEvent *event); + virtual void closeEvent(QCloseEvent *event); + virtual bool eventFilter(QObject *obj, QEvent *event); + virtual void resizeEvent(QResizeEvent *event); + +private: + + void refreshESPList(); + void refreshModList(bool saveChanges = true); + + void actionToToolButton(QAction *&sourceAction); + bool verifyPlugin(MOBase::IPlugin *plugin); + void registerPluginTool(MOBase::IPluginTool *tool); + void registerModPage(MOBase::IPluginModPage *modPage); + bool registerPlugin(QObject *pluginObj, const QString &fileName); + + void updateToolBar(); + void activateSelectedProfile(); + + void setExecutableIndex(int index); + + bool testForSteam(); + void startSteam(); + + HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); + void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); + + void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); + void refreshDirectoryStructure(); + bool refreshProfiles(bool selectProfile = true); + void refreshExecutablesList(); + void installMod(); + MOBase::IModInterface *installMod(const QString &fileName); + MOBase::IModInterface *getMod(const QString &name); + MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + bool removeMod(MOBase::IModInterface *mod); + + QList findFileInfos(const QString &path, const std::function &filter) const; + + bool modifyExecutablesDialog(); + void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + void displayModInformation(int row, int tab = 0); + void testExtractBSA(int modIndex); + + void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); + + void renameModInList(QFile &modList, const QString &oldName, const QString &newName); + + void refreshFilters(); + + /** + * Sets category selections from menu; for multiple mods, this will only apply + * the changes made in the menu (which is the delta between the current menu selection and the reference mod) + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + * @param referenceRow row of the reference mod + */ + void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); + + /** + * Sets category selections from menu; for multiple mods, this will completely + * replace the current set of categories on each selected with those selected in the menu + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + */ + void replaceCategoriesFromMenu(QMenu *menu, int modRow); + + bool populateMenuCategories(QMenu *menu, int targetID); + + void updateDownloadListDelegate(); + + // remove invalid category-references from mods + void fixCategories(); + + void storeSettings(); + + bool queryLogin(QString &username, QString &password); + + void createHelpWidget(); + + bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); + + int checkForProblems(); + + int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); + QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID); + void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); + + void setCategoryListVisible(bool visible); + + SaveGameGamebryo *getSaveGame(const QString &name); + SaveGameGamebryo *getSaveGame(QListWidgetItem *item); + + void displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos); + + HANDLE nextChildProcess(); + + bool errorReported(QString &logFile); + + QIcon iconForExecutable(const QString &filePath); + + void updateESPLock(bool locked); + + static void setupNetworkProxy(bool activate); + void activateProxy(bool activate); + void installTranslator(const QString &name); + void setBrowserGeometry(const QByteArray &geometry); + + bool createBackup(const QString &filePath, const QDateTime &time); + QString queryRestore(const QString &filePath); + + QMenu *modListContextMenu(); + + std::set enabledArchives(); + + void scheduleUpdateButton(); + + void updateModActiveState(int index, bool active); + +private: + + static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; + static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; + + static const char *PATTERN_BACKUP_GLOB; + static const char *PATTERN_BACKUP_REGEX; + static const char *PATTERN_BACKUP_DATE; + +private: + + Ui::MainWindow *ui; + + MOBase::TutorialControl m_Tutorial; + + QString m_ExeName; + + int m_OldProfileIndex; + + QThread m_RefresherThread; + DirectoryRefresher m_DirectoryRefresher; + MOShared::DirectoryEntry *m_DirectoryStructure; + std::vector m_ModNameList; // the mod-list to go with the directory structure + QProgressBar *m_RefreshProgress; + bool m_Refreshing; + + ModList m_ModList; + QAbstractItemModel *m_ModListGroupingProxy; + ModListSortProxy *m_ModListSortProxy; + + PluginList m_PluginList; + PluginListSortProxy *m_PluginListSortProxy; + + ExecutablesList m_ExecutablesList; + int m_OldExecutableIndex; + + QString m_GamePath; + + int m_ContextRow; + QPersistentModelIndex m_ContextIdx; + QTreeWidgetItem *m_ContextItem; + QAction *m_ContextAction; + + //int m_SelectedSaveGame; + + Settings m_Settings; + + DownloadManager m_DownloadManager; + InstallationManager m_InstallationManager; + + SelfUpdater m_Updater; + + CategoryFactory &m_CategoryFactory; + + Profile *m_CurrentProfile; + + int m_ModsToUpdate; + + QStringList m_PendingDownloads; + QList > m_PostLoginTasks; + bool m_AskForNexusPW; + bool m_LoginAttempted; + + QStringList m_DefaultArchives; + QStringList m_ActiveArchives; + bool m_DirectoryUpdate; + bool m_ArchivesInit; + QTimer m_CheckBSATimer; + QTimer m_SaveMetaTimer; + QTimer m_UpdateProblemsTimer; + + QTime m_StartTime; + SaveGameInfoWidget *m_CurrentSaveView; + + MOBase::IGameInfo *m_GameInfo; + + std::vector m_DiagnosisPlugins; + std::vector m_ModPages; + std::vector m_UnloadedPlugins; + + QFile m_PluginsCheck; + + SignalAboutToRunApplication m_AboutToRun; + SignalModInstalled m_ModInstalled; + + QString m_CurrentLanguage; + std::vector m_Translators; + + PreviewGenerator m_PreviewGenerator; + BrowserDialog m_IntegratedBrowser; + + QFileSystemWatcher m_SavesWatcher; + + std::vector m_RemoveWidget; + + QByteArray m_ArchiveListHash; + + bool m_DidUpdateMasterList; + +private slots: + + void showMessage(const QString &message); + void showError(const QString &message); + + // main window actions + void helpTriggered(); + void issueTriggered(); + void wikiTriggered(); + void tutorialTriggered(); + void extractBSATriggered(); + + // modlist context menu + void installMod_clicked(); + void restoreBackup_clicked(); + void renameMod_clicked(); + void removeMod_clicked(); + void reinstallMod_clicked(); + void endorse_clicked(); + void dontendorse_clicked(); + void unendorse_clicked(); + void ignoreMissingData_clicked(); + void visitOnNexus_clicked(); + void openExplorer_clicked(); + void information_clicked(); + // savegame context menu + void deleteSavegame_clicked(); + void fixMods_clicked(); + // data-tree context menu + void writeDataToFile(); + void openDataFile(); + void addAsExecutable(); + void previewDataFile(); + void hideFile(); + void unhideFile(); + + void linkToolbar(); + void linkDesktop(); + void linkMenu(); + + void languageChange(const QString &newLanguage); + void modStatusChanged(unsigned int index); + void saveSelectionChanged(QListWidgetItem *newItem); + + bool saveCurrentLists(); + + void windowTutorialFinished(const QString &windowName); + + BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); + + void syncOverwrite(); + + void createModFromOverwrite(); + + void removeOrigin(const QString &name); + + void procError(QProcess::ProcessError error); + void procFinished(int exitCode, QProcess::ExitStatus exitStatus); + + // nexus related + void checkModsForUpdates(); + void nexusLinkActivated(const QString &link); + + void linkClicked(const QString &url); + + bool nexusLogin(); + + void loginSuccessful(bool necessary); + void loginSuccessfulUpdate(bool necessary); + void loginFailed(const QString &message); + void loginFailedUpdate(const QString &message); + + void downloadRequestedNXM(const QString &url); + void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName); + + void installDownload(int index); + void updateAvailable(); + + void motdReceived(const QString &motd); + void notEndorsedYet(); + + void originModified(int originID); + + void addRemoveCategories_MenuHandler(); + void replaceCategories_MenuHandler(); + + void savePrimaryCategory(); + void addPrimaryCategoryCandidates(); + + void modDetailsUpdated(bool success); + void modlistChanged(int row); + + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); + void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); + + void editCategories(); + void deselectFilters(); + + void displayModInformation(const QString &modName, int tab); + void modOpenNext(); + void modOpenPrev(); + + void modRenamed(const QString &oldName, const QString &newName); + void modRemoved(const QString &fileName); + + void hideSaveGameInfo(); + + void hookUpWindowTutorials(); + + void resumeDownload(int downloadIndex); + void endorseMod(ModInfo::Ptr mod); + void cancelModListEditor(); + + void lockESPIndex(); + void unlockESPIndex(); + + void enableVisibleMods(); + void disableVisibleMods(); + void exportModListCSV(); + + void startExeAction(); + + void checkBSAList(); + + void updateProblemsButton(); + + void saveModMetas(); + + void updateStyle(const QString &style); + + void modlistChanged(const QModelIndex &index, int role); + void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); + + void savePluginList(); + + void modFilterActive(bool active); + void espFilterChanged(const QString &filter); + void downloadFilterChanged(const QString &filter); + + void expandModList(const QModelIndex &index); + + /** + * @brief resize columns in mod list and plugin list to content + */ + void resizeLists(bool modListCustom, bool pluginListCustom); + + /** + * @brief allow columns in mod list and plugin list to be resized + */ + void allowListResize(); + + void downloadSpeed(const QString &serverName, int bytesPerSecond); + + void toolBar_customContextMenuRequested(const QPoint &point); + void removeFromToolbar(); + void overwriteClosed(int); + + void changeVersioningScheme(); + void ignoreUpdate(); + void unignoreUpdate(); + + void refreshSavesIfOpen(); + void expandDataTreeItem(QTreeWidgetItem *item); + void about(); + void delayedRemove(); + + void requestDownload(const QUrl &url, QNetworkReply *reply); + void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void modListSortIndicatorChanged(int column, Qt::SortOrder order); + +private slots: // ui slots + void profileRefresh(); + // actions + void on_actionAdd_Profile_triggered(); + void on_actionInstallMod_triggered(); + void on_actionModify_Executables_triggered(); + void on_actionNexus_triggered(); + void on_actionProblems_triggered(); + void on_actionSettings_triggered(); + void on_actionUpdate_triggered(); + void on_actionEndorseMO_triggered(); + + void on_bsaList_customContextMenuRequested(const QPoint &pos); + void bsaList_itemMoved(); + void on_btnRefreshData_clicked(); + void on_categoriesList_customContextMenuRequested(const QPoint &pos); + void on_conflictsCheckBox_toggled(bool checked); + void on_dataTree_customContextMenuRequested(const QPoint &pos); + void on_executablesListBox_currentIndexChanged(int index); + void on_modList_customContextMenuRequested(const QPoint &pos); + void on_modList_doubleClicked(const QModelIndex &index); + void on_profileBox_currentIndexChanged(int index); + void on_savegameList_customContextMenuRequested(const QPoint &pos); + void on_startButton_clicked(); + void on_tabWidget_currentChanged(int index); + + void on_espList_customContextMenuRequested(const QPoint &pos); + void on_displayCategoriesBtn_toggled(bool checked); + void on_groupCombo_currentIndexChanged(int index); + void on_categoriesList_itemSelectionChanged(); + void on_linkButton_pressed(); + void on_showHiddenBox_toggled(bool checked); + void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); + + void on_saveButton_clicked(); + void on_restoreButton_clicked(); + void on_restoreModsButton_clicked(); + void on_saveModsButton_clicked(); + void on_actionCopy_Log_to_Clipboard_triggered(); + void on_categoriesAndBtn_toggled(bool checked); + void on_categoriesOrBtn_toggled(bool checked); + void on_managedArchiveLabel_linkHovered(const QString &link); + void on_manageArchivesBox_toggled(bool checked); +}; + + + +#endif // MAINWINDOW_H diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 2aa07ccf..863d7628 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -1,92 +1,92 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "messagedialog.h" -#include "ui_messagedialog.h" -#include -#include -#include - -MessageDialog::MessageDialog(const QString &text, QWidget *reference) : - QDialog(reference), - ui(new Ui::MessageDialog) -{ - ui->setupUi(this); - - // very crude way to ensure no single word in the test is wider than the message window. ellide in the center if necessary - QFontMetrics metrics(ui->message->font()); - QString restrictedText; - QStringList lines = text.split("\n"); - foreach (const QString &line, lines) { - QString newLine; - QStringList words = line.split(" "); - foreach (const QString &word, words) { - if (word.length() > 10) { - newLine += "" + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + ""; - } else { - newLine += word; - } - newLine += " "; - } - restrictedText += newLine + "\n"; - } - - ui->message->setText(restrictedText); - this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); - this->setFocusPolicy(Qt::NoFocus); - this->setAttribute(Qt::WA_ShowWithoutActivating); - QTimer::singleShot(1000 + (text.length() * 40), this, SLOT(hide())); - if (reference != NULL) { - QPoint position = reference->mapToGlobal(QPoint(reference->width() / 2, reference->height())); - position.rx() -= this->width() / 2; - position.ry() -= this->height() + 5; - move(position); - } -} - - -MessageDialog::~MessageDialog() -{ - delete ui; -} - - -void MessageDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != NULL) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height())); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() + 5; - move(position); - } -} - - -void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) -{ - qDebug("%s", qPrintable(text)); - if (reference != NULL) { - if (bringToFront || (qApp->activeWindow() != NULL)) { - MessageDialog *dialog = new MessageDialog(text, reference); - dialog->show(); - reference->activateWindow(); - } - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "messagedialog.h" +#include "ui_messagedialog.h" +#include +#include +#include + +MessageDialog::MessageDialog(const QString &text, QWidget *reference) : + QDialog(reference), + ui(new Ui::MessageDialog) +{ + ui->setupUi(this); + + // very crude way to ensure no single word in the test is wider than the message window. ellide in the center if necessary + QFontMetrics metrics(ui->message->font()); + QString restrictedText; + QStringList lines = text.split("\n"); + foreach (const QString &line, lines) { + QString newLine; + QStringList words = line.split(" "); + foreach (const QString &word, words) { + if (word.length() > 10) { + newLine += "" + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + ""; + } else { + newLine += word; + } + newLine += " "; + } + restrictedText += newLine + "\n"; + } + + ui->message->setText(restrictedText); + this->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); + this->setFocusPolicy(Qt::NoFocus); + this->setAttribute(Qt::WA_ShowWithoutActivating); + QTimer::singleShot(1000 + (text.length() * 40), this, SLOT(hide())); + if (reference != NULL) { + QPoint position = reference->mapToGlobal(QPoint(reference->width() / 2, reference->height())); + position.rx() -= this->width() / 2; + position.ry() -= this->height() + 5; + move(position); + } +} + + +MessageDialog::~MessageDialog() +{ + delete ui; +} + + +void MessageDialog::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != NULL) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height())); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() + 5; + move(position); + } +} + + +void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) +{ + qDebug("%s", qPrintable(text)); + if (reference != NULL) { + if (bringToFront || (qApp->activeWindow() != NULL)) { + MessageDialog *dialog = new MessageDialog(text, reference); + dialog->show(); + reference->activateWindow(); + } + } +} diff --git a/src/messagedialog.h b/src/messagedialog.h index 0dbc0e6e..7e9b9c3b 100644 --- a/src/messagedialog.h +++ b/src/messagedialog.h @@ -1,67 +1,67 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MESSAGEDIALOG_H -#define MESSAGEDIALOG_H - -#include - -namespace Ui { - class MessageDialog; -} - -/** - * borderless dialog used to display short messages that will automatically - * vanish after a moment - **/ -class MessageDialog : public QDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param text the message to display - * @param reference parent widget. This will also be used to position the message at the bottom center of the dialog - **/ - - explicit MessageDialog(const QString &text, QWidget *reference); - - ~MessageDialog(); - - /** - * factory function for message dialogs. This can be used as a fire-and-forget. The message - * will automatically positioned to the reference dialog and get a reasonable view time - * - * @param text the text to display. The length of this text is used to determine how long the dialog is to be shown - * @param reference the reference widget on top of which the message should be displayed - * @param true if the message should bring MO to front to ensure this message is visible - **/ - static void showMessage(const QString &text, QWidget *reference, bool bringToFront = true); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private: - Ui::MessageDialog *ui; -}; - -#endif // MESSAGEDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MESSAGEDIALOG_H +#define MESSAGEDIALOG_H + +#include + +namespace Ui { + class MessageDialog; +} + +/** + * borderless dialog used to display short messages that will automatically + * vanish after a moment + **/ +class MessageDialog : public QDialog +{ + Q_OBJECT + +public: + /** + * @brief constructor + * + * @param text the message to display + * @param reference parent widget. This will also be used to position the message at the bottom center of the dialog + **/ + + explicit MessageDialog(const QString &text, QWidget *reference); + + ~MessageDialog(); + + /** + * factory function for message dialogs. This can be used as a fire-and-forget. The message + * will automatically positioned to the reference dialog and get a reasonable view time + * + * @param text the text to display. The length of this text is used to determine how long the dialog is to be shown + * @param reference the reference widget on top of which the message should be displayed + * @param true if the message should bring MO to front to ensure this message is visible + **/ + static void showMessage(const QString &text, QWidget *reference, bool bringToFront = true); + +protected: + + virtual void resizeEvent(QResizeEvent *event); + +private: + Ui::MessageDialog *ui; +}; + +#endif // MESSAGEDIALOG_H diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 12ff713b..df0fdd09 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -1,148 +1,148 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "moapplication.h" -#include "report.h" -#include -#include -#include -#include -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) -#include -#include -#endif -#include -#include -#include -#include - - -#include - - -class ProxyStyle : public QProxyStyle { -public: - ProxyStyle(QStyle *baseStyle = 0) - : QProxyStyle(baseStyle) - { - } - - void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { - if(element == QStyle::PE_IndicatorItemViewItemDrop) { - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor col(option->palette.foreground().color()); - QPen pen(col); - pen.setWidth(2); - col.setAlpha(50); - QBrush brush(col); - - painter->setPen(pen); - painter->setBrush(brush); - if(option->rect.height() == 0) { - QPoint tri[3] = { - option->rect.topLeft(), - option->rect.topLeft() + QPoint(-5, 5), - option->rect.topLeft() + QPoint(-5, -5) - }; - painter->drawPolygon(tri, 3); - painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), option->rect.topRight()); - } else { - painter->drawRoundedRect(option->rect, 5, 5); - } - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } - } -}; - - -MOApplication::MOApplication(int argc, char **argv) - : QApplication(argc, argv) -{ - connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); - m_DefaultStyle = style()->objectName(); - setStyle(new ProxyStyle(style())); -} - - -bool MOApplication::setStyleFile(const QString &styleName) -{ - // remove all files from watch - QStringList currentWatch = m_StyleWatcher.files(); - if (currentWatch.count() != 0) { - m_StyleWatcher.removePaths(currentWatch); - } - // set new stylesheet or clear it - if (styleName.length() != 0) { - QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName; - if (QFile::exists(styleSheetName)) { - m_StyleWatcher.addPath(styleSheetName); - updateStyle(styleSheetName); - } else { - updateStyle(styleName); - } - } else { - setStyleSheet(""); - } - return true; -} - - -bool MOApplication::notify(QObject *receiver, QEvent *event) -{ - try { - return QApplication::notify(receiver, event); - } catch (const std::exception &e) { - qCritical("uncaught exception in handler (object %s, eventtype %d): %s", - receiver->objectName().toUtf8().constData(), event->type(), e.what()); - reportError(tr("an error occured: %1").arg(e.what())); - return false; - } catch (...) { - qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", - receiver->objectName().toUtf8().constData(), event->type()); - reportError(tr("an error occured")); - return false; - } -} - - -void MOApplication::updateStyle(const QString &fileName) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - if (fileName == "Fusion") { - setStyle(QStyleFactory::create("fusion")); - setStyleSheet(""); -#else - if (fileName == "Plastique") { - setStyle(new ProxyStyle(new QPlastiqueStyle)); - setStyleSheet(""); - } else if (fileName == "Cleanlooks") { - setStyle(new ProxyStyle(new QCleanlooksStyle)); - setStyleSheet(""); -#endif - } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); - if (QFile::exists(fileName)) { - setStyleSheet(QString("file:///%1").arg(fileName)); - } else { - qWarning("invalid stylesheet: %s", qPrintable(fileName)); - } - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "moapplication.h" +#include "report.h" +#include +#include +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) +#include +#include +#endif +#include +#include +#include +#include + + +#include + + +class ProxyStyle : public QProxyStyle { +public: + ProxyStyle(QStyle *baseStyle = 0) + : QProxyStyle(baseStyle) + { + } + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { + if(element == QStyle::PE_IndicatorItemViewItemDrop) { + painter->setRenderHint(QPainter::Antialiasing, true); + + QColor col(option->palette.foreground().color()); + QPen pen(col); + pen.setWidth(2); + col.setAlpha(50); + QBrush brush(col); + + painter->setPen(pen); + painter->setBrush(brush); + if(option->rect.height() == 0) { + QPoint tri[3] = { + option->rect.topLeft(), + option->rect.topLeft() + QPoint(-5, 5), + option->rect.topLeft() + QPoint(-5, -5) + }; + painter->drawPolygon(tri, 3); + painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), option->rect.topRight()); + } else { + painter->drawRoundedRect(option->rect, 5, 5); + } + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + } +}; + + +MOApplication::MOApplication(int argc, char **argv) + : QApplication(argc, argv) +{ + connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); + m_DefaultStyle = style()->objectName(); + setStyle(new ProxyStyle(style())); +} + + +bool MOApplication::setStyleFile(const QString &styleName) +{ + // remove all files from watch + QStringList currentWatch = m_StyleWatcher.files(); + if (currentWatch.count() != 0) { + m_StyleWatcher.removePaths(currentWatch); + } + // set new stylesheet or clear it + if (styleName.length() != 0) { + QString styleSheetName = applicationDirPath() + "/" + MOBase::ToQString(AppConfig::stylesheetsPath()) + "/" + styleName; + if (QFile::exists(styleSheetName)) { + m_StyleWatcher.addPath(styleSheetName); + updateStyle(styleSheetName); + } else { + updateStyle(styleName); + } + } else { + setStyleSheet(""); + } + return true; +} + + +bool MOApplication::notify(QObject *receiver, QEvent *event) +{ + try { + return QApplication::notify(receiver, event); + } catch (const std::exception &e) { + qCritical("uncaught exception in handler (object %s, eventtype %d): %s", + receiver->objectName().toUtf8().constData(), event->type(), e.what()); + reportError(tr("an error occured: %1").arg(e.what())); + return false; + } catch (...) { + qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", + receiver->objectName().toUtf8().constData(), event->type()); + reportError(tr("an error occured")); + return false; + } +} + + +void MOApplication::updateStyle(const QString &fileName) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + if (fileName == "Fusion") { + setStyle(QStyleFactory::create("fusion")); + setStyleSheet(""); +#else + if (fileName == "Plastique") { + setStyle(new ProxyStyle(new QPlastiqueStyle)); + setStyleSheet(""); + } else if (fileName == "Cleanlooks") { + setStyle(new ProxyStyle(new QCleanlooksStyle)); + setStyleSheet(""); +#endif + } else { + setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); + if (QFile::exists(fileName)) { + setStyleSheet(QString("file:///%1").arg(fileName)); + } else { + qWarning("invalid stylesheet: %s", qPrintable(fileName)); + } + } +} diff --git a/src/moapplication.h b/src/moapplication.h index 645b9144..cf6bc625 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -1,51 +1,51 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MOAPPLICATION_H -#define MOAPPLICATION_H - -#include -#include - - -class MOApplication : public QApplication { -Q_OBJECT -public: - - MOApplication(int argc, char **argv); - - virtual bool notify (QObject *receiver, QEvent *event); - -public slots: - - bool setStyleFile(const QString &style); - -private slots: - - void updateStyle(const QString &fileName); - -private: - - QFileSystemWatcher m_StyleWatcher; - QString m_DefaultStyle; - -}; - - -#endif // MOAPPLICATION_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MOAPPLICATION_H +#define MOAPPLICATION_H + +#include +#include + + +class MOApplication : public QApplication { +Q_OBJECT +public: + + MOApplication(int argc, char **argv); + + virtual bool notify (QObject *receiver, QEvent *event); + +public slots: + + bool setStyleFile(const QString &style); + +private slots: + + void updateStyle(const QString &fileName); + +private: + + QFileSystemWatcher m_StyleWatcher; + QString m_DefaultStyle; + +}; + + +#endif // MOAPPLICATION_H diff --git a/src/modeltest.cpp b/src/modeltest.cpp index b2e495b6..90061261 100644 --- a/src/modeltest.cpp +++ b/src/modeltest.cpp @@ -1,583 +1,583 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include - -#include "modeltest.h" - -#include - -#undef QVERIFY -#define QVERIFY Q_ASSERT -#undef QCOMPARE -#define QCOMPARE(x, y) Q_ASSERT(x == y) - -Q_DECLARE_METATYPE ( QModelIndex ) - -/*! - Connect to all of the models signals. Whenever anything happens recheck everything. -*/ -ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) -{ - if (!model) - qFatal("%s: model must not be null", Q_FUNC_INFO); - - connect ( model, SIGNAL ( columnsAboutToBeInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( columnsAboutToBeRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( columnsInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( columnsRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( dataChanged ( const QModelIndex &, const QModelIndex & ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( headerDataChanged ( Qt::Orientation, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( layoutAboutToBeChanged () ), this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( layoutChanged () ), this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( modelReset () ), this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( runAllTests() ) ); - - // Special checks for inserting/removing - connect ( model, SIGNAL ( layoutAboutToBeChanged() ), - this, SLOT ( layoutAboutToBeChanged() ) ); - connect ( model, SIGNAL ( layoutChanged() ), - this, SLOT ( layoutChanged() ) ); - - connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ) ); - connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ) ); - connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), - this, SLOT ( rowsInserted ( const QModelIndex &, int, int ) ) ); - connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), - this, SLOT ( rowsRemoved ( const QModelIndex &, int, int ) ) ); - - runAllTests(); -} - -void ModelTest::runAllTests() -{ - if ( fetchingMore ) - return; - nonDestructiveBasicTest(); - rowCount(); - columnCount(); - hasIndex(); - index(); - parent(); - data(); -} - -/*! - nonDestructiveBasicTest tries to call a number of the basic functions (not all) - to make sure the model doesn't outright segfault, testing the functions that makes sense. -*/ -void ModelTest::nonDestructiveBasicTest() -{ - QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() ); - model->canFetchMore ( QModelIndex() ); - QVERIFY( model->columnCount ( QModelIndex() ) >= 0 ); - QVERIFY( model->data ( QModelIndex() ) == QVariant() ); - fetchingMore = true; - model->fetchMore ( QModelIndex() ); - fetchingMore = false; - Qt::ItemFlags flags = model->flags ( QModelIndex() ); - QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 ); - model->hasChildren ( QModelIndex() ); - model->hasIndex ( 0, 0 ); - model->headerData ( 0, Qt::Horizontal ); - model->index ( 0, 0 ); - model->itemData ( QModelIndex() ); - QVariant cache; - model->match ( QModelIndex(), -1, cache ); - model->mimeTypes(); - QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); - QVERIFY( model->rowCount() >= 0 ); - QVariant variant; - model->setData ( QModelIndex(), variant, -1 ); - model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); - model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); - QMap roles; - model->sibling ( 0, 0, QModelIndex() ); - model->span ( QModelIndex() ); - model->supportedDropActions(); -} - -/*! - Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() - - Models that are dynamically populated are not as fully tested here. - */ -void ModelTest::rowCount() -{ -// qDebug() << "rc"; - // check top row - - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - if (!model->hasChildren ( topIndex ) && model->rowCount(topIndex) > 0) { - qDebug() << "it's gonna blow: " << topIndex; - } - - int rows = model->rowCount ( topIndex ); - QVERIFY( rows >= 0 ); - if ( rows > 0 ) { - QVERIFY( model->hasChildren ( topIndex ) ); - } - - QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); - if ( secondLevelIndex.isValid() ) { // not the top level - // check a row count where parent is valid - rows = model->rowCount ( secondLevelIndex ); - QVERIFY( rows >= 0 ); - if ( rows > 0 ) - QVERIFY( model->hasChildren ( secondLevelIndex ) ); - } - - // The models rowCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() - */ -void ModelTest::columnCount() -{ - // check top row - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - QVERIFY( model->columnCount ( topIndex ) >= 0 ); - - // check a column count where parent is valid - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - if ( childIndex.isValid() ) - QVERIFY( model->columnCount ( childIndex ) >= 0 ); - - // columnCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::hasIndex() - */ -void ModelTest::hasIndex() -{ -// qDebug() << "hi"; - // Make sure that invalid values returns an invalid index - QVERIFY( !model->hasIndex ( -2, -2 ) ); - QVERIFY( !model->hasIndex ( -2, 0 ) ); - QVERIFY( !model->hasIndex ( 0, -2 ) ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - // check out of bounds - QVERIFY( !model->hasIndex ( rows, columns ) ); - QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); - - if ( rows > 0 ) - QVERIFY( model->hasIndex ( 0, 0 ) ); - - // hasIndex() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::index() - */ -void ModelTest::index() -{ -// qDebug() << "i"; - // Make sure that invalid values returns an invalid index -/* QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); - QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); - QVERIFY( model->index ( 0, -2 ) == QModelIndex() );*/ - QVERIFY( !model->index ( -2, -2 ).isValid() ); - QVERIFY( !model->index ( -2, 0 ).isValid() ); - QVERIFY( !model->index ( 0, -2 ).isValid() ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - if ( rows == 0 ) - return; - - // Catch off by one errors - //QVERIFY( model->index ( rows, columns ) == QModelIndex() ); - QVERIFY( !model->index ( rows, columns ).isValid() ); - QVERIFY( model->index ( 0, 0 ).isValid() ); - - // Make sure that the same index is *always* returned - QModelIndex a = model->index ( 0, 0 ); - QModelIndex b = model->index ( 0, 0 ); - QVERIFY( a == b ); - - // index() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::parent() - */ -void ModelTest::parent() -{ -// qDebug() << "p"; - // Make sure the model wont crash and will return an invalid QModelIndex - // when asked for the parent of an invalid index. - QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); - - if ( model->rowCount() == 0 ) - return; - - // Column 0 | Column 1 | - // QModelIndex() | | - // \- topIndex | topIndex1 | - // \- childIndex | childIndex1 | - - // Common error test #1, make sure that a top level index has a parent - // that is a invalid QModelIndex. - QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); - QVERIFY( model->parent ( topIndex ) == QModelIndex() ); - - // Common error test #2, make sure that a second level index has a parent - // that is the first level index. - if ( model->rowCount ( topIndex ) > 0 ) { - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - QVERIFY( model->parent ( childIndex ) == topIndex ); - } - - // Common error test #3, the second column should NOT have the same children - // as the first column in a row. - // Usually the second column shouldn't have children. - QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); - if ( model->rowCount ( topIndex1 ) > 0 ) { - QModelIndex childIndex = model->index ( 0, 0, topIndex ); - QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); - QVERIFY( childIndex != childIndex1 ); - } - - // Full test, walk n levels deep through the model making sure that all - // parent's children correctly specify their parent. - checkChildren ( QModelIndex() ); -} - -/*! - Called from the parent() test. - - A model that returns an index of parent X should also return X when asking - for the parent of the index. - - This recursive function does pretty extensive testing on the whole model in an - effort to catch edge cases. - - This function assumes that rowCount(), columnCount() and index() already work. - If they have a bug it will point it out, but the above tests should have already - found the basic bugs because it is easier to figure out the problem in - those tests then this one. - */ -void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) -{ - // First just try walking back up the tree. - QModelIndex p = parent; - while ( p.isValid() ) - p = p.parent(); - - // For models that are dynamically populated - if ( model->canFetchMore ( parent ) ) { - fetchingMore = true; - model->fetchMore ( parent ); - fetchingMore = false; - } - - int rows = model->rowCount ( parent ); - int columns = model->columnCount ( parent ); - - if ( rows > 0 ) - QVERIFY( model->hasChildren ( parent ) ); - - // Some further testing against rows(), columns(), and hasChildren() - QVERIFY( rows >= 0 ); - QVERIFY( columns >= 0 ); - if ( rows > 0 ) - QVERIFY( model->hasChildren ( parent ) ); - - //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows - // << "columns:" << columns << "parent column:" << parent.column(); - - QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) ); - for ( int r = 0; r < rows; ++r ) { - if ( model->canFetchMore ( parent ) ) { - fetchingMore = true; - model->fetchMore ( parent ); - fetchingMore = false; - } - QVERIFY( !model->hasIndex ( r, columns + 1, parent ) ); - for ( int c = 0; c < columns; ++c ) { - QVERIFY( model->hasIndex ( r, c, parent ) ); - QModelIndex index = model->index ( r, c, parent ); - // rowCount() and columnCount() said that it existed... - QVERIFY( index.isValid() ); - - // index() should always return the same index when called twice in a row - QModelIndex modifiedIndex = model->index ( r, c, parent ); - QVERIFY( index == modifiedIndex ); - - // Make sure we get the same index if we request it twice in a row - QModelIndex a = model->index ( r, c, parent ); - QModelIndex b = model->index ( r, c, parent ); - QVERIFY( a == b ); - - // Some basic checking on the index that is returned - QVERIFY( index.model() == model ); - QCOMPARE( index.row(), r ); - QCOMPARE( index.column(), c ); - // While you can technically return a QVariant usually this is a sign - // of a bug in data(). Disable if this really is ok in your model. -// QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); - - // If the next test fails here is some somewhat useful debug you play with. - - if (model->parent(index) != parent) { - qDebug() << r << c << currentDepth << model->data(index).toString() - << model->data(parent).toString(); - qDebug() << index << parent << model->parent(index); -// And a view that you can even use to show the model. -// QTreeView view; -// view.setModel(model); -// view.show(); - } - - // Check that we can get back our real parent. - QCOMPARE( model->parent ( index ), parent ); - - // recursively go down the children - if ( model->hasChildren ( index ) && currentDepth < 10 ) { - //qDebug() << r << c << "has children" << model->rowCount(index); - checkChildren ( index, ++currentDepth ); - }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ - - // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index ( r, c, parent ); - QVERIFY( index == newerIndex ); - } - } -} - -/*! - Tests model's implementation of QAbstractItemModel::data() - */ -void ModelTest::data() -{ - // Invalid index should return an invalid qvariant - QVERIFY( !model->data ( QModelIndex() ).isValid() ); - - if ( model->rowCount() == 0 ) - return; - - // A valid index should have a valid QVariant data - QVERIFY( model->index ( 0, 0 ).isValid() ); - - // shouldn't be able to set data on an invalid index - QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) ); - - // General Purpose roles that should return a QString - QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); - if ( variant.isValid() ) { - QVERIFY( variant.canConvert(QMetaType::QString) ); - } - variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); - if ( variant.isValid() ) { - QVERIFY( variant.canConvert(QMetaType::QString) ); - } - variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); - if ( variant.isValid() ) { - QVERIFY( variant.canConvert(QMetaType::QString) ); - } - - // General Purpose roles that should return a QSize - variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); - if ( variant.isValid() ) { - QVERIFY( variant.canConvert(QMetaType::QSize) ); - } - - // General Purpose roles that should return a QFont - QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); - if ( fontVariant.isValid() ) { - QVERIFY( fontVariant.canConvert(QMetaType::QFont) ); - } - - // Check that the alignment is one we know about - QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); - if ( textAlignmentVariant.isValid() ) { - int alignment = textAlignmentVariant.toInt(); - QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); - } - - // General Purpose roles that should return a QColor - QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole ); - if ( colorVariant.isValid() ) { - QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); - } - - colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole ); - if ( colorVariant.isValid() ) { - QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); - } - - // Check that the "check state" is one we know about. - QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); - if ( checkStateVariant.isValid() ) { - int state = checkStateVariant.toInt(); - QVERIFY( state == Qt::Unchecked || - state == Qt::PartiallyChecked || - state == Qt::Checked ); - } -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsInserted() - */ -void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end ) -{ -// Q_UNUSED(end); -// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() -// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); -// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); - Changing c; - c.parent = parent; - c.oldSize = model->rowCount ( parent ); - c.last = model->data ( model->index ( start - 1, 0, parent ) ); - c.next = model->data ( model->index ( start, 0, parent ) ); -qDebug() << start << " - " << parent << " - " << model->data ( model->index ( start, 0, parent ), Qt::UserRole ); - insert.push ( c ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeInserted() - */ -void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) -{ - Changing c = insert.pop(); - QVERIFY( c.parent == parent ); -// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize -// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); - -// for (int ii=start; ii <= end; ii++) -// { -// qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); -// } -// qDebug(); - - QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); - QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); - - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug() << start << end; - for (int i=0; i < model->rowCount(); ++i) - qDebug() << model->index(i, 0).data().toString(); - qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); - } - -if (c.next != model->data ( model->index ( end + 1, 0, c.parent ) )) { - qDebug("break"); -} - QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); -} - -void ModelTest::layoutAboutToBeChanged() -{ - for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) - changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); -} - -void ModelTest::layoutChanged() -{ - for ( int i = 0; i < changing.count(); ++i ) { - QPersistentModelIndex p = changing[i]; - QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); - } - changing.clear(); -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsRemoved() - */ -void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) -{ -qDebug() << "ratbr" << parent << start << end; - Changing c; - c.parent = parent; - c.oldSize = model->rowCount ( parent ); - c.last = model->data ( model->index ( start - 1, 0, parent ) ); - c.next = model->data ( model->index ( end + 1, 0, parent ) ); - remove.push ( c ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeRemoved() - */ -void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) -{ - qDebug() << "rr" << parent << start << end; - Changing c = remove.pop(); - QVERIFY( c.parent == parent ); - QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); - QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); - QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); -} - - +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include + +#include "modeltest.h" + +#include + +#undef QVERIFY +#define QVERIFY Q_ASSERT +#undef QCOMPARE +#define QCOMPARE(x, y) Q_ASSERT(x == y) + +Q_DECLARE_METATYPE ( QModelIndex ) + +/*! + Connect to all of the models signals. Whenever anything happens recheck everything. +*/ +ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) +{ + if (!model) + qFatal("%s: model must not be null", Q_FUNC_INFO); + + connect ( model, SIGNAL ( columnsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( dataChanged ( const QModelIndex &, const QModelIndex & ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( headerDataChanged ( Qt::Orientation, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutAboutToBeChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( modelReset () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + + // Special checks for inserting/removing + connect ( model, SIGNAL ( layoutAboutToBeChanged() ), + this, SLOT ( layoutAboutToBeChanged() ) ); + connect ( model, SIGNAL ( layoutChanged() ), + this, SLOT ( layoutChanged() ) ); + + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsRemoved ( const QModelIndex &, int, int ) ) ); + + runAllTests(); +} + +void ModelTest::runAllTests() +{ + if ( fetchingMore ) + return; + nonDestructiveBasicTest(); + rowCount(); + columnCount(); + hasIndex(); + index(); + parent(); + data(); +} + +/*! + nonDestructiveBasicTest tries to call a number of the basic functions (not all) + to make sure the model doesn't outright segfault, testing the functions that makes sense. +*/ +void ModelTest::nonDestructiveBasicTest() +{ + QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() ); + model->canFetchMore ( QModelIndex() ); + QVERIFY( model->columnCount ( QModelIndex() ) >= 0 ); + QVERIFY( model->data ( QModelIndex() ) == QVariant() ); + fetchingMore = true; + model->fetchMore ( QModelIndex() ); + fetchingMore = false; + Qt::ItemFlags flags = model->flags ( QModelIndex() ); + QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 ); + model->hasChildren ( QModelIndex() ); + model->hasIndex ( 0, 0 ); + model->headerData ( 0, Qt::Horizontal ); + model->index ( 0, 0 ); + model->itemData ( QModelIndex() ); + QVariant cache; + model->match ( QModelIndex(), -1, cache ); + model->mimeTypes(); + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); + QVERIFY( model->rowCount() >= 0 ); + QVariant variant; + model->setData ( QModelIndex(), variant, -1 ); + model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); + model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); + QMap roles; + model->sibling ( 0, 0, QModelIndex() ); + model->span ( QModelIndex() ); + model->supportedDropActions(); +} + +/*! + Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() + + Models that are dynamically populated are not as fully tested here. + */ +void ModelTest::rowCount() +{ +// qDebug() << "rc"; + // check top row + + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + if (!model->hasChildren ( topIndex ) && model->rowCount(topIndex) > 0) { + qDebug() << "it's gonna blow: " << topIndex; + } + + int rows = model->rowCount ( topIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) { + QVERIFY( model->hasChildren ( topIndex ) ); + } + + QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); + if ( secondLevelIndex.isValid() ) { // not the top level + // check a row count where parent is valid + rows = model->rowCount ( secondLevelIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( secondLevelIndex ) ); + } + + // The models rowCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() + */ +void ModelTest::columnCount() +{ + // check top row + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->columnCount ( topIndex ) >= 0 ); + + // check a column count where parent is valid + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + if ( childIndex.isValid() ) + QVERIFY( model->columnCount ( childIndex ) >= 0 ); + + // columnCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::hasIndex() + */ +void ModelTest::hasIndex() +{ +// qDebug() << "hi"; + // Make sure that invalid values returns an invalid index + QVERIFY( !model->hasIndex ( -2, -2 ) ); + QVERIFY( !model->hasIndex ( -2, 0 ) ); + QVERIFY( !model->hasIndex ( 0, -2 ) ); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + // check out of bounds + QVERIFY( !model->hasIndex ( rows, columns ) ); + QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); + + if ( rows > 0 ) + QVERIFY( model->hasIndex ( 0, 0 ) ); + + // hasIndex() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::index() + */ +void ModelTest::index() +{ +// qDebug() << "i"; + // Make sure that invalid values returns an invalid index +/* QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); + QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); + QVERIFY( model->index ( 0, -2 ) == QModelIndex() );*/ + QVERIFY( !model->index ( -2, -2 ).isValid() ); + QVERIFY( !model->index ( -2, 0 ).isValid() ); + QVERIFY( !model->index ( 0, -2 ).isValid() ); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + if ( rows == 0 ) + return; + + // Catch off by one errors + //QVERIFY( model->index ( rows, columns ) == QModelIndex() ); + QVERIFY( !model->index ( rows, columns ).isValid() ); + QVERIFY( model->index ( 0, 0 ).isValid() ); + + // Make sure that the same index is *always* returned + QModelIndex a = model->index ( 0, 0 ); + QModelIndex b = model->index ( 0, 0 ); + QVERIFY( a == b ); + + // index() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::parent() + */ +void ModelTest::parent() +{ +// qDebug() << "p"; + // Make sure the model wont crash and will return an invalid QModelIndex + // when asked for the parent of an invalid index. + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); + + if ( model->rowCount() == 0 ) + return; + + // Column 0 | Column 1 | + // QModelIndex() | | + // \- topIndex | topIndex1 | + // \- childIndex | childIndex1 | + + // Common error test #1, make sure that a top level index has a parent + // that is a invalid QModelIndex. + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->parent ( topIndex ) == QModelIndex() ); + + // Common error test #2, make sure that a second level index has a parent + // that is the first level index. + if ( model->rowCount ( topIndex ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QVERIFY( model->parent ( childIndex ) == topIndex ); + } + + // Common error test #3, the second column should NOT have the same children + // as the first column in a row. + // Usually the second column shouldn't have children. + QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); + if ( model->rowCount ( topIndex1 ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); + QVERIFY( childIndex != childIndex1 ); + } + + // Full test, walk n levels deep through the model making sure that all + // parent's children correctly specify their parent. + checkChildren ( QModelIndex() ); +} + +/*! + Called from the parent() test. + + A model that returns an index of parent X should also return X when asking + for the parent of the index. + + This recursive function does pretty extensive testing on the whole model in an + effort to catch edge cases. + + This function assumes that rowCount(), columnCount() and index() already work. + If they have a bug it will point it out, but the above tests should have already + found the basic bugs because it is easier to figure out the problem in + those tests then this one. + */ +void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) +{ + // First just try walking back up the tree. + QModelIndex p = parent; + while ( p.isValid() ) + p = p.parent(); + + // For models that are dynamically populated + if ( model->canFetchMore ( parent ) ) { + fetchingMore = true; + model->fetchMore ( parent ); + fetchingMore = false; + } + + int rows = model->rowCount ( parent ); + int columns = model->columnCount ( parent ); + + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); + + // Some further testing against rows(), columns(), and hasChildren() + QVERIFY( rows >= 0 ); + QVERIFY( columns >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); + + //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows + // << "columns:" << columns << "parent column:" << parent.column(); + + QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) ); + for ( int r = 0; r < rows; ++r ) { + if ( model->canFetchMore ( parent ) ) { + fetchingMore = true; + model->fetchMore ( parent ); + fetchingMore = false; + } + QVERIFY( !model->hasIndex ( r, columns + 1, parent ) ); + for ( int c = 0; c < columns; ++c ) { + QVERIFY( model->hasIndex ( r, c, parent ) ); + QModelIndex index = model->index ( r, c, parent ); + // rowCount() and columnCount() said that it existed... + QVERIFY( index.isValid() ); + + // index() should always return the same index when called twice in a row + QModelIndex modifiedIndex = model->index ( r, c, parent ); + QVERIFY( index == modifiedIndex ); + + // Make sure we get the same index if we request it twice in a row + QModelIndex a = model->index ( r, c, parent ); + QModelIndex b = model->index ( r, c, parent ); + QVERIFY( a == b ); + + // Some basic checking on the index that is returned + QVERIFY( index.model() == model ); + QCOMPARE( index.row(), r ); + QCOMPARE( index.column(), c ); + // While you can technically return a QVariant usually this is a sign + // of a bug in data(). Disable if this really is ok in your model. +// QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); + + // If the next test fails here is some somewhat useful debug you play with. + + if (model->parent(index) != parent) { + qDebug() << r << c << currentDepth << model->data(index).toString() + << model->data(parent).toString(); + qDebug() << index << parent << model->parent(index); +// And a view that you can even use to show the model. +// QTreeView view; +// view.setModel(model); +// view.show(); + } + + // Check that we can get back our real parent. + QCOMPARE( model->parent ( index ), parent ); + + // recursively go down the children + if ( model->hasChildren ( index ) && currentDepth < 10 ) { + //qDebug() << r << c << "has children" << model->rowCount(index); + checkChildren ( index, ++currentDepth ); + }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ + + // make sure that after testing the children that the index doesn't change. + QModelIndex newerIndex = model->index ( r, c, parent ); + QVERIFY( index == newerIndex ); + } + } +} + +/*! + Tests model's implementation of QAbstractItemModel::data() + */ +void ModelTest::data() +{ + // Invalid index should return an invalid qvariant + QVERIFY( !model->data ( QModelIndex() ).isValid() ); + + if ( model->rowCount() == 0 ) + return; + + // A valid index should have a valid QVariant data + QVERIFY( model->index ( 0, 0 ).isValid() ); + + // shouldn't be able to set data on an invalid index + QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) ); + + // General Purpose roles that should return a QString + QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + + // General Purpose roles that should return a QSize + variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QSize) ); + } + + // General Purpose roles that should return a QFont + QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); + if ( fontVariant.isValid() ) { + QVERIFY( fontVariant.canConvert(QMetaType::QFont) ); + } + + // Check that the alignment is one we know about + QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); + if ( textAlignmentVariant.isValid() ) { + int alignment = textAlignmentVariant.toInt(); + QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); + } + + // General Purpose roles that should return a QColor + QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); + } + + colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); + } + + // Check that the "check state" is one we know about. + QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); + if ( checkStateVariant.isValid() ) { + int state = checkStateVariant.toInt(); + QVERIFY( state == Qt::Unchecked || + state == Qt::PartiallyChecked || + state == Qt::Checked ); + } +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsInserted() + */ +void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end ) +{ +// Q_UNUSED(end); +// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() +// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); +// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); + Changing c; + c.parent = parent; + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( start, 0, parent ) ); +qDebug() << start << " - " << parent << " - " << model->data ( model->index ( start, 0, parent ), Qt::UserRole ); + insert.push ( c ); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeInserted() + */ +void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) +{ + Changing c = insert.pop(); + QVERIFY( c.parent == parent ); +// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize +// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); + +// for (int ii=start; ii <= end; ii++) +// { +// qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); +// } +// qDebug(); + + QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); + + if (c.next != model->data(model->index(end + 1, 0, c.parent))) { + qDebug() << start << end; + for (int i=0; i < model->rowCount(); ++i) + qDebug() << model->index(i, 0).data().toString(); + qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); + } + +if (c.next != model->data ( model->index ( end + 1, 0, c.parent ) )) { + qDebug("break"); +} + QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); +} + +void ModelTest::layoutAboutToBeChanged() +{ + for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) + changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); +} + +void ModelTest::layoutChanged() +{ + for ( int i = 0; i < changing.count(); ++i ) { + QPersistentModelIndex p = changing[i]; + QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); + } + changing.clear(); +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsRemoved() + */ +void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) +{ +qDebug() << "ratbr" << parent << start << end; + Changing c; + c.parent = parent; + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( end + 1, 0, parent ) ); + remove.push ( c ); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeRemoved() + */ +void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) +{ + qDebug() << "rr" << parent << start << end; + Changing c = remove.pop(); + QVERIFY( c.parent == parent ); + QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); + QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); +} + + diff --git a/src/modeltest.h b/src/modeltest.h index 7e8c8267..68090f3f 100644 --- a/src/modeltest.h +++ b/src/modeltest.h @@ -1,94 +1,94 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#ifndef MODELTEST_H -#define MODELTEST_H - -#include -#include -#include - -class ModelTest : public QObject -{ - Q_OBJECT - -public: - ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); - -private Q_SLOTS: - void nonDestructiveBasicTest(); - void rowCount(); - void columnCount(); - void hasIndex(); - void index(); - void parent(); - void data(); - -protected Q_SLOTS: - void runAllTests(); - void layoutAboutToBeChanged(); - void layoutChanged(); - void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); - void rowsInserted( const QModelIndex & parent, int start, int end ); - void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); - void rowsRemoved( const QModelIndex & parent, int start, int end ); - -private: - void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); - - QAbstractItemModel *model; - - struct Changing { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack insert; - QStack remove; - - bool fetchingMore; - - QList changing; -}; - -#endif +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef MODELTEST_H +#define MODELTEST_H + +#include +#include +#include + +class ModelTest : public QObject +{ + Q_OBJECT + +public: + ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); + +private Q_SLOTS: + void nonDestructiveBasicTest(); + void rowCount(); + void columnCount(); + void hasIndex(); + void index(); + void parent(); + void data(); + +protected Q_SLOTS: + void runAllTests(); + void layoutAboutToBeChanged(); + void layoutChanged(); + void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); + void rowsInserted( const QModelIndex & parent, int start, int end ); + void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); + void rowsRemoved( const QModelIndex & parent, int start, int end ); + +private: + void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); + + QAbstractItemModel *model; + + struct Changing { + QModelIndex parent; + int oldSize; + QVariant last; + QVariant next; + }; + QStack insert; + QStack remove; + + bool fetchingMore; + + QList changing; +}; + +#endif diff --git a/src/modinfo.cpp b/src/modinfo.cpp index f72b5d82..b0351cf4 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -1,1003 +1,1003 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modinfo.h" -#include "utility.h" -#include "installationtester.h" -#include "categories.h" -#include "report.h" -#include "modinfodialog.h" -#include "overwriteinfodialog.h" -#include "json.h" -#include "messagedialog.h" - -#include -#include - -#include - -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -std::vector ModInfo::s_Collection; -std::map ModInfo::s_ModsByName; -std::map > ModInfo::s_ModsByModID; -int ModInfo::s_NextID; -QMutex ModInfo::s_Mutex(QMutex::Recursive); - -QString ModInfo::s_HiddenExt(".mohidden"); - - -static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) -{ - return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; -} - - -ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure) -{ - QMutexLocker locker(&s_Mutex); -// int id = s_NextID++; - static QRegExp backupExp(".*backup[0-9]*"); - ModInfo::Ptr result; - if (backupExp.exactMatch(dir.dirName())) { - result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); - } else { - result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); - } - s_Collection.push_back(result); - return result; -} - - -ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames - , DirectoryEntry ** directoryStructure) -{ - QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); - s_Collection.push_back(result); - return result; -} - - -void ModInfo::createFromOverwrite() -{ - QMutexLocker locker(&s_Mutex); - - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); -} - - -unsigned int ModInfo::getNumMods() -{ - QMutexLocker locker(&s_Mutex); - return s_Collection.size(); -} - - -ModInfo::Ptr ModInfo::getByIndex(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - return s_Collection[index]; -} - - -std::vector ModInfo::getByModID(int modID) -{ - QMutexLocker locker(&s_Mutex); - - auto iter = s_ModsByModID.find(modID); - if (iter == s_ModsByModID.end()) { - return std::vector(); - } - - std::vector result; - for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { - result.push_back(getByIndex(*idxIter)); - } - - return result; -} - - -bool ModInfo::removeMod(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - // update the indices first - ModInfo::Ptr modInfo = s_Collection[index]; - s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - - auto iter = s_ModsByModID.find(modInfo->getNexusID()); - if (iter != s_ModsByModID.end()) { - std::vector indices = iter->second; - std::remove(indices.begin(), indices.end(), index); - s_ModsByModID[modInfo->getNexusID()] = indices; - } - - // physically remove the mod directory - //TODO the return value is ignored because the indices were already removed here, so stopping - // would cause data inconsistencies. Instead we go through with the removal but the mod will show up - // again if the user refreshes - modInfo->remove(); - - // finally, remove the mod from the collection - s_Collection.erase(s_Collection.begin() + index); - - // and update the indices - updateIndices(); - return true; -} - - -unsigned int ModInfo::getIndex(const QString &name) -{ - QMutexLocker locker(&s_Mutex); - - std::map::iterator iter = s_ModsByName.find(name); - if (iter == s_ModsByName.end()) { - return UINT_MAX; - } - - return iter->second; -} - -unsigned int ModInfo::findMod(const boost::function &filter) -{ - for (unsigned int i = 0U; i < s_Collection.size(); ++i) { - if (filter(s_Collection[i])) { - return i; - } - } - return UINT_MAX; -} - - -void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign) -{ - QMutexLocker lock(&s_Mutex); - s_Collection.clear(); - s_NextID = 0; - - { // list all directories in the mod directory and make a mod out of each - QDir mods(QDir::fromNativeSeparators(modDirectory)); - mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); - QDirIterator modIter(mods); - while (modIter.hasNext()) { - createFrom(QDir(modIter.next()), directoryStructure); - } - } - - { // list plugins in the data directory and make a foreign-managed mod out of each - std::vector dlcPlugins = GameInfo::instance().getDLCPlugins(); - QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"); - foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) { - if ((file.baseName() != "Update") // hide update - && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp - && (displayForeign // show non-dlc bundles only if the user wants them - || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) { - QStringList archives; - foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) { - archives.append(dataDir.absoluteFilePath(archiveName)); - } - - createFromPlugin(file.fileName(), archives, directoryStructure); - } - } - } - - createFromOverwrite(); - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - - updateIndices(); -} - - -void ModInfo::updateIndices() -{ - s_ModsByName.clear(); - s_ModsByModID.clear(); - QRegExp backupRegEx(".*backup[0-9]*$"); - - for (unsigned int i = 0; i < s_Collection.size(); ++i) { - QString modName = s_Collection[i]->name(); - int modID = s_Collection[i]->getNexusID(); - s_ModsByName[modName] = i; - s_ModsByModID[modID].push_back(i); - } -} - - -ModInfo::ModInfo() - : m_Valid(false), m_PrimaryCategory(-1) -{ -} - - -void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver) -{ - if (modIDs.size() != 0) { - NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); - } -} - - -int ModInfo::checkAllForUpdate(QObject *receiver) -{ - int result = 0; - std::vector modIDs; - - modIDs.push_back(GameInfo::instance().getNexusModID()); - - for (std::vector::iterator iter = s_Collection.begin(); - iter != s_Collection.end(); ++iter) { - if ((*iter)->canBeUpdated()) { - modIDs.push_back((*iter)->getNexusID()); - if (modIDs.size() >= 255) { - checkChunkForUpdate(modIDs, receiver); - modIDs.clear(); - } - } - } - - checkChunkForUpdate(modIDs, receiver); - - return result; -} - -void ModInfo::setVersion(const VersionInfo &version) -{ - m_Version = version; -} - -bool ModInfo::hasFlag(ModInfo::EFlag flag) const -{ - std::vector flags = getFlags(); - return std::find(flags.begin(), flags.end(), flag) != flags.end(); -} - - -bool ModInfo::categorySet(int categoryID) const -{ - for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if ((*iter == categoryID) || - (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { - return true; - } - } - - return false; -} - - -void ModInfo::testValid() -{ - m_Valid = false; - QDirIterator dirIter(absolutePath()); - while (dirIter.hasNext()) { - dirIter.next(); - if (dirIter.fileInfo().isDir()) { - if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { - m_Valid = true; - break; - } - } else { - if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { - m_Valid = true; - break; - } - } - } - - // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the - // end - while (dirIter.hasNext()) { - dirIter.next(); - } -} - - -ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) - : m_DirectoryStructure(directoryStructure) {} - -void ModInfoWithConflictInfo::clearCaches() -{ - m_LastConflictCheck = QTime(); -} - -std::vector ModInfoWithConflictInfo::getFlags() const -{ - std::vector result; - switch (isConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); - } break; - default: { /* NOP */ } - } - return result; -} - - -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const -{ - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - bool regular = false; - - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } - - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - // for all files in this origin - for (auto iter = files.begin(); iter != files.end(); ++iter) { - const std::vector &alternatives = (*iter)->getAlternatives(); - if ((alternatives.size() == 0) - || (alternatives[0] == dataID)) { - // no alternatives -> no conflict - regular = true; - } else { - if ((*iter)->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } - // for all non-providing alternative origins - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - if ((*altIter != dataID) && (*altIter != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } - } - } - } - } - } - - m_LastConflictCheck = QTime::currentTime(); - - if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) { - if (!regular) { - m_CurrentConflictState = CONFLICT_REDUNDANT; - } else { - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } - } - else m_CurrentConflictState = CONFLICT_NONE; - } - - return m_CurrentConflictState; -} - - -bool ModInfoWithConflictInfo::isRedundant() const -{ - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - bool ignore = false; - for (auto iter = files.begin(); iter != files.end(); ++iter) { - if ((*iter)->getOrigin(ignore) == origin.getID()) { - return false; - } - } - return true; - } else { - return false; - } -} - - - -ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_Name(path.dirName()) - , m_Path(path.absolutePath()) - , m_MetaInfoChanged(false) - , m_EndorsedState(ENDORSED_UNKNOWN) -{ - testValid(); - m_CreationTime = QFileInfo(path.absolutePath()).created(); - // read out the meta-file for information - readMeta(); - - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) - , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) - , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) - , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); -} - - -ModInfoRegular::~ModInfoRegular() -{ - try { - saveMeta(); - } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - m_Name.toUtf8().constData(), e.what()); - } -} - -bool ModInfoRegular::isEmpty() const -{ - QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - - -void ModInfoRegular::readMeta() -{ - QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); - - m_Notes = metaFile.value("notes", "").toString(); - m_NexusID = metaFile.value("modid", -1).toInt(); - m_Version.parse(metaFile.value("version", "").toString()); - m_NewestVersion = metaFile.value("newestVersion", "").toString(); - m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); - m_InstallationFile = metaFile.value("installationFile", "").toString(); - m_NexusDescription = metaFile.value("nexusDescription", "").toString(); - m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - if (metaFile.contains("endorsed")) { - if (metaFile.value("endorsed").canConvert()) { - switch (metaFile.value("endorsed").toInt()) { - case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; - case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; - case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; - default: m_EndorsedState = ENDORSED_UNKNOWN; break; - } - } else { - m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - } - - QString categoriesString = metaFile.value("category", "").toString(); - - QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); - for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - bool ok = false; - int categoryID = iter->toInt(&ok); - if (categoryID < 0) { - // ignore invalid id - continue; - } - if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { - m_Categories.insert(categoryID); - if (iter == categories.begin()) { - m_PrimaryCategory = categoryID; - } - } - } - - m_MetaInfoChanged = false; -} - -void ModInfoRegular::saveMeta() -{ - // only write meta data if the mod directory exists - if (m_MetaInfoChanged && QFile::exists(absolutePath())) { - QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); - if (metaFile.status() == QSettings::NoError) { - std::set temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - if (m_NexusID != -1) { - metaFile.setValue("modid", m_NexusID); - } - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - m_MetaInfoChanged = false; - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } -} - - -bool ModInfoRegular::updateAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); -} - - -bool ModInfoRegular::downgradeAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); -} - - -void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) -{ - QVariantMap result = resultData.toMap(); - m_NewestVersion.parse(result["version"].toString()); - m_NexusDescription = result["description"].toString(); - if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { - m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - m_LastNexusQuery = QDateTime::currentDateTime(); - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) -{ - m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) -{ - QString fullMessage = errorMessage; - if (userData.canConvert() && (userData.toInt() == 1)) { - fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; - } - if (QApplication::activeWindow() != NULL) { - MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); - } - emit modDetailsUpdated(false); -} - - -bool ModInfoRegular::updateNXMInfo() -{ - if (m_NexusID > 0) { - m_NexusBridge.requestDescription(m_NexusID, QVariant()); - return true; - } - return false; -} - - -void ModInfoRegular::setCategory(int categoryID, bool active) -{ - m_MetaInfoChanged = true; - - if (active) { - m_Categories.insert(categoryID); - if (m_PrimaryCategory == -1) { - m_PrimaryCategory = categoryID; - } - } else { - std::set::iterator iter = m_Categories.find(categoryID); - if (iter != m_Categories.end()) { - m_Categories.erase(iter); - } - if (categoryID == m_PrimaryCategory) { - if (m_Categories.size() == 0) { - m_PrimaryCategory = -1; - } else { - m_PrimaryCategory = *(m_Categories.begin()); - } - } - } -} - - -bool ModInfoRegular::setName(const QString &name) -{ - if (name.contains('/') || name.contains('\\')) { - return false; - } - - QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); - QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); - - if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { - QString tempName = name; - tempName.append("_temp"); - while (modDir.exists(tempName)) { - tempName.append("_"); - } - if (!modDir.rename(m_Name, tempName)) { - return false; - } - if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); - modDir.rename(tempName, m_Name); - return false; - } - } else { - if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qPrintable(name), ::GetLastError()); - return false; - } - } - - std::map::iterator nameIter = s_ModsByName.find(m_Name); - if (nameIter != s_ModsByName.end()) { - unsigned int index = nameIter->second; - s_ModsByName.erase(nameIter); - - m_Name = name; - m_Path = newPath; - - s_ModsByName[m_Name] = index; - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - updateIndices(); - } else { // otherwise mod isn't registered yet? - m_Name = name; - m_Path = newPath; - } - - return true; -} - -void ModInfoRegular::setNotes(const QString ¬es) -{ - m_Notes = notes; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNexusID(int modID) -{ - m_NexusID = modID; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setVersion(const VersionInfo &version) -{ - m_Version = version; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNewestVersion(const VersionInfo &version) { - m_NewestVersion = version; -} - -void ModInfoRegular::setNexusDescription(const QString &description) -{ - m_NexusDescription = description; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::addNexusCategory(int categoryID) -{ - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); -} - -void ModInfoRegular::setIsEndorsed(bool endorsed) -{ - if (m_EndorsedState != ENDORSED_NEVER) { - m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - } -} - - -void ModInfoRegular::setNeverEndorse() -{ - m_EndorsedState = ENDORSED_NEVER; - m_MetaInfoChanged = true; -} - - -bool ModInfoRegular::remove() -{ - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); -} - -void ModInfoRegular::endorse(bool doEndorse) -{ - if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); - } -} - - -QString ModInfoRegular::absolutePath() const -{ - return m_Path; -} - -void ModInfoRegular::ignoreUpdate(bool ignore) -{ - if (ignore) { - m_IgnoredVersion = m_NewestVersion; - } else { - m_IgnoredVersion.clear(); - } - m_MetaInfoChanged = true; -} - - -std::vector ModInfoRegular::getFlags() const -{ - std::vector result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) { - result.push_back(ModInfo::FLAG_NOTENDORSED); - } - if (!isValid()) { - result.push_back(ModInfo::FLAG_INVALID); - } - if (m_Notes.length() != 0) { - result.push_back(ModInfo::FLAG_NOTES); - } - return result; -} - - -int ModInfoRegular::getHighlight() const -{ - return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; -} - - -QString ModInfoRegular::getDescription() const -{ - if (!isValid()) { - return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); - } else { - const std::set &categories = getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
            ")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; - } - - return ToQString(categoryString.str()); - } -} - -QString ModInfoRegular::notes() const -{ - return m_Notes; -} - -QDateTime ModInfoRegular::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoRegular::getNexusDescription() const -{ - return m_NexusDescription; -} - -ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const -{ - return m_EndorsedState; -} - -QDateTime ModInfoRegular::getLastNexusQuery() const -{ - return m_LastNexusQuery; -} - - -QStringList ModInfoRegular::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -std::vector ModInfoRegular::getIniTweaks() const -{ - QString metaFileName = absolutePath().append("/meta.ini"); - QSettings metaFile(metaFileName, QSettings::IniFormat); - - std::vector result; - - int numTweaks = metaFile.beginReadArray("INI Tweaks"); - - if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); - } - - for (int i = 0; i < numTweaks; ++i) { - metaFile.setArrayIndex(i); - QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); - result.push_back(filename); - } - metaFile.endArray(); - return result; -} - -std::vector ModInfoBackup::getFlags() const -{ - std::vector result = ModInfoRegular::getFlags(); - result.insert(result.begin(), ModInfo::FLAG_BACKUP); - return result; -} - - -QString ModInfoBackup::getDescription() const -{ - return tr("This is the backup of a mod"); -} - - -ModInfoBackup::ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoRegular(path, directoryStructure) -{ -} - - -ModInfoOverwrite::ModInfoOverwrite() - : m_StartupTime(QDateTime::currentDateTime()) -{ - testValid(); -} - - -bool ModInfoOverwrite::isEmpty() const -{ - QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - -QString ModInfoOverwrite::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir())); -} - -std::vector ModInfoOverwrite::getFlags() const -{ - std::vector result; - result.push_back(FLAG_OVERWRITE); - return result; -} - -int ModInfoOverwrite::getHighlight() const -{ - return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; -} - -QString ModInfoOverwrite::getDescription() const -{ - return tr("This pseudo mod contains files from the virtual data tree that got " - "modified (i.e. by the construction kit)"); -} - -QStringList ModInfoOverwrite::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -QString ModInfoForeign::name() const -{ - return m_Name; -} - -QDateTime ModInfoForeign::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoForeign::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; -} - -std::vector ModInfoForeign::getFlags() const -{ - std::vector result = ModInfoWithConflictInfo::getFlags(); - result.push_back(FLAG_FOREIGN); - - return result; -} - -int ModInfoForeign::getHighlight() const -{ - return 0; -} - -QString ModInfoForeign::getDescription() const -{ - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); -} - -ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, - DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_ReferenceFile(referenceFile) - , m_Archives(archives) -{ - m_CreationTime = QFileInfo(referenceFile).created(); - m_Name = QFileInfo(m_ReferenceFile).baseName(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modinfo.h" +#include "utility.h" +#include "installationtester.h" +#include "categories.h" +#include "report.h" +#include "modinfodialog.h" +#include "overwriteinfodialog.h" +#include "json.h" +#include "messagedialog.h" + +#include +#include + +#include + +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +std::vector ModInfo::s_Collection; +std::map ModInfo::s_ModsByName; +std::map > ModInfo::s_ModsByModID; +int ModInfo::s_NextID; +QMutex ModInfo::s_Mutex(QMutex::Recursive); + +QString ModInfo::s_HiddenExt(".mohidden"); + + +static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) +{ + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; +} + + +ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure) +{ + QMutexLocker locker(&s_Mutex); +// int id = s_NextID++; + static QRegExp backupExp(".*backup[0-9]*"); + ModInfo::Ptr result; + if (backupExp.exactMatch(dir.dirName())) { + result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); + } else { + result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); + } + s_Collection.push_back(result); + return result; +} + + +ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames + , DirectoryEntry ** directoryStructure) +{ + QMutexLocker locker(&s_Mutex); + ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); + s_Collection.push_back(result); + return result; +} + + +void ModInfo::createFromOverwrite() +{ + QMutexLocker locker(&s_Mutex); + + s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); +} + + +unsigned int ModInfo::getNumMods() +{ + QMutexLocker locker(&s_Mutex); + return s_Collection.size(); +} + + +ModInfo::Ptr ModInfo::getByIndex(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); + + if (index >= s_Collection.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + return s_Collection[index]; +} + + +std::vector ModInfo::getByModID(int modID) +{ + QMutexLocker locker(&s_Mutex); + + auto iter = s_ModsByModID.find(modID); + if (iter == s_ModsByModID.end()) { + return std::vector(); + } + + std::vector result; + for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { + result.push_back(getByIndex(*idxIter)); + } + + return result; +} + + +bool ModInfo::removeMod(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); + + if (index >= s_Collection.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + // update the indices first + ModInfo::Ptr modInfo = s_Collection[index]; + s_ModsByName.erase(s_ModsByName.find(modInfo->name())); + + auto iter = s_ModsByModID.find(modInfo->getNexusID()); + if (iter != s_ModsByModID.end()) { + std::vector indices = iter->second; + std::remove(indices.begin(), indices.end(), index); + s_ModsByModID[modInfo->getNexusID()] = indices; + } + + // physically remove the mod directory + //TODO the return value is ignored because the indices were already removed here, so stopping + // would cause data inconsistencies. Instead we go through with the removal but the mod will show up + // again if the user refreshes + modInfo->remove(); + + // finally, remove the mod from the collection + s_Collection.erase(s_Collection.begin() + index); + + // and update the indices + updateIndices(); + return true; +} + + +unsigned int ModInfo::getIndex(const QString &name) +{ + QMutexLocker locker(&s_Mutex); + + std::map::iterator iter = s_ModsByName.find(name); + if (iter == s_ModsByName.end()) { + return UINT_MAX; + } + + return iter->second; +} + +unsigned int ModInfo::findMod(const boost::function &filter) +{ + for (unsigned int i = 0U; i < s_Collection.size(); ++i) { + if (filter(s_Collection[i])) { + return i; + } + } + return UINT_MAX; +} + + +void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign) +{ + QMutexLocker lock(&s_Mutex); + s_Collection.clear(); + s_NextID = 0; + + { // list all directories in the mod directory and make a mod out of each + QDir mods(QDir::fromNativeSeparators(modDirectory)); + mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + QDirIterator modIter(mods); + while (modIter.hasNext()) { + createFrom(QDir(modIter.next()), directoryStructure); + } + } + + { // list plugins in the data directory and make a foreign-managed mod out of each + std::vector dlcPlugins = GameInfo::instance().getDLCPlugins(); + QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"); + foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) { + if ((file.baseName() != "Update") // hide update + && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp + && (displayForeign // show non-dlc bundles only if the user wants them + || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) { + QStringList archives; + foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) { + archives.append(dataDir.absoluteFilePath(archiveName)); + } + + createFromPlugin(file.fileName(), archives, directoryStructure); + } + } + } + + createFromOverwrite(); + + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + + updateIndices(); +} + + +void ModInfo::updateIndices() +{ + s_ModsByName.clear(); + s_ModsByModID.clear(); + QRegExp backupRegEx(".*backup[0-9]*$"); + + for (unsigned int i = 0; i < s_Collection.size(); ++i) { + QString modName = s_Collection[i]->name(); + int modID = s_Collection[i]->getNexusID(); + s_ModsByName[modName] = i; + s_ModsByModID[modID].push_back(i); + } +} + + +ModInfo::ModInfo() + : m_Valid(false), m_PrimaryCategory(-1) +{ +} + + +void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver) +{ + if (modIDs.size() != 0) { + NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); + } +} + + +int ModInfo::checkAllForUpdate(QObject *receiver) +{ + int result = 0; + std::vector modIDs; + + modIDs.push_back(GameInfo::instance().getNexusModID()); + + for (std::vector::iterator iter = s_Collection.begin(); + iter != s_Collection.end(); ++iter) { + if ((*iter)->canBeUpdated()) { + modIDs.push_back((*iter)->getNexusID()); + if (modIDs.size() >= 255) { + checkChunkForUpdate(modIDs, receiver); + modIDs.clear(); + } + } + } + + checkChunkForUpdate(modIDs, receiver); + + return result; +} + +void ModInfo::setVersion(const VersionInfo &version) +{ + m_Version = version; +} + +bool ModInfo::hasFlag(ModInfo::EFlag flag) const +{ + std::vector flags = getFlags(); + return std::find(flags.begin(), flags.end(), flag) != flags.end(); +} + + +bool ModInfo::categorySet(int categoryID) const +{ + for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if ((*iter == categoryID) || + (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { + return true; + } + } + + return false; +} + + +void ModInfo::testValid() +{ + m_Valid = false; + QDirIterator dirIter(absolutePath()); + while (dirIter.hasNext()) { + dirIter.next(); + if (dirIter.fileInfo().isDir()) { + if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { + m_Valid = true; + break; + } + } else { + if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { + m_Valid = true; + break; + } + } + } + + // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the + // end + while (dirIter.hasNext()) { + dirIter.next(); + } +} + + +ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) + : m_DirectoryStructure(directoryStructure) {} + +void ModInfoWithConflictInfo::clearCaches() +{ + m_LastConflictCheck = QTime(); +} + +std::vector ModInfoWithConflictInfo::getFlags() const +{ + std::vector result; + switch (isConflicted()) { + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); + } break; + case CONFLICT_REDUNDANT: { + result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); + } break; + default: { /* NOP */ } + } + return result; +} + + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + bool regular = false; + + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } + + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + // for all files in this origin + for (auto iter = files.begin(); iter != files.end(); ++iter) { + const std::vector &alternatives = (*iter)->getAlternatives(); + if ((alternatives.size() == 0) + || (alternatives[0] == dataID)) { + // no alternatives -> no conflict + regular = true; + } else { + if ((*iter)->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } + // for all non-providing alternative origins + for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { + if ((*altIter != dataID) && (*altIter != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); + } else { + m_OverwrittenList.insert(altIndex); + } + } + } + } + } + } + + m_LastConflictCheck = QTime::currentTime(); + + if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) { + if (!regular) { + m_CurrentConflictState = CONFLICT_REDUNDANT; + } else { + m_CurrentConflictState = CONFLICT_OVERWRITTEN; + } + } + else m_CurrentConflictState = CONFLICT_NONE; + } + + return m_CurrentConflictState; +} + + +bool ModInfoWithConflictInfo::isRedundant() const +{ + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + bool ignore = false; + for (auto iter = files.begin(); iter != files.end(); ++iter) { + if ((*iter)->getOrigin(ignore) == origin.getID()) { + return false; + } + } + return true; + } else { + return false; + } +} + + + +ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_Name(path.dirName()) + , m_Path(path.absolutePath()) + , m_MetaInfoChanged(false) + , m_EndorsedState(ENDORSED_UNKNOWN) +{ + testValid(); + m_CreationTime = QFileInfo(path.absolutePath()).created(); + // read out the meta-file for information + readMeta(); + + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) + , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) + , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) + , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); +} + + +ModInfoRegular::~ModInfoRegular() +{ + try { + saveMeta(); + } catch (const std::exception &e) { + qCritical("failed to save meta information for \"%s\": %s", + m_Name.toUtf8().constData(), e.what()); + } +} + +bool ModInfoRegular::isEmpty() const +{ + QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + + +void ModInfoRegular::readMeta() +{ + QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); + + m_Notes = metaFile.value("notes", "").toString(); + m_NexusID = metaFile.value("modid", -1).toInt(); + m_Version.parse(metaFile.value("version", "").toString()); + m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); + m_InstallationFile = metaFile.value("installationFile", "").toString(); + m_NexusDescription = metaFile.value("nexusDescription", "").toString(); + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + if (metaFile.contains("endorsed")) { + if (metaFile.value("endorsed").canConvert()) { + switch (metaFile.value("endorsed").toInt()) { + case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; + case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; + case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; + default: m_EndorsedState = ENDORSED_UNKNOWN; break; + } + } else { + m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + } + + QString categoriesString = metaFile.value("category", "").toString(); + + QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); + for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { + bool ok = false; + int categoryID = iter->toInt(&ok); + if (categoryID < 0) { + // ignore invalid id + continue; + } + if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { + m_Categories.insert(categoryID); + if (iter == categories.begin()) { + m_PrimaryCategory = categoryID; + } + } + } + + m_MetaInfoChanged = false; +} + +void ModInfoRegular::saveMeta() +{ + // only write meta data if the mod directory exists + if (m_MetaInfoChanged && QFile::exists(absolutePath())) { + QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); + if (metaFile.status() == QSettings::NoError) { + std::set temp = m_Categories; + temp.erase(m_PrimaryCategory); + metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); + metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); + metaFile.setValue("version", m_Version.canonicalString()); + if (m_NexusID != -1) { + metaFile.setValue("modid", m_NexusID); + } + metaFile.setValue("notes", m_Notes); + metaFile.setValue("nexusDescription", m_NexusDescription); + metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + if (m_EndorsedState != ENDORSED_UNKNOWN) { + metaFile.setValue("endorsed", m_EndorsedState); + } + metaFile.sync(); // sync needs to be called to ensure the file is created + + if (metaFile.status() == QSettings::NoError) { + m_MetaInfoChanged = false; + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } +} + + +bool ModInfoRegular::updateAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); +} + + +bool ModInfoRegular::downgradeAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); +} + + +void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) +{ + QVariantMap result = resultData.toMap(); + m_NewestVersion.parse(result["version"].toString()); + m_NexusDescription = result["description"].toString(); + if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { + m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + m_LastNexusQuery = QDateTime::currentDateTime(); + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) +{ + m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) +{ + QString fullMessage = errorMessage; + if (userData.canConvert() && (userData.toInt() == 1)) { + fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; + } + if (QApplication::activeWindow() != NULL) { + MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); + } + emit modDetailsUpdated(false); +} + + +bool ModInfoRegular::updateNXMInfo() +{ + if (m_NexusID > 0) { + m_NexusBridge.requestDescription(m_NexusID, QVariant()); + return true; + } + return false; +} + + +void ModInfoRegular::setCategory(int categoryID, bool active) +{ + m_MetaInfoChanged = true; + + if (active) { + m_Categories.insert(categoryID); + if (m_PrimaryCategory == -1) { + m_PrimaryCategory = categoryID; + } + } else { + std::set::iterator iter = m_Categories.find(categoryID); + if (iter != m_Categories.end()) { + m_Categories.erase(iter); + } + if (categoryID == m_PrimaryCategory) { + if (m_Categories.size() == 0) { + m_PrimaryCategory = -1; + } else { + m_PrimaryCategory = *(m_Categories.begin()); + } + } + } +} + + +bool ModInfoRegular::setName(const QString &name) +{ + if (name.contains('/') || name.contains('\\')) { + return false; + } + + QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); + QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); + + if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { + QString tempName = name; + tempName.append("_temp"); + while (modDir.exists(tempName)) { + tempName.append("_"); + } + if (!modDir.rename(m_Name, tempName)) { + return false; + } + if (!modDir.rename(tempName, name)) { + qCritical("rename to final name failed after successful rename to intermediate name"); + modDir.rename(tempName, m_Name); + return false; + } + } else { + if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { + qCritical("failed to rename mod %s (errorcode %d)", + qPrintable(name), ::GetLastError()); + return false; + } + } + + std::map::iterator nameIter = s_ModsByName.find(m_Name); + if (nameIter != s_ModsByName.end()) { + unsigned int index = nameIter->second; + s_ModsByName.erase(nameIter); + + m_Name = name; + m_Path = newPath; + + s_ModsByName[m_Name] = index; + + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + updateIndices(); + } else { // otherwise mod isn't registered yet? + m_Name = name; + m_Path = newPath; + } + + return true; +} + +void ModInfoRegular::setNotes(const QString ¬es) +{ + m_Notes = notes; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNexusID(int modID) +{ + m_NexusID = modID; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setVersion(const VersionInfo &version) +{ + m_Version = version; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNewestVersion(const VersionInfo &version) { + m_NewestVersion = version; +} + +void ModInfoRegular::setNexusDescription(const QString &description) +{ + m_NexusDescription = description; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::addNexusCategory(int categoryID) +{ + m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); +} + +void ModInfoRegular::setIsEndorsed(bool endorsed) +{ + if (m_EndorsedState != ENDORSED_NEVER) { + m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + } +} + + +void ModInfoRegular::setNeverEndorse() +{ + m_EndorsedState = ENDORSED_NEVER; + m_MetaInfoChanged = true; +} + + +bool ModInfoRegular::remove() +{ + m_MetaInfoChanged = false; + return shellDelete(QStringList(absolutePath()), true); +} + +void ModInfoRegular::endorse(bool doEndorse) +{ + if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { + m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); + } +} + + +QString ModInfoRegular::absolutePath() const +{ + return m_Path; +} + +void ModInfoRegular::ignoreUpdate(bool ignore) +{ + if (ignore) { + m_IgnoredVersion = m_NewestVersion; + } else { + m_IgnoredVersion.clear(); + } + m_MetaInfoChanged = true; +} + + +std::vector ModInfoRegular::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) { + result.push_back(ModInfo::FLAG_NOTENDORSED); + } + if (!isValid()) { + result.push_back(ModInfo::FLAG_INVALID); + } + if (m_Notes.length() != 0) { + result.push_back(ModInfo::FLAG_NOTES); + } + return result; +} + + +int ModInfoRegular::getHighlight() const +{ + return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; +} + + +QString ModInfoRegular::getDescription() const +{ + if (!isValid()) { + return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); + } else { + const std::set &categories = getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
            ")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; + } + categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; + } + + return ToQString(categoryString.str()); + } +} + +QString ModInfoRegular::notes() const +{ + return m_Notes; +} + +QDateTime ModInfoRegular::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoRegular::getNexusDescription() const +{ + return m_NexusDescription; +} + +ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const +{ + return m_EndorsedState; +} + +QDateTime ModInfoRegular::getLastNexusQuery() const +{ + return m_LastNexusQuery; +} + + +QStringList ModInfoRegular::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} + +std::vector ModInfoRegular::getIniTweaks() const +{ + QString metaFileName = absolutePath().append("/meta.ini"); + QSettings metaFile(metaFileName, QSettings::IniFormat); + + std::vector result; + + int numTweaks = metaFile.beginReadArray("INI Tweaks"); + + if (numTweaks != 0) { + qDebug("%d active ini tweaks in %s", + numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + } + + for (int i = 0; i < numTweaks; ++i) { + metaFile.setArrayIndex(i); + QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); + result.push_back(filename); + } + metaFile.endArray(); + return result; +} + +std::vector ModInfoBackup::getFlags() const +{ + std::vector result = ModInfoRegular::getFlags(); + result.insert(result.begin(), ModInfo::FLAG_BACKUP); + return result; +} + + +QString ModInfoBackup::getDescription() const +{ + return tr("This is the backup of a mod"); +} + + +ModInfoBackup::ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoRegular(path, directoryStructure) +{ +} + + +ModInfoOverwrite::ModInfoOverwrite() + : m_StartupTime(QDateTime::currentDateTime()) +{ + testValid(); +} + + +bool ModInfoOverwrite::isEmpty() const +{ + QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + +QString ModInfoOverwrite::absolutePath() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir())); +} + +std::vector ModInfoOverwrite::getFlags() const +{ + std::vector result; + result.push_back(FLAG_OVERWRITE); + return result; +} + +int ModInfoOverwrite::getHighlight() const +{ + return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; +} + +QString ModInfoOverwrite::getDescription() const +{ + return tr("This pseudo mod contains files from the virtual data tree that got " + "modified (i.e. by the construction kit)"); +} + +QStringList ModInfoOverwrite::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} + +QString ModInfoForeign::name() const +{ + return m_Name; +} + +QDateTime ModInfoForeign::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoForeign::absolutePath() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; +} + +std::vector ModInfoForeign::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + result.push_back(FLAG_FOREIGN); + + return result; +} + +int ModInfoForeign::getHighlight() const +{ + return 0; +} + +QString ModInfoForeign::getDescription() const +{ + return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); +} + +ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, + DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_ReferenceFile(referenceFile) + , m_Archives(archives) +{ + m_CreationTime = QFileInfo(referenceFile).created(); + m_Name = QFileInfo(m_ReferenceFile).baseName(); +} diff --git a/src/modinfo.h b/src/modinfo.h index cacddadf..0c144936 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -1,1028 +1,1028 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MODINFO_H -#define MODINFO_H - -#include "nexusinterface.h" -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using MOBase::ModRepositoryFileInfo; - -/** - * @brief Represents meta information about a single mod. - * - * Represents meta information about a single mod. The class interface is used - * to manage the mod collection - * - **/ -class ModInfo : public QObject, public MOBase::IModInterface -{ - - Q_OBJECT - -public: - - typedef QSharedPointer Ptr; - - static QString s_HiddenExt; - - enum EFlag { - FLAG_INVALID, - FLAG_BACKUP, - FLAG_OVERWRITE, - FLAG_FOREIGN, - FLAG_NOTENDORSED, - FLAG_NOTES, - FLAG_CONFLICT_OVERWRITE, - FLAG_CONFLICT_OVERWRITTEN, - FLAG_CONFLICT_MIXED, - FLAG_CONFLICT_REDUNDANT - }; - - enum EHighlight { - HIGHLIGHT_NONE = 0, - HIGHLIGHT_INVALID = 1, - HIGHLIGHT_CENTER = 2, - HIGHLIGHT_IMPORTANT = 4 - }; - - enum EEndorsedState { - ENDORSED_FALSE, - ENDORSED_TRUE, - ENDORSED_UNKNOWN, - ENDORSED_NEVER - }; - -public: - - /** - * @brief read the mod directory and Mod ModInfo objects for all subdirectories - **/ - static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure, bool displayForeign); - - static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } - - /** - * @brief retrieve the number of mods - * - * @return number of mods - **/ - static unsigned int getNumMods(); - - /** - * @brief retrieve a ModInfo object based on its index - * - * @param index the index to look up. the maximum is getNumMods() - 1 - * @return a reference counting pointer to the mod info. - * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread - **/ - static ModInfo::Ptr getByIndex(unsigned int index); - - /** - * @brief retrieve a ModInfo object based on its nexus mod id - * - * @param modID the nexus mod id to look up - * @return a reference counting pointer to the mod info - * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, - * this function will return only one of them - **/ - static std::vector getByModID(int modID); - - /** - * @brief remove a mod by index - * - * this physically deletes the specified mod from the disc and updates the ModInfo collection - * but not other structures that reference mods - * @param index index of the mod to delete - * @return true if removal was successful, fals otherwise - **/ - static bool removeMod(unsigned int index); - - /** - * @brief retrieve the mod index by the mod name - * - * @param name name of the mod to look up - * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned - **/ - static unsigned int getIndex(const QString &name); - - /** - * @brief find the first mod that fulfills the filter function (after no particular order) - * @param filter a function to filter by. should return true for a match - * @return index of the matching mod or UINT_MAX if there wasn't a match - */ - static unsigned int findMod(const boost::function &filter); - - /** - * @brief check a bunch of mods for updates - * @param modIDs list of mods (Nexus Mod IDs) to check for updates - * @return - */ - static void checkChunkForUpdate(const std::vector &modIDs, QObject *receiver); - - /** - * @brief query nexus information for every mod and update the "newest version" information - **/ - static int checkAllForUpdate(QObject *receiver); - - /** - * @brief create a new mod from the specified directory and add it to the collection - * @param dir directory to create from - * @return pointer to the info-structure of the newly created/added mod - */ - static ModInfo::Ptr createFrom(const QDir &dir, MOShared::DirectoryEntry **directoryStructure); - - /** - * @brief create a new "foreign-managed" mod from a tuple of plugin and archives - * @param espName name of the plugin - * @param bsaNames names of archives - * @return a new mod - */ - static ModInfo::Ptr createFromPlugin(const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure); - - virtual bool isRegular() const { return false; } - - virtual bool isEmpty() const { return false; } - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - virtual bool updateAvailable() const = 0; - - /** - * @return true if the update currently available is ignored - */ - virtual bool updateIgnored() const = 0; - - /** - * @brief test if the "newest" version of the mod is older than the installed version - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if the newest version is older than the installed one - **/ - virtual bool downgradeAvailable() const = 0; - - /** - * @brief request an update of nexus description for this mod. - * - * This requests mod information from the nexus. This is an asynchronous request, - * so there is no immediate effect of this call. - * Right now, Mod Organizer interprets the "newest version" and "description" from the - * response, though the description is only stored in memory - * - **/ - virtual bool updateNXMInfo() = 0; - - /** - * @brief assign or unassign the specified category - * - * Every mod can have an arbitrary number of categories assigned to it - * - * @param categoryID id of the category to set - * @param active determines wheter the category is assigned or unassigned - * @note this function does not test whether categoryID actually identifies a valid category - **/ - virtual void setCategory(int categoryID, bool active) = 0; - - /** - * @brief change the notes (manually set information) for this mod - * @param notes new notes - */ - virtual void setNotes(const QString ¬es) = 0; - - /** - * @brief set/change the nexus mod id of this mod - * - * @param modID the nexus mod id - **/ - virtual void setNexusID(int modID) = 0; - - /** - * @brief set/change the version of this mod - * @param version new version of the mod - */ - virtual void setVersion(const MOBase::VersionInfo &version); - - /** - * @brief set the newest version of this mod on the nexus - * - * this can be used to overwrite the version of a mod without actually - * updating the mod - * - * @param version the new version to use - * @todo this function should be made obsolete. All queries for mod information should go through - * this class so no public function for this change is required - **/ - virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; - - /** - * @brief changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString &description) = 0; - - /** - * @brief sets the category id from a nexus category id. Conversion to MO id happens internally - * @param categoryID the nexus category id - * @note if a mapping is not possible, the category is set to the default value - */ - virtual void addNexusCategory(int categoryID) = 0; - - /** - * update the endorsement state for the mod. This only changes the - * buffered state, it does not sync with Nexus - * @param endorsed the new endorsement state - */ - virtual void setIsEndorsed(bool endorsed) = 0; - - /** - * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed - */ - virtual void setNeverEndorse() = 0; - - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - virtual bool remove() = 0; - - /** - * @brief endorse or un-endorse the mod. This will sync with nexus! - * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. - * @note if doEndorse doesn't differ from the current value, nothing happens. - */ - virtual void endorse(bool doEndorse) = 0; - - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches() {} - - /** - * @brief getter for the mod name - * - * @return the mod name - **/ - virtual QString name() const = 0; - - /** - * @brief getter for the mod path - * - * @return the (absolute) path to the mod - **/ - virtual QString absolutePath() const = 0; - - /** - * @brief getter for the installation file - * - * @return file used to install this mod from - */ - virtual QString getInstallationFile() const = 0; - - /** - * @return version object for machine based comparisons - **/ - virtual MOBase::VersionInfo getVersion() const { return m_Version; } - - /** - * @brief getter for the newest version number of this mod - * - * @return newest version of the mod - **/ - virtual MOBase::VersionInfo getNewestVersion() const = 0; - - /** - * @brief ignore the newest version for updates - */ - virtual void ignoreUpdate(bool ignore) = 0; - - /** - * @brief getter for the nexus mod id - * - * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist - **/ - virtual int getNexusID() const = 0; - - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods - * or INT_MAX to force priority above all user-modifiables - */ - virtual int getFixedPriority() const = 0; - - /** - * @return true if the mod is always enabled - */ - virtual bool alwaysEnabled() const { return false; } - - /** - * @return true if the mod can be updated - */ - virtual bool canBeUpdated() const { return false; } - - /** - * @return true if the mod can be enabled/disabled - */ - virtual bool canBeEnabled() const { return false; } - - /** - * @return a list of flags for this mod - */ - virtual std::vector getFlags() const = 0; - - /** - * @brief test if the specified flag is set for this mod - * @param flag the flag to test - * @return true if the flag is set, false otherwise - */ - bool hasFlag(EFlag flag) const; - - /** - * @return an indicator if and how this mod should be highlighted by the UI - */ - virtual int getHighlight() const { return HIGHLIGHT_NONE; } - - /** - * @return list of names of ini tweaks - **/ - virtual std::vector getIniTweaks() const = 0; - - /** - * @return a description about the mod, to be displayed in the ui - */ - virtual QString getDescription() const = 0; - - /** - * @return notes for this mod - */ - virtual QString notes() const = 0; - - /** - * @return creation time of this mod - */ - virtual QDateTime creationTime() const = 0; - - /** - * @return nexus description of the mod (html) - */ - virtual QString getNexusDescription() const = 0; - - /** - * @return last time nexus was queried for infos on this mod - */ - virtual QDateTime getLastNexusQuery() const = 0; - - /** - * @return a list of files that, if they exist in the data directory are treated as files in THIS mod - */ - virtual QStringList stealFiles() const { return QStringList(); } - - /** - * @return a list of archives belonging to this mod (as absolute file paths) - */ - virtual QStringList archives() const = 0; - - /** - * @brief test if the mod belongs to the specified category - * - * @param categoryID the category to test for. - * @return true if the mod belongs to the specified category - * @note this does not verify the id actually identifies a category - **/ - bool categorySet(int categoryID) const; - - /** - * @brief retrive the whole list of categories (as ids) this mod belongs to - * - * @return list of categories - **/ - const std::set &getCategories() const { return m_Categories; } - - /** - * @return id of the primary category of this mod - */ - int getPrimaryCategory() const { return m_PrimaryCategory; } - - /** - * @brief sets the new primary category of the mod - * @param categoryID the category to set - */ - virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; } - - /** - * @return true if this mod is considered "valid", that is: it contains data used by the game - **/ - bool isValid() const { return m_Valid; } - - /** - * @return true if the file has been endorsed on nexus - */ - virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } - - /** - * @brief updates the valid-flag for this mod - */ - void testValid(); - - /** - * @brief reads meta information from disk - */ - virtual void readMeta() {} - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta() {} - - /** - * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - * @return - */ - virtual std::set getModOverwrite() { return std::set(); } - - /** - * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed - * @return - */ - virtual std::set getModOverwritten() { return std::set(); } - -signals: - - /** - * @brief emitted whenever the information of a mod changes - * - * @param success true if the mod details were updated successfully, false if not - **/ - void modDetailsUpdated(bool success); - -protected: - - ModInfo(); - - static void updateIndices(); - -private: - - static void createFromOverwrite(); - -protected: - - static std::vector s_Collection; - static std::map s_ModsByName; - - int m_PrimaryCategory; - std::set m_Categories; - - MOBase::VersionInfo m_Version; - -private: - - static QMutex s_Mutex; - static std::map > s_ModsByModID; - static int s_NextID; - - bool m_Valid; - -}; - - -class ModInfoWithConflictInfo : public ModInfo -{ - -public: - - ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure); - - std::vector getFlags() const; - - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches(); - - virtual std::set getModOverwrite() { return m_OverwriteList; } - - virtual std::set getModOverwritten() { return m_OverwrittenList; } - -private: - - enum EConflictType { - CONFLICT_NONE, - CONFLICT_OVERWRITE, - CONFLICT_OVERWRITTEN, - CONFLICT_MIXED, - CONFLICT_REDUNDANT - }; - -private: - - /** - * @return true if there is a conflict for files in this mod - */ - EConflictType isConflicted() const; - - /** - * @return true if this mod is completely replaced by others - */ - bool isRedundant() const; - -private: - - MOShared::DirectoryEntry **m_DirectoryStructure; - - mutable EConflictType m_CurrentConflictState; - mutable QTime m_LastConflictCheck; - - mutable std::set m_OverwriteList; // indices of mods overritten by this mod - mutable std::set m_OverwrittenList; // indices of mods overwriting this mod - -}; - - -/** - * @brief Represents meta information about a single mod. - * - * Represents meta information about a single mod. The class interface is used - * to manage the mod collection - * - **/ -class ModInfoRegular : public ModInfoWithConflictInfo -{ - - Q_OBJECT - - friend class ModInfo; - -public: - - ~ModInfoRegular(); - - virtual bool isRegular() const { return true; } - - virtual bool isEmpty() const; - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - bool updateAvailable() const; - - /** - * @return true if the current update is being ignored - */ - virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - bool downgradeAvailable() const; - - /** - * @brief request an update of nexus description for this mod. - * - * This requests mod information from the nexus. This is an asynchronous request, - * so there is no immediate effect of this call. - * - * @return returns true if information for this mod will be updated, false if there is no nexus mod id to use - **/ - bool updateNXMInfo(); - - /** - * @brief assign or unassign the specified category - * - * Every mod can have an arbitrary number of categories assigned to it - * - * @param categoryID id of the category to set - * @param active determines wheter the category is assigned or unassigned - * @note this function does not test whether categoryID actually identifies a valid category - **/ - void setCategory(int categoryID, bool active); - - /** - * @brief set the name of this mod - * - * set the name of this mod. This will also update the name of the - * directory that contains this mod - * - * @param name new name of the mod - * @return true on success, false if the new name can't be used (i.e. because the new - * directory name wouldn't be valid) - **/ - bool setName(const QString &name); - - /** - * @brief change the notes (manually set information) for this mod - * @param notes new notes - */ - void setNotes(const QString ¬es); - - /** - * @brief set/change the nexus mod id of this mod - * - * @param modID the nexus mod id - **/ - void setNexusID(int modID); - - /** - * @brief set the version of this mod - * - * this can be used to overwrite the version of a mod without actually - * updating the mod - * - * @param version the new version to use - **/ - void setVersion(const MOBase::VersionInfo &version); - - /** - * @brief set the newest version of this mod on the nexus - * - * this can be used to overwrite the version of a mod without actually - * updating the mod - * - * @param version the new version to use - * @todo this function should be made obsolete. All queries for mod information should go through - * this class so no public function for this change is required - **/ - void setNewestVersion(const MOBase::VersionInfo &version); - - /** - * @brief changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString &description); - - /** - * @brief sets the category id from a nexus category id. Conversion to MO id happens internally - * @param categoryID the nexus category id - * @note if a mapping is not possible, the category is set to the default value - */ - virtual void addNexusCategory(int categoryID); - - /** - * @brief sets the new primary category of the mod - * @param categoryID the category to set - */ - virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; } - - /** - * update the endorsement state for the mod. This only changes the - * buffered state, it does not sync with Nexus - * @param endorsed the new endorsement state - */ - virtual void setIsEndorsed(bool endorsed); - - /** - * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed - */ - virtual void setNeverEndorse(); - - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - bool remove(); - - /** - * @brief endorse or un-endorse the mod - * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. - * @note if doEndorse doesn't differ from the current value, nothing happens. - */ - virtual void endorse(bool doEndorse); - - /** - * @brief getter for the mod name - * - * @return the mod name - **/ - QString name() const { return m_Name; } - - /** - * @brief getter for the mod path - * - * @return the (absolute) path to the mod - **/ - QString absolutePath() const; - - /** - * @brief getter for the newest version number of this mod - * - * @return newest version of the mod - **/ - MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } - - /** - * @brief ignore the newest version for updates - */ - void ignoreUpdate(bool ignore); - - /** - * @brief getter for the installation file - * - * @return file used to install this mod from - */ - virtual QString getInstallationFile() const { return m_InstallationFile; } - /** - * @brief getter for the nexus mod id - * - * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist - **/ - int getNexusID() const { return m_NexusID; } - - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable - */ - virtual int getFixedPriority() const { return INT_MIN; } - - /** - * @return true if the mod can be updated - */ - virtual bool canBeUpdated() const { return m_NexusID >= 0; } - - /** - * @return true if the mod can be enabled/disabled - */ - virtual bool canBeEnabled() const { return true; } - - /** - * @return a list of flags for this mod - */ - virtual std::vector getFlags() const; - - /** - * @return an indicator if and how this mod should be highlighted by the UI - */ - virtual int getHighlight() const; - - /** - * @return list of names of ini tweaks - **/ - std::vector getIniTweaks() const; - - /** - * @return a description about the mod, to be displayed in the ui - */ - virtual QString getDescription() const; - - /** - * @return manually set notes for this mod - */ - virtual QString notes() const; - - /** - * @return time this mod was created (file time of the directory) - */ - virtual QDateTime creationTime() const; - - /** - * @return nexus description of the mod (html) - */ - QString getNexusDescription() const; - - /** - * @return true if the file has been endorsed on nexus - */ - virtual EEndorsedState endorsedState() const; - - /** - * @return last time nexus was queried for infos on this mod - */ - virtual QDateTime getLastNexusQuery() const; - - virtual QStringList archives() const; - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta(); - - void readMeta(); - -private slots: - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); - void nxmEndorsementToggled(int, QVariant userData, QVariant resultData); - void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage); - -protected: - - ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); - -private: - - QString m_Name; - QString m_Path; - QString m_InstallationFile; - QString m_Notes; - QString m_NexusDescription; - - QDateTime m_CreationTime; - QDateTime m_LastNexusQuery; - - int m_NexusID; - - bool m_MetaInfoChanged; - MOBase::VersionInfo m_NewestVersion; - MOBase::VersionInfo m_IgnoredVersion; - - EEndorsedState m_EndorsedState; - - NexusBridge m_NexusBridge; - -}; - - -class ModInfoBackup : public ModInfoRegular -{ - - friend class ModInfo; - -public: - - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setNexusID(int) {} - virtual void endorse(bool) {} - virtual int getFixedPriority() const { return -1; } - virtual void ignoreUpdate(bool) {} - virtual bool canBeUpdated() const { return false; } - virtual bool canBeEnabled() const { return false; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void getNexusFiles(QList::const_iterator&, - QList::const_iterator&) {} - virtual QString getNexusDescription() const { return QString(); } - -private: - - ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); - -}; - - -class ModInfoOverwrite : public ModInfo -{ - - Q_OBJECT - - friend class ModInfo; - -public: - - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setCategory(int, bool) {} - virtual bool setName(const QString&) { return false; } - virtual void setNotes(const QString&) {} - virtual void setNexusID(int) {} - virtual void setNewestVersion(const MOBase::VersionInfo&) {} - virtual void ignoreUpdate(bool) {} - virtual void setNexusDescription(const QString&) {} - virtual void addNexusCategory(int) {} - virtual void setIsEndorsed(bool) {} - virtual void setNeverEndorse() {} - virtual bool remove() { return false; } - virtual void endorse(bool) {} - virtual bool alwaysEnabled() const { return true; } - virtual bool isEmpty() const; - virtual QString name() const { return "Overwrite"; } - virtual QString notes() const { return ""; } - virtual QDateTime creationTime() const { return m_StartupTime; } - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return ""; } - virtual QString getInstallationFile() const { return ""; } - virtual int getFixedPriority() const { return INT_MAX; } - virtual int getNexusID() const { return -1; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual int getHighlight() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual QString getNexusDescription() const { return QString(); } - virtual QStringList archives() const; - -private: - - ModInfoOverwrite(); - -private: - - QDateTime m_StartupTime; - -}; - - -class ModInfoForeign : public ModInfoWithConflictInfo -{ - - Q_OBJECT - - friend class ModInfo; - -public: - - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setCategory(int, bool) {} - virtual bool setName(const QString&) { return false; } - virtual void setNotes(const QString&) {} - virtual void setNexusID(int) {} - virtual void setNewestVersion(const MOBase::VersionInfo&) {} - virtual void ignoreUpdate(bool) {} - virtual void setNexusDescription(const QString&) {} - virtual void addNexusCategory(int) {} - virtual void setIsEndorsed(bool) {} - virtual void setNeverEndorse() {} - virtual bool remove() { return false; } - virtual void endorse(bool) {} - virtual bool isEmpty() const { return false; } - virtual QString name() const; - virtual QString notes() const { return ""; } - virtual QDateTime creationTime() const; - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return ""; } - virtual QString getInstallationFile() const { return ""; } - virtual int getNexusID() const { return -1; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual int getHighlight() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual QString getNexusDescription() const { return QString(); } - virtual int getFixedPriority() const { return INT_MIN; } - virtual QStringList archives() const { return m_Archives; } - virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } - virtual bool alwaysEnabled() const { return true; } - -protected: - - ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure); - -private: - - QString m_Name; - QString m_ReferenceFile; - QStringList m_Archives; - QDateTime m_CreationTime; - int m_Priority; - -}; - -#endif // MODINFO_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MODINFO_H +#define MODINFO_H + +#include "nexusinterface.h" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using MOBase::ModRepositoryFileInfo; + +/** + * @brief Represents meta information about a single mod. + * + * Represents meta information about a single mod. The class interface is used + * to manage the mod collection + * + **/ +class ModInfo : public QObject, public MOBase::IModInterface +{ + + Q_OBJECT + +public: + + typedef QSharedPointer Ptr; + + static QString s_HiddenExt; + + enum EFlag { + FLAG_INVALID, + FLAG_BACKUP, + FLAG_OVERWRITE, + FLAG_FOREIGN, + FLAG_NOTENDORSED, + FLAG_NOTES, + FLAG_CONFLICT_OVERWRITE, + FLAG_CONFLICT_OVERWRITTEN, + FLAG_CONFLICT_MIXED, + FLAG_CONFLICT_REDUNDANT + }; + + enum EHighlight { + HIGHLIGHT_NONE = 0, + HIGHLIGHT_INVALID = 1, + HIGHLIGHT_CENTER = 2, + HIGHLIGHT_IMPORTANT = 4 + }; + + enum EEndorsedState { + ENDORSED_FALSE, + ENDORSED_TRUE, + ENDORSED_UNKNOWN, + ENDORSED_NEVER + }; + +public: + + /** + * @brief read the mod directory and Mod ModInfo objects for all subdirectories + **/ + static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure, bool displayForeign); + + static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } + + /** + * @brief retrieve the number of mods + * + * @return number of mods + **/ + static unsigned int getNumMods(); + + /** + * @brief retrieve a ModInfo object based on its index + * + * @param index the index to look up. the maximum is getNumMods() - 1 + * @return a reference counting pointer to the mod info. + * @note since the pointer is reference counting, the pointer remains valid even if the collection is refreshed in a different thread + **/ + static ModInfo::Ptr getByIndex(unsigned int index); + + /** + * @brief retrieve a ModInfo object based on its nexus mod id + * + * @param modID the nexus mod id to look up + * @return a reference counting pointer to the mod info + * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, + * this function will return only one of them + **/ + static std::vector getByModID(int modID); + + /** + * @brief remove a mod by index + * + * this physically deletes the specified mod from the disc and updates the ModInfo collection + * but not other structures that reference mods + * @param index index of the mod to delete + * @return true if removal was successful, fals otherwise + **/ + static bool removeMod(unsigned int index); + + /** + * @brief retrieve the mod index by the mod name + * + * @param name name of the mod to look up + * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned + **/ + static unsigned int getIndex(const QString &name); + + /** + * @brief find the first mod that fulfills the filter function (after no particular order) + * @param filter a function to filter by. should return true for a match + * @return index of the matching mod or UINT_MAX if there wasn't a match + */ + static unsigned int findMod(const boost::function &filter); + + /** + * @brief check a bunch of mods for updates + * @param modIDs list of mods (Nexus Mod IDs) to check for updates + * @return + */ + static void checkChunkForUpdate(const std::vector &modIDs, QObject *receiver); + + /** + * @brief query nexus information for every mod and update the "newest version" information + **/ + static int checkAllForUpdate(QObject *receiver); + + /** + * @brief create a new mod from the specified directory and add it to the collection + * @param dir directory to create from + * @return pointer to the info-structure of the newly created/added mod + */ + static ModInfo::Ptr createFrom(const QDir &dir, MOShared::DirectoryEntry **directoryStructure); + + /** + * @brief create a new "foreign-managed" mod from a tuple of plugin and archives + * @param espName name of the plugin + * @param bsaNames names of archives + * @return a new mod + */ + static ModInfo::Ptr createFromPlugin(const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure); + + virtual bool isRegular() const { return false; } + + virtual bool isEmpty() const { return false; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + virtual bool updateAvailable() const = 0; + + /** + * @return true if the update currently available is ignored + */ + virtual bool updateIgnored() const = 0; + + /** + * @brief test if the "newest" version of the mod is older than the installed version + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if the newest version is older than the installed one + **/ + virtual bool downgradeAvailable() const = 0; + + /** + * @brief request an update of nexus description for this mod. + * + * This requests mod information from the nexus. This is an asynchronous request, + * so there is no immediate effect of this call. + * Right now, Mod Organizer interprets the "newest version" and "description" from the + * response, though the description is only stored in memory + * + **/ + virtual bool updateNXMInfo() = 0; + + /** + * @brief assign or unassign the specified category + * + * Every mod can have an arbitrary number of categories assigned to it + * + * @param categoryID id of the category to set + * @param active determines wheter the category is assigned or unassigned + * @note this function does not test whether categoryID actually identifies a valid category + **/ + virtual void setCategory(int categoryID, bool active) = 0; + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + virtual void setNotes(const QString ¬es) = 0; + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + virtual void setNexusID(int modID) = 0; + + /** + * @brief set/change the version of this mod + * @param version new version of the mod + */ + virtual void setVersion(const MOBase::VersionInfo &version); + + /** + * @brief set the newest version of this mod on the nexus + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + * @todo this function should be made obsolete. All queries for mod information should go through + * this class so no public function for this change is required + **/ + virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description) = 0; + + /** + * @brief sets the category id from a nexus category id. Conversion to MO id happens internally + * @param categoryID the nexus category id + * @note if a mapping is not possible, the category is set to the default value + */ + virtual void addNexusCategory(int categoryID) = 0; + + /** + * update the endorsement state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param endorsed the new endorsement state + */ + virtual void setIsEndorsed(bool endorsed) = 0; + + /** + * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed + */ + virtual void setNeverEndorse() = 0; + + /** + * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices + * @return true if the mod was successfully removed + **/ + virtual bool remove() = 0; + + /** + * @brief endorse or un-endorse the mod. This will sync with nexus! + * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. + * @note if doEndorse doesn't differ from the current value, nothing happens. + */ + virtual void endorse(bool doEndorse) = 0; + + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches() {} + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + virtual QString name() const = 0; + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + virtual QString absolutePath() const = 0; + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const = 0; + + /** + * @return version object for machine based comparisons + **/ + virtual MOBase::VersionInfo getVersion() const { return m_Version; } + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + virtual MOBase::VersionInfo getNewestVersion() const = 0; + + /** + * @brief ignore the newest version for updates + */ + virtual void ignoreUpdate(bool ignore) = 0; + + /** + * @brief getter for the nexus mod id + * + * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist + **/ + virtual int getNexusID() const = 0; + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods + * or INT_MAX to force priority above all user-modifiables + */ + virtual int getFixedPriority() const = 0; + + /** + * @return true if the mod is always enabled + */ + virtual bool alwaysEnabled() const { return false; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return false; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return false; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector getFlags() const = 0; + + /** + * @brief test if the specified flag is set for this mod + * @param flag the flag to test + * @return true if the flag is set, false otherwise + */ + bool hasFlag(EFlag flag) const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const { return HIGHLIGHT_NONE; } + + /** + * @return list of names of ini tweaks + **/ + virtual std::vector getIniTweaks() const = 0; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const = 0; + + /** + * @return notes for this mod + */ + virtual QString notes() const = 0; + + /** + * @return creation time of this mod + */ + virtual QDateTime creationTime() const = 0; + + /** + * @return nexus description of the mod (html) + */ + virtual QString getNexusDescription() const = 0; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const = 0; + + /** + * @return a list of files that, if they exist in the data directory are treated as files in THIS mod + */ + virtual QStringList stealFiles() const { return QStringList(); } + + /** + * @return a list of archives belonging to this mod (as absolute file paths) + */ + virtual QStringList archives() const = 0; + + /** + * @brief test if the mod belongs to the specified category + * + * @param categoryID the category to test for. + * @return true if the mod belongs to the specified category + * @note this does not verify the id actually identifies a category + **/ + bool categorySet(int categoryID) const; + + /** + * @brief retrive the whole list of categories (as ids) this mod belongs to + * + * @return list of categories + **/ + const std::set &getCategories() const { return m_Categories; } + + /** + * @return id of the primary category of this mod + */ + int getPrimaryCategory() const { return m_PrimaryCategory; } + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; } + + /** + * @return true if this mod is considered "valid", that is: it contains data used by the game + **/ + bool isValid() const { return m_Valid; } + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } + + /** + * @brief updates the valid-flag for this mod + */ + void testValid(); + + /** + * @brief reads meta information from disk + */ + virtual void readMeta() {} + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta() {} + + /** + * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed + * @return + */ + virtual std::set getModOverwrite() { return std::set(); } + + /** + * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed + * @return + */ + virtual std::set getModOverwritten() { return std::set(); } + +signals: + + /** + * @brief emitted whenever the information of a mod changes + * + * @param success true if the mod details were updated successfully, false if not + **/ + void modDetailsUpdated(bool success); + +protected: + + ModInfo(); + + static void updateIndices(); + +private: + + static void createFromOverwrite(); + +protected: + + static std::vector s_Collection; + static std::map s_ModsByName; + + int m_PrimaryCategory; + std::set m_Categories; + + MOBase::VersionInfo m_Version; + +private: + + static QMutex s_Mutex; + static std::map > s_ModsByModID; + static int s_NextID; + + bool m_Valid; + +}; + + +class ModInfoWithConflictInfo : public ModInfo +{ + +public: + + ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure); + + std::vector getFlags() const; + + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches(); + + virtual std::set getModOverwrite() { return m_OverwriteList; } + + virtual std::set getModOverwritten() { return m_OverwrittenList; } + +private: + + enum EConflictType { + CONFLICT_NONE, + CONFLICT_OVERWRITE, + CONFLICT_OVERWRITTEN, + CONFLICT_MIXED, + CONFLICT_REDUNDANT + }; + +private: + + /** + * @return true if there is a conflict for files in this mod + */ + EConflictType isConflicted() const; + + /** + * @return true if this mod is completely replaced by others + */ + bool isRedundant() const; + +private: + + MOShared::DirectoryEntry **m_DirectoryStructure; + + mutable EConflictType m_CurrentConflictState; + mutable QTime m_LastConflictCheck; + + mutable std::set m_OverwriteList; // indices of mods overritten by this mod + mutable std::set m_OverwrittenList; // indices of mods overwriting this mod + +}; + + +/** + * @brief Represents meta information about a single mod. + * + * Represents meta information about a single mod. The class interface is used + * to manage the mod collection + * + **/ +class ModInfoRegular : public ModInfoWithConflictInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + ~ModInfoRegular(); + + virtual bool isRegular() const { return true; } + + virtual bool isEmpty() const; + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + bool updateAvailable() const; + + /** + * @return true if the current update is being ignored + */ + virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + bool downgradeAvailable() const; + + /** + * @brief request an update of nexus description for this mod. + * + * This requests mod information from the nexus. This is an asynchronous request, + * so there is no immediate effect of this call. + * + * @return returns true if information for this mod will be updated, false if there is no nexus mod id to use + **/ + bool updateNXMInfo(); + + /** + * @brief assign or unassign the specified category + * + * Every mod can have an arbitrary number of categories assigned to it + * + * @param categoryID id of the category to set + * @param active determines wheter the category is assigned or unassigned + * @note this function does not test whether categoryID actually identifies a valid category + **/ + void setCategory(int categoryID, bool active); + + /** + * @brief set the name of this mod + * + * set the name of this mod. This will also update the name of the + * directory that contains this mod + * + * @param name new name of the mod + * @return true on success, false if the new name can't be used (i.e. because the new + * directory name wouldn't be valid) + **/ + bool setName(const QString &name); + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + void setNotes(const QString ¬es); + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + void setNexusID(int modID); + + /** + * @brief set the version of this mod + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + **/ + void setVersion(const MOBase::VersionInfo &version); + + /** + * @brief set the newest version of this mod on the nexus + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + * @todo this function should be made obsolete. All queries for mod information should go through + * this class so no public function for this change is required + **/ + void setNewestVersion(const MOBase::VersionInfo &version); + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description); + + /** + * @brief sets the category id from a nexus category id. Conversion to MO id happens internally + * @param categoryID the nexus category id + * @note if a mapping is not possible, the category is set to the default value + */ + virtual void addNexusCategory(int categoryID); + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; } + + /** + * update the endorsement state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param endorsed the new endorsement state + */ + virtual void setIsEndorsed(bool endorsed); + + /** + * set the mod to "i don't intend to endorse". The mod will not show as unendorsed but can still be endorsed + */ + virtual void setNeverEndorse(); + + /** + * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices + * @return true if the mod was successfully removed + **/ + bool remove(); + + /** + * @brief endorse or un-endorse the mod + * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. + * @note if doEndorse doesn't differ from the current value, nothing happens. + */ + virtual void endorse(bool doEndorse); + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + QString name() const { return m_Name; } + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + QString absolutePath() const; + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } + + /** + * @brief ignore the newest version for updates + */ + void ignoreUpdate(bool ignore); + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const { return m_InstallationFile; } + /** + * @brief getter for the nexus mod id + * + * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist + **/ + int getNexusID() const { return m_NexusID; } + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable + */ + virtual int getFixedPriority() const { return INT_MIN; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return m_NexusID >= 0; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return true; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector getFlags() const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const; + + /** + * @return list of names of ini tweaks + **/ + std::vector getIniTweaks() const; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const; + + /** + * @return manually set notes for this mod + */ + virtual QString notes() const; + + /** + * @return time this mod was created (file time of the directory) + */ + virtual QDateTime creationTime() const; + + /** + * @return nexus description of the mod (html) + */ + QString getNexusDescription() const; + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const; + + virtual QStringList archives() const; + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta(); + + void readMeta(); + +private slots: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); + void nxmEndorsementToggled(int, QVariant userData, QVariant resultData); + void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage); + +protected: + + ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); + +private: + + QString m_Name; + QString m_Path; + QString m_InstallationFile; + QString m_Notes; + QString m_NexusDescription; + + QDateTime m_CreationTime; + QDateTime m_LastNexusQuery; + + int m_NexusID; + + bool m_MetaInfoChanged; + MOBase::VersionInfo m_NewestVersion; + MOBase::VersionInfo m_IgnoredVersion; + + EEndorsedState m_EndorsedState; + + NexusBridge m_NexusBridge; + +}; + + +class ModInfoBackup : public ModInfoRegular +{ + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setNexusID(int) {} + virtual void endorse(bool) {} + virtual int getFixedPriority() const { return -1; } + virtual void ignoreUpdate(bool) {} + virtual bool canBeUpdated() const { return false; } + virtual bool canBeEnabled() const { return false; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void getNexusFiles(QList::const_iterator&, + QList::const_iterator&) {} + virtual QString getNexusDescription() const { return QString(); } + +private: + + ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); + +}; + + +class ModInfoOverwrite : public ModInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool alwaysEnabled() const { return true; } + virtual bool isEmpty() const; + virtual QString name() const { return "Overwrite"; } + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const { return m_StartupTime; } + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return ""; } + virtual QString getInstallationFile() const { return ""; } + virtual int getFixedPriority() const { return INT_MAX; } + virtual int getNexusID() const { return -1; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual QString getNexusDescription() const { return QString(); } + virtual QStringList archives() const; + +private: + + ModInfoOverwrite(); + +private: + + QDateTime m_StartupTime; + +}; + + +class ModInfoForeign : public ModInfoWithConflictInfo +{ + + Q_OBJECT + + friend class ModInfo; + +public: + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool isEmpty() const { return false; } + virtual QString name() const; + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const; + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return ""; } + virtual QString getInstallationFile() const { return ""; } + virtual int getNexusID() const { return -1; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual QString getNexusDescription() const { return QString(); } + virtual int getFixedPriority() const { return INT_MIN; } + virtual QStringList archives() const { return m_Archives; } + virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } + virtual bool alwaysEnabled() const { return true; } + +protected: + + ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure); + +private: + + QString m_Name; + QString m_ReferenceFile; + QStringList m_Archives; + QDateTime m_CreationTime; + int m_Priority; + +}; + +#endif // MODINFO_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 96a575a0..d22fe138 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1,1256 +1,1256 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modinfodialog.h" -#include "ui_modinfodialog.h" - -#include "report.h" -#include "utility.h" -#include "messagedialog.h" -#include "bbcode.h" -#include "questionboxmemory.h" -#include "settings.h" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -class ModFileListWidget : public QListWidgetItem { - friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); -public: - ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) - : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} -private: - int m_SortValue; -}; - - -bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) -{ - return LHS.m_SortValue < RHS.m_SortValue; -} - - -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent) - : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), - m_ThumbnailMapper(this), m_RequestStarted(false), - m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL), - m_Directory(directory), m_Origin(NULL) -{ - ui->setupUi(this); - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); - - m_RootPath = modInfo->absolutePath(); - - QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); - m_Settings = new QSettings(metaFileName, QSettings::IniFormat); - - QLineEdit *modIDEdit = findChild("modIDEdit"); - ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); - ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); - - ui->notesEdit->setText(modInfo->notes()); - - connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); - connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); - connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); - connect(ui->descriptionView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - - if (directory->originExists(ToWString(modInfo->name()))) { - m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = NULL; - } - } - - if (unmanaged) { - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - } else { - initFiletree(modInfo); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); - refreshPrimaryCategoriesBox(); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); - ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); - } - refreshLists(); - initINITweaks(); - - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL); - - if (ui->tabWidget->currentIndex() == TAB_NEXUS) { - activateNexusTab(); - } - - ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || - (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); - - // activate first enabled tab - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->isTabEnabled(i)) { - ui->tabWidget->setCurrentIndex(i); - break; - } - } -} - - -ModInfoDialog::~ModInfoDialog() -{ - m_ModInfo->setNotes(ui->notesEdit->toPlainText()); - saveCategories(ui->categoriesTree->invisibleRootItem()); - saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo - delete ui; - delete m_Settings; -} - - -void ModInfoDialog::initINITweaks() -{ - int numTweaks = m_Settings->beginReadArray("INI Tweaks"); - for (int i = 0; i < numTweaks; ++i) { - m_Settings->setArrayIndex(i); - QList items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); - if (items.size() != 0) { - items.at(0)->setCheckState(Qt::Checked); - } - } - m_Settings->endArray(); -} - -void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) -{ - ui->fileTree = findChild("fileTree"); - - m_FileSystemModel = new QFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - m_FileSystemModel->setRootPath(m_RootPath); - ui->fileTree->setModel(m_FileSystemModel); - ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); - ui->fileTree->setColumnWidth(0, 300); - - m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); - m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); - m_HideAction = new QAction(tr("&Hide"), ui->fileTree); - m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - m_OpenAction = new QAction(tr("&Open"), ui->fileTree); - m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); - QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); - QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); - connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); -} - - -int ModInfoDialog::tabIndex(const QString &tabId) -{ - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } - } - return -1; -} - - -void ModInfoDialog::restoreTabState(const QByteArray &state) -{ - QDataStream stream(state); - int count = 0; - stream >> count; - - QStringList tabIds; - - // first, only determine the new mapping - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId; - stream >> tabId; - tabIds.append(tabId); - int oldPos = tabIndex(tabId); - if (oldPos != -1) { - m_RealTabPos[newPos] = oldPos; - } else { - m_RealTabPos[newPos] = newPos; - } - } - // then actually move the tabs - QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad - ui->tabWidget->blockSignals(true); - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId = tabIds.at(newPos); - int oldPos = tabIndex(tabId); - tabBar->moveTab(oldPos, newPos); - } - ui->tabWidget->blockSignals(false); -} - - -QByteArray ModInfoDialog::saveTabState() const -{ - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); - } - - return result; -} - - -void ModInfoDialog::refreshLists() -{ - int numNonConflicting = 0; - int numOverwrite = 0; - int numOverwritten = 0; - - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - - if (m_Origin != NULL) { - std::vector files = m_Origin->getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath())); - QString fileName = relativeName.mid(0).prepend(m_RootPath); - bool archive; - if ((*iter)->getOrigin(archive) == m_Origin->getID()) { - std::vector alternatives = (*iter)->getAlternatives(); - if (!alternatives.empty()) { - std::wostringstream altString; - for (std::vector::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << ", "; - } - altString << m_Directory->getOriginByID(*altIter).getName(); - } - QStringList fields(relativeName.prepend("...")); - fields.append(ToQString(altString.str())); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives[0]).getName())); - item->setData(1, Qt::UserRole + 1, alternatives[0]); - item->setData(1, Qt::UserRole + 2, archive); - ui->overwriteTree->addTopLevelItem(item); - ++numOverwrite; - } else {// otherwise don't display the file - ++numNonConflicting; - } - } else { - FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive)); - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - QTreeWidgetItem *item = new QTreeWidgetItem(fields); - item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); - ui->overwrittenTree->addTopLevelItem(item); - ++numOverwritten; - } - } - } - - if (m_RootPath.length() > 0) { - QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); - } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && - !fileName.endsWith("meta.ini")) { - QString namePart = fileName.mid(m_RootPath.length() + 1); - if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { - QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); - newItem->setData(Qt::UserRole, namePart); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newItem); - } else { - ui->iniFileList->addItem(namePart); - } - } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || - fileName.endsWith(".esm", Qt::CaseInsensitive)) { - QString relativePath = fileName.mid(m_RootPath.length() + 1); - if (relativePath.contains('/')) { - QFileInfo fileInfo(fileName); - QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName()); - newItem->setData(Qt::UserRole, relativePath); - ui->inactiveESPList->addItem(newItem); - } else { - ui->activeESPList->addItem(relativePath); - } - } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || - (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { - QImage image = QImage(fileName); - if (!image.isNull()) { - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(128); - } else { - image = image.scaledToHeight(96); - } - - QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); - thumbnailButton->setIconSize(QSize(image.width(), image.height())); - connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); - m_ThumbnailMapper.setMapping(thumbnailButton, fileName); - ui->thumbnailArea->addWidget(thumbnailButton); - } - } - } - } - - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); -} - - -void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) -{ - for (unsigned int i = 0; i < factory.numCategories(); ++i) { - if (factory.getParentID(i) != rootLevel) { - continue; - } - int categoryID = factory.getCategoryID(i); - QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, enabledCategories.find(categoryID) != enabledCategories.end() ? Qt::Checked : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, categoryID); - if (factory.hasChildren(i)) { - addCategories(factory, enabledCategories, newItem, categoryID); - } - root->addChild(newItem); - } -} - - -void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) -{ - for (int i = 0; i < currentNode->childCount(); ++i) { - QTreeWidgetItem *childNode = currentNode->child(i); - m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); - saveCategories(childNode); - } -} - - -void ModInfoDialog::on_closeButton_clicked() -{ - if (allowNavigateFromTXT() && allowNavigateFromINI()) { - this->close(); - } -} - - - -QString ModInfoDialog::getModVersion() const -{ - return m_Settings->value("version", "").toString(); -} - - -const int ModInfoDialog::getModID() const -{ - return m_Settings->value("modid", 0).toInt(); -} - - -void ModInfoDialog::openTab(int tab) -{ - QTabWidget *tabWidget = findChild("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); - } -} - -void ModInfoDialog::thumbnailClicked(const QString &fileName) -{ - QLabel *imageLabel = findChild("imageLabel"); - QImage image(fileName); - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(imageLabel->width()); - } else { - image = image.scaledToHeight(imageLabel->height()); - } - imageLabel->setPixmap(QPixmap::fromImage(image)); -} - - -bool ModInfoDialog::allowNavigateFromTXT() -{ - if (ui->saveTXTButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentTextFile(); - } - } - return true; -} - - -bool ModInfoDialog::allowNavigateFromINI() -{ - if (ui->saveButton->isEnabled()) { - int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentIniFile(); - } - } - return true; -} - - -void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->text(); - - QVariant currentFile = ui->textFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromTXT()) { - openTextFile(fullPath); - } else { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - - -void ModInfoDialog::openTextFile(const QString &fileName) -{ - QString encoding; - ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); - ui->textFileView->setProperty("currentFile", fileName); - ui->textFileView->setProperty("encoding", encoding); - ui->saveTXTButton->setEnabled(false); -} - - -void ModInfoDialog::openIniFile(const QString &fileName) -{ - QFile iniFile(fileName); - iniFile.open(QIODevice::ReadOnly); - QByteArray buffer = iniFile.readAll(); - - QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); - QTextEdit *iniFileView = findChild("iniFileView"); - iniFileView->setText(codec->toUnicode(buffer)); - iniFileView->setProperty("currentFile", fileName); - iniFileView->setProperty("encoding", codec->name()); - iniFile.close(); - - ui->saveButton->setEnabled(false); -} - - -void ModInfoDialog::saveIniTweaks() -{ - m_Settings->beginWriteArray("INI Tweaks"); - - int countEnabled = 0; - for (int i = 0; i < ui->iniTweaksList->count(); ++i) { - if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { - m_Settings->setArrayIndex(countEnabled++); - m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); - } - } - m_Settings->endArray(); -} - - -void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->text(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } -} - - -void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); - - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } - - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } - -} - - -void ModInfoDialog::on_saveButton_clicked() -{ - saveCurrentIniFile(); -} - - -void ModInfoDialog::on_saveTXTButton_clicked() -{ - saveCurrentTextFile(); -} - - -void ModInfoDialog::saveCurrentTextFile() -{ - QVariant fileNameVar = ui->textFileView->property("currentFile"); - QVariant encodingVar = ui->textFileView->property("encoding"); - if (fileNameVar.isValid() && encodingVar.isValid()) { - QString fileName = fileNameVar.toString(); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveTXTButton->setEnabled(false); -} - - -void ModInfoDialog::saveCurrentIniFile() -{ - QVariant fileNameVar = ui->iniFileView->property("currentFile"); - QVariant encodingVar = ui->iniFileView->property("encoding"); - if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { - QString fileName = fileNameVar.toString(); - QDir().mkpath(QFileInfo(fileName).absolutePath()); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveButton->setEnabled(false); -} - - -void ModInfoDialog::on_iniFileView_textChanged() -{ - QPushButton* saveButton = findChild("saveButton"); - saveButton->setEnabled(true); -} - - -void ModInfoDialog::on_textFileView_textChanged() -{ - ui->saveTXTButton->setEnabled(true); -} - - -void ModInfoDialog::on_activateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = inactiveESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QListWidgetItem *selectedItem = inactiveESPList->takeItem(selectedRow); - - QDir root(m_RootPath); - bool renamed = false; - - while (root.exists(selectedItem->text())) { - bool okClicked = false; - QString newName = QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, selectedItem->text(), &okClicked); - if (!okClicked) { - inactiveESPList->insertItem(selectedRow, selectedItem); - return; - } else if (newName.size() > 0) { - selectedItem->setText(newName); - renamed = true; - } - } - - if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { - activeESPList->addItem(selectedItem); - if (renamed) { - selectedItem->setData(Qt::UserRole, QVariant()); - } - } else { - inactiveESPList->insertItem(selectedRow, selectedItem); - reportError(tr("failed to move file")); - } -} - - -void ModInfoDialog::on_deactivateESP_clicked() -{ - QListWidget *activeESPList = findChild("activeESPList"); - QListWidget *inactiveESPList = findChild("inactiveESPList"); - - int selectedRow = activeESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QDir root(m_RootPath); - - QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); - - // if we moved the file from optional to active in this session, we move the file back to - // where it came from. Otherwise, it is moved to the new folder "optional" - if (selectedItem->data(Qt::UserRole).isNull()) { - selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); - if (!root.exists("optional")) { - if (!root.mkdir("optional")) { - reportError(tr("failed to create directory \"optional\"")); - activeESPList->insertItem(selectedRow, selectedItem); - return; - } - } - } - - if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { - inactiveESPList->addItem(selectedItem); - } else { - activeESPList->insertItem(selectedRow, selectedItem); - } -} - -void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) -{ - this->close(); - emit nexusLinkActivated(link); -} - -void ModInfoDialog::linkClicked(const QUrl &url) -{ - if (url.toString().startsWith(ToQString(GameInfo::instance().getNexusPage(false)))) { - this->close(); - emit nexusLinkActivated(url.toString()); - } else { - ::ShellExecuteW(NULL, L"open", ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); - } -} - - -void ModInfoDialog::refreshNexusData(int modID) -{ - if ((!m_RequestStarted) && (modID > 0)) { - m_RequestStarted = true; - - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); - } -} - - -/*void ModInfoDialog::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) -{ - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - QVariantMap result = resultData.toMap(); - - if (!result["description"].isNull()) { - QString descriptionAsHTML = - QString("" - "" - "%1" - "").arg(BBCode::convertToHTML(result["description"].toString())); - -// QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); - ui->descriptionView->setHtml(descriptionAsHTML); - } else { - ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); - } - - QLineEdit *versionEdit = findChild("versionEdit"); - QString version = result["version"].toString(); - - if (!version.isEmpty()) { - m_ModInfo->setNewestVersion(version); - - VersionInfo currentVersion(versionEdit->text()); - VersionInfo newestVersion(version); - - QPalette versionColor; - if (currentVersion < newestVersion) { - versionColor.setColor(QPalette::Text, Qt::red); - versionEdit->setToolTip(tr("Current Version: %1").arg(version)); - } else { - versionColor.setColor(QPalette::Text, Qt::green); - versionEdit->setToolTip(tr("No update available")); - } - versionEdit->setPalette(versionColor); - } -}*/ - - -QString ModInfoDialog::getFileCategory(int categoryID) -{ - switch (categoryID) { - case 1: return tr("Main"); - case 2: return tr("Update"); - case 3: return tr("Optional"); - case 4: return tr("Old"); - case 5: return tr("Misc"); - default: return tr("Unknown"); - } -} - - -void ModInfoDialog::updateVersionColor() -{ -// QPalette versionColor; - if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { - ui->versionEdit->setStyleSheet("color: red"); -// versionColor.setColor(QPalette::Text, Qt::red); - ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); - } else { - ui->versionEdit->setStyleSheet("color: green"); -// versionColor.setColor(QPalette::Text, Qt::green); - ui->versionEdit->setToolTip(tr("No update available")); - } -// ui->versionEdit->setPalette(versionColor); -} - - -void ModInfoDialog::modDetailsUpdated(bool success) -{ - if (success) { - QString nexusDescription = m_ModInfo->getNexusDescription(); - if (!nexusDescription.isEmpty()) { - /* QString input = - "[size=20]sizetest[/size]\r\n" - "[COLOR=yellow]colortest[/COLOR]\r\n" - "[center]centertest[/center]\r\n" - "[quote]quotetest 1[/quote]\r\n" - "[quote=bla]quotetest 2[/quote]\r\n" - "[url]www.skyrimnexus.com[/url]\r\n" - "[url=www.skyrimnexus.com]urltest 2[/url]\r\n" - "[ol]\r\n" - "[li]item 2[/li]" - "[*]item 1\r\n" - "[/ol]\r\n" - "[img]http://www.bbcode.org/images/bbcode_logo.png[/img]\r\n" - "[table][tr][th]headertest1[/th]" - "[th]headertest2[/th][/tr]" - "[tr][td]rowtest11[/td][td]rowtest12[/td][/tr]" - "[tr][td]rowtest21[/td][td]rowtest22[/td][/tr][/table]" - "[email=\"sherb@gmx.net\"]mail me[/email]"; - ui->descriptionView->setHtml(BBCode::convertToHTML(input));*/ - - QString descriptionAsHTML = - QString("" - "" - "%1" - "").arg(BBCode::convertToHTML(nexusDescription)); - - // QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); - ui->descriptionView->setHtml(descriptionAsHTML); - } else { - // ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); - ui->descriptionView->setHtml(tr("(description incomplete, please visit nexus)")); - } - - updateVersionColor(); - } -} - - -void ModInfoDialog::activateNexusTab() -{ - QLineEdit *modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - if (modID != 0) { - QString nexusLink = QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID); - QLabel *visitNexusLabel = findChild("visitNexusLabel"); - visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); - visitNexusLabel->setToolTip(nexusLink); - - - if (m_ModInfo->getNexusDescription().isEmpty() || - QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) { - refreshNexusData(modID); - } else { - this->modDetailsUpdated(true); - } - } - QLineEdit *versionEdit = findChild("versionEdit"); - QString currentVersion = m_Settings->value("version", "0.0").toString(); - versionEdit->setText(currentVersion); -} - - -void ModInfoDialog::on_tabWidget_currentChanged(int index) -{ - if (m_RealTabPos[index] == TAB_NEXUS) { - activateNexusTab(); - } -} - - -void ModInfoDialog::on_modIDEdit_editingFinished() -{ - int oldID = m_Settings->value("modid", 0).toInt(); - int modID = ui->modIDEdit->text().toInt(); - if (oldID != modID){ - m_ModInfo->setNexusID(modID); - - ui->descriptionView->setHtml(""); - if (modID != 0) { - m_RequestStarted = false; - refreshNexusData(modID); - } - } -} - -void ModInfoDialog::on_versionEdit_editingFinished() -{ - VersionInfo version(ui->versionEdit->text()); - m_ModInfo->setVersion(version); - updateVersionColor(); -} - - -bool ModInfoDialog::recursiveDelete(const QModelIndex &index) -{ - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; -} - - -void ModInfoDialog::deleteFile(const QModelIndex &index) -{ - - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); - if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete %1").arg(fileName)); - } -} - - -void ModInfoDialog::deleteTriggered() -{ - if (m_FileSelection.count() == 0) { - return; - } else if (m_FileSelection.count() == 1) { - QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, m_FileSelection) { - deleteFile(index); - } -} - - -void ModInfoDialog::renameTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - - ui->fileTree->edit(index); -} - - -void ModInfoDialog::hideTriggered() -{ - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(path); - } - } -} - - -void ModInfoDialog::unhideTriggered() -{ - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); - iter != m_FileSelection.constEnd(); ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(path); - } - } -} - - -void ModInfoDialog::openFile(const QModelIndex &index) -{ - QString fileName = m_FileSystemModel->filePath(index); - - HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW); - if ((int)res <= 32) { - qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); - } -} - - -void ModInfoDialog::openTriggered() -{ - foreach(QModelIndex idx, m_FileSelection) { - openFile(idx); - } -} - -void ModInfoDialog::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->fileTree->setCurrentIndex(newIndex); - ui->fileTree->edit(newIndex); -} - - -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - -// m_FileSelection = ui->fileTree->indexAt(pos); - QMenu menu(ui->fileTree); - - menu.addAction(m_NewFolderAction); - - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (selectionModel->hasSelection()) { - if (hasFiles) { - menu.addAction(m_OpenAction); - } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(m_UnhideAction); - } else { - menu.addAction(m_HideAction); - } - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); - } - menu.exec(ui->fileTree->mapToGlobal(pos)); -} - - -void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem *item, int) -{ - QTreeWidgetItem *parent = item->parent(); - while ((parent != NULL) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { - parent->setCheckState(0, Qt::Checked); - parent = parent->parent(); - } - refreshPrimaryCategoriesBox(); -} - - -void ModInfoDialog::addCheckedCategories(QTreeWidgetItem *tree) -{ - for (int i = 0; i < tree->childCount(); ++i) { - QTreeWidgetItem *child = tree->child(i); - if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); - addCheckedCategories(child); - } - } -} - - -void ModInfoDialog::refreshPrimaryCategoriesBox() -{ - ui->primaryCategoryBox->clear(); - int primaryCategory = m_ModInfo->getPrimaryCategory(); - addCheckedCategories(ui->categoriesTree->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); - break; - } - } -} - - -void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) -{ - if (index != -1) { - m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); - } -} - - -void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - this->close(); - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); -} - - -bool ModInfoDialog::hideFile(const QString &oldName) -{ - QString newName = oldName + ModInfo::s_HiddenExt; - - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } - - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); - return false; - } -} - - -bool ModInfoDialog::unhideFile(const QString &oldName) -{ - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); - return false; - } -} - - -void ModInfoDialog::hideConflictFile() -{ - if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { - emit originModified(m_Origin->getID()); - refreshLists(); - } -} - - -void ModInfoDialog::unhideConflictFile() -{ - if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { - emit originModified(m_Origin->getID()); - refreshLists(); - } -} - - -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) -{ - m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); - - if (m_ConflictsContextItem != NULL) { - // offer to hide/unhide file, but not for files from archives - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); - } - menu.exec(ui->overwriteTree->mapToGlobal(pos)); - } - } -} - - -void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) -{ - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); - this->accept(); -} - -void ModInfoDialog::on_refreshButton_clicked() -{ - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); -} - -void ModInfoDialog::on_endorseBtn_clicked() -{ - emit endorseMod(m_ModInfo); -} - -void ModInfoDialog::on_nextButton_clicked() -{ - emit modOpenNext(); - this->accept(); -} - -void ModInfoDialog::on_prevButton_clicked() -{ - emit modOpenPrev(); - this->accept(); -} - - -void ModInfoDialog::createTweak() -{ - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); - if (name.isNull()) { - return; - } else if (!fixDirectoryName(name)) { - QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); - return; - } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { - QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); - return; - } - - QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); - newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); - newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); - newTweak->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newTweak); -} - -void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); - menu.exec(ui->iniTweaksList->mapToGlobal(pos)); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modinfodialog.h" +#include "ui_modinfodialog.h" + +#include "report.h" +#include "utility.h" +#include "messagedialog.h" +#include "bbcode.h" +#include "questionboxmemory.h" +#include "settings.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +class ModFileListWidget : public QListWidgetItem { + friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); +public: + ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) + : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} +private: + int m_SortValue; +}; + + +bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) +{ + return LHS.m_SortValue < RHS.m_SortValue; +} + + +ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent) + : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), + m_ThumbnailMapper(this), m_RequestStarted(false), + m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL), + m_Directory(directory), m_Origin(NULL) +{ + ui->setupUi(this); + this->setWindowTitle(modInfo->name()); + this->setWindowModality(Qt::WindowModal); + + m_RootPath = modInfo->absolutePath(); + + QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); + m_Settings = new QSettings(metaFileName, QSettings::IniFormat); + + QLineEdit *modIDEdit = findChild("modIDEdit"); + ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); + ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); + + ui->notesEdit->setText(modInfo->notes()); + + connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); + connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); + connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); + connect(ui->descriptionView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + + if (directory->originExists(ToWString(modInfo->name()))) { + m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); + if (m_Origin->isDisabled()) { + m_Origin = NULL; + } + } + + if (unmanaged) { + ui->tabWidget->setTabEnabled(TAB_INIFILES, false); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); + ui->tabWidget->setTabEnabled(TAB_NEXUS, false); + ui->tabWidget->setTabEnabled(TAB_FILETREE, false); + ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_ESPS, false); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); + ui->tabWidget->setTabEnabled(TAB_IMAGES, false); + } else { + initFiletree(modInfo); + addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); + refreshPrimaryCategoriesBox(); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); + ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); + ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); + } + refreshLists(); + initINITweaks(); + + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL); + + if (ui->tabWidget->currentIndex() == TAB_NEXUS) { + activateNexusTab(); + } + + ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || + (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); + + // activate first enabled tab + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->isTabEnabled(i)) { + ui->tabWidget->setCurrentIndex(i); + break; + } + } +} + + +ModInfoDialog::~ModInfoDialog() +{ + m_ModInfo->setNotes(ui->notesEdit->toPlainText()); + saveCategories(ui->categoriesTree->invisibleRootItem()); + saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo + delete ui; + delete m_Settings; +} + + +void ModInfoDialog::initINITweaks() +{ + int numTweaks = m_Settings->beginReadArray("INI Tweaks"); + for (int i = 0; i < numTweaks; ++i) { + m_Settings->setArrayIndex(i); + QList items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); + if (items.size() != 0) { + items.at(0)->setCheckState(Qt::Checked); + } + } + m_Settings->endArray(); +} + +void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) +{ + ui->fileTree = findChild("fileTree"); + + m_FileSystemModel = new QFileSystemModel(this); + m_FileSystemModel->setReadOnly(false); + m_FileSystemModel->setRootPath(m_RootPath); + ui->fileTree->setModel(m_FileSystemModel); + ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); + ui->fileTree->setColumnWidth(0, 300); + + m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); + m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); + m_HideAction = new QAction(tr("&Hide"), ui->fileTree); + m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); + m_OpenAction = new QAction(tr("&Open"), ui->fileTree); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); + QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); + QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); + QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); + connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); +} + + +int ModInfoDialog::tabIndex(const QString &tabId) +{ + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->widget(i)->objectName() == tabId) { + return i; + } + } + return -1; +} + + +void ModInfoDialog::restoreTabState(const QByteArray &state) +{ + QDataStream stream(state); + int count = 0; + stream >> count; + + QStringList tabIds; + + // first, only determine the new mapping + for (int newPos = 0; newPos < count; ++newPos) { + QString tabId; + stream >> tabId; + tabIds.append(tabId); + int oldPos = tabIndex(tabId); + if (oldPos != -1) { + m_RealTabPos[newPos] = oldPos; + } else { + m_RealTabPos[newPos] = newPos; + } + } + // then actually move the tabs + QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad + ui->tabWidget->blockSignals(true); + for (int newPos = 0; newPos < count; ++newPos) { + QString tabId = tabIds.at(newPos); + int oldPos = tabIndex(tabId); + tabBar->moveTab(oldPos, newPos); + } + ui->tabWidget->blockSignals(false); +} + + +QByteArray ModInfoDialog::saveTabState() const +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + stream << ui->tabWidget->count(); + for (int i = 0; i < ui->tabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName(); + } + + return result; +} + + +void ModInfoDialog::refreshLists() +{ + int numNonConflicting = 0; + int numOverwrite = 0; + int numOverwritten = 0; + + ui->overwriteTree->clear(); + ui->overwrittenTree->clear(); + + if (m_Origin != NULL) { + std::vector files = m_Origin->getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath())); + QString fileName = relativeName.mid(0).prepend(m_RootPath); + bool archive; + if ((*iter)->getOrigin(archive) == m_Origin->getID()) { + std::vector alternatives = (*iter)->getAlternatives(); + if (!alternatives.empty()) { + std::wostringstream altString; + for (std::vector::iterator altIter = alternatives.begin(); + altIter != alternatives.end(); ++altIter) { + if (altIter != alternatives.begin()) { + altString << ", "; + } + altString << m_Directory->getOriginByID(*altIter).getName(); + } + QStringList fields(relativeName.prepend("...")); + fields.append(ToQString(altString.str())); + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(0, Qt::UserRole, fileName); + item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives[0]).getName())); + item->setData(1, Qt::UserRole + 1, alternatives[0]); + item->setData(1, Qt::UserRole + 2, archive); + ui->overwriteTree->addTopLevelItem(item); + ++numOverwrite; + } else {// otherwise don't display the file + ++numNonConflicting; + } + } else { + FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive)); + QStringList fields(relativeName); + fields.append(ToQString(realOrigin.getName())); + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); + ui->overwrittenTree->addTopLevelItem(item); + ++numOverwritten; + } + } + } + + if (m_RootPath.length() > 0) { + QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { + ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); + } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && + !fileName.endsWith("meta.ini")) { + QString namePart = fileName.mid(m_RootPath.length() + 1); + if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { + QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); + newItem->setData(Qt::UserRole, namePart); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + newItem->setCheckState(Qt::Unchecked); + ui->iniTweaksList->addItem(newItem); + } else { + ui->iniFileList->addItem(namePart); + } + } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || + fileName.endsWith(".esm", Qt::CaseInsensitive)) { + QString relativePath = fileName.mid(m_RootPath.length() + 1); + if (relativePath.contains('/')) { + QFileInfo fileInfo(fileName); + QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName()); + newItem->setData(Qt::UserRole, relativePath); + ui->inactiveESPList->addItem(newItem); + } else { + ui->activeESPList->addItem(relativePath); + } + } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || + (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { + QImage image = QImage(fileName); + if (!image.isNull()) { + if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { + image = image.scaledToWidth(128); + } else { + image = image.scaledToHeight(96); + } + + QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); + thumbnailButton->setIconSize(QSize(image.width(), image.height())); + connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); + m_ThumbnailMapper.setMapping(thumbnailButton, fileName); + ui->thumbnailArea->addWidget(thumbnailButton); + } + } + } + } + + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); +} + + +void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) +{ + for (unsigned int i = 0; i < factory.numCategories(); ++i) { + if (factory.getParentID(i) != rootLevel) { + continue; + } + int categoryID = factory.getCategoryID(i); + QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(factory.getCategoryName(i))); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + newItem->setCheckState(0, enabledCategories.find(categoryID) != enabledCategories.end() ? Qt::Checked : Qt::Unchecked); + newItem->setData(0, Qt::UserRole, categoryID); + if (factory.hasChildren(i)) { + addCategories(factory, enabledCategories, newItem, categoryID); + } + root->addChild(newItem); + } +} + + +void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) +{ + for (int i = 0; i < currentNode->childCount(); ++i) { + QTreeWidgetItem *childNode = currentNode->child(i); + m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); + saveCategories(childNode); + } +} + + +void ModInfoDialog::on_closeButton_clicked() +{ + if (allowNavigateFromTXT() && allowNavigateFromINI()) { + this->close(); + } +} + + + +QString ModInfoDialog::getModVersion() const +{ + return m_Settings->value("version", "").toString(); +} + + +const int ModInfoDialog::getModID() const +{ + return m_Settings->value("modid", 0).toInt(); +} + + +void ModInfoDialog::openTab(int tab) +{ + QTabWidget *tabWidget = findChild("tabWidget"); + if (tabWidget->isTabEnabled(tab)) { + tabWidget->setCurrentIndex(tab); + } +} + +void ModInfoDialog::thumbnailClicked(const QString &fileName) +{ + QLabel *imageLabel = findChild("imageLabel"); + QImage image(fileName); + if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { + image = image.scaledToWidth(imageLabel->width()); + } else { + image = image.scaledToHeight(imageLabel->height()); + } + imageLabel->setPixmap(QPixmap::fromImage(image)); +} + + +bool ModInfoDialog::allowNavigateFromTXT() +{ + if (ui->saveTXTButton->isEnabled()) { + int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return false; + } else if (res == QMessageBox::Yes) { + saveCurrentTextFile(); + } + } + return true; +} + + +bool ModInfoDialog::allowNavigateFromINI() +{ + if (ui->saveButton->isEnabled()) { + int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return false; + } else if (res == QMessageBox::Yes) { + saveCurrentIniFile(); + } + } + return true; +} + + +void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->text(); + + QVariant currentFile = ui->textFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromTXT()) { + openTextFile(fullPath); + } else { + ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } +} + + +void ModInfoDialog::openTextFile(const QString &fileName) +{ + QString encoding; + ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); + ui->textFileView->setProperty("currentFile", fileName); + ui->textFileView->setProperty("encoding", encoding); + ui->saveTXTButton->setEnabled(false); +} + + +void ModInfoDialog::openIniFile(const QString &fileName) +{ + QFile iniFile(fileName); + iniFile.open(QIODevice::ReadOnly); + QByteArray buffer = iniFile.readAll(); + + QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); + QTextEdit *iniFileView = findChild("iniFileView"); + iniFileView->setText(codec->toUnicode(buffer)); + iniFileView->setProperty("currentFile", fileName); + iniFileView->setProperty("encoding", codec->name()); + iniFile.close(); + + ui->saveButton->setEnabled(false); +} + + +void ModInfoDialog::saveIniTweaks() +{ + m_Settings->beginWriteArray("INI Tweaks"); + + int countEnabled = 0; + for (int i = 0; i < ui->iniTweaksList->count(); ++i) { + if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { + m_Settings->setArrayIndex(countEnabled++); + m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); + } + } + m_Settings->endArray(); +} + + +void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->text(); + + QVariant currentFile = ui->iniFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromINI()) { + openIniFile(fullPath); + } else { + ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } +} + + +void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); + + QVariant currentFile = ui->iniFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromINI()) { + openIniFile(fullPath); + } else { + ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } + +} + + +void ModInfoDialog::on_saveButton_clicked() +{ + saveCurrentIniFile(); +} + + +void ModInfoDialog::on_saveTXTButton_clicked() +{ + saveCurrentTextFile(); +} + + +void ModInfoDialog::saveCurrentTextFile() +{ + QVariant fileNameVar = ui->textFileView->property("currentFile"); + QVariant encodingVar = ui->textFileView->property("encoding"); + if (fileNameVar.isValid() && encodingVar.isValid()) { + QString fileName = fileNameVar.toString(); + QFile txtFile(fileName); + txtFile.open(QIODevice::WriteOnly); + txtFile.resize(0); + QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); + QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); + txtFile.write(codec->fromUnicode(data)); + } else { + reportError("no file selected"); + } + ui->saveTXTButton->setEnabled(false); +} + + +void ModInfoDialog::saveCurrentIniFile() +{ + QVariant fileNameVar = ui->iniFileView->property("currentFile"); + QVariant encodingVar = ui->iniFileView->property("encoding"); + if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { + QString fileName = fileNameVar.toString(); + QDir().mkpath(QFileInfo(fileName).absolutePath()); + QFile txtFile(fileName); + txtFile.open(QIODevice::WriteOnly); + txtFile.resize(0); + QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); + QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); + txtFile.write(codec->fromUnicode(data)); + } else { + reportError("no file selected"); + } + ui->saveButton->setEnabled(false); +} + + +void ModInfoDialog::on_iniFileView_textChanged() +{ + QPushButton* saveButton = findChild("saveButton"); + saveButton->setEnabled(true); +} + + +void ModInfoDialog::on_textFileView_textChanged() +{ + ui->saveTXTButton->setEnabled(true); +} + + +void ModInfoDialog::on_activateESP_clicked() +{ + QListWidget *activeESPList = findChild("activeESPList"); + QListWidget *inactiveESPList = findChild("inactiveESPList"); + + int selectedRow = inactiveESPList->currentRow(); + if (selectedRow < 0) { + return; + } + + QListWidgetItem *selectedItem = inactiveESPList->takeItem(selectedRow); + + QDir root(m_RootPath); + bool renamed = false; + + while (root.exists(selectedItem->text())) { + bool okClicked = false; + QString newName = QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, selectedItem->text(), &okClicked); + if (!okClicked) { + inactiveESPList->insertItem(selectedRow, selectedItem); + return; + } else if (newName.size() > 0) { + selectedItem->setText(newName); + renamed = true; + } + } + + if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { + activeESPList->addItem(selectedItem); + if (renamed) { + selectedItem->setData(Qt::UserRole, QVariant()); + } + } else { + inactiveESPList->insertItem(selectedRow, selectedItem); + reportError(tr("failed to move file")); + } +} + + +void ModInfoDialog::on_deactivateESP_clicked() +{ + QListWidget *activeESPList = findChild("activeESPList"); + QListWidget *inactiveESPList = findChild("inactiveESPList"); + + int selectedRow = activeESPList->currentRow(); + if (selectedRow < 0) { + return; + } + + QDir root(m_RootPath); + + QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); + + // if we moved the file from optional to active in this session, we move the file back to + // where it came from. Otherwise, it is moved to the new folder "optional" + if (selectedItem->data(Qt::UserRole).isNull()) { + selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); + if (!root.exists("optional")) { + if (!root.mkdir("optional")) { + reportError(tr("failed to create directory \"optional\"")); + activeESPList->insertItem(selectedRow, selectedItem); + return; + } + } + } + + if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { + inactiveESPList->addItem(selectedItem); + } else { + activeESPList->insertItem(selectedRow, selectedItem); + } +} + +void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) +{ + this->close(); + emit nexusLinkActivated(link); +} + +void ModInfoDialog::linkClicked(const QUrl &url) +{ + if (url.toString().startsWith(ToQString(GameInfo::instance().getNexusPage(false)))) { + this->close(); + emit nexusLinkActivated(url.toString()); + } else { + ::ShellExecuteW(NULL, L"open", ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); + } +} + + +void ModInfoDialog::refreshNexusData(int modID) +{ + if ((!m_RequestStarted) && (modID > 0)) { + m_RequestStarted = true; + + m_ModInfo->updateNXMInfo(); + + MessageDialog::showMessage(tr("Info requested, please wait"), this); + } +} + + +/*void ModInfoDialog::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + QVariantMap result = resultData.toMap(); + + if (!result["description"].isNull()) { + QString descriptionAsHTML = + QString("" + "" + "%1" + "").arg(BBCode::convertToHTML(result["description"].toString())); + +// QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); + ui->descriptionView->setHtml(descriptionAsHTML); + } else { + ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); + } + + QLineEdit *versionEdit = findChild("versionEdit"); + QString version = result["version"].toString(); + + if (!version.isEmpty()) { + m_ModInfo->setNewestVersion(version); + + VersionInfo currentVersion(versionEdit->text()); + VersionInfo newestVersion(version); + + QPalette versionColor; + if (currentVersion < newestVersion) { + versionColor.setColor(QPalette::Text, Qt::red); + versionEdit->setToolTip(tr("Current Version: %1").arg(version)); + } else { + versionColor.setColor(QPalette::Text, Qt::green); + versionEdit->setToolTip(tr("No update available")); + } + versionEdit->setPalette(versionColor); + } +}*/ + + +QString ModInfoDialog::getFileCategory(int categoryID) +{ + switch (categoryID) { + case 1: return tr("Main"); + case 2: return tr("Update"); + case 3: return tr("Optional"); + case 4: return tr("Old"); + case 5: return tr("Misc"); + default: return tr("Unknown"); + } +} + + +void ModInfoDialog::updateVersionColor() +{ +// QPalette versionColor; + if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { + ui->versionEdit->setStyleSheet("color: red"); +// versionColor.setColor(QPalette::Text, Qt::red); + ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); + } else { + ui->versionEdit->setStyleSheet("color: green"); +// versionColor.setColor(QPalette::Text, Qt::green); + ui->versionEdit->setToolTip(tr("No update available")); + } +// ui->versionEdit->setPalette(versionColor); +} + + +void ModInfoDialog::modDetailsUpdated(bool success) +{ + if (success) { + QString nexusDescription = m_ModInfo->getNexusDescription(); + if (!nexusDescription.isEmpty()) { + /* QString input = + "[size=20]sizetest[/size]\r\n" + "[COLOR=yellow]colortest[/COLOR]\r\n" + "[center]centertest[/center]\r\n" + "[quote]quotetest 1[/quote]\r\n" + "[quote=bla]quotetest 2[/quote]\r\n" + "[url]www.skyrimnexus.com[/url]\r\n" + "[url=www.skyrimnexus.com]urltest 2[/url]\r\n" + "[ol]\r\n" + "[li]item 2[/li]" + "[*]item 1\r\n" + "[/ol]\r\n" + "[img]http://www.bbcode.org/images/bbcode_logo.png[/img]\r\n" + "[table][tr][th]headertest1[/th]" + "[th]headertest2[/th][/tr]" + "[tr][td]rowtest11[/td][td]rowtest12[/td][/tr]" + "[tr][td]rowtest21[/td][td]rowtest22[/td][/tr][/table]" + "[email=\"sherb@gmx.net\"]mail me[/email]"; + ui->descriptionView->setHtml(BBCode::convertToHTML(input));*/ + + QString descriptionAsHTML = + QString("" + "" + "%1" + "").arg(BBCode::convertToHTML(nexusDescription)); + + // QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); + ui->descriptionView->setHtml(descriptionAsHTML); + } else { + // ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); + ui->descriptionView->setHtml(tr("(description incomplete, please visit nexus)")); + } + + updateVersionColor(); + } +} + + +void ModInfoDialog::activateNexusTab() +{ + QLineEdit *modIDEdit = findChild("modIDEdit"); + int modID = modIDEdit->text().toInt(); + if (modID != 0) { + QString nexusLink = QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID); + QLabel *visitNexusLabel = findChild("visitNexusLabel"); + visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); + visitNexusLabel->setToolTip(nexusLink); + + + if (m_ModInfo->getNexusDescription().isEmpty() || + QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) { + refreshNexusData(modID); + } else { + this->modDetailsUpdated(true); + } + } + QLineEdit *versionEdit = findChild("versionEdit"); + QString currentVersion = m_Settings->value("version", "0.0").toString(); + versionEdit->setText(currentVersion); +} + + +void ModInfoDialog::on_tabWidget_currentChanged(int index) +{ + if (m_RealTabPos[index] == TAB_NEXUS) { + activateNexusTab(); + } +} + + +void ModInfoDialog::on_modIDEdit_editingFinished() +{ + int oldID = m_Settings->value("modid", 0).toInt(); + int modID = ui->modIDEdit->text().toInt(); + if (oldID != modID){ + m_ModInfo->setNexusID(modID); + + ui->descriptionView->setHtml(""); + if (modID != 0) { + m_RequestStarted = false; + refreshNexusData(modID); + } + } +} + +void ModInfoDialog::on_versionEdit_editingFinished() +{ + VersionInfo version(ui->versionEdit->text()); + m_ModInfo->setVersion(version); + updateVersionColor(); +} + + +bool ModInfoDialog::recursiveDelete(const QModelIndex &index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; +} + + +void ModInfoDialog::deleteFile(const QModelIndex &index) +{ + + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete %1").arg(fileName)); + } +} + + +void ModInfoDialog::deleteTriggered() +{ + if (m_FileSelection.count() == 0) { + return; + } else if (m_FileSelection.count() == 1) { + QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + + +void ModInfoDialog::renameTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_FileSystemModel->isReadOnly()) { + return; + } + + ui->fileTree->edit(index); +} + + +void ModInfoDialog::hideTriggered() +{ + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); + iter != m_FileSelection.constEnd(); ++iter) { + QString path = m_FileSystemModel->filePath(*iter); + if (!path.endsWith(ModInfo::s_HiddenExt)) { + hideFile(path); + } + } +} + + +void ModInfoDialog::unhideTriggered() +{ + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); + iter != m_FileSelection.constEnd(); ++iter) { + QString path = m_FileSystemModel->filePath(*iter); + if (path.endsWith(ModInfo::s_HiddenExt)) { + unhideFile(path); + } + } +} + + +void ModInfoDialog::openFile(const QModelIndex &index) +{ + QString fileName = m_FileSystemModel->filePath(index); + + HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW); + if ((int)res <= 32) { + qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); + } +} + + +void ModInfoDialog::openTriggered() +{ + foreach(QModelIndex idx, m_FileSelection) { + openFile(idx); + } +} + +void ModInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + + QModelIndex index = m_FileSystemModel->isDir(selection) ? selection + : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_FileSystemModel->filePath(index).append("/"); + + QModelIndex existingIndex = m_FileSystemModel->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_FileSystemModel->index(path + name); + } + + QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->fileTree->setCurrentIndex(newIndex); + ui->fileTree->edit(newIndex); +} + + +void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + +// m_FileSelection = ui->fileTree->indexAt(pos); + QMenu menu(ui->fileTree); + + menu.addAction(m_NewFolderAction); + + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); + } + menu.addAction(m_RenameAction); + menu.addAction(m_DeleteAction); + if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(m_UnhideAction); + } else { + menu.addAction(m_HideAction); + } + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + menu.exec(ui->fileTree->mapToGlobal(pos)); +} + + +void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem *item, int) +{ + QTreeWidgetItem *parent = item->parent(); + while ((parent != NULL) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { + parent->setCheckState(0, Qt::Checked); + parent = parent->parent(); + } + refreshPrimaryCategoriesBox(); +} + + +void ModInfoDialog::addCheckedCategories(QTreeWidgetItem *tree) +{ + for (int i = 0; i < tree->childCount(); ++i) { + QTreeWidgetItem *child = tree->child(i); + if (child->checkState(0) == Qt::Checked) { + ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + addCheckedCategories(child); + } + } +} + + +void ModInfoDialog::refreshPrimaryCategoriesBox() +{ + ui->primaryCategoryBox->clear(); + int primaryCategory = m_ModInfo->getPrimaryCategory(); + addCheckedCategories(ui->categoriesTree->invisibleRootItem()); + for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { + if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { + ui->primaryCategoryBox->setCurrentIndex(i); + break; + } + } +} + + +void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) +{ + if (index != -1) { + m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + } +} + + +void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) +{ + this->close(); + emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); +} + + +bool ModInfoDialog::hideFile(const QString &oldName) +{ + QString newName = oldName + ModInfo::s_HiddenExt; + + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return false; + } + } else { + return false; + } + } + + if (QFile::rename(oldName, newName)) { + return true; + } else { + reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); + return false; + } +} + + +bool ModInfoDialog::unhideFile(const QString &oldName) +{ + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return false; + } + } else { + return false; + } + } + if (QFile::rename(oldName, newName)) { + return true; + } else { + reportError(tr("failed to rename %1 to %2").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); + return false; + } +} + + +void ModInfoDialog::hideConflictFile() +{ + if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + emit originModified(m_Origin->getID()); + refreshLists(); + } +} + + +void ModInfoDialog::unhideConflictFile() +{ + if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + emit originModified(m_Origin->getID()); + refreshLists(); + } +} + + +void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) +{ + m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); + + if (m_ConflictsContextItem != NULL) { + // offer to hide/unhide file, but not for files from archives + if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { + QMenu menu; + if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); + } else { + menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); + } + menu.exec(ui->overwriteTree->mapToGlobal(pos)); + } + } +} + + +void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) +{ + emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); + this->accept(); +} + +void ModInfoDialog::on_refreshButton_clicked() +{ + m_ModInfo->updateNXMInfo(); + + MessageDialog::showMessage(tr("Info requested, please wait"), this); +} + +void ModInfoDialog::on_endorseBtn_clicked() +{ + emit endorseMod(m_ModInfo); +} + +void ModInfoDialog::on_nextButton_clicked() +{ + emit modOpenNext(); + this->accept(); +} + +void ModInfoDialog::on_prevButton_clicked() +{ + emit modOpenPrev(); + this->accept(); +} + + +void ModInfoDialog::createTweak() +{ + QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); + if (name.isNull()) { + return; + } else if (!fixDirectoryName(name)) { + QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); + return; + } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { + QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); + return; + } + + QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); + newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); + newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); + newTweak->setCheckState(Qt::Unchecked); + ui->iniTweaksList->addItem(newTweak); +} + +void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); + menu.exec(ui->iniTweaksList->mapToGlobal(pos)); +} diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 63875b70..652bcab6 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -1,237 +1,237 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MODINFODIALOG_H -#define MODINFODIALOG_H - - -#include "modinfo.h" -#include "categories.h" -#include "tutorabledialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace Ui { - class ModInfoDialog; -} - -class QFileSystemModel; -class QTreeView; - - -/** - * this is a larger dialog used to visualise information abount the mod. - * @todo this would probably a good place for a plugin-system - **/ -class ModInfoDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - enum ETabs { - TAB_TEXTFILES, - TAB_INIFILES, - TAB_IMAGES, - TAB_ESPS, - TAB_CONFLICTS, - TAB_CATEGORIES, - TAB_NEXUS, - TAB_NOTES, - TAB_FILETREE - }; - -public: - - /** - * @brief constructor - * - * @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); - ~ModInfoDialog(); - - /** - * @brief retrieve the (user-modified) version of the mod - * - * @return the (user-modified) version of the mod - **/ - QString getModVersion() const; - - /** - * @brief retrieve the (user-modified) mod id - * - * @return the (user-modified) id of the mod - **/ - const int getModID() const; - - /** - * @brief open the specified tab in the dialog if it's enabled - * - * @param tab the tab to activate - **/ - void openTab(int tab); - - void restoreTabState(const QByteArray &state); - - QByteArray saveTabState() const; - -signals: - - void thumbnailClickedSignal(const QString &filename); - void nexusLinkActivated(const QString &link); - void downloadRequest(const QString &link); - void modOpen(const QString &modName, int tab); - void modOpenNext(); - void modOpenPrev(); - void originModified(int originID); - void endorseMod(ModInfo::Ptr nexusID); - -public slots: - - void modDetailsUpdated(bool success); - -private: - - void initFiletree(ModInfo::Ptr modInfo); - void initINITweaks(); - - void refreshLists(); - - void addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel); - - void updateVersionColor(); - - void refreshNexusData(int modID); - void activateNexusTab(); - QString getFileCategory(int categoryID); - bool recursiveDelete(const QModelIndex &index); - void deleteFile(const QModelIndex &index); - void openFile(const QModelIndex &index); - void saveIniTweaks(); - void saveCategories(QTreeWidgetItem *currentNode); - void saveCurrentTextFile(); - void saveCurrentIniFile(); - void openTextFile(const QString &fileName); - void openIniFile(const QString &fileName); - bool allowNavigateFromTXT(); - bool allowNavigateFromINI(); - bool hideFile(const QString &oldName); - bool unhideFile(const QString &oldName); - void addCheckedCategories(QTreeWidgetItem *tree); - void refreshPrimaryCategoriesBox(); - - int tabIndex(const QString &tabId); - -private slots: - - void hideConflictFile(); - void unhideConflictFile(); - - void thumbnailClicked(const QString &fileName); - void linkClicked(const QUrl &url); - - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); - void createDirectoryTriggered(); - void hideTriggered(); - void unhideTriggered(); - - void on_closeButton_clicked(); - void on_saveButton_clicked(); - void on_activateESP_clicked(); - void on_deactivateESP_clicked(); - void on_saveTXTButton_clicked(); - void on_visitNexusLabel_linkActivated(const QString &link); - void on_modIDEdit_editingFinished(); - void on_versionEdit_editingFinished(); - void on_iniFileView_textChanged(); - void on_textFileView_textChanged(); - void on_tabWidget_currentChanged(int index); - void on_primaryCategoryBox_currentIndexChanged(int index); - void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); - void on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - 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_fileTree_customContextMenuRequested(const QPoint &pos); - - void on_refreshButton_clicked(); - - void on_endorseBtn_clicked(); - - void on_nextButton_clicked(); - - void on_prevButton_clicked(); - - void on_iniTweaksList_customContextMenuRequested(const QPoint &pos); - - void createTweak(); -private: - - Ui::ModInfoDialog *ui; - - ModInfo::Ptr m_ModInfo; - int m_OriginID; - - QSignalMapper m_ThumbnailMapper; - QString m_RootPath; - - QFileSystemModel *m_FileSystemModel; - QTreeView *m_FileTree; - QModelIndexList m_FileSelection; - - QSettings *m_Settings; - - std::set m_RequestIDs; - bool m_RequestStarted; - - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; - QAction *m_NewFolderAction; - QAction *m_HideAction; - QAction *m_UnhideAction; - - QTreeWidgetItem *m_ConflictsContextItem; - - const MOShared::DirectoryEntry *m_Directory; - MOShared::FilesOrigin *m_Origin; - - std::map m_RealTabPos; - -}; - -#endif // MODINFODIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MODINFODIALOG_H +#define MODINFODIALOG_H + + +#include "modinfo.h" +#include "categories.h" +#include "tutorabledialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Ui { + class ModInfoDialog; +} + +class QFileSystemModel; +class QTreeView; + + +/** + * this is a larger dialog used to visualise information abount the mod. + * @todo this would probably a good place for a plugin-system + **/ +class ModInfoDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + enum ETabs { + TAB_TEXTFILES, + TAB_INIFILES, + TAB_IMAGES, + TAB_ESPS, + TAB_CONFLICTS, + TAB_CATEGORIES, + TAB_NEXUS, + TAB_NOTES, + TAB_FILETREE + }; + +public: + + /** + * @brief constructor + * + * @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); + ~ModInfoDialog(); + + /** + * @brief retrieve the (user-modified) version of the mod + * + * @return the (user-modified) version of the mod + **/ + QString getModVersion() const; + + /** + * @brief retrieve the (user-modified) mod id + * + * @return the (user-modified) id of the mod + **/ + const int getModID() const; + + /** + * @brief open the specified tab in the dialog if it's enabled + * + * @param tab the tab to activate + **/ + void openTab(int tab); + + void restoreTabState(const QByteArray &state); + + QByteArray saveTabState() const; + +signals: + + void thumbnailClickedSignal(const QString &filename); + void nexusLinkActivated(const QString &link); + void downloadRequest(const QString &link); + void modOpen(const QString &modName, int tab); + void modOpenNext(); + void modOpenPrev(); + void originModified(int originID); + void endorseMod(ModInfo::Ptr nexusID); + +public slots: + + void modDetailsUpdated(bool success); + +private: + + void initFiletree(ModInfo::Ptr modInfo); + void initINITweaks(); + + void refreshLists(); + + void addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel); + + void updateVersionColor(); + + void refreshNexusData(int modID); + void activateNexusTab(); + QString getFileCategory(int categoryID); + bool recursiveDelete(const QModelIndex &index); + void deleteFile(const QModelIndex &index); + void openFile(const QModelIndex &index); + void saveIniTweaks(); + void saveCategories(QTreeWidgetItem *currentNode); + void saveCurrentTextFile(); + void saveCurrentIniFile(); + void openTextFile(const QString &fileName); + void openIniFile(const QString &fileName); + bool allowNavigateFromTXT(); + bool allowNavigateFromINI(); + bool hideFile(const QString &oldName); + bool unhideFile(const QString &oldName); + void addCheckedCategories(QTreeWidgetItem *tree); + void refreshPrimaryCategoriesBox(); + + int tabIndex(const QString &tabId); + +private slots: + + void hideConflictFile(); + void unhideConflictFile(); + + void thumbnailClicked(const QString &fileName); + void linkClicked(const QUrl &url); + + void deleteTriggered(); + void renameTriggered(); + void openTriggered(); + void createDirectoryTriggered(); + void hideTriggered(); + void unhideTriggered(); + + void on_closeButton_clicked(); + void on_saveButton_clicked(); + void on_activateESP_clicked(); + void on_deactivateESP_clicked(); + void on_saveTXTButton_clicked(); + void on_visitNexusLabel_linkActivated(const QString &link); + void on_modIDEdit_editingFinished(); + void on_versionEdit_editingFinished(); + void on_iniFileView_textChanged(); + void on_textFileView_textChanged(); + void on_tabWidget_currentChanged(int index); + void on_primaryCategoryBox_currentIndexChanged(int index); + void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); + void on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + 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_fileTree_customContextMenuRequested(const QPoint &pos); + + void on_refreshButton_clicked(); + + void on_endorseBtn_clicked(); + + void on_nextButton_clicked(); + + void on_prevButton_clicked(); + + void on_iniTweaksList_customContextMenuRequested(const QPoint &pos); + + void createTweak(); +private: + + Ui::ModInfoDialog *ui; + + ModInfo::Ptr m_ModInfo; + int m_OriginID; + + QSignalMapper m_ThumbnailMapper; + QString m_RootPath; + + QFileSystemModel *m_FileSystemModel; + QTreeView *m_FileTree; + QModelIndexList m_FileSelection; + + QSettings *m_Settings; + + std::set m_RequestIDs; + bool m_RequestStarted; + + QAction *m_DeleteAction; + QAction *m_RenameAction; + QAction *m_OpenAction; + QAction *m_NewFolderAction; + QAction *m_HideAction; + QAction *m_UnhideAction; + + QTreeWidgetItem *m_ConflictsContextItem; + + const MOShared::DirectoryEntry *m_Directory; + MOShared::FilesOrigin *m_Origin; + + std::map m_RealTabPos; + +}; + +#endif // MODINFODIALOG_H diff --git a/src/modlist.cpp b/src/modlist.cpp index 7e5a5f58..ff126faa 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1,993 +1,993 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modlist.h" - -#include "report.h" -#include "messagedialog.h" -#include "installationtester.h" -#include "qtgroupingproxy.h" -#include "viewmarkingscrollbar.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; - - -ModList::ModList(QObject *parent) - : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), - m_FontMetrics(QFont()), m_DropOnItems(false) -{ -} - - -void ModList::setProfile(Profile *profile) -{ - m_Profile = profile; -} - - -int ModList::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return ModInfo::getNumMods(); - } else { - return 0; - } -} - - -bool ModList::hasChildren(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return ModInfo::getNumMods() > 0; - } else { - return false; - } -} - - -int ModList::columnCount(const QModelIndex &) const -{ - return COL_LASTCOLUMN + 1; -} - - -QVariant ModList::getOverwriteData(int column, int role) const -{ - switch (role) { - case Qt::DisplayRole: { - if (column == 0) { - return "Overwrite"; - } else { - return QVariant(); - } - } break; - case Qt::CheckStateRole: { - if (column == 0) { - return Qt::Checked; - } else { - return QVariant(); - } - } break; - case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case Qt::UserRole: return -1; - case Qt::ForegroundRole: return QBrush(Qt::red); - case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); - default: return QVariant(); - } -} - - -QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const -{ - switch (flag) { - case ModInfo::FLAG_BACKUP: return tr("Backup"); - case ModInfo::FLAG_INVALID: return tr("No valid game data"); - case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); - case ModInfo::FLAG_NOTES: return QString("%1").arg(modInfo->notes().replace("\n", "
            ")); - case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten files"); - case ModInfo::FLAG_CONFLICT_MIXED: return tr("Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); - default: return ""; - } -} - - -QVariant ModList::data(const QModelIndex &modelIndex, int role) const -{ - if (m_Profile == NULL) return QVariant(); - if (!modelIndex.isValid()) return QVariant(); - unsigned int modIndex = modelIndex.row(); - int column = modelIndex.column(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if ((role == Qt::DisplayRole) || - (role == Qt::EditRole)) { - if (column == COL_FLAGS) { - return QVariant(); - } else if (column == COL_NAME) { - return modInfo->name(); - } else if (column == COL_VERSION) { - VersionInfo verInfo = modInfo->getVersion(); - QString version = verInfo.displayString(); - if (role != Qt::EditRole) { - if (version.isEmpty() && modInfo->canBeUpdated()) { - version = "?"; - } - } - return version; - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return QVariant(); // hide priority for mods where it's fixed - } else { - return m_Profile->getModPriority(modIndex); - } - } else if (column == COL_MODID) { - int modID = modInfo->getNexusID(); - if (modID >= 0) { - return modID; - } else { - return QVariant(); - } - } else if (column == COL_CATEGORY) { - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - return tr("Non-MO"); - } else { - int category = modInfo->getPrimaryCategory(); - if (category != -1) { - CategoryFactory &categoryFactory = CategoryFactory::instance(); - if (categoryFactory.categoryExists(category)) { - try { - int categoryIdx = categoryFactory.getCategoryIndex(category); - return categoryFactory.getCategoryName(categoryIdx); - } catch (const std::exception &e) { - qCritical("failed to retrieve category name: %s", e.what()); - return QString(); - } - } else { - qWarning("category %d doesn't exist (may have been removed)", category); - modInfo->setCategory(category, false); - return QString(); - } - } else { - //return tr("None"); - return QVariant(); - } - } - } else if (column == COL_INSTALLTIME) { - // display installation time for mods that can be updated - if (modInfo->canBeUpdated()) { - return modInfo->creationTime(); - } else { - return QVariant(); - } - } else { - return tr("invalid"); - } - } else if ((role == Qt::CheckStateRole) && (column == 0)) { - if (modInfo->canBeEnabled()) { - return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; - } else { - return QVariant(); - } - } else if (role == Qt::TextAlignmentRole) { - if (column == COL_NAME) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } - } else if (column == COL_VERSION) { - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } - } else if (role == Qt::UserRole) { - if (column == COL_CATEGORY) { - QVariantList categoryNames; - std::set categories = modInfo->getCategories(); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (auto iter = categories.begin(); iter != categories.end(); ++iter) { - categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); - } - if (categoryNames.count() != 0) { - return categoryNames; - } else { - return QVariant(); - } - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return priority; - } else { - return m_Profile->getModPriority(modIndex); - } - } else { - return modInfo->getNexusID(); - } - } else if (role == Qt::UserRole + 1) { - return modIndex; - } else if (role == Qt::UserRole + 2) { - switch (column) { - case COL_MODID: return QtGroupingProxy::AGGR_FIRST; - case COL_VERSION: return QtGroupingProxy::AGGR_MAX; - case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST; - case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; - default: return QtGroupingProxy::AGGR_NONE; - } - } else if (role == Qt::FontRole) { - QFont result; - if (column == COL_NAME) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { - result.setItalic(true); - } - } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { - result.setItalic(true); - } else if (column == COL_VERSION) { - if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - result.setWeight(QFont::Bold); - } - } - return result; - } else if (role == Qt::DecorationRole) { - if (column == COL_VERSION) { - if (modInfo->updateAvailable()) { - return QIcon(":/MO/gui/update_available"); - } else if (modInfo->downgradeAvailable()) { - return QIcon(":/MO/gui/warning"); - } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { - return QIcon(":/MO/gui/version_date"); - } - } - return QVariant(); - } else if (role == Qt::ForegroundRole) { - if (column == COL_NAME) { - int highlight = modInfo->getHighlight(); - if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) return QBrush(Qt::darkRed); - else if (highlight & ModInfo::HIGHLIGHT_INVALID) return QBrush(Qt::darkGray); - } else if (column == COL_VERSION) { - if (!modInfo->getNewestVersion().isValid()) { - return QVariant(); - } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - return QBrush(Qt::red); - } else { - return QBrush(Qt::darkGreen); - } - } - return QVariant(); - } else if ((role == Qt::BackgroundRole) - || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { - if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { - return QColor(0, 255, 0, 64); - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { - return QColor(255, 0, 0, 64); - } else { - return QVariant(); - } - } else if (role == Qt::ToolTipRole) { - if (column == COL_FLAGS) { - QString result; - - std::vector flags = modInfo->getFlags(); - - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - if (result.length() != 0) result += "
            "; - result += getFlagText(*iter, modInfo); - } - - return result; - } else if (column == COL_NAME) { - try { - return modInfo->getDescription(); - } catch (const std::exception &e) { - qCritical("invalid mod description: %s", e.what()); - return QString(); - } - } else if (column == COL_VERSION) { - QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString()).arg(modInfo->getNewestVersion().displayString()); - if (modInfo->downgradeAvailable()) { - text += "
            " + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " - "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " - "Either way you may want to \"upgrade\"."); - } - return text; - } else if (column == COL_CATEGORY) { - const std::set &categories = modInfo->getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
            ")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - try { - categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; - } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); - return QString(); - } - } - - return ToQString(categoryString.str()); - } else { - return QVariant(); - } - } else { - return QVariant(); - } -} - - -bool ModList::renameMod(int index, const QString &newName) -{ - QString nameFixed = newName; - if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { - MessageDialog::showMessage(tr("Invalid name"), NULL); - return false; - } - - // before we rename, write back the current profile so we don't lose changes and to ensure - // there is no scheduled asynchronous rewrite anytime soon - m_Profile->writeModlistNow(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QString oldName = modInfo->name(); - if (modInfo->setName(nameFixed)) { - // this just disabled the mod in all profiles. The recipient of modRenamed must fix that - emit modRenamed(oldName, nameFixed); - } - - // invalidate the currently displayed state of this list - notifyChange(-1); - return true; -} - - -bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (m_Profile == NULL) return false; - - if (static_cast(index.row()) >= ModInfo::getNumMods()) { - return false; - } - - int modID = index.row(); - - ModInfo::Ptr info = ModInfo::getByIndex(modID); - IModList::ModStates oldState = state(modID); - - bool result = false; - - if (role == Qt::CheckStateRole) { - bool enabled = value.toInt() == Qt::Checked; - if (m_Profile->modEnabled(modID) != enabled) { - m_Profile->setModEnabled(modID, enabled); - m_Modified = true; - emit modlist_changed(index, role); - } - result = true; - } else if (role == Qt::EditRole) { - switch (index.column()) { - case COL_NAME: { - result = renameMod(modID, value.toString()); - } break; - case COL_PRIORITY: { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok) { - m_Profile->setModPriority(modID, newPriority); - - emit modlist_changed(index, role); - result = true; - } else { - result = false; - } - } break; - case COL_MODID: { - bool ok = false; - int newID = value.toInt(&ok); - if (ok) { - info->setNexusID(newID); - emit modlist_changed(index, role); - result = true; - } else { - result = false; - } - } break; - case COL_VERSION: { - VersionInfo::VersionScheme scheme = info->getVersion().scheme(); - VersionInfo version(value.toString(), scheme); - if (version.isValid()) { - info->setVersion(version); - result = true; - } else { - result = false; - } - } break; - default: { - qWarning("edit on column \"%s\" not supported", - getColumnName(index.column()).toUtf8().constData()); - result = false; - } break; - } - if (result) { - emit dataChanged(index, index); - } - } - - IModList::ModStates newState = state(modID); - if (oldState != newState) { - try { - m_ModStateChanged(info->name(), newState); - } catch (const std::exception &e) { - qCritical("failed to invoke state changed notification: %s", e.what()); - } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); - } - } - - return result; -} - - -ModList::EColumn ModList::getEnabledColumn(int index) const -{ - for (int i = 0; i <= COL_LASTCOLUMN; ++i) { - if (index == 0) { - return static_cast(i); - } else { - --index; - } - } - return ModList::COL_NAME; -} - - -QVariant ModList::headerData(int section, Qt::Orientation orientation, - int role) const -{ - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole) { - return getColumnName(section); - } else if (role == Qt::ToolTipRole) { - return getColumnToolTip(section); - } else if (role == Qt::TextAlignmentRole) { - return QVariant(Qt::AlignCenter); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 20; - temp.rheight() += 12; - return temp; - } - } - return QAbstractItemModel::headerData(section, orientation, role); -} - - -Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); - if (modelIndex.internalId() < 0) { - return Qt::ItemIsEnabled; - } - if (modelIndex.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); - if (modInfo->getFixedPriority() == INT_MIN) { - result |= Qt::ItemIsDragEnabled; - result |= Qt::ItemIsUserCheckable; - if ((modelIndex.column() == COL_NAME) || - (modelIndex.column() == COL_PRIORITY) || - (modelIndex.column() == COL_VERSION) || - (modelIndex.column() == COL_MODID)) { - result |= Qt::ItemIsEditable; - } - } - std::vector flags = modInfo->getFlags(); - if ((m_DropOnItems) && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { - result |= Qt::ItemIsDropEnabled; - } - } else { - if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; - } - return result; -} - - -QStringList ModList::mimeTypes() const -{ - QStringList result = QAbstractItemModel::mimeTypes(); - result.append("text/uri-list"); - return result; -} - - -void ModList::changeModPriority(std::vector sourceIndices, int newPriority) -{ - if (m_Profile == NULL) return; - - emit layoutAboutToBeChanged(); - Profile *profile = m_Profile; - // sort rows to insert by their old priority (ascending) and insert them move them in that order - std::sort(sourceIndices.begin(), sourceIndices.end(), - [profile](const int &LHS, const int &RHS) { - return profile->getModPriority(LHS) < profile->getModPriority(RHS); - }); - - // odd stuff: if any of the dragged sources has priority lower than the destination then the - // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? - for (std::vector::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - if (profile->getModPriority(*iter) < newPriority) { - --newPriority; - break; - } - } - - for (std::vector::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - int oldPriority = m_Profile->getModPriority(*iter); - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); - } - - emit layoutChanged(); - - emit modorder_changed(); -} - - -void ModList::changeModPriority(int sourceIndex, int newPriority) -{ - if (m_Profile == NULL) return; - emit layoutAboutToBeChanged(); - - m_Profile->setModPriority(sourceIndex, newPriority); - - emit layoutChanged(); - - emit modorder_changed(); -} - -void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) -{ - m_Overwrite = overwrite; - m_Overwritten = overwritten; - notifyChange(0, rowCount()); -} - -void ModList::modInfoAboutToChange(ModInfo::Ptr info) -{ - m_ChangeInfo.name = info->name(); - m_ChangeInfo.state = state(info->name()); -} - -void ModList::modInfoChanged(ModInfo::Ptr info) -{ - if (info->name() == m_ChangeInfo.name) { - IModList::ModStates newState = state(info->name()); - if (m_ChangeInfo.state != newState) { - m_ModStateChanged(info->name(), newState); - } - int row = ModInfo::getIndex(info->name()); - info->testValid(); - emit dataChanged(index(row, 0), index(row, columnCount())); - } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); - } -} - -IModList::ModStates ModList::state(unsigned int modIndex) const -{ - IModList::ModStates result; - if (modIndex != UINT_MAX) { - result |= IModList::STATE_EXISTS; - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (modInfo->isEmpty()) { - result |= IModList::STATE_EMPTY; - } - if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { - result |= IModList::STATE_ENDORSED; - } - if (modInfo->isValid()) { - result |= IModList::STATE_VALID; - } - if (modInfo->canBeEnabled()) { - if (m_Profile->modEnabled(modIndex)) { - result |= IModList::STATE_ACTIVE; - } - } else { - result |= IModList::STATE_ESSENTIAL; - } - } - return result; -} - -IModList::ModStates ModList::state(const QString &name) const -{ - unsigned int modIndex = ModInfo::getIndex(name); - - return state(modIndex); -} - -int ModList::priority(const QString &name) const -{ - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return -1; - } else { - return m_Profile->getModPriority(modIndex); - } -} - -bool ModList::setPriority(const QString &name, int newPriority) -{ - if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { - return false; - } - - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return false; - } else { - m_Profile->setModPriority(modIndex, newPriority); - notifyChange(modIndex); - return true; - } -} - -bool ModList::onModStateChanged(const std::function &func) -{ - auto conn = m_ModStateChanged.connect(func); - return conn.connected(); -} - -bool ModList::onModMoved(const std::function &func) -{ - auto conn = m_ModMoved.connect(func); - return conn.connected(); -} - -bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - QStringList source; - QStringList target; - - if (row == -1) { - row = parent.row(); - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - QDir modDirectory(modInfo->absolutePath()); - QDir gameDirectory(QDir::fromNativeSeparators(ToQString(MOShared::GameInfo::instance().getOverwriteDir()))); - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); - - foreach (const QUrl &url, mimeData->urls()) { - QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); - if (relativePath.startsWith("..")) { - qDebug("%s drop ignored", qPrintable(url.toLocalFile())); - continue; - } - source.append(url.toLocalFile()); - target.append(modDirectory.absoluteFilePath(relativePath)); - emit fileMoved(relativePath, overwriteName, modInfo->name()); - } - - if (source.count() != 0) { - shellMove(source, target); - } - - return true; -} - - -bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) -{ - - try { - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { - return false; - } - - int newPriority = 0; - { - if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { - newPriority = m_Profile->numRegularMods() + 1; - } else { - newPriority = m_Profile->getModPriority(row); - } - if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; - } - } - changeModPriority(sourceRows, newPriority); - } catch (const std::exception &e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); - } - - return false; -} - - -bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - if (m_Profile == NULL) return false; - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else { - return dropMod(mimeData, row, parent); - } - -} - - -void ModList::removeRowForce(int row) -{ - if (static_cast(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == NULL) return; - - m_Profile->setModEnabled(row, false); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - - bool wasEnabled = m_Profile->modEnabled(row); - beginRemoveRows(QModelIndex(), row, row); - - m_Profile->cancelWriteModlist(); // don't write modlist while we're changing it - ModInfo::removeMod(row); - m_Profile->refreshModStatus(); // removes the mod from the status list - m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed - - endRemoveRows(); - if (wasEnabled) { - emit removeOrigin(modInfo->name()); - } - - emit modUninstalled(modInfo->getInstallationFile()); -} - - -void ModList::removeRow(int row, const QModelIndex&) -{ - if (static_cast(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == NULL) return; - - m_Profile->setModEnabled(row, false); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - if (!modInfo->isRegular()) return; - - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), - QMessageBox::Yes | QMessageBox::No); - - if (confirmBox.exec() == QMessageBox::Yes) { - removeRowForce(row); - } -} - - -void ModList::notifyChange(int rowStart, int rowEnd) -{ - if (rowStart < 0) { - beginResetModel(); - endResetModel(); - } else { - if (rowEnd == -1) rowEnd = rowStart; - emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); - } -} - - -QModelIndex ModList::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - QModelIndex res = createIndex(row, column, row); - return res; -} - - -QModelIndex ModList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -QMap ModList::itemData(const QModelIndex &index) const -{ - QMap result = QAbstractItemModel::itemData(index); - result[Qt::UserRole] = data(index, Qt::UserRole); - return result; -} - - -void ModList::dropModeUpdate(bool dropOnItems) -{ - if (m_DropOnItems != dropOnItems) { - m_DropOnItems = dropOnItems; - } -} - - -QString ModList::getColumnName(int column) -{ - switch (column) { - case COL_FLAGS: return tr("Flags"); - case COL_NAME: return tr("Mod Name"); - case COL_VERSION: return tr("Version"); - case COL_PRIORITY: return tr("Priority"); - case COL_CATEGORY: return tr("Category"); - case COL_MODID: return tr("Nexus ID"); - case COL_INSTALLTIME: return tr("Installation"); - default: return tr("unknown"); - } -} - - -QString ModList::getColumnToolTip(int column) -{ - switch (column) { - case COL_NAME: return tr("Name of your mods"); - case COL_VERSION: return tr("Version of the mod (if available)"); - case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus " - "overwrites files from mods with lower priority."); - case COL_CATEGORY: return tr("Category of the mod."); - case COL_MODID: return tr("Id of the mod as used on Nexus."); - case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); - case COL_INSTALLTIME: return tr("Time this mod was installed"); - default: return tr("unknown"); - } -} - - -bool ModList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::ContextMenu) { - QContextMenuEvent *contextEvent = static_cast(event); - QWidget *object = qobject_cast(obj); - if ((object != NULL) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { - emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); - - return true; - } - } else if ((event->type() == QEvent::KeyPress) && (m_Profile != NULL)) { - QAbstractItemView *itemView = qobject_cast(obj); - QKeyEvent *keyEvent = static_cast(event); - if ((itemView != NULL) - && (keyEvent->modifiers() == Qt::ControlModifier) - && ((keyEvent->key() == Qt::Key_Up) - || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); - const QSortFilterProxyModel *filterModel = NULL; - while ((filterModel == NULL) && (proxyModel != NULL)) { - filterModel = qobject_cast(proxyModel); - if (filterModel == NULL) { - proxyModel = qobject_cast(proxyModel->sourceModel()); - } - } - if (filterModel == NULL) { - return true; - } - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - foreach (QModelIndex idx, rows) { - if (filterModel != NULL) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + diff; - if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); - } - } - return true; - } else if (keyEvent->key() == Qt::Key_Delete) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - QModelIndexList rows = selectionModel->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } else if (rows.count() == 1) { - removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); - } - return true; - } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - - QModelIndex minRow, maxRow; - foreach (QModelIndex idx, selectionModel->selectedRows()) { - if (proxyModel != NULL) { - idx = proxyModel->mapToSource(idx); - } - if (!minRow.isValid() || (idx.row() < minRow.row())) { - minRow = idx; - } - if (!maxRow.isValid() || (idx.row() > maxRow.row())) { - maxRow = idx; - } - int oldState = idx.data(Qt::CheckStateRole).toInt(); - setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } - emit dataChanged(minRow, maxRow); - return true; - } - } - return QObject::eventFilter(obj, event); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modlist.h" + +#include "report.h" +#include "messagedialog.h" +#include "installationtester.h" +#include "qtgroupingproxy.h" +#include "viewmarkingscrollbar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; + + +ModList::ModList(QObject *parent) + : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), + m_FontMetrics(QFont()), m_DropOnItems(false) +{ +} + + +void ModList::setProfile(Profile *profile) +{ + m_Profile = profile; +} + + +int ModList::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods(); + } else { + return 0; + } +} + + +bool ModList::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods() > 0; + } else { + return false; + } +} + + +int ModList::columnCount(const QModelIndex &) const +{ + return COL_LASTCOLUMN + 1; +} + + +QVariant ModList::getOverwriteData(int column, int role) const +{ + switch (role) { + case Qt::DisplayRole: { + if (column == 0) { + return "Overwrite"; + } else { + return QVariant(); + } + } break; + case Qt::CheckStateRole: { + if (column == 0) { + return Qt::Checked; + } else { + return QVariant(); + } + } break; + case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case Qt::UserRole: return -1; + case Qt::ForegroundRole: return QBrush(Qt::red); + case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); + default: return QVariant(); + } +} + + +QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return tr("Backup"); + case ModInfo::FLAG_INVALID: return tr("No valid game data"); + case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); + case ModInfo::FLAG_NOTES: return QString("%1").arg(modInfo->notes().replace("\n", "
            ")); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); + default: return ""; + } +} + + +QVariant ModList::data(const QModelIndex &modelIndex, int role) const +{ + if (m_Profile == NULL) return QVariant(); + if (!modelIndex.isValid()) return QVariant(); + unsigned int modIndex = modelIndex.row(); + int column = modelIndex.column(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if ((role == Qt::DisplayRole) || + (role == Qt::EditRole)) { + if (column == COL_FLAGS) { + return QVariant(); + } else if (column == COL_NAME) { + return modInfo->name(); + } else if (column == COL_VERSION) { + VersionInfo verInfo = modInfo->getVersion(); + QString version = verInfo.displayString(); + if (role != Qt::EditRole) { + if (version.isEmpty() && modInfo->canBeUpdated()) { + version = "?"; + } + } + return version; + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return QVariant(); // hide priority for mods where it's fixed + } else { + return m_Profile->getModPriority(modIndex); + } + } else if (column == COL_MODID) { + int modID = modInfo->getNexusID(); + if (modID >= 0) { + return modID; + } else { + return QVariant(); + } + } else if (column == COL_CATEGORY) { + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + return tr("Non-MO"); + } else { + int category = modInfo->getPrimaryCategory(); + if (category != -1) { + CategoryFactory &categoryFactory = CategoryFactory::instance(); + if (categoryFactory.categoryExists(category)) { + try { + int categoryIdx = categoryFactory.getCategoryIndex(category); + return categoryFactory.getCategoryName(categoryIdx); + } catch (const std::exception &e) { + qCritical("failed to retrieve category name: %s", e.what()); + return QString(); + } + } else { + qWarning("category %d doesn't exist (may have been removed)", category); + modInfo->setCategory(category, false); + return QString(); + } + } else { + //return tr("None"); + return QVariant(); + } + } + } else if (column == COL_INSTALLTIME) { + // display installation time for mods that can be updated + if (modInfo->canBeUpdated()) { + return modInfo->creationTime(); + } else { + return QVariant(); + } + } else { + return tr("invalid"); + } + } else if ((role == Qt::CheckStateRole) && (column == 0)) { + if (modInfo->canBeEnabled()) { + return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; + } else { + return QVariant(); + } + } else if (role == Qt::TextAlignmentRole) { + if (column == COL_NAME) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } + } else if (column == COL_VERSION) { + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } + } else if (role == Qt::UserRole) { + if (column == COL_CATEGORY) { + QVariantList categoryNames; + std::set categories = modInfo->getCategories(); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (auto iter = categories.begin(); iter != categories.end(); ++iter) { + categoryNames.append(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*iter))); + } + if (categoryNames.count() != 0) { + return categoryNames; + } else { + return QVariant(); + } + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return priority; + } else { + return m_Profile->getModPriority(modIndex); + } + } else { + return modInfo->getNexusID(); + } + } else if (role == Qt::UserRole + 1) { + return modIndex; + } else if (role == Qt::UserRole + 2) { + switch (column) { + case COL_MODID: return QtGroupingProxy::AGGR_FIRST; + case COL_VERSION: return QtGroupingProxy::AGGR_MAX; + case COL_CATEGORY: return QtGroupingProxy::AGGR_FIRST; + case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; + default: return QtGroupingProxy::AGGR_NONE; + } + } else if (role == Qt::FontRole) { + QFont result; + if (column == COL_NAME) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { + result.setItalic(true); + } + } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { + result.setItalic(true); + } else if (column == COL_VERSION) { + if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + result.setWeight(QFont::Bold); + } + } + return result; + } else if (role == Qt::DecorationRole) { + if (column == COL_VERSION) { + if (modInfo->updateAvailable()) { + return QIcon(":/MO/gui/update_available"); + } else if (modInfo->downgradeAvailable()) { + return QIcon(":/MO/gui/warning"); + } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { + return QIcon(":/MO/gui/version_date"); + } + } + return QVariant(); + } else if (role == Qt::ForegroundRole) { + if (column == COL_NAME) { + int highlight = modInfo->getHighlight(); + if (highlight & ModInfo::HIGHLIGHT_IMPORTANT) return QBrush(Qt::darkRed); + else if (highlight & ModInfo::HIGHLIGHT_INVALID) return QBrush(Qt::darkGray); + } else if (column == COL_VERSION) { + if (!modInfo->getNewestVersion().isValid()) { + return QVariant(); + } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + return QBrush(Qt::red); + } else { + return QBrush(Qt::darkGreen); + } + } + return QVariant(); + } else if ((role == Qt::BackgroundRole) + || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { + return QColor(0, 255, 0, 64); + } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { + return QColor(255, 0, 0, 64); + } else { + return QVariant(); + } + } else if (role == Qt::ToolTipRole) { + if (column == COL_FLAGS) { + QString result; + + std::vector flags = modInfo->getFlags(); + + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + if (result.length() != 0) result += "
            "; + result += getFlagText(*iter, modInfo); + } + + return result; + } else if (column == COL_NAME) { + try { + return modInfo->getDescription(); + } catch (const std::exception &e) { + qCritical("invalid mod description: %s", e.what()); + return QString(); + } + } else if (column == COL_VERSION) { + QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString()).arg(modInfo->getNewestVersion().displayString()); + if (modInfo->downgradeAvailable()) { + text += "
            " + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " + "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " + "Either way you may want to \"upgrade\"."); + } + return text; + } else if (column == COL_CATEGORY) { + const std::set &categories = modInfo->getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
            ")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; + } + try { + categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; + } catch (const std::exception &e) { + qCritical("failed to generate tooltip: %s", e.what()); + return QString(); + } + } + + return ToQString(categoryString.str()); + } else { + return QVariant(); + } + } else { + return QVariant(); + } +} + + +bool ModList::renameMod(int index, const QString &newName) +{ + QString nameFixed = newName; + if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { + MessageDialog::showMessage(tr("Invalid name"), NULL); + return false; + } + + // before we rename, write back the current profile so we don't lose changes and to ensure + // there is no scheduled asynchronous rewrite anytime soon + m_Profile->writeModlistNow(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + QString oldName = modInfo->name(); + if (modInfo->setName(nameFixed)) { + // this just disabled the mod in all profiles. The recipient of modRenamed must fix that + emit modRenamed(oldName, nameFixed); + } + + // invalidate the currently displayed state of this list + notifyChange(-1); + return true; +} + + +bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (m_Profile == NULL) return false; + + if (static_cast(index.row()) >= ModInfo::getNumMods()) { + return false; + } + + int modID = index.row(); + + ModInfo::Ptr info = ModInfo::getByIndex(modID); + IModList::ModStates oldState = state(modID); + + bool result = false; + + if (role == Qt::CheckStateRole) { + bool enabled = value.toInt() == Qt::Checked; + if (m_Profile->modEnabled(modID) != enabled) { + m_Profile->setModEnabled(modID, enabled); + m_Modified = true; + emit modlist_changed(index, role); + } + result = true; + } else if (role == Qt::EditRole) { + switch (index.column()) { + case COL_NAME: { + result = renameMod(modID, value.toString()); + } break; + case COL_PRIORITY: { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + m_Profile->setModPriority(modID, newPriority); + + emit modlist_changed(index, role); + result = true; + } else { + result = false; + } + } break; + case COL_MODID: { + bool ok = false; + int newID = value.toInt(&ok); + if (ok) { + info->setNexusID(newID); + emit modlist_changed(index, role); + result = true; + } else { + result = false; + } + } break; + case COL_VERSION: { + VersionInfo::VersionScheme scheme = info->getVersion().scheme(); + VersionInfo version(value.toString(), scheme); + if (version.isValid()) { + info->setVersion(version); + result = true; + } else { + result = false; + } + } break; + default: { + qWarning("edit on column \"%s\" not supported", + getColumnName(index.column()).toUtf8().constData()); + result = false; + } break; + } + if (result) { + emit dataChanged(index, index); + } + } + + IModList::ModStates newState = state(modID); + if (oldState != newState) { + try { + m_ModStateChanged(info->name(), newState); + } catch (const std::exception &e) { + qCritical("failed to invoke state changed notification: %s", e.what()); + } catch (...) { + qCritical("failed to invoke state changed notification: unknown exception"); + } + } + + return result; +} + + +ModList::EColumn ModList::getEnabledColumn(int index) const +{ + for (int i = 0; i <= COL_LASTCOLUMN; ++i) { + if (index == 0) { + return static_cast(i); + } else { + --index; + } + } + return ModList::COL_NAME; +} + + +QVariant ModList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + return getColumnName(section); + } else if (role == Qt::ToolTipRole) { + return getColumnToolTip(section); + } else if (role == Qt::TextAlignmentRole) { + return QVariant(Qt::AlignCenter); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 20; + temp.rheight() += 12; + return temp; + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + + +Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); + if (modelIndex.internalId() < 0) { + return Qt::ItemIsEnabled; + } + if (modelIndex.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); + if (modInfo->getFixedPriority() == INT_MIN) { + result |= Qt::ItemIsDragEnabled; + result |= Qt::ItemIsUserCheckable; + if ((modelIndex.column() == COL_NAME) || + (modelIndex.column() == COL_PRIORITY) || + (modelIndex.column() == COL_VERSION) || + (modelIndex.column() == COL_MODID)) { + result |= Qt::ItemIsEditable; + } + } + std::vector flags = modInfo->getFlags(); + if ((m_DropOnItems) && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { + result |= Qt::ItemIsDropEnabled; + } + } else { + if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; + } + return result; +} + + +QStringList ModList::mimeTypes() const +{ + QStringList result = QAbstractItemModel::mimeTypes(); + result.append("text/uri-list"); + return result; +} + + +void ModList::changeModPriority(std::vector sourceIndices, int newPriority) +{ + if (m_Profile == NULL) return; + + emit layoutAboutToBeChanged(); + Profile *profile = m_Profile; + // sort rows to insert by their old priority (ascending) and insert them move them in that order + std::sort(sourceIndices.begin(), sourceIndices.end(), + [profile](const int &LHS, const int &RHS) { + return profile->getModPriority(LHS) < profile->getModPriority(RHS); + }); + + // odd stuff: if any of the dragged sources has priority lower than the destination then the + // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + if (profile->getModPriority(*iter) < newPriority) { + --newPriority; + break; + } + } + + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + int oldPriority = m_Profile->getModPriority(*iter); + m_Profile->setModPriority(*iter, newPriority); + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } + + emit layoutChanged(); + + emit modorder_changed(); +} + + +void ModList::changeModPriority(int sourceIndex, int newPriority) +{ + if (m_Profile == NULL) return; + emit layoutAboutToBeChanged(); + + m_Profile->setModPriority(sourceIndex, newPriority); + + emit layoutChanged(); + + emit modorder_changed(); +} + +void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_Overwrite = overwrite; + m_Overwritten = overwritten; + notifyChange(0, rowCount()); +} + +void ModList::modInfoAboutToChange(ModInfo::Ptr info) +{ + m_ChangeInfo.name = info->name(); + m_ChangeInfo.state = state(info->name()); +} + +void ModList::modInfoChanged(ModInfo::Ptr info) +{ + if (info->name() == m_ChangeInfo.name) { + IModList::ModStates newState = state(info->name()); + if (m_ChangeInfo.state != newState) { + m_ModStateChanged(info->name(), newState); + } + int row = ModInfo::getIndex(info->name()); + info->testValid(); + emit dataChanged(index(row, 0), index(row, columnCount())); + } else { + qCritical("modInfoChanged not called after modInfoAboutToChange"); + } +} + +IModList::ModStates ModList::state(unsigned int modIndex) const +{ + IModList::ModStates result; + if (modIndex != UINT_MAX) { + result |= IModList::STATE_EXISTS; + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (modInfo->isEmpty()) { + result |= IModList::STATE_EMPTY; + } + if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { + result |= IModList::STATE_ENDORSED; + } + if (modInfo->isValid()) { + result |= IModList::STATE_VALID; + } + if (modInfo->canBeEnabled()) { + if (m_Profile->modEnabled(modIndex)) { + result |= IModList::STATE_ACTIVE; + } + } else { + result |= IModList::STATE_ESSENTIAL; + } + } + return result; +} + +IModList::ModStates ModList::state(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); + + return state(modIndex); +} + +int ModList::priority(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return -1; + } else { + return m_Profile->getModPriority(modIndex); + } +} + +bool ModList::setPriority(const QString &name, int newPriority) +{ + if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { + return false; + } + + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return false; + } else { + m_Profile->setModPriority(modIndex, newPriority); + notifyChange(modIndex); + return true; + } +} + +bool ModList::onModStateChanged(const std::function &func) +{ + auto conn = m_ModStateChanged.connect(func); + return conn.connected(); +} + +bool ModList::onModMoved(const std::function &func) +{ + auto conn = m_ModMoved.connect(func); + return conn.connected(); +} + +bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + QStringList source; + QStringList target; + + if (row == -1) { + row = parent.row(); + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + QDir modDirectory(modInfo->absolutePath()); + QDir gameDirectory(QDir::fromNativeSeparators(ToQString(MOShared::GameInfo::instance().getOverwriteDir()))); + + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); + + foreach (const QUrl &url, mimeData->urls()) { + QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); + if (relativePath.startsWith("..")) { + qDebug("%s drop ignored", qPrintable(url.toLocalFile())); + continue; + } + source.append(url.toLocalFile()); + target.append(modDirectory.absoluteFilePath(relativePath)); + emit fileMoved(relativePath, overwriteName, modInfo->name()); + } + + if (source.count() != 0) { + shellMove(source, target); + } + + return true; +} + + +bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + + try { + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + std::vector sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { + sourceRows.push_back(sourceRow); + } + } + + if (row == -1) { + row = parent.row(); + } + + if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { + return false; + } + + int newPriority = 0; + { + if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { + newPriority = m_Profile->numRegularMods() + 1; + } else { + newPriority = m_Profile->getModPriority(row); + } + if (newPriority == -1) { + newPriority = m_Profile->numRegularMods() + 1; + } + } + changeModPriority(sourceRows, newPriority); + } catch (const std::exception &e) { + reportError(tr("drag&drop failed: %1").arg(e.what())); + } + + return false; +} + + +bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + if (m_Profile == NULL) return false; + if (mimeData->hasUrls()) { + return dropURLs(mimeData, row, parent); + } else { + return dropMod(mimeData, row, parent); + } + +} + + +void ModList::removeRowForce(int row) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return; + } + if (m_Profile == NULL) return; + + m_Profile->setModEnabled(row, false); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + + bool wasEnabled = m_Profile->modEnabled(row); + beginRemoveRows(QModelIndex(), row, row); + + m_Profile->cancelWriteModlist(); // don't write modlist while we're changing it + ModInfo::removeMod(row); + m_Profile->refreshModStatus(); // removes the mod from the status list + m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed + + endRemoveRows(); + if (wasEnabled) { + emit removeOrigin(modInfo->name()); + } + + emit modUninstalled(modInfo->getInstallationFile()); +} + + +void ModList::removeRow(int row, const QModelIndex&) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return; + } + if (m_Profile == NULL) return; + + m_Profile->setModEnabled(row, false); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + if (!modInfo->isRegular()) return; + + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), + QMessageBox::Yes | QMessageBox::No); + + if (confirmBox.exec() == QMessageBox::Yes) { + removeRowForce(row); + } +} + + +void ModList::notifyChange(int rowStart, int rowEnd) +{ + if (rowStart < 0) { + beginResetModel(); + endResetModel(); + } else { + if (rowEnd == -1) rowEnd = rowStart; + emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); + } +} + + +QModelIndex ModList::index(int row, int column, const QModelIndex&) const +{ + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + QModelIndex res = createIndex(row, column, row); + return res; +} + + +QModelIndex ModList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +QMap ModList::itemData(const QModelIndex &index) const +{ + QMap result = QAbstractItemModel::itemData(index); + result[Qt::UserRole] = data(index, Qt::UserRole); + return result; +} + + +void ModList::dropModeUpdate(bool dropOnItems) +{ + if (m_DropOnItems != dropOnItems) { + m_DropOnItems = dropOnItems; + } +} + + +QString ModList::getColumnName(int column) +{ + switch (column) { + case COL_FLAGS: return tr("Flags"); + case COL_NAME: return tr("Mod Name"); + case COL_VERSION: return tr("Version"); + case COL_PRIORITY: return tr("Priority"); + case COL_CATEGORY: return tr("Category"); + case COL_MODID: return tr("Nexus ID"); + case COL_INSTALLTIME: return tr("Installation"); + default: return tr("unknown"); + } +} + + +QString ModList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: return tr("Name of your mods"); + case COL_VERSION: return tr("Version of the mod (if available)"); + case COL_PRIORITY: return tr("Installation priority of your mod. The higher, the more \"important\" it is and thus " + "overwrites files from mods with lower priority."); + case COL_CATEGORY: return tr("Category of the mod."); + case COL_MODID: return tr("Id of the mod as used on Nexus."); + case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_INSTALLTIME: return tr("Time this mod was installed"); + default: return tr("unknown"); + } +} + + +bool ModList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ContextMenu) { + QContextMenuEvent *contextEvent = static_cast(event); + QWidget *object = qobject_cast(obj); + if ((object != NULL) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { + emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); + + return true; + } + } else if ((event->type() == QEvent::KeyPress) && (m_Profile != NULL)) { + QAbstractItemView *itemView = qobject_cast(obj); + QKeyEvent *keyEvent = static_cast(event); + if ((itemView != NULL) + && (keyEvent->modifiers() == Qt::ControlModifier) + && ((keyEvent->key() == Qt::Key_Up) + || (keyEvent->key() == Qt::Key_Down))) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); + const QSortFilterProxyModel *filterModel = NULL; + while ((filterModel == NULL) && (proxyModel != NULL)) { + filterModel = qobject_cast(proxyModel); + if (filterModel == NULL) { + proxyModel = qobject_cast(proxyModel->sourceModel()); + } + } + if (filterModel == NULL) { + return true; + } + int diff = -1; + if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) { + diff = 1; + } + QModelIndexList rows = selectionModel->selectedRows(); + if (keyEvent->key() == Qt::Key_Down) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + foreach (QModelIndex idx, rows) { + if (filterModel != NULL) { + idx = filterModel->mapToSource(idx); + } + int newPriority = m_Profile->getModPriority(idx.row()) + diff; + if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { + m_Profile->setModPriority(idx.row(), newPriority); + notifyChange(idx.row()); + } + } + return true; + } else if (keyEvent->key() == Qt::Key_Delete) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + QModelIndexList rows = selectionModel->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } else if (rows.count() == 1) { + removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); + } + return true; + } else if (keyEvent->key() == Qt::Key_Space) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); + + QModelIndex minRow, maxRow; + foreach (QModelIndex idx, selectionModel->selectedRows()) { + if (proxyModel != NULL) { + idx = proxyModel->mapToSource(idx); + } + if (!minRow.isValid() || (idx.row() < minRow.row())) { + minRow = idx; + } + if (!maxRow.isValid() || (idx.row() > maxRow.row())) { + maxRow = idx; + } + int oldState = idx.data(Qt::CheckStateRole).toInt(); + setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); + } + emit dataChanged(minRow, maxRow); + return true; + } + } + return QObject::eventFilter(obj, event); +} diff --git a/src/modlist.h b/src/modlist.h index 18342aa5..2e8d6a84 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -1,293 +1,293 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MODLIST_H -#define MODLIST_H - - -#include "categories.h" -#include "nexusinterface.h" -#include "modinfo.h" -#include "profile.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -/** - * Model presenting an overview of the installed mod - * This is used in a view in the main window of MO. It combines general information about - * the mods from ModInfo with status information from the Profile - **/ -class ModList : public QAbstractItemModel, public MOBase::IModList -{ - Q_OBJECT - -public: - - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_CATEGORY, - COL_MODID, - COL_VERSION, - COL_INSTALLTIME, - COL_PRIORITY, - - COL_LASTCOLUMN = COL_PRIORITY - }; - - typedef boost::signals2::signal SignalModStateChanged; - typedef boost::signals2::signal SignalModMoved; - -public: - - /** - * @brief constructor - * @todo ensure this view works without a profile set, otherwise there are intransparent dependencies on the initialisation order - **/ - ModList(QObject *parent = NULL); - - /** - * @brief set the profile used for status information - * - * @param profile the profile to use - **/ - void setProfile(Profile *profile); - - /** - * @brief retrieve the current sorting mode - * @note this is used to store the sorting mode between sessions - * @return current sorting mode, encoded to be compatible to previous versions - **/ - int getCurrentSortingMode() const; - - /** - * @brief remove the specified mod without asking for confirmation - * @param row the row to remove - */ - void removeRowForce(int row); - - void notifyChange(int rowStart, int rowEnd = -1); - static QString getColumnName(int column); - - void changeModPriority(int sourceIndex, int newPriority); - - void setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - - void modInfoAboutToChange(ModInfo::Ptr info); - void modInfoChanged(ModInfo::Ptr info); - -public: - - /// \copydoc MOBase::IModList::state - virtual ModStates state(const QString &name) const; - - /// \copydoc MOBase::IModList::priority - virtual int priority(const QString &name) const; - - /// \copydoc MOBase::IModList::setPriority - virtual bool setPriority(const QString &name, int newPriority); - - /// \copydoc MOBase::IModList::onModStateChanged - virtual bool onModStateChanged(const std::function &func); - - /// \copydoc MOBase::IModList::onModMoved - virtual bool onModMoved(const std::function &func); - -public: // implementation of virtual functions of QAbstractItemModel - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } - virtual QStringList mimeTypes() const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual void removeRow(int row, const QModelIndex &parent); - - virtual QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - - virtual QMap itemData(const QModelIndex &index) const; - -public slots: - - void dropModeUpdate(bool dropOnItems); - -signals: - - /** - * @brief emitted whenever the sorting in the list was changed by the user - * - * the sorting of the list can only be manually changed if the list is sorted by priority - * in which case the move is intended to change the priority of a mod - **/ - void modorder_changed(); - - /** - * @brief emitted when the model wants a text to be displayed by the UI - * - * @param message the message to display - **/ - void showMessage(const QString &message); - - /** - * @brief signals change to the count of headers - */ - void resizeHeaders(); - - /** - * @brief requestColumnSelect is emitted whenever the user requested a menu to select visible columns - * @param pos the position to display the menu at - */ - void requestColumnSelect(QPoint pos); - - /** - * @brief emitted to remove a file origin - * @param name name of the orign to remove - */ - void removeOrigin(const QString &name); - - /** - * @brief emitted after a mod has been renamed - * This signal MUST be used to fix the mod names in profiles (except the active one) and to invalidate/refresh other - * structures that may have become invalid with the rename - * - * @param oldName the old name of the mod - * @param newName new name of the mod - */ - void modRenamed(const QString &oldName, const QString &newName); - - /** - * @brief emitted after a mod has been uninstalled - * @param fileName filename of the mod being uninstalled - */ - void modUninstalled(const QString &fileName); - - /** - * @brief emitted whenever a row in the list has changed - * - * @param index the index of the changed field - * @param role role of the field that changed - * @note this signal must only be emitted if the row really did change. - * Slots handling this signal therefore do not have to verify that a change has happened - * @note this signal is currently only used in tutorials - **/ - void modlist_changed(const QModelIndex &index, int role); - - /** - * @brief emitted to have all selected mods deleted - */ - void removeSelectedMods(); - - /** - * @brief fileMoved emitted when a file is moved from one mod to another - * @param relativePath relative path of the file moved - * @param oldOriginName name of the origin that previously contained the file - * @param newOriginName name of the origin that now contains the file - */ - void fileMoved(const QString &relativePath, const QString &oldOriginName, const QString &newOriginName); - -protected: - - // event filter, handles event from the header and the tree view itself - bool eventFilter(QObject *obj, QEvent *event); - -private: - - bool testValid(const QString &modDir); - - void changeModPriority(std::vector sourceIndices, int newPriority); - - QVariant getOverwriteData(int column, int role) const; - - QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; - - static QString getColumnToolTip(int column); - - ModList::EColumn getEnabledColumn(int index) const; - - QVariant categoryData(int categoryID, int column, int role) const; - QVariant modData(int modID, int modelColumn, int role) const; - - bool renameMod(int index, const QString &newName); - - bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent); - - bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent); - - ModStates state(unsigned int modIndex) const; - -private slots: - -private: - - struct TModInfo { - TModInfo(unsigned int index, ModInfo::Ptr modInfo) - : modInfo(modInfo), nameOrder(index) {} - ModInfo::Ptr modInfo; - unsigned int nameOrder; - unsigned int priorityOrder; - unsigned int modIDOrder; - unsigned int categoryOrder; - }; - - struct TModInfoChange { - QString name; - QFlags state; - }; - -private: - - Profile *m_Profile; - - NexusInterface *m_NexusInterface; - std::set m_RequestIDs; - - mutable bool m_Modified; - - QFontMetrics m_FontMetrics; - - bool m_DropOnItems; - - std::set m_Overwrite; - std::set m_Overwritten; - - TModInfoChange m_ChangeInfo; - - SignalModStateChanged m_ModStateChanged; - SignalModMoved m_ModMoved; - -}; - -#endif // MODLIST_H - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MODLIST_H +#define MODLIST_H + + +#include "categories.h" +#include "nexusinterface.h" +#include "modinfo.h" +#include "profile.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Model presenting an overview of the installed mod + * This is used in a view in the main window of MO. It combines general information about + * the mods from ModInfo with status information from the Profile + **/ +class ModList : public QAbstractItemModel, public MOBase::IModList +{ + Q_OBJECT + +public: + + enum EColumn { + COL_NAME, + COL_FLAGS, + COL_CATEGORY, + COL_MODID, + COL_VERSION, + COL_INSTALLTIME, + COL_PRIORITY, + + COL_LASTCOLUMN = COL_PRIORITY + }; + + typedef boost::signals2::signal SignalModStateChanged; + typedef boost::signals2::signal SignalModMoved; + +public: + + /** + * @brief constructor + * @todo ensure this view works without a profile set, otherwise there are intransparent dependencies on the initialisation order + **/ + ModList(QObject *parent = NULL); + + /** + * @brief set the profile used for status information + * + * @param profile the profile to use + **/ + void setProfile(Profile *profile); + + /** + * @brief retrieve the current sorting mode + * @note this is used to store the sorting mode between sessions + * @return current sorting mode, encoded to be compatible to previous versions + **/ + int getCurrentSortingMode() const; + + /** + * @brief remove the specified mod without asking for confirmation + * @param row the row to remove + */ + void removeRowForce(int row); + + void notifyChange(int rowStart, int rowEnd = -1); + static QString getColumnName(int column); + + void changeModPriority(int sourceIndex, int newPriority); + + void setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); + + void modInfoAboutToChange(ModInfo::Ptr info); + void modInfoChanged(ModInfo::Ptr info); + +public: + + /// \copydoc MOBase::IModList::state + virtual ModStates state(const QString &name) const; + + /// \copydoc MOBase::IModList::priority + virtual int priority(const QString &name) const; + + /// \copydoc MOBase::IModList::setPriority + virtual bool setPriority(const QString &name, int newPriority); + + /// \copydoc MOBase::IModList::onModStateChanged + virtual bool onModStateChanged(const std::function &func); + + /// \copydoc MOBase::IModList::onModMoved + virtual bool onModMoved(const std::function &func); + +public: // implementation of virtual functions of QAbstractItemModel + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; + virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } + virtual QStringList mimeTypes() const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + virtual void removeRow(int row, const QModelIndex &parent); + + virtual QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + + virtual QMap itemData(const QModelIndex &index) const; + +public slots: + + void dropModeUpdate(bool dropOnItems); + +signals: + + /** + * @brief emitted whenever the sorting in the list was changed by the user + * + * the sorting of the list can only be manually changed if the list is sorted by priority + * in which case the move is intended to change the priority of a mod + **/ + void modorder_changed(); + + /** + * @brief emitted when the model wants a text to be displayed by the UI + * + * @param message the message to display + **/ + void showMessage(const QString &message); + + /** + * @brief signals change to the count of headers + */ + void resizeHeaders(); + + /** + * @brief requestColumnSelect is emitted whenever the user requested a menu to select visible columns + * @param pos the position to display the menu at + */ + void requestColumnSelect(QPoint pos); + + /** + * @brief emitted to remove a file origin + * @param name name of the orign to remove + */ + void removeOrigin(const QString &name); + + /** + * @brief emitted after a mod has been renamed + * This signal MUST be used to fix the mod names in profiles (except the active one) and to invalidate/refresh other + * structures that may have become invalid with the rename + * + * @param oldName the old name of the mod + * @param newName new name of the mod + */ + void modRenamed(const QString &oldName, const QString &newName); + + /** + * @brief emitted after a mod has been uninstalled + * @param fileName filename of the mod being uninstalled + */ + void modUninstalled(const QString &fileName); + + /** + * @brief emitted whenever a row in the list has changed + * + * @param index the index of the changed field + * @param role role of the field that changed + * @note this signal must only be emitted if the row really did change. + * Slots handling this signal therefore do not have to verify that a change has happened + * @note this signal is currently only used in tutorials + **/ + void modlist_changed(const QModelIndex &index, int role); + + /** + * @brief emitted to have all selected mods deleted + */ + void removeSelectedMods(); + + /** + * @brief fileMoved emitted when a file is moved from one mod to another + * @param relativePath relative path of the file moved + * @param oldOriginName name of the origin that previously contained the file + * @param newOriginName name of the origin that now contains the file + */ + void fileMoved(const QString &relativePath, const QString &oldOriginName, const QString &newOriginName); + +protected: + + // event filter, handles event from the header and the tree view itself + bool eventFilter(QObject *obj, QEvent *event); + +private: + + bool testValid(const QString &modDir); + + void changeModPriority(std::vector sourceIndices, int newPriority); + + QVariant getOverwriteData(int column, int role) const; + + QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; + + static QString getColumnToolTip(int column); + + ModList::EColumn getEnabledColumn(int index) const; + + QVariant categoryData(int categoryID, int column, int role) const; + QVariant modData(int modID, int modelColumn, int role) const; + + bool renameMod(int index, const QString &newName); + + bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent); + + bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent); + + ModStates state(unsigned int modIndex) const; + +private slots: + +private: + + struct TModInfo { + TModInfo(unsigned int index, ModInfo::Ptr modInfo) + : modInfo(modInfo), nameOrder(index) {} + ModInfo::Ptr modInfo; + unsigned int nameOrder; + unsigned int priorityOrder; + unsigned int modIDOrder; + unsigned int categoryOrder; + }; + + struct TModInfoChange { + QString name; + QFlags state; + }; + +private: + + Profile *m_Profile; + + NexusInterface *m_NexusInterface; + std::set m_RequestIDs; + + mutable bool m_Modified; + + QFontMetrics m_FontMetrics; + + bool m_DropOnItems; + + std::set m_Overwrite; + std::set m_Overwritten; + + TModInfoChange m_ChangeInfo; + + SignalModStateChanged m_ModStateChanged; + SignalModMoved m_ModMoved; + +}; + +#endif // MODLIST_H + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 88b7bef0..77702689 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -1,376 +1,376 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modlistsortproxy.h" -#include "modinfo.h" -#include "profile.h" -#include "modlist.h" -#include -#include -#include -#include - - -ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) - : QSortFilterProxyModel(parent) - , m_Profile(profile) - , m_CategoryFilter() - , m_CurrentFilter() - , m_FilterActive(false) - , m_FilterMode(FILTER_AND) -{ - m_EnabledColumns.set(ModList::COL_FLAGS); - m_EnabledColumns.set(ModList::COL_NAME); - m_EnabledColumns.set(ModList::COL_VERSION); - m_EnabledColumns.set(ModList::COL_PRIORITY); - setDynamicSortFilter(true); // this seems to work without dynamicsortfilter - // but I don't know why. This should be necessary -} - - -void ModListSortProxy::setProfile(Profile *profile) -{ - m_Profile = profile; -} - -void ModListSortProxy::updateFilterActive() -{ - m_FilterActive = (m_CategoryFilter.size() > 0) || !m_CurrentFilter.isEmpty(); - emit filterActive(m_FilterActive); -} - -void ModListSortProxy::setCategoryFilter(const std::vector &categories) -{ - m_CategoryFilter = categories; - updateFilterActive(); - this->invalidate(); -} - -Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); - if (sortColumn() != ModList::COL_PRIORITY) { - flags &= ~Qt::ItemIsDragEnabled; - } - - return flags; -} - - -void ModListSortProxy::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - for (int i = 0; i <= ModList::COL_LASTCOLUMN; ++i) { - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(ModList::getColumnName(i)); - checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - int i = 0; - - emit layoutAboutToBeChanged(); - m_EnabledColumns.reset(); - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); - if (checkBox != NULL) { - m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); - } - } - ++i; - } - emit layoutChanged(); -} - - -void ModListSortProxy::enableAllVisible() -{ - if (m_Profile == NULL) return; - - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, true); - } - invalidate(); -} - - -void ModListSortProxy::disableAllVisible() -{ - if (m_Profile == NULL) return; - - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, false); - } - invalidate(); -} - - -bool ModListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - bool lOk, rOk; - int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk); - int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk); - if (!lOk || !rOk) { - return false; - } - - ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); - ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); - - bool lt = false; - - { - QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY); - QVariant leftPrio = leftPrioIdx.data(); - if (!leftPrio.isValid()) leftPrio = left.data(Qt::UserRole); - QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY); - QVariant rightPrio = rightPrioIdx.data(); - if (!rightPrio.isValid()) rightPrio = right.data(Qt::UserRole); - - lt = leftPrio.toInt() < rightPrio.toInt(); - } - - switch (left.column()) { - case ModList::COL_FLAGS: { - if (leftMod->getFlags().size() != rightMod->getFlags().size()) - lt = leftMod->getFlags().size() < rightMod->getFlags().size(); - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->getPrimaryCategory() != rightMod->getPrimaryCategory()) { - if (leftMod->getPrimaryCategory() < 0) lt = false; - else if (rightMod->getPrimaryCategory() < 0) lt = true; - else { - try { - CategoryFactory &categories = CategoryFactory::instance(); - QString leftCatName = categories.getCategoryName(categories.getCategoryIndex(leftMod->getPrimaryCategory())); - QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception &e) { - qCritical("failed to compare categories: %s", e.what()); - } - } - } - } break; - case ModList::COL_MODID: { - if (leftMod->getNexusID() != rightMod->getNexusID()) - lt = leftMod->getNexusID() < rightMod->getNexusID(); - } break; - case ModList::COL_VERSION: { - if (leftMod->getVersion() != rightMod->getVersion()) - lt = leftMod->getVersion() < rightMod->getVersion(); - } break; - case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; - } break; - case ModList::COL_PRIORITY: { - // nop, already compared by priority - } break; - } - return lt; -} - - -void ModListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - updateFilterActive(); - invalidateFilter(); -} - - -bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const -{ - foreach (ModInfo::EFlag flag, flags) { - if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || - (flag == ModInfo::FLAG_CONFLICT_REDUNDANT)) { - return true; - } - } - - return false; -} - - -bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const -{ - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (!enabled && !info->alwaysEnabled()) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (enabled || info->alwaysEnabled()) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (!info->updateAvailable() && !info->downgradeAvailable()) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() > 0) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (!hasConflictFlag(info->getFlags())) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if (state != ModInfo::ENDORSED_FALSE) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; - } break; - default: { - if (!info->categorySet(*iter)) return false; - } break; - } - } - return true; -} - -bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const -{ - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (enabled || info->alwaysEnabled()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (!enabled && !info->alwaysEnabled()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (info->updateAvailable() || info->downgradeAvailable()) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() == 0) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (hasConflictFlag(info->getFlags())) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if ((state == ModInfo::ENDORSED_FALSE) && (state != ModInfo::ENDORSED_NEVER)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; - } break; - default: { - if (info->categorySet(*iter)) return true; - } break; - } - } - return false; -} - - - -bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const -{ - if (!m_CurrentFilter.isEmpty() && - !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) { - return false; - } - - if (m_FilterMode == FILTER_AND) { - return filterMatchesModAnd(info, enabled); - } else { - return (m_CategoryFilter.size() == 0) || filterMatchesModOr(info, enabled); - } -} - -void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) -{ - if (m_FilterMode != mode) { - m_FilterMode = mode; - this->invalidate(); - } -} - - -bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const -{ - if (m_Profile == NULL) { - return false; - } - - if (row >= static_cast(m_Profile->numMods())) { - qWarning("invalid row idx %d", row); - return false; - } - - QModelIndex idx = sourceModel()->index(row, 0, parent); - if (!idx.isValid()) { - qDebug("invalid index"); - return false; - } - if (idx.isValid() && sourceModel()->hasChildren(idx)) { - for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { - if (filterAcceptsRow(i, idx)) { - return true; - } - } - - return false; - } else { - bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; - unsigned int index = idx.data(Qt::UserRole + 1).toInt(); - return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); - } -} - - -bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - if ((row == -1) && (column == -1)) { - return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering. - // This should fix that - if (sortOrder() == Qt::DescendingOrder) { - --row; - } - - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modlistsortproxy.h" +#include "modinfo.h" +#include "profile.h" +#include "modlist.h" +#include +#include +#include +#include + + +ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) + : QSortFilterProxyModel(parent) + , m_Profile(profile) + , m_CategoryFilter() + , m_CurrentFilter() + , m_FilterActive(false) + , m_FilterMode(FILTER_AND) +{ + m_EnabledColumns.set(ModList::COL_FLAGS); + m_EnabledColumns.set(ModList::COL_NAME); + m_EnabledColumns.set(ModList::COL_VERSION); + m_EnabledColumns.set(ModList::COL_PRIORITY); + setDynamicSortFilter(true); // this seems to work without dynamicsortfilter + // but I don't know why. This should be necessary +} + + +void ModListSortProxy::setProfile(Profile *profile) +{ + m_Profile = profile; +} + +void ModListSortProxy::updateFilterActive() +{ + m_FilterActive = (m_CategoryFilter.size() > 0) || !m_CurrentFilter.isEmpty(); + emit filterActive(m_FilterActive); +} + +void ModListSortProxy::setCategoryFilter(const std::vector &categories) +{ + m_CategoryFilter = categories; + updateFilterActive(); + this->invalidate(); +} + +Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); + if (sortColumn() != ModList::COL_PRIORITY) { + flags &= ~Qt::ItemIsDragEnabled; + } + + return flags; +} + + +void ModListSortProxy::displayColumnSelection(const QPoint &pos) +{ + QMenu menu; + + for (int i = 0; i <= ModList::COL_LASTCOLUMN; ++i) { + QCheckBox *checkBox = new QCheckBox(&menu); + checkBox->setText(ModList::getColumnName(i)); + checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); + QWidgetAction *checkableAction = new QWidgetAction(&menu); + checkableAction->setDefaultWidget(checkBox); + menu.addAction(checkableAction); + } + menu.exec(pos); + int i = 0; + + emit layoutAboutToBeChanged(); + m_EnabledColumns.reset(); + foreach (const QAction *action, menu.actions()) { + const QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); + if (checkBox != NULL) { + m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); + } + } + ++i; + } + emit layoutChanged(); +} + + +void ModListSortProxy::enableAllVisible() +{ + if (m_Profile == NULL) return; + + for (int i = 0; i < this->rowCount(); ++i) { + int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); + m_Profile->setModEnabled(modID, true); + } + invalidate(); +} + + +void ModListSortProxy::disableAllVisible() +{ + if (m_Profile == NULL) return; + + for (int i = 0; i < this->rowCount(); ++i) { + int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); + m_Profile->setModEnabled(modID, false); + } + invalidate(); +} + + +bool ModListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + bool lOk, rOk; + int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk); + int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk); + if (!lOk || !rOk) { + return false; + } + + ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); + ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); + + bool lt = false; + + { + QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY); + QVariant leftPrio = leftPrioIdx.data(); + if (!leftPrio.isValid()) leftPrio = left.data(Qt::UserRole); + QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY); + QVariant rightPrio = rightPrioIdx.data(); + if (!rightPrio.isValid()) rightPrio = right.data(Qt::UserRole); + + lt = leftPrio.toInt() < rightPrio.toInt(); + } + + switch (left.column()) { + case ModList::COL_FLAGS: { + if (leftMod->getFlags().size() != rightMod->getFlags().size()) + lt = leftMod->getFlags().size() < rightMod->getFlags().size(); + } break; + case ModList::COL_NAME: { + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; + } break; + case ModList::COL_CATEGORY: { + if (leftMod->getPrimaryCategory() != rightMod->getPrimaryCategory()) { + if (leftMod->getPrimaryCategory() < 0) lt = false; + else if (rightMod->getPrimaryCategory() < 0) lt = true; + else { + try { + CategoryFactory &categories = CategoryFactory::instance(); + QString leftCatName = categories.getCategoryName(categories.getCategoryIndex(leftMod->getPrimaryCategory())); + QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception &e) { + qCritical("failed to compare categories: %s", e.what()); + } + } + } + } break; + case ModList::COL_MODID: { + if (leftMod->getNexusID() != rightMod->getNexusID()) + lt = leftMod->getNexusID() < rightMod->getNexusID(); + } break; + case ModList::COL_VERSION: { + if (leftMod->getVersion() != rightMod->getVersion()) + lt = leftMod->getVersion() < rightMod->getVersion(); + } break; + case ModList::COL_INSTALLTIME: { + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; + } break; + case ModList::COL_PRIORITY: { + // nop, already compared by priority + } break; + } + return lt; +} + + +void ModListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + updateFilterActive(); + invalidateFilter(); +} + + +bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const +{ + foreach (ModInfo::EFlag flag, flags) { + if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_CONFLICT_REDUNDANT)) { + return true; + } + } + + return false; +} + + +bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const +{ + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + switch (*iter) { + case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { + if (!enabled && !info->alwaysEnabled()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { + if (enabled || info->alwaysEnabled()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { + if (!info->updateAvailable() && !info->downgradeAvailable()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { + if (info->getCategories().size() > 0) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { + if (!hasConflictFlag(info->getFlags())) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { + ModInfo::EEndorsedState state = info->endorsedState(); + if (state != ModInfo::ENDORSED_FALSE) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { + if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { + if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; + } break; + default: { + if (!info->categorySet(*iter)) return false; + } break; + } + } + return true; +} + +bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const +{ + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + switch (*iter) { + case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { + if (enabled || info->alwaysEnabled()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { + if (!enabled && !info->alwaysEnabled()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { + if (info->updateAvailable() || info->downgradeAvailable()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { + if (info->getCategories().size() == 0) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { + if (hasConflictFlag(info->getFlags())) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { + ModInfo::EEndorsedState state = info->endorsedState(); + if ((state == ModInfo::ENDORSED_FALSE) && (state != ModInfo::ENDORSED_NEVER)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { + if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { + if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; + } break; + default: { + if (info->categorySet(*iter)) return true; + } break; + } + } + return false; +} + + + +bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const +{ + if (!m_CurrentFilter.isEmpty() && + !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) { + return false; + } + + if (m_FilterMode == FILTER_AND) { + return filterMatchesModAnd(info, enabled); + } else { + return (m_CategoryFilter.size() == 0) || filterMatchesModOr(info, enabled); + } +} + +void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) +{ + if (m_FilterMode != mode) { + m_FilterMode = mode; + this->invalidate(); + } +} + + +bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + if (m_Profile == NULL) { + return false; + } + + if (row >= static_cast(m_Profile->numMods())) { + qWarning("invalid row idx %d", row); + return false; + } + + QModelIndex idx = sourceModel()->index(row, 0, parent); + if (!idx.isValid()) { + qDebug("invalid index"); + return false; + } + if (idx.isValid() && sourceModel()->hasChildren(idx)) { + for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { + if (filterAcceptsRow(i, idx)) { + return true; + } + } + + return false; + } else { + bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; + unsigned int index = idx.data(Qt::UserRole + 1).toInt(); + return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); + } +} + + +bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if ((row == -1) && (column == -1)) { + return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } + // in the regular model, when dropping between rows, the row-value passed to + // the sourceModel is inconsistent between ascending and descending ordering. + // This should fix that + if (sortOrder() == Qt::DescendingOrder) { + --row; + } + + QModelIndex proxyIndex = index(row, column, parent); + QModelIndex sourceIndex = mapToSource(proxyIndex); + return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), + sourceIndex.parent()); +} diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index c04ba28d..fc7edcd5 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -1,121 +1,121 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef MODLISTSORTPROXY_H -#define MODLISTSORTPROXY_H - -#include -#include -#include "modlist.h" - -class Profile; - -class ModListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - - enum FilterMode { - FILTER_AND, - FILTER_OR - }; - -public: - - explicit ModListSortProxy(Profile *profile, QObject *parent = 0); - - void setProfile(Profile *profile); - - void setCategoryFilter(const std::vector &categories); - - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent); - - /** - * @brief enable all mods visible under the current filter - **/ - void enableAllVisible(); - - /** - * @brief disable all mods visible under the current filter - **/ - void disableAllVisible(); - - /** - * @brief tests if a filtere matches for a mod - * @param info mod information - * @param enabled true if the mod is currently active - * @return true if current active filters match for the specified mod - */ - bool filterMatchesMod(ModInfo::Ptr info, bool enabled) const; - - /** - * @return true if a filter is currently active - */ - bool isFilterActive() const { return m_FilterActive; } - - void setFilterMode(FilterMode mode); - - /** - * @brief tests if the specified index has child nodes - * @param parent the node to test - * @return true if there are child nodes - */ - virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const { - return rowCount(parent) > 0; - } - - -public slots: - - void displayColumnSelection(const QPoint &pos); - void updateFilter(const QString &filter); - -signals: - - void filterActive(bool active); - -protected: - - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - -private: - - bool hasConflictFlag(const std::vector &flags) const; - void updateFilterActive(); - bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; - bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; - -private: - - Profile *m_Profile; - - std::vector m_CategoryFilter; - std::bitset m_EnabledColumns; - QString m_CurrentFilter; - - bool m_FilterActive; - FilterMode m_FilterMode; - -}; - -#endif // MODLISTSORTPROXY_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef MODLISTSORTPROXY_H +#define MODLISTSORTPROXY_H + +#include +#include +#include "modlist.h" + +class Profile; + +class ModListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + + enum FilterMode { + FILTER_AND, + FILTER_OR + }; + +public: + + explicit ModListSortProxy(Profile *profile, QObject *parent = 0); + + void setProfile(Profile *profile); + + void setCategoryFilter(const std::vector &categories); + + virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); + + /** + * @brief enable all mods visible under the current filter + **/ + void enableAllVisible(); + + /** + * @brief disable all mods visible under the current filter + **/ + void disableAllVisible(); + + /** + * @brief tests if a filtere matches for a mod + * @param info mod information + * @param enabled true if the mod is currently active + * @return true if current active filters match for the specified mod + */ + bool filterMatchesMod(ModInfo::Ptr info, bool enabled) const; + + /** + * @return true if a filter is currently active + */ + bool isFilterActive() const { return m_FilterActive; } + + void setFilterMode(FilterMode mode); + + /** + * @brief tests if the specified index has child nodes + * @param parent the node to test + * @return true if there are child nodes + */ + virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const { + return rowCount(parent) > 0; + } + + +public slots: + + void displayColumnSelection(const QPoint &pos); + void updateFilter(const QString &filter); + +signals: + + void filterActive(bool active); + +protected: + + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; + +private: + + bool hasConflictFlag(const std::vector &flags) const; + void updateFilterActive(); + bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; + bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; + +private: + + Profile *m_Profile; + + std::vector m_CategoryFilter; + std::bitset m_EnabledColumns; + QString m_CurrentFilter; + + bool m_FilterActive; + FilterMode m_FilterMode; + +}; + +#endif // MODLISTSORTPROXY_H diff --git a/src/motddialog.cpp b/src/motddialog.cpp index 84c67aa5..9fa2f005 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -1,49 +1,49 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "motddialog.h" -#include "bbcode.h" -#include "utility.h" -#include "ui_motddialog.h" -#include - -MotDDialog::MotDDialog(const QString &message, QWidget *parent) - : QDialog(parent), ui(new Ui::MotDDialog) -{ - ui->setupUi(this); - ui->motdView->setHtml(BBCode::convertToHTML(message)); -// ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); -// connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); -} - -MotDDialog::~MotDDialog() -{ - delete ui; -} - -void MotDDialog::on_okButton_clicked() -{ - this->close(); -} - -void MotDDialog::linkClicked(const QUrl &url) -{ - ::ShellExecuteW(NULL, L"open", MOBase::ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "motddialog.h" +#include "bbcode.h" +#include "utility.h" +#include "ui_motddialog.h" +#include + +MotDDialog::MotDDialog(const QString &message, QWidget *parent) + : QDialog(parent), ui(new Ui::MotDDialog) +{ + ui->setupUi(this); + ui->motdView->setHtml(BBCode::convertToHTML(message)); +// ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); +// connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); +} + +MotDDialog::~MotDDialog() +{ + delete ui; +} + +void MotDDialog::on_okButton_clicked() +{ + this->close(); +} + +void MotDDialog::linkClicked(const QUrl &url) +{ + ::ShellExecuteW(NULL, L"open", MOBase::ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); +} diff --git a/src/motddialog.h b/src/motddialog.h index 1e9a5078..5791321f 100644 --- a/src/motddialog.h +++ b/src/motddialog.h @@ -1,30 +1,30 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef MOTDDIALOG_H -#define MOTDDIALOG_H - -#include -#include - -namespace Ui { -class MotDDialog; +#define MOTDDIALOG_H + +#include +#include + +namespace Ui { +class MotDDialog; } class MotDDialog : public QDialog @@ -34,13 +34,13 @@ class MotDDialog : public QDialog public: explicit MotDDialog(const QString &message, QWidget *parent = 0); ~MotDDialog(); - -private slots: - void on_okButton_clicked(); - void linkClicked(const QUrl &url); - -private: - Ui::MotDDialog *ui; + +private slots: + void on_okButton_clicked(); + void linkClicked(const QUrl &url); + +private: + Ui::MotDDialog *ui; }; #endif // MOTDDIALOG_H diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 48426834..c7e2df9e 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -1,622 +1,622 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "nexusinterface.h" -#include "nxmaccessmanager.h" -#include "utility.h" -#include -#include "selectiondialog.h" -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -NexusBridge::NexusBridge(const QString &subModule) - : m_Interface(NexusInterface::instance()) - , m_Url(MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())) - , m_SubModule(subModule) -{ -} - - -void NexusBridge::requestDescription(int modID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestFiles(int modID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - - emit descriptionAvailable(modID, userData, resultData); - } -} - -void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - - QList fileInfoList; - - QVariantList resultList = resultData.toList(); - - foreach(QVariant file, resultList) { - ModRepositoryFileInfo temp; - QVariantMap fileInfo = file.toMap(); - temp.uri = fileInfo["uri"].toString(); - temp.name = fileInfo["name"].toString(); - temp.description = fileInfo["description"].toString(); - temp.version = VersionInfo(fileInfo["version"].toString()); - temp.categoryID = fileInfo["category_id"].toInt(); - temp.fileID = fileInfo["id"].toInt(); - temp.fileSize = fileInfo["size"].toInt(); - fileInfoList.append(temp); - } - - emit filesAvailable(modID, userData, fileInfoList); - } -} - -void NexusBridge::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit fileInfoAvailable(modID, fileID, userData, resultData); - } -} - -void NexusBridge::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit downloadURLsAvailable(modID, fileID, userData, resultData); - } -} - -void NexusBridge::nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit endorsementToggled(modID, userData, resultData); - } -} - -void NexusBridge::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage) -{ - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit requestFailed(modID, fileID, userData, errorMessage); - } -} - - -QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); - - -NexusInterface::NexusInterface() - : m_NMMVersion() -{ - atexit(&cleanup); - m_AccessManager = new NXMAccessManager(this); - - m_DiskCache = new QNetworkDiskCache(this); - connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); - - - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16); -} - - -void NexusInterface::cleanup() -{ -} - - -NXMAccessManager *NexusInterface::getAccessManager() -{ - return m_AccessManager; -} - - -NexusInterface *NexusInterface::instance() -{ - static NexusInterface s_Instance; - return &s_Instance; -} - - -void NexusInterface::setCacheDirectory(const QString &directory) -{ - m_DiskCache->setCacheDirectory(directory); - m_AccessManager->setCache(m_DiskCache); -} - - -void NexusInterface::setNMMVersion(const QString &nmmVersion) -{ - m_NMMVersion = nmmVersion; -} - -void NexusInterface::loginCompleted() -{ - nextRequest(); -} - - -void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) -{ - static std::tr1::regex exp("^([a-zA-Z0-9_\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*"); - static std::tr1::regex simpleexp("^([a-zA-Z0-9_]+)"); - - QByteArray fileNameUTF8 = fileName.toUtf8(); - std::tr1::cmatch result; - if (std::tr1::regex_search(fileNameUTF8.constData(), result, exp)) { - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - - std::string candidate = result[3].str(); - std::string candidate2 = result[2].str(); - if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) { - // well, that second match might be an id too... - unsigned offset = strspn(candidate2.c_str(), "-_ "); - if (offset < candidate2.length() && query) { - SelectionDialog selection(tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName)); - QString r2Highlight(fileName); - r2Highlight.insert(result.position(2) + result.length(2), "* ").insert(result.position(2) + offset, " *"); - QString r3Highlight(fileName); - r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); - - selection.addChoice(candidate.c_str(), r3Highlight, strtol(candidate.c_str(), NULL, 10)); - selection.addChoice(candidate2.c_str() + offset, r2Highlight, abs(strtol(candidate2.c_str() + offset, NULL, 10))); - if (selection.exec() == QDialog::Accepted) { - modID = selection.getChoiceData().toInt(); - } else { - modID = -1; - } - } else { - modID = -1; - } - } else { - modID = strtol(candidate.c_str(), NULL, 10); - } - qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID); - } else if (std::tr1::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - qDebug("simple expression matched, using name only"); - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - - modID = -1; - } else { - qDebug("no expression matched!"); - modName.clear(); - modID = -1; - } -} - - -int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url, int nexusGameId) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, url, - nexusGameId == -1 ? GameInfo::instance().getNexusGameID() : nexusGameId); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant,int)), - receiver, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), - receiver, SLOT(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - - nextRequest(); - return requestInfo.m_ID; -} - - -void NexusInterface::fakeFiles() -{ - static int id = 42; - - QVariantList result; - QVariantMap fileMap; - fileMap["uri"] = "fakeURI"; - fileMap["name"] = "fakeName"; - fileMap["description"] = "fakeDescription"; - fileMap["version"] = "1.0.0"; - fileMap["category_id"] = "1"; - fileMap["id"] = "1"; - fileMap["size"] = "512"; - result.append(fileMap); - - emit nxmFilesAvailable(1234, "fake", result, id++); -} - - -int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmFilesAvailable(int,QVariant,QVariant,int)), - receiver, SLOT(nxmFilesAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - -// QTimer::singleShot(1000, this, SLOT(fakeFiles())); -// static int fID = 42; -// return fID++; - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url) -{ - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), - receiver, SLOT(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), - receiver, SLOT(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, url, GameInfo::instance().getNexusGameID()); - requestInfo.m_Endorse = endorse; - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmEndorsementToggled(int,QVariant,QVariant,int)), - receiver, SLOT(nxmEndorsementToggled(int,QVariant,QVariant,int)), Qt::UniqueConnection); - - connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), - receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - - nextRequest(); - return requestInfo.m_ID; -} - -bool NexusInterface::requiresLogin(const NXMRequestInfo &info) -{ - return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) - || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); -} - -void NexusInterface::nextRequest() -{ - if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) - || m_RequestQueue.isEmpty()) { - return; - } - - if (!getAccessManager()->loggedIn() - && requiresLogin(m_RequestQueue.head())) { - if (!getAccessManager()->loginAttempted()) { - emit needLogin(); - return; - } else if (getAccessManager()->loginWaiting()) { - return; - } - } - - NXMRequestInfo info = m_RequestQueue.dequeue(); - info.m_Timeout = new QTimer(this); - info.m_Timeout->setInterval(60000); - - QString url; - if (!info.m_Reroute) { - bool hasParams = false; - switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILES: { - url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); - hasParams = true; - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - QString modIDList = VectorJoin(info.m_ModIDList, ","); - modIDList = "[" + modIDList + "]"; - url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); - hasParams = true; - } break; - } - url.append(QString("%1game_id=%2").arg(hasParams ? '&' : '?').arg(info.m_NexusGameID)); - } else { - url = info.m_URL; - } - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); - QStringList comments; - comments << "compatible to Nexus Client v" + m_NMMVersion; - if (!info.m_SubModule.isEmpty()) { - comments << "module: " + info.m_SubModule; - } - - QString userAgent = QString("Mod Organizer v%1 (%2)") - .arg(m_MOVersion.displayString()) - .arg(comments.join("; ")); - request.setRawHeader("User-Agent", userAgent.toUtf8()); - - info.m_Reply = m_AccessManager->get(request); - - connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); - connect(info.m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); - connect(info.m_Timeout, SIGNAL(timeout()), this, SLOT(requestTimeout())); - info.m_Timeout->start(); - m_ActiveRequest.push_back(info); -} - - -void NexusInterface::downloadRequestedNXM(const QString &url) -{ - emit requestNXMDownload(url); -} - -void NexusInterface::requestFinished(std::list::iterator iter) -{ - QNetworkReply *reply = iter->m_Reply; - - if (reply->error() != QNetworkReply::NoError) { - qWarning("request failed: %s", reply->errorString().toUtf8().constData()); - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->errorString()); - } else { - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode == 301) { - // redirect request, return request to queue - iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); - iter->m_Reroute = true; - m_RequestQueue.enqueue(*iter); - nextRequest(); - return; - } - QByteArray data = reply->readAll(); - if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { - QString nexusError(reply->rawHeader("NexusErrorInfo")); - if (nexusError.length() == 0) { - nexusError = tr("empty response"); - } - qDebug("nexus error: %s", qPrintable(nexusError)); - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, nexusError); - } else { - bool ok; - QVariant result = QtJson::parse(data, ok); - if (result.isValid() && ok) { - switch (iter->m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - emit nxmDescriptionAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILES: { - emit nxmFilesAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - emit nxmFileInfoAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - emit nxmDownloadURLsAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - emit nxmUpdatesAvailable(iter->m_ModIDList, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - emit nxmEndorsementToggled(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - } - } else { - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, tr("invalid response")); - } - } - } -} - - -void NexusInterface::requestFinished() -{ - QNetworkReply *reply = static_cast(sender()); - for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { - if (iter->m_Reply == reply) { - iter->m_Timeout->stop(); - iter->m_Timeout->deleteLater(); - requestFinished(iter); - iter->m_Reply->deleteLater(); - m_ActiveRequest.erase(iter); - nextRequest(); - return; - } - } -} - - -void NexusInterface::requestError(QNetworkReply::NetworkError) -{ - QNetworkReply *reply = qobject_cast(sender()); - if (reply == NULL) { - qWarning("invalid sender type"); - return; - } - - qCritical("request (%s) error: %s", - qPrintable(reply->url().toString()), qPrintable(reply->errorString())); -} - - -void NexusInterface::requestTimeout() -{ - QTimer *timer = qobject_cast(sender()); - if (timer == NULL) { - qWarning("invalid sender type"); - return; - } - for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { - if (iter->m_Timeout == timer) { - // this abort causes a "request failed" which cleans up the rest - iter->m_Reply->abort(); - return; - } - } -} - - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(modID) - , m_FileID(0) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(-1) - , m_ModIDList(modIDList) - , m_FileID(0) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , int fileID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(modID) - , m_FileID(fileID) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "nexusinterface.h" +#include "nxmaccessmanager.h" +#include "utility.h" +#include +#include "selectiondialog.h" +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +NexusBridge::NexusBridge(const QString &subModule) + : m_Interface(NexusInterface::instance()) + , m_Url(MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())) + , m_SubModule(subModule) +{ +} + + +void NexusBridge::requestDescription(int modID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule, m_Url)); +} + +void NexusBridge::requestFiles(int modID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule, m_Url)); +} + +void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule, m_Url)); +} + +void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule, m_Url)); +} + +void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule, m_Url)); +} + +void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + + emit descriptionAvailable(modID, userData, resultData); + } +} + +void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + + QList fileInfoList; + + QVariantList resultList = resultData.toList(); + + foreach(QVariant file, resultList) { + ModRepositoryFileInfo temp; + QVariantMap fileInfo = file.toMap(); + temp.uri = fileInfo["uri"].toString(); + temp.name = fileInfo["name"].toString(); + temp.description = fileInfo["description"].toString(); + temp.version = VersionInfo(fileInfo["version"].toString()); + temp.categoryID = fileInfo["category_id"].toInt(); + temp.fileID = fileInfo["id"].toInt(); + temp.fileSize = fileInfo["size"].toInt(); + fileInfoList.append(temp); + } + + emit filesAvailable(modID, userData, fileInfoList); + } +} + +void NexusBridge::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit fileInfoAvailable(modID, fileID, userData, resultData); + } +} + +void NexusBridge::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit downloadURLsAvailable(modID, fileID, userData, resultData); + } +} + +void NexusBridge::nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit endorsementToggled(modID, userData, resultData); + } +} + +void NexusBridge::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit requestFailed(modID, fileID, userData, errorMessage); + } +} + + +QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); + + +NexusInterface::NexusInterface() + : m_NMMVersion() +{ + atexit(&cleanup); + m_AccessManager = new NXMAccessManager(this); + + m_DiskCache = new QNetworkDiskCache(this); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); + + + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16); +} + + +void NexusInterface::cleanup() +{ +} + + +NXMAccessManager *NexusInterface::getAccessManager() +{ + return m_AccessManager; +} + + +NexusInterface *NexusInterface::instance() +{ + static NexusInterface s_Instance; + return &s_Instance; +} + + +void NexusInterface::setCacheDirectory(const QString &directory) +{ + m_DiskCache->setCacheDirectory(directory); + m_AccessManager->setCache(m_DiskCache); +} + + +void NexusInterface::setNMMVersion(const QString &nmmVersion) +{ + m_NMMVersion = nmmVersion; +} + +void NexusInterface::loginCompleted() +{ + nextRequest(); +} + + +void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) +{ + static std::tr1::regex exp("^([a-zA-Z0-9_\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*"); + static std::tr1::regex simpleexp("^([a-zA-Z0-9_]+)"); + + QByteArray fileNameUTF8 = fileName.toUtf8(); + std::tr1::cmatch result; + if (std::tr1::regex_search(fileNameUTF8.constData(), result, exp)) { + modName = QString::fromUtf8(result[1].str().c_str()); + modName = modName.replace('_', ' ').trimmed(); + + std::string candidate = result[3].str(); + std::string candidate2 = result[2].str(); + if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) { + // well, that second match might be an id too... + unsigned offset = strspn(candidate2.c_str(), "-_ "); + if (offset < candidate2.length() && query) { + SelectionDialog selection(tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName)); + QString r2Highlight(fileName); + r2Highlight.insert(result.position(2) + result.length(2), "* ").insert(result.position(2) + offset, " *"); + QString r3Highlight(fileName); + r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); + + selection.addChoice(candidate.c_str(), r3Highlight, strtol(candidate.c_str(), NULL, 10)); + selection.addChoice(candidate2.c_str() + offset, r2Highlight, abs(strtol(candidate2.c_str() + offset, NULL, 10))); + if (selection.exec() == QDialog::Accepted) { + modID = selection.getChoiceData().toInt(); + } else { + modID = -1; + } + } else { + modID = -1; + } + } else { + modID = strtol(candidate.c_str(), NULL, 10); + } + qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID); + } else if (std::tr1::regex_search(fileNameUTF8.constData(), result, simpleexp)) { + qDebug("simple expression matched, using name only"); + modName = QString::fromUtf8(result[1].str().c_str()); + modName = modName.replace('_', ' ').trimmed(); + + modID = -1; + } else { + qDebug("no expression matched!"); + modName.clear(); + modID = -1; + } +} + + +int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData, + const QString &subModule, const QString &url, int nexusGameId) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, url, + nexusGameId == -1 ? GameInfo::instance().getNexusGameID() : nexusGameId); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant,int)), + receiver, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + + +int NexusInterface::requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, + const QString &subModule, const QString &url) +{ + NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, url, GameInfo::instance().getNexusGameID()); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), + receiver, SLOT(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + + +void NexusInterface::fakeFiles() +{ + static int id = 42; + + QVariantList result; + QVariantMap fileMap; + fileMap["uri"] = "fakeURI"; + fileMap["name"] = "fakeName"; + fileMap["description"] = "fakeDescription"; + fileMap["version"] = "1.0.0"; + fileMap["category_id"] = "1"; + fileMap["id"] = "1"; + fileMap["size"] = "512"; + result.append(fileMap); + + emit nxmFilesAvailable(1234, "fake", result, id++); +} + + +int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, + const QString &subModule, const QString &url) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, url, GameInfo::instance().getNexusGameID()); + m_RequestQueue.enqueue(requestInfo); + connect(this, SIGNAL(nxmFilesAvailable(int,QVariant,QVariant,int)), + receiver, SLOT(nxmFilesAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + +// QTimer::singleShot(1000, this, SLOT(fakeFiles())); +// static int fID = 42; +// return fID++; + + nextRequest(); + return requestInfo.m_ID; +} + + +int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url) +{ + NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, url, GameInfo::instance().getNexusGameID()); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), + receiver, SLOT(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + + +int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, + const QString &subModule, const QString &url) +{ + NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, subModule, url, GameInfo::instance().getNexusGameID()); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), + receiver, SLOT(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + + +int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, + const QString &subModule, const QString &url) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, url, GameInfo::instance().getNexusGameID()); + requestInfo.m_Endorse = endorse; + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmEndorsementToggled(int,QVariant,QVariant,int)), + receiver, SLOT(nxmEndorsementToggled(int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; +} + +bool NexusInterface::requiresLogin(const NXMRequestInfo &info) +{ + return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) + || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); +} + +void NexusInterface::nextRequest() +{ + if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) + || m_RequestQueue.isEmpty()) { + return; + } + + if (!getAccessManager()->loggedIn() + && requiresLogin(m_RequestQueue.head())) { + if (!getAccessManager()->loginAttempted()) { + emit needLogin(); + return; + } else if (getAccessManager()->loginWaiting()) { + return; + } + } + + NXMRequestInfo info = m_RequestQueue.dequeue(); + info.m_Timeout = new QTimer(this); + info.m_Timeout->setInterval(60000); + + QString url; + if (!info.m_Reroute) { + bool hasParams = false; + switch (info.m_Type) { + case NXMRequestInfo::TYPE_DESCRIPTION: { + url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILES: { + url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); + hasParams = true; + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + QString modIDList = VectorJoin(info.m_ModIDList, ","); + modIDList = "[" + modIDList + "]"; + url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); + hasParams = true; + } break; + } + url.append(QString("%1game_id=%2").arg(hasParams ? '&' : '?').arg(info.m_NexusGameID)); + } else { + url = info.m_URL; + } + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); + QStringList comments; + comments << "compatible to Nexus Client v" + m_NMMVersion; + if (!info.m_SubModule.isEmpty()) { + comments << "module: " + info.m_SubModule; + } + + QString userAgent = QString("Mod Organizer v%1 (%2)") + .arg(m_MOVersion.displayString()) + .arg(comments.join("; ")); + request.setRawHeader("User-Agent", userAgent.toUtf8()); + + info.m_Reply = m_AccessManager->get(request); + + connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(info.m_Reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(info.m_Timeout, SIGNAL(timeout()), this, SLOT(requestTimeout())); + info.m_Timeout->start(); + m_ActiveRequest.push_back(info); +} + + +void NexusInterface::downloadRequestedNXM(const QString &url) +{ + emit requestNXMDownload(url); +} + +void NexusInterface::requestFinished(std::list::iterator iter) +{ + QNetworkReply *reply = iter->m_Reply; + + if (reply->error() != QNetworkReply::NoError) { + qWarning("request failed: %s", reply->errorString().toUtf8().constData()); + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->errorString()); + } else { + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode == 301) { + // redirect request, return request to queue + iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); + iter->m_Reroute = true; + m_RequestQueue.enqueue(*iter); + nextRequest(); + return; + } + QByteArray data = reply->readAll(); + if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { + QString nexusError(reply->rawHeader("NexusErrorInfo")); + if (nexusError.length() == 0) { + nexusError = tr("empty response"); + } + qDebug("nexus error: %s", qPrintable(nexusError)); + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, nexusError); + } else { + bool ok; + QVariant result = QtJson::parse(data, ok); + if (result.isValid() && ok) { + switch (iter->m_Type) { + case NXMRequestInfo::TYPE_DESCRIPTION: { + emit nxmDescriptionAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILES: { + emit nxmFilesAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + emit nxmFileInfoAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + emit nxmDownloadURLsAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + emit nxmUpdatesAvailable(iter->m_ModIDList, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + emit nxmEndorsementToggled(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + } + } else { + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, tr("invalid response")); + } + } + } +} + + +void NexusInterface::requestFinished() +{ + QNetworkReply *reply = static_cast(sender()); + for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { + if (iter->m_Reply == reply) { + iter->m_Timeout->stop(); + iter->m_Timeout->deleteLater(); + requestFinished(iter); + iter->m_Reply->deleteLater(); + m_ActiveRequest.erase(iter); + nextRequest(); + return; + } + } +} + + +void NexusInterface::requestError(QNetworkReply::NetworkError) +{ + QNetworkReply *reply = qobject_cast(sender()); + if (reply == NULL) { + qWarning("invalid sender type"); + return; + } + + qCritical("request (%s) error: %s", + qPrintable(reply->url().toString()), qPrintable(reply->errorString())); +} + + +void NexusInterface::requestTimeout() +{ + QTimer *timer = qobject_cast(sender()); + if (timer == NULL) { + qWarning("invalid sender type"); + return; + } + for (std::list::iterator iter = m_ActiveRequest.begin(); iter != m_ActiveRequest.end(); ++iter) { + if (iter->m_Timeout == timer) { + // this abort causes a "request failed" which cleans up the rest + iter->m_Reply->abort(); + return; + } + } +} + + +NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , const QString &url + , int nexusGameId) + : m_ModID(modID) + , m_FileID(0) + , m_Reply(NULL) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(NULL) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(url) + , m_SubModule(subModule) + , m_NexusGameID(nexusGameId) +{} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , const QString &url + , int nexusGameId) + : m_ModID(-1) + , m_ModIDList(modIDList) + , m_FileID(0) + , m_Reply(NULL) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(NULL) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(url) + , m_SubModule(subModule) + , m_NexusGameID(nexusGameId) +{} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID + , int fileID + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , const QString &url + , int nexusGameId) + : m_ModID(modID) + , m_FileID(fileID) + , m_Reply(NULL) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(NULL) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(url) + , m_SubModule(subModule) + , m_NexusGameID(nexusGameId) +{} diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 5a46eebf..db61b0bf 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -1,322 +1,322 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef NEXUSINTERFACE_H -#define NEXUSINTERFACE_H - - -#include "nxmaccessmanager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -class NexusInterface; - - -/** - * @brief convenience class to make nxm requests easier - * usually, all objects that started a nxm request will be signaled if one finished. - * Therefore, the objects need to store the id of the requests they started and then filter - * the result. - * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend - * to handle and only receive the signals the caused - **/ -class NexusBridge : public MOBase::IModRepositoryBridge -{ - - Q_OBJECT - -public: - - NexusBridge(const QString &subModule = ""); - - /** - * @brief request description for a mod - * - * @param modID id of the mod caller is interested in - * @param userData user data to be returned with the result - * @param url the url to request from - **/ - virtual void requestDescription(int modID, QVariant userData); - - /** - * @brief request a list of the files belonging to a mod - * - * @param modID id of the mod caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestFiles(int modID, QVariant userData); - - /** - * @brief request info about a single file of a mod - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestFileInfo(int modID, int fileID, QVariant userData); - - /** - * @brief request the download url of a file - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestDownloadURL(int modID, int fileID, QVariant userData); - - /** - * @brief requestToggleEndorsement - * @param modID id of the mod caller is interested in - * @param userData user data to be returned with the result - */ - virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData); - -public slots: - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); - -private: - - NexusInterface *m_Interface; - QString m_Url; - QString m_SubModule; - std::set m_RequestIDs; - -}; - - -/** - * @brief Makes asynchronous requests to the nexus API - * - * This class can be used to make asynchronous requests to the Nexus API. - * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the - * recipient has to filter the response by the id returned when making the request - **/ -class NexusInterface : public QObject -{ - Q_OBJECT - -public: - - static NexusInterface *instance(); - - /** - * @return the access manager object used to connect to nexus - **/ - NXMAccessManager *getAccessManager(); - - /** - * @brief request description for a mod - * - * @param modID id of the mod caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - **/ - int requestDescription(int modID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()), - int nexusGameId = -1); - - /** - * @brief request nexus descriptions for multiple mods at once - * @param modIDs a list of ids of mods the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - */ - int requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @brief request a list of the files belonging to a mod - * - * @param modID id of the mod caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - **/ - int requestFiles(int modID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @brief request info about a single file of a mod - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - **/ - int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @brief request the download url of a file - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - **/ - int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @brief toggle endorsement state of the mod - * @param modID id of the mod - * @param endorse true if the mod should be endorsed, false for un-endorse - * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - */ - int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @param directory the directory to store cache files - **/ - void setCacheDirectory(const QString &directory); - - /** - * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API - * @param nmmVersion the version of nmm to impersonate - **/ - void setNMMVersion(const QString &nmmVersion); - - /** - * @brief called when the log-in completes. This was, requests waiting for the log-in can be run - */ - void loginCompleted(); - -public: - - /** - * @brief guess the mod id from a filename as delivered by Nexus - * @param fileName name of the file - * @return the guessed mod id - * @note this currently doesn't fit well with the remaining interface but this is the best place for the function - */ - static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query); - -signals: - - void requestNXMDownload(const QString &url); - - void needLogin(); - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - -private slots: - - void requestFinished(); - void requestError(QNetworkReply::NetworkError error); - void requestTimeout(); - - void downloadRequestedNXM(const QString &url); - - void fakeFiles(); - -private: - - struct NXMRequestInfo { - int m_ModID; - std::vector m_ModIDList; - int m_FileID; - QNetworkReply *m_Reply; - enum Type { - TYPE_DESCRIPTION, - TYPE_FILES, - TYPE_FILEINFO, - TYPE_DOWNLOADURL, - TYPE_TOGGLEENDORSEMENT, - TYPE_GETUPDATES - } m_Type; - QVariant m_UserData; - QTimer *m_Timeout; - QString m_URL; - QString m_SubModule; - int m_NexusGameID; - bool m_Reroute; - int m_ID; - int m_Endorse; - - NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); - NXMRequestInfo(std::vector modIDList, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); - NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); - - private: - static QAtomicInt s_NextID; - }; - - static const int MAX_ACTIVE_DOWNLOADS = 2; - -private: - - NexusInterface(); - void nextRequest(); - void requestFinished(std::list::iterator iter); - bool requiresLogin(const NXMRequestInfo &info); - static void cleanup(); - -private: - -// static NexusInterface *s_Instance; - - QNetworkDiskCache *m_DiskCache; - - NXMAccessManager *m_AccessManager; - - std::list m_ActiveRequest; - QQueue m_RequestQueue; - - MOBase::VersionInfo m_MOVersion; - QString m_NMMVersion; - -}; - -#endif // NEXUSINTERFACE_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef NEXUSINTERFACE_H +#define NEXUSINTERFACE_H + + +#include "nxmaccessmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class NexusInterface; + + +/** + * @brief convenience class to make nxm requests easier + * usually, all objects that started a nxm request will be signaled if one finished. + * Therefore, the objects need to store the id of the requests they started and then filter + * the result. + * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend + * to handle and only receive the signals the caused + **/ +class NexusBridge : public MOBase::IModRepositoryBridge +{ + + Q_OBJECT + +public: + + NexusBridge(const QString &subModule = ""); + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + virtual void requestDescription(int modID, QVariant userData); + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestFiles(int modID, QVariant userData); + + /** + * @brief request info about a single file of a mod + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestFileInfo(int modID, int fileID, QVariant userData); + + /** + * @brief request the download url of a file + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestDownloadURL(int modID, int fileID, QVariant userData); + + /** + * @brief requestToggleEndorsement + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + */ + virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData); + +public slots: + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); + +private: + + NexusInterface *m_Interface; + QString m_Url; + QString m_SubModule; + std::set m_RequestIDs; + +}; + + +/** + * @brief Makes asynchronous requests to the nexus API + * + * This class can be used to make asynchronous requests to the Nexus API. + * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the + * recipient has to filter the response by the id returned when making the request + **/ +class NexusInterface : public QObject +{ + Q_OBJECT + +public: + + static NexusInterface *instance(); + + /** + * @return the access manager object used to connect to nexus + **/ + NXMAccessManager *getAccessManager(); + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestDescription(int modID, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()), + int nexusGameId = -1); + + /** + * @brief request nexus descriptions for multiple mods at once + * @param modIDs a list of ids of mods the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmDescriptionAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + */ + int requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestFiles(int modID, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request info about a single file of a mod + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief request the download url of a file + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + **/ + int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @brief toggle endorsement state of the mod + * @param modID id of the mod + * @param endorse true if the mod should be endorsed, false for un-endorse + * @param receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) + * @param userData user data to be returned with the result + * @param url the url to request from + * @return int an id to identify the request + */ + int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, + const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); + + /** + * @param directory the directory to store cache files + **/ + void setCacheDirectory(const QString &directory); + + /** + * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API + * @param nmmVersion the version of nmm to impersonate + **/ + void setNMMVersion(const QString &nmmVersion); + + /** + * @brief called when the log-in completes. This was, requests waiting for the log-in can be run + */ + void loginCompleted(); + +public: + + /** + * @brief guess the mod id from a filename as delivered by Nexus + * @param fileName name of the file + * @return the guessed mod id + * @note this currently doesn't fit well with the remaining interface but this is the best place for the function + */ + static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query); + +signals: + + void requestNXMDownload(const QString &url); + + void needLogin(); + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); + +private slots: + + void requestFinished(); + void requestError(QNetworkReply::NetworkError error); + void requestTimeout(); + + void downloadRequestedNXM(const QString &url); + + void fakeFiles(); + +private: + + struct NXMRequestInfo { + int m_ModID; + std::vector m_ModIDList; + int m_FileID; + QNetworkReply *m_Reply; + enum Type { + TYPE_DESCRIPTION, + TYPE_FILES, + TYPE_FILEINFO, + TYPE_DOWNLOADURL, + TYPE_TOGGLEENDORSEMENT, + TYPE_GETUPDATES + } m_Type; + QVariant m_UserData; + QTimer *m_Timeout; + QString m_URL; + QString m_SubModule; + int m_NexusGameID; + bool m_Reroute; + int m_ID; + int m_Endorse; + + NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); + NXMRequestInfo(std::vector modIDList, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); + NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); + + private: + static QAtomicInt s_NextID; + }; + + static const int MAX_ACTIVE_DOWNLOADS = 2; + +private: + + NexusInterface(); + void nextRequest(); + void requestFinished(std::list::iterator iter); + bool requiresLogin(const NXMRequestInfo &info); + static void cleanup(); + +private: + +// static NexusInterface *s_Instance; + + QNetworkDiskCache *m_DiskCache; + + NXMAccessManager *m_AccessManager; + + std::list m_ActiveRequest; + QQueue m_RequestQueue; + + MOBase::VersionInfo m_MOVersion; + QString m_NMMVersion; + +}; + +#endif // NEXUSINTERFACE_H diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index ed872870..a238d1c8 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -1,212 +1,212 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "nxmaccessmanager.h" -#include "nxmurl.h" -#include "report.h" -#include "utility.h" -#include "selfupdater.h" -#include "persistentcookiejar.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) -#include -#endif - -using namespace MOBase; -using namespace MOShared; - - -NXMAccessManager::NXMAccessManager(QObject *parent) - : QNetworkAccessManager(parent) - , m_LoginReply(NULL) - , m_ProgressDialog() - , m_LoginAttempted(false) -{ - setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/nexus_cookies.dat", this)); -} - -NXMAccessManager::~NXMAccessManager() -{ - if (m_LoginReply != NULL) { - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - } -} - - -QNetworkReply *NXMAccessManager::createRequest( - QNetworkAccessManager::Operation operation, const QNetworkRequest &request, - QIODevice *device) -{ - if (request.url().scheme() != "nxm") { - return QNetworkAccessManager::createRequest(operation, request, device); - } - if (operation == GetOperation) { - emit requestNXMDownload(request.url().toString()); - - // eat the request, everything else will be done by the download manager - return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, - QNetworkRequest(QUrl())); - } else if (operation == PostOperation) { - return QNetworkAccessManager::createRequest(operation, request, device);; - } else { - return QNetworkAccessManager::createRequest(operation, request, device); - } -} - - -void NXMAccessManager::showCookies() -{ - QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); - foreach (QNetworkCookie cookie, cookies) { - qDebug("%s - %s", cookie.name().constData(), cookie.value().constData()); - } -} - - -bool NXMAccessManager::loggedIn() const -{ - return hasLoginCookies(); -} - -bool NXMAccessManager::loginWaiting() const -{ - return m_LoginReply != NULL; -} - - -void NXMAccessManager::login(const QString &username, const QString &password) -{ - if (m_LoginReply != NULL) { - return; - } - - if (hasLoginCookies()) { - emit loginSuccessful(false); - return; - } - - m_LoginAttempted = true; - - m_Username = username; - m_Password = password; - pageLogin(); -} - - -void NXMAccessManager::pageLogin() -{ - QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/sessions/?Login&uri=%1") - .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage())))); - - QNetworkRequest request(requestString); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QByteArray postDataQuery; -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QUrlQuery postData; - postData.addQueryItem("username", m_Username); - postData.addQueryItem("password", m_Password); - postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8(); -#else - QUrl postData; - postData.addQueryItem("username", m_Username); - postData.addQueryItem("password", m_Password); - postDataQuery = postData.encodedQuery(); -#endif - - m_ProgressDialog.setLabelText(tr("Logging into Nexus")); - QList buttons = m_ProgressDialog.findChildren(); - buttons.at(0)->setEnabled(false); - m_ProgressDialog.show(); - QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback - - m_LoginReply = post(request, postDataQuery); - m_LoginTimeout.start(); - connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); - connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); -} - - -void NXMAccessManager::loginTimeout() -{ - emit loginFailed(tr("timeout")); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - m_LoginAttempted = false; // this usually means we might have usccess later - m_LoginTimeout.stop(); - m_Username.clear(); - m_Password.clear(); -} - - -void NXMAccessManager::loginError(QNetworkReply::NetworkError) -{ - m_ProgressDialog.hide(); - m_LoginTimeout.stop(); - if (m_LoginReply != NULL) { - emit loginFailed(m_LoginReply->errorString()); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - } else { - emit loginFailed(tr("Unknown error")); - } - m_Username.clear(); - m_Password.clear(); -} - - -bool NXMAccessManager::hasLoginCookies() const -{ - bool sidCookie = false; - QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); - foreach (QNetworkCookie cookie, cookies) { - if (cookie.name() == "sid") { - sidCookie = true; - } - } - return sidCookie; -} - - -void NXMAccessManager::loginFinished() -{ - m_ProgressDialog.hide(); - if (hasLoginCookies()) { - emit loginSuccessful(true); - } else { - emit loginFailed(tr("Please check your password")); - } - - m_LoginTimeout.stop(); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - m_Username.clear(); - m_Password.clear(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "nxmaccessmanager.h" +#include "nxmurl.h" +#include "report.h" +#include "utility.h" +#include "selfupdater.h" +#include "persistentcookiejar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) +#include +#endif + +using namespace MOBase; +using namespace MOShared; + + +NXMAccessManager::NXMAccessManager(QObject *parent) + : QNetworkAccessManager(parent) + , m_LoginReply(NULL) + , m_ProgressDialog() + , m_LoginAttempted(false) +{ + setCookieJar(new PersistentCookieJar( + QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/nexus_cookies.dat", this)); +} + +NXMAccessManager::~NXMAccessManager() +{ + if (m_LoginReply != NULL) { + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + } +} + + +QNetworkReply *NXMAccessManager::createRequest( + QNetworkAccessManager::Operation operation, const QNetworkRequest &request, + QIODevice *device) +{ + if (request.url().scheme() != "nxm") { + return QNetworkAccessManager::createRequest(operation, request, device); + } + if (operation == GetOperation) { + emit requestNXMDownload(request.url().toString()); + + // eat the request, everything else will be done by the download manager + return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, + QNetworkRequest(QUrl())); + } else if (operation == PostOperation) { + return QNetworkAccessManager::createRequest(operation, request, device);; + } else { + return QNetworkAccessManager::createRequest(operation, request, device); + } +} + + +void NXMAccessManager::showCookies() +{ + QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); + foreach (QNetworkCookie cookie, cookies) { + qDebug("%s - %s", cookie.name().constData(), cookie.value().constData()); + } +} + + +bool NXMAccessManager::loggedIn() const +{ + return hasLoginCookies(); +} + +bool NXMAccessManager::loginWaiting() const +{ + return m_LoginReply != NULL; +} + + +void NXMAccessManager::login(const QString &username, const QString &password) +{ + if (m_LoginReply != NULL) { + return; + } + + if (hasLoginCookies()) { + emit loginSuccessful(false); + return; + } + + m_LoginAttempted = true; + + m_Username = username; + m_Password = password; + pageLogin(); +} + + +void NXMAccessManager::pageLogin() +{ + QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/sessions/?Login&uri=%1") + .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage())))); + + QNetworkRequest request(requestString); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QByteArray postDataQuery; +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QUrlQuery postData; + postData.addQueryItem("username", m_Username); + postData.addQueryItem("password", m_Password); + postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8(); +#else + QUrl postData; + postData.addQueryItem("username", m_Username); + postData.addQueryItem("password", m_Password); + postDataQuery = postData.encodedQuery(); +#endif + + m_ProgressDialog.setLabelText(tr("Logging into Nexus")); + QList buttons = m_ProgressDialog.findChildren(); + buttons.at(0)->setEnabled(false); + m_ProgressDialog.show(); + QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback + + m_LoginReply = post(request, postDataQuery); + m_LoginTimeout.start(); + connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); + connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); +} + + +void NXMAccessManager::loginTimeout() +{ + emit loginFailed(tr("timeout")); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + m_LoginAttempted = false; // this usually means we might have usccess later + m_LoginTimeout.stop(); + m_Username.clear(); + m_Password.clear(); +} + + +void NXMAccessManager::loginError(QNetworkReply::NetworkError) +{ + m_ProgressDialog.hide(); + m_LoginTimeout.stop(); + if (m_LoginReply != NULL) { + emit loginFailed(m_LoginReply->errorString()); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + } else { + emit loginFailed(tr("Unknown error")); + } + m_Username.clear(); + m_Password.clear(); +} + + +bool NXMAccessManager::hasLoginCookies() const +{ + bool sidCookie = false; + QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); + foreach (QNetworkCookie cookie, cookies) { + if (cookie.name() == "sid") { + sidCookie = true; + } + } + return sidCookie; +} + + +void NXMAccessManager::loginFinished() +{ + m_ProgressDialog.hide(); + if (hasLoginCookies()) { + emit loginSuccessful(true); + } else { + emit loginFailed(tr("Please check your password")); + } + + m_LoginTimeout.stop(); + m_LoginReply->deleteLater(); + m_LoginReply = NULL; + m_Username.clear(); + m_Password.clear(); +} diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 6c0dedb9..525394c9 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -1,103 +1,103 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef NXMACCESSMANAGER_H -#define NXMACCESSMANAGER_H - - -#include -#include -#include -#include - - -/** - * @brief access manager extended to handle nxm links - **/ -class NXMAccessManager : public QNetworkAccessManager -{ - Q_OBJECT -public: - - explicit NXMAccessManager(QObject *parent); - - ~NXMAccessManager(); - - bool loggedIn() const; - - bool loginAttempted() const { return m_LoginAttempted; } - bool loginWaiting() const; - - void login(const QString &username, const QString &password); - - void showCookies(); - -signals: - - /** - * @brief emitted when a nxm:// link is opened - * - * @param url the nxm-link - **/ - void requestNXMDownload(const QString &url); - - /** - * @brief emitted after a successful login or if login was not necessary - * - * @param necessary true if a login was necessary and succeeded, false if the user is still logged in - **/ - void loginSuccessful(bool necessary); - - void loginFailed(const QString &message); - - private slots: - - void loginFinished(); - void loginError(QNetworkReply::NetworkError errorCode); - void loginTimeout(); - -public slots: - -protected: - - virtual QNetworkReply *createRequest( - QNetworkAccessManager::Operation operation, const QNetworkRequest &request, - QIODevice *device); - -private: - - void pageLogin(); -// void dlLogin(); - - bool hasLoginCookies() const; - -private: - - QTimer m_LoginTimeout; - QNetworkReply *m_LoginReply; - QProgressDialog m_ProgressDialog; - - QString m_Username; - QString m_Password; - - bool m_LoginAttempted; - -}; - -#endif // NXMACCESSMANAGER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef NXMACCESSMANAGER_H +#define NXMACCESSMANAGER_H + + +#include +#include +#include +#include + + +/** + * @brief access manager extended to handle nxm links + **/ +class NXMAccessManager : public QNetworkAccessManager +{ + Q_OBJECT +public: + + explicit NXMAccessManager(QObject *parent); + + ~NXMAccessManager(); + + bool loggedIn() const; + + bool loginAttempted() const { return m_LoginAttempted; } + bool loginWaiting() const; + + void login(const QString &username, const QString &password); + + void showCookies(); + +signals: + + /** + * @brief emitted when a nxm:// link is opened + * + * @param url the nxm-link + **/ + void requestNXMDownload(const QString &url); + + /** + * @brief emitted after a successful login or if login was not necessary + * + * @param necessary true if a login was necessary and succeeded, false if the user is still logged in + **/ + void loginSuccessful(bool necessary); + + void loginFailed(const QString &message); + + private slots: + + void loginFinished(); + void loginError(QNetworkReply::NetworkError errorCode); + void loginTimeout(); + +public slots: + +protected: + + virtual QNetworkReply *createRequest( + QNetworkAccessManager::Operation operation, const QNetworkRequest &request, + QIODevice *device); + +private: + + void pageLogin(); +// void dlLogin(); + + bool hasLoginCookies() const; + +private: + + QTimer m_LoginTimeout; + QNetworkReply *m_LoginReply; + QProgressDialog m_ProgressDialog; + + QString m_Username; + QString m_Password; + + bool m_LoginAttempted; + +}; + +#endif // NXMACCESSMANAGER_H diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 16242506..d06922bc 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -1,260 +1,260 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "overwriteinfodialog.h" -#include "ui_overwriteinfodialog.h" -#include "report.h" -#include "utility.h" -#include -#include -#include - - -using namespace MOBase; - - -class MyFileSystemModel : public QFileSystemModel { - -public: - MyFileSystemModel(QObject *parent) - : QFileSystemModel(parent), m_RegularColumnCount(0) {} - - virtual int columnCount(const QModelIndex &parent) const { - m_RegularColumnCount = QFileSystemModel::columnCount(parent); - return m_RegularColumnCount; - } - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const { - if ((orientation == Qt::Horizontal) && - (section >= m_RegularColumnCount)) { - if (role == Qt::DisplayRole) { - return tr("Overwrites"); - } else { - return QVariant(); - } - } else { - return QFileSystemModel::headerData(section, orientation, role); - } - } - - virtual QVariant data(const QModelIndex &index, int role) const { - if (index.column() == m_RegularColumnCount + 0) { - if (role == Qt::DisplayRole) { - return tr("not implemented"); - } else { - return QVariant(); - } - } else { - return QFileSystemModel::data(index, role); - } - } - -private: - mutable int m_RegularColumnCount; -}; - - -OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) - : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL), - m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL) -{ - ui->setupUi(this); - - this->setWindowModality(Qt::NonModal); - - m_FileSystemModel = new MyFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - setModInfo(modInfo); - ui->filesView->setModel(m_FileSystemModel); - ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); - ui->filesView->setColumnWidth(0, 250); - - m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); - m_RenameAction = new QAction(tr("&Rename"), ui->filesView); - m_OpenAction = new QAction(tr("&Open"), ui->filesView); - m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView); - QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); - QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); - QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); - QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); -} - -OverwriteInfoDialog::~OverwriteInfoDialog() -{ - delete ui; -} - -void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) -{ - m_ModInfo = modInfo; - if (QDir(modInfo->absolutePath()).exists()) { - m_FileSystemModel->setRootPath(modInfo->absolutePath()); - } else { - throw MyException(tr("%1 not found").arg(modInfo->absolutePath())); - } -} - -bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) -{ - for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { - QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); - if (m_FileSystemModel->isDir(childIndex)) { - if (!recursiveDelete(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); - return false; - } - return true; -} - - -void OverwriteInfoDialog::deleteFile(const QModelIndex &index) -{ - - bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) - : m_FileSystemModel->remove(index); - if (!res) { - QString fileName = m_FileSystemModel->fileName(index); - reportError(tr("Failed to delete \"%1\"").arg(fileName)); - } -} - - -void OverwriteInfoDialog::deleteTriggered() -{ - if (m_FileSelection.count() == 0) { - return; - } else if (m_FileSelection.count() == 1) { - QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } - - foreach(QModelIndex index, m_FileSelection) { - deleteFile(index); - } -} - - -void OverwriteInfoDialog::renameTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - - ui->filesView->edit(index); -} - - -void OverwriteInfoDialog::openFile(const QModelIndex &index) -{ - QString fileName = m_FileSystemModel->filePath(index); - - HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW); - if ((int)res <= 32) { - qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); - } -} - - -void OverwriteInfoDialog::openTriggered() -{ - foreach(QModelIndex idx, m_FileSelection) { - openFile(idx); - } -} - -void OverwriteInfoDialog::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->filesView->setCurrentIndex(newIndex); - ui->filesView->edit(newIndex); -} - - -void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - - QMenu menu(ui->filesView); - - menu.addAction(m_NewFolderAction); - - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (selectionModel->hasSelection()) { - if (hasFiles) { - menu.addAction(m_OpenAction); - } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); - } - menu.exec(ui->filesView->mapToGlobal(pos)); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "overwriteinfodialog.h" +#include "ui_overwriteinfodialog.h" +#include "report.h" +#include "utility.h" +#include +#include +#include + + +using namespace MOBase; + + +class MyFileSystemModel : public QFileSystemModel { + +public: + MyFileSystemModel(QObject *parent) + : QFileSystemModel(parent), m_RegularColumnCount(0) {} + + virtual int columnCount(const QModelIndex &parent) const { + m_RegularColumnCount = QFileSystemModel::columnCount(parent); + return m_RegularColumnCount; + } + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const { + if ((orientation == Qt::Horizontal) && + (section >= m_RegularColumnCount)) { + if (role == Qt::DisplayRole) { + return tr("Overwrites"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::headerData(section, orientation, role); + } + } + + virtual QVariant data(const QModelIndex &index, int role) const { + if (index.column() == m_RegularColumnCount + 0) { + if (role == Qt::DisplayRole) { + return tr("not implemented"); + } else { + return QVariant(); + } + } else { + return QFileSystemModel::data(index, role); + } + } + +private: + mutable int m_RegularColumnCount; +}; + + +OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) + : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL), + m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL) +{ + ui->setupUi(this); + + this->setWindowModality(Qt::NonModal); + + m_FileSystemModel = new MyFileSystemModel(this); + m_FileSystemModel->setReadOnly(false); + setModInfo(modInfo); + ui->filesView->setModel(m_FileSystemModel); + ui->filesView->setRootIndex(m_FileSystemModel->index(modInfo->absolutePath())); + ui->filesView->setColumnWidth(0, 250); + + m_DeleteAction = new QAction(tr("&Delete"), ui->filesView); + m_RenameAction = new QAction(tr("&Rename"), ui->filesView); + m_OpenAction = new QAction(tr("&Open"), ui->filesView); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->filesView); + QObject::connect(m_DeleteAction, SIGNAL(triggered()), this, SLOT(deleteTriggered())); + QObject::connect(m_RenameAction, SIGNAL(triggered()), this, SLOT(renameTriggered())); + QObject::connect(m_OpenAction, SIGNAL(triggered()), this, SLOT(openTriggered())); + QObject::connect(m_NewFolderAction, SIGNAL(triggered()), this, SLOT(createDirectoryTriggered())); +} + +OverwriteInfoDialog::~OverwriteInfoDialog() +{ + delete ui; +} + +void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) +{ + m_ModInfo = modInfo; + if (QDir(modInfo->absolutePath()).exists()) { + m_FileSystemModel->setRootPath(modInfo->absolutePath()); + } else { + throw MyException(tr("%1 not found").arg(modInfo->absolutePath())); + } +} + +bool OverwriteInfoDialog::recursiveDelete(const QModelIndex &index) +{ + for (int childRow = 0; childRow < m_FileSystemModel->rowCount(index); ++childRow) { + QModelIndex childIndex = m_FileSystemModel->index(childRow, 0, index); + if (m_FileSystemModel->isDir(childIndex)) { + if (!recursiveDelete(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; +} + + +void OverwriteInfoDialog::deleteFile(const QModelIndex &index) +{ + + bool res = m_FileSystemModel->isDir(index) ? recursiveDelete(index) + : m_FileSystemModel->remove(index); + if (!res) { + QString fileName = m_FileSystemModel->fileName(index); + reportError(tr("Failed to delete \"%1\"").arg(fileName)); + } +} + + +void OverwriteInfoDialog::deleteTriggered() +{ + if (m_FileSelection.count() == 0) { + return; + } else if (m_FileSelection.count() == 1) { + QString fileName = m_FileSystemModel->fileName(m_FileSelection.at(0)); + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } + + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } +} + + +void OverwriteInfoDialog::renameTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_FileSystemModel->isReadOnly()) { + return; + } + + ui->filesView->edit(index); +} + + +void OverwriteInfoDialog::openFile(const QModelIndex &index) +{ + QString fileName = m_FileSystemModel->filePath(index); + + HINSTANCE res = ::ShellExecuteW(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, SW_SHOW); + if ((int)res <= 32) { + qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); + } +} + + +void OverwriteInfoDialog::openTriggered() +{ + foreach(QModelIndex idx, m_FileSelection) { + openFile(idx); + } +} + +void OverwriteInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + + QModelIndex index = m_FileSystemModel->isDir(selection) ? selection + : selection.parent(); + index = index.sibling(index.row(), 0); + + QString name = tr("New Folder"); + QString path = m_FileSystemModel->filePath(index).append("/"); + + QModelIndex existingIndex = m_FileSystemModel->index(path + name); + int suffix = 1; + while (existingIndex.isValid()) { + name = tr("New Folder") + QString::number(suffix++); + existingIndex = m_FileSystemModel->index(path + name); + } + + QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } + + ui->filesView->setCurrentIndex(newIndex); + ui->filesView->edit(newIndex); +} + + +void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); + + QMenu menu(ui->filesView); + + menu.addAction(m_NewFolderAction); + + bool hasFiles = false; + + foreach(QModelIndex idx, m_FileSelection) { + if (m_FileSystemModel->fileInfo(idx).isFile()) { + hasFiles = true; + break; + } + } + + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); + } + menu.addAction(m_RenameAction); + menu.addAction(m_DeleteAction); + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + menu.exec(ui->filesView->mapToGlobal(pos)); +} diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 0726f1f9..eb182292 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -1,73 +1,73 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef OVERWRITEINFODIALOG_H -#define OVERWRITEINFODIALOG_H - -#include "modinfo.h" -#include -#include - -namespace Ui { -class OverwriteInfoDialog; -} - -class OverwriteInfoDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); - ~OverwriteInfoDialog(); - - ModInfo::Ptr modInfo() const { return m_ModInfo; } - - void setModInfo(ModInfo::Ptr modInfo); - -private: - - void openFile(const QModelIndex &index); - bool recursiveDelete(const QModelIndex &index); - void deleteFile(const QModelIndex &index); - -private slots: - - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); - void createDirectoryTriggered(); - - void on_filesView_customContextMenuRequested(const QPoint &pos); - -private: - - Ui::OverwriteInfoDialog *ui; - QFileSystemModel *m_FileSystemModel; - QModelIndexList m_FileSelection; - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; - QAction *m_NewFolderAction; - - ModInfo::Ptr m_ModInfo; - -}; - -#endif // OVERWRITEINFODIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef OVERWRITEINFODIALOG_H +#define OVERWRITEINFODIALOG_H + +#include "modinfo.h" +#include +#include + +namespace Ui { +class OverwriteInfoDialog; +} + +class OverwriteInfoDialog : public QDialog +{ + Q_OBJECT + +public: + + explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); + ~OverwriteInfoDialog(); + + ModInfo::Ptr modInfo() const { return m_ModInfo; } + + void setModInfo(ModInfo::Ptr modInfo); + +private: + + void openFile(const QModelIndex &index); + bool recursiveDelete(const QModelIndex &index); + void deleteFile(const QModelIndex &index); + +private slots: + + void deleteTriggered(); + void renameTriggered(); + void openTriggered(); + void createDirectoryTriggered(); + + void on_filesView_customContextMenuRequested(const QPoint &pos); + +private: + + Ui::OverwriteInfoDialog *ui; + QFileSystemModel *m_FileSystemModel; + QModelIndexList m_FileSelection; + QAction *m_DeleteAction; + QAction *m_RenameAction; + QAction *m_OpenAction; + QAction *m_NewFolderAction; + + ModInfo::Ptr m_ModInfo; + +}; + +#endif // OVERWRITEINFODIALOG_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 9f0924a2..4794414a 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1,1159 +1,1159 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "pluginlist.h" -#include "report.h" -#include "inject.h" -#include "settings.h" -#include "safewritefile.h" -#include "scopeguard.h" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); -} - -bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - if (LHS.m_IsMaster && !RHS.m_IsMaster) { - return true; - } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { - return false; - } else { - return LHS.m_Priority < RHS.m_Priority; - } -} - -bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - QString lhsExtension = LHS.m_Name.right(3).toLower(); - QString rhsExtension = RHS.m_Name.right(3).toLower(); - if (lhsExtension != rhsExtension) { - return lhsExtension == "esm"; - } - - return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0; -} - -PluginList::PluginList(QObject *parent) - : QAbstractItemModel(parent) - , m_FontMetrics(QFont()) - , m_SaveTimer(this) -{ - m_SaveTimer.setSingleShot(true); - connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); - - m_Utf8Codec = QTextCodec::codecForName("utf-8"); - m_LocalCodec = QTextCodec::codecForName("Windows-1252"); - - if (m_LocalCodec == NULL) { - qCritical("required 8-bit string-encoding not supported."); - m_LocalCodec = m_Utf8Codec; - } - -} - -PluginList::~PluginList() -{ -} - - -QString PluginList::getColumnName(int column) -{ - switch (column) { - case COL_NAME: return tr("Name"); - case COL_PRIORITY: return tr("Priority"); - case COL_MODINDEX: return tr("Mod Index"); - case COL_FLAGS: return tr("Flags"); - default: return tr("unknown"); - } -} - - -QString PluginList::getColumnToolTip(int column) -{ - switch (column) { - case COL_NAME: return tr("Name of your mods"); - case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " - "overwrites data from plugins with lower priority."); - case COL_MODINDEX: return tr("The modindex determins the formids of objects originating from this mods."); - default: return tr("unknown"); - } -} - - -void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory, - const QString &pluginsFile, const QString &loadOrderFile, - const QString &lockedOrderFile) -{ - ChangeBracket layoutChange(this); - - m_ESPsByName.clear(); - m_ESPsByPriority.clear(); - m_ESPs.clear(); - std::vector primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - - m_CurrentProfile = profileName; - - std::vector files = baseDirectory.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - if (current.get() == NULL) { - continue; - } - QString filename = ToQString(current->getName()); - QString extension = filename.right(3).toLower(); - - if ((extension == "esp") || (extension == "esm")) { - bool forceEnabled = Settings::instance().forceEnableCoreFiles() && - std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end(); - - bool archive = false; - try { - FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); - - QString iniPath = QFileInfo(filename).baseName() + ".ini"; - bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != NULL; - - m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni)); - } catch (const std::exception &e) { - reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); - } - } - } - - if (readLoadOrder(loadOrderFile)) { - int maxPriority = 0; - // assign known load orders - for (std::vector::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { - std::map::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower()); - if (priorityIter != m_ESPLoadOrder.end()) { - if (priorityIter->second > maxPriority) { - maxPriority = priorityIter->second; - } - espIter->m_Priority = priorityIter->second; - } else { - espIter->m_Priority = -1; - } - } - - ++maxPriority; - - // assign maximum priorities for plugins with unknown priority - for (std::vector::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { - if (espIter->m_Priority == -1) { - espIter->m_Priority = maxPriority++; - } - } - } else { - // no load order stored, determine by date - std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate); - - for (size_t i = 0; i < m_ESPs.size(); ++i) { - m_ESPs[i].m_Priority = i; - } - } - - std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority - // remove gaps from the priorities so we can use them as array indices without overflow - for (int i = 0; i < static_cast(m_ESPs.size()); ++i) { - m_ESPs[i].m_Priority = i; - } - - std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works - - updateIndices(); - - readEnabledFrom(pluginsFile); - - readLockedOrderFrom(lockedOrderFile); - - layoutChange.finish(); - - refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); - - m_Refreshed(); -} - - -void PluginList::enableESP(const QString &name, bool enable) -{ - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = enable; - startSaveTime(); - } else { - reportError(tr("esp not found: %1").arg(name)); - } -} - - -void PluginList::enableAll() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - iter->m_Enabled = true; - } - startSaveTime(); - } -} - - -void PluginList::disableAll() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (!iter->m_ForceEnabled) { - iter->m_Enabled = false; - } - } - startSaveTime(); - } -} - - -bool PluginList::isEnabled(const QString &name) -{ - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - return m_ESPs[iter->second].m_Enabled; - } else { - return false; - } -} - -void PluginList::clearInformation(const QString &name) -{ - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.clear(); - } -} - -void PluginList::clearAdditionalInformation() -{ - m_AdditionalInfo.clear(); -} - -void PluginList::addInformation(const QString &name, const QString &message) -{ - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.append(message); - } -} - - -bool PluginList::isEnabled(int index) -{ - return m_ESPs.at(index).m_Enabled; -} - - -bool PluginList::readLoadOrder(const QString &fileName) -{ - std::set availableESPs; - for (std::vector::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - availableESPs.insert(iter->m_Name.toLower()); - } - - m_ESPLoadOrder.clear(); - - int priority = 0; - - std::vector primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - for (std::vector::iterator iter = primaryPlugins.begin(); - iter != primaryPlugins.end(); ++iter) { - if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) { - m_ESPLoadOrder[ToQString(*iter)] = priority++; - } - } - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - return false; - } - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - QString modName; - if ((line.size() > 0) && (line.at(0) != '#')) { - modName = QString::fromUtf8(line.constData()).toLower(); - } - - if ((modName.size() > 0) && - (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) && - (availableESPs.find(modName) != availableESPs.end())) { - m_ESPLoadOrder[modName] = priority++; - } - } - - file.close(); - return true; -} - - -void PluginList::readEnabledFrom(const QString &fileName) -{ - for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (!iter->m_ForceEnabled) { - iter->m_Enabled = false; - } - iter->m_LoadOrder = -1; - } - - QFile file(fileName); - if (!file.exists()) { - throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData()); - } - - file.open(QIODevice::ReadOnly); - while (!file.atEnd()) { - QByteArray line = file.readLine(); - QString modName; - if ((line.size() > 0) && (line.at(0) != '#')) { - modName = m_LocalCodec->toUnicode(line.trimmed().constData()); - } - if (modName.size() > 0) { - std::map::iterator iter = m_ESPsByName.find(modName.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = true; - } else { - qWarning("plugin %s not found", modName.toUtf8().constData()); - startSaveTime(); - } - } - } - - file.close(); - - testMasters(); -} - - -void PluginList::readLockedOrderFrom(const QString &fileName) -{ - m_LockedOrder.clear(); - - QFile file(fileName); - if (!file.exists()) { - // no locked load order, that's ok - return; - } - - file.open(QIODevice::ReadOnly); - while (!file.atEnd()) { - QByteArray line = file.readLine(); - if ((line.size() > 0) && (line.at(0) != '#')) { - QList fields = line.split('|'); - if (fields.count() == 2) { - m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt(); - } else { - reportError(tr("The file containing locked plugin indices is broken")); - break; - } - } - } - file.close(); -} - - - -void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const -{ - SafeWriteFile file(fileName); - - QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; - - file->resize(0); - - file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); - - QStringList saveList; - - bool invalidFileNames = false; - int writtenCount = 0; - for (size_t i = 0; i < m_ESPs.size(); ++i) { - int priority = m_ESPsByPriority[i]; - if (m_ESPs[priority].m_Enabled || writeUnchecked) { - //file.write(m_ESPs[priority].m_Name.toUtf8()); - if (!textCodec->canEncode(m_ESPs[priority].m_Name)) { - invalidFileNames = true; - qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); - } else { - saveList << m_ESPs[priority].m_Name; - file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); - } - file->write("\r\n"); - ++writtenCount; - } - } - - if (invalidFileNames) { - reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " - "Please see mo_interface.log for a list of affected plugins and rename them.")); - } - - if (file.commitIfDifferent(m_LastSaveHash[fileName])) { - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); - } -} - - -void PluginList::writeLockedOrder(const QString &fileName) const -{ - SafeWriteFile file(fileName); - - file->resize(0); - file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) { - file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); - } - file.commit(); -} - - -void PluginList::saveTo(const QString &pluginFileName - , const QString &loadOrderFileName - , const QString &lockedOrderFileName - , const QString& deleterFileName - , bool hideUnchecked) const -{ - writePlugins(pluginFileName, false); - writePlugins(loadOrderFileName, true); - writeLockedOrder(lockedOrderFileName); - - if (hideUnchecked) { - SafeWriteFile deleterFile(deleterFileName); - deleterFile->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - - for (size_t i = 0; i < m_ESPs.size(); ++i) { - int priority = m_ESPsByPriority[i]; - if (!m_ESPs[priority].m_Enabled) { - deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); - deleterFile->write("\r\n"); - } - } - if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); - } - } else { - shellDelete(QStringList() << deleterFileName); - } - - m_SaveTimer.stop(); -} - - -bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) -{ - if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) { - // nothing to do - return true; - } - - for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - std::wstring espName = ToWString(iter->m_Name); - const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName); - if (fileEntry.get() != NULL) { - QString fileName; - bool archive = false; - int originid = fileEntry->getOrigin(archive); - fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name); - - HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (file == INVALID_HANDLE_VALUE) { - if (::GetLastError() == ERROR_SHARING_VIOLATION) { - // file is locked, probably the game is running - return false; - } else { - throw windows_error(QObject::tr("failed to access %1").arg(fileName).toUtf8().constData()); - } - } - - ULONGLONG temp = 0; - temp = (145731ULL + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL; - - FILETIME newWriteTime; - - newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); - newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); - iter->m_Time = newWriteTime; - fileEntry->setFileTime(newWriteTime); - if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) { - throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); - } - - CloseHandle(file); - } - } - return true; -} - -int PluginList::enabledCount() const -{ - int enabled = 0; - foreach (auto info, m_ESPs) { - if (info.m_Enabled) { - ++enabled; - } - } - return enabled; -} - -bool PluginList::isESPLocked(int index) const -{ - return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); -} - - -void PluginList::lockESPIndex(int index, bool lock) -{ - if (lock) { - m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; - } else { - auto iter = m_LockedOrder.find(getName(index).toLower()); - if (iter != m_LockedOrder.end()) { - m_LockedOrder.erase(iter); - } - } - startSaveTime(); -} - - -void PluginList::syncLoadOrder() -{ - int loadOrder = 0; - for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - int index = m_ESPsByPriority[i]; - - if (m_ESPs[index].m_Enabled) { - m_ESPs[index].m_LoadOrder = loadOrder++; - } else { - m_ESPs[index].m_LoadOrder = -1; - } - } -} - -void PluginList::refreshLoadOrder() -{ - ChangeBracket layoutChange(this); - syncLoadOrder(); - // set priorities according to locked load order - std::map lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair &ele) { lockedLoadOrder[ele.second] = ele.first; }); - - int targetPrio = 0; - // this is guaranteed to iterate from lowest key (load order) to highest - for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { - auto nameIter = m_ESPsByName.find(iter->second); - if (nameIter != m_ESPsByName.end()) { - // locked esp exists - - // find the location to insert at - while ((targetPrio < static_cast(m_ESPs.size() - 1)) && - (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) { - ++targetPrio; - } - - if (static_cast(targetPrio) >= m_ESPs.size()) { - continue; - } - - int temp = targetPrio; - int index = nameIter->second; - if (m_ESPs[index].m_Priority != temp) { - setPluginPriority(index, temp); - m_ESPs[index].m_LoadOrder = iter->first; - syncLoadOrder(); - startSaveTime(); - } - } - } -} - - - - -IPluginList::PluginState PluginList::state(const QString &name) const -{ - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return IPluginList::STATE_MISSING; - } else { - return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; - } -} - -int PluginList::priority(const QString &name) const -{ - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].m_Priority; - } -} - -int PluginList::loadOrder(const QString &name) const -{ - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].m_LoadOrder; - } -} - -bool PluginList::isMaster(const QString &name) const -{ - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return false; - } else { - return m_ESPs[iter->second].m_IsMaster; - } -} - -QString PluginList::origin(const QString &name) const -{ - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return false; - } else { - return m_ESPs[iter->second].m_OriginName; - } -} - -bool PluginList::onRefreshed(const std::function &callback) -{ - auto conn = m_Refreshed.connect(callback); - return conn.connected(); -} - - -bool PluginList::onPluginMoved(const std::function &func) -{ - auto conn = m_PluginMoved.connect(func); - return conn.connected(); -} - - -void PluginList::updateIndices() -{ - m_ESPsByName.clear(); - m_ESPsByPriority.clear(); - m_ESPsByPriority.resize(m_ESPs.size()); - - for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; - m_ESPsByPriority[m_ESPs[i].m_Priority] = i; - } -} - - -int PluginList::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return m_ESPs.size(); - } else { - return 0; - } -} - -int PluginList::columnCount(const QModelIndex &) const -{ - return COL_LASTCOLUMN + 1; -} - - -void PluginList::testMasters() -{ -// emit layoutAboutToBeChanged(); - - std::set enabledMasters; - for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (iter->m_Enabled) { - enabledMasters.insert(iter->m_Name.toLower()); - } - } - - for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - iter->m_MasterUnset.clear(); - if (iter->m_Enabled) { - for (auto master = iter->m_Masters.begin(); master != iter->m_Masters.end(); ++master) { - if (enabledMasters.find(master->toLower()) == enabledMasters.end()) { - iter->m_MasterUnset.insert(*master); - } - } - } - } - -#pragma message("emitting this seems to cause a crash!") -// emit layoutChanged(); -} - - -QVariant PluginList::data(const QModelIndex &modelIndex, int role) const -{ - int index = modelIndex.row(); - - if ((role == Qt::DisplayRole) - || (role == Qt::EditRole)) { - switch (modelIndex.column()) { - case COL_NAME: { - return m_ESPs[index].m_Name; - } break; - case COL_PRIORITY: { - return m_ESPs[index].m_Priority; - } break; - case COL_MODINDEX: { - if (m_ESPs[index].m_LoadOrder == -1) { - return QString(); - } else { - return QString("%1").arg(m_ESPs[index].m_LoadOrder, 2, 16, QChar('0')).toUpper(); - } - } break; - default: { - return QVariant(); - } break; - } - } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_ForceEnabled) { - return QVariant(); - } else { - return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; - } - } else if (role == Qt::ForegroundRole) { - if ((modelIndex.column() == COL_NAME) && - m_ESPs[index].m_ForceEnabled) { - return QBrush(Qt::gray); - } - } else if (role == Qt::FontRole) { - QFont result; - if (m_ESPs[index].m_IsMaster) { - result.setItalic(true); - result.setWeight(QFont::Bold); - } else if (m_ESPs[index].m_IsDummy) { - result.setItalic(true); - } - return result; - } else if (role == Qt::TextAlignmentRole) { - if (modelIndex.column() == 0) { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); - } - } else if (role == Qt::ToolTipRole) { - QString name = m_ESPs[index].m_Name.toLower(); - auto addInfoIter = m_AdditionalInfo.find(name); - QString toolTip; - if (addInfoIter != m_AdditionalInfo.end()) { - if (!addInfoIter->second.m_Messages.isEmpty()) { - toolTip += addInfoIter->second.m_Messages.join("
            ") + "

            "; - } - } - if (m_ESPs[index].m_ForceEnabled) { - toolTip += tr("This plugin can't be disabled (enforced by the game)"); - } else { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - if (m_ESPs[index].m_Author.size() > 0) { - text += "
            " + tr("Author") + ": " + m_ESPs[index].m_Author; - } - if (m_ESPs[index].m_Description.size() > 0) { - text += "
            " + tr("Description") + ": " + m_ESPs[index].m_Description; - } - if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "
            " + tr("Missing Masters") + ": " + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + ""; - } - std::set enabledMasters; - std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), - m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), - std::inserter(enabledMasters, enabledMasters.end())); - if (enabledMasters.size() > 0) { - 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."; - } else if (m_ESPs[index].m_IsDummy) { - text += "
            This file is a dummy! It exists only so the bsa with the same name gets loaded. If you let MO manage archives you " - "don't need this: Enable the archive with the same name in the \"Archive\" tab and disable this plugin."; - } - toolTip += text; - } - return toolTip; - } else if (role == Qt::UserRole + 1) { - QVariantList result; - QString nameLower = m_ESPs[index].m_Name.toLower(); - if (m_ESPs[index].m_MasterUnset.size() > 0) { - result.append(QIcon(":/MO/gui/warning")); - } - if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { - result.append(QIcon(":/MO/gui/locked")); - } - auto bossInfoIter = m_AdditionalInfo.find(nameLower); - if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.m_Messages.isEmpty()) { - result.append(QIcon(":/MO/gui/information")); - } - /*if (bossInfoIter->second.m_LOOTUnrecognized) { - result.append(QIcon(":/MO/gui/help")); - }*/ - } - if (m_ESPs[index].m_HasIni) { - result.append(QIcon(":/MO/gui/attachment")); - } - if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { - result.append(QIcon(":/MO/gui/edit_clear")); - } - return result; - } - return QVariant(); -} - - -bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) -{ - bool result = false; - if (role == Qt::CheckStateRole) { - m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; - emit dataChanged(modIndex, modIndex); - - refreshLoadOrder(); - startSaveTime(); - - result = true; - } else if (role == Qt::EditRole) { - if (modIndex.column() == COL_PRIORITY) { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok) { - setPluginPriority(modIndex.row(), newPriority); - result = true; - } - refreshLoadOrder(); - } - } - return result; -} - - -QVariant PluginList::headerData(int section, Qt::Orientation orientation, - int role) const -{ - if (orientation == Qt::Horizontal) { - if (role == Qt::DisplayRole) { - return getColumnName(section); - } else if (role == Qt::ToolTipRole) { - return getColumnToolTip(section); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 25; - temp.rheight() += 12; - return temp; - } - } - return QAbstractItemModel::headerData(section, orientation, role); -} - - -Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const -{ - int index = modelIndex.row(); - Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); - - if (modelIndex.isValid()) { - if (!m_ESPs[index].m_ForceEnabled) { - result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; - } - if (modelIndex.column() == COL_PRIORITY) { - result |= Qt::ItemIsEditable; - } - } else { - result |= Qt::ItemIsDropEnabled; - } - - return result; -} - - -void PluginList::setPluginPriority(int row, int &newPriority) -{ - int newPriorityTemp = newPriority; - - if (!m_ESPs[row].m_IsMaster) { - // don't allow esps to be moved above esms - while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { - ++newPriorityTemp; - } - } else { - // don't allow esms to be moved below esps - while ((newPriorityTemp > 0) && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { - --newPriorityTemp; - } - // also don't allow "regular" esms to be moved above primary plugins - while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_ForceEnabled)) { - ++newPriorityTemp; - } - } - - // enforce valid range - if (newPriorityTemp < 0) newPriorityTemp = 0; - else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) newPriorityTemp = m_ESPsByPriority.size() - 1; - - try { - int oldPriority = m_ESPs.at(row).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; - } - emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount())); - } else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; - } - emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); - ++newPriority; - } - - m_ESPs.at(row).m_Priority = newPriorityTemp; - emit dataChanged(index(row, 0), index(row, columnCount())); - m_PluginMoved(m_ESPs[row].m_Name, oldPriority, newPriorityTemp); - } catch (const std::out_of_range&) { - reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name)); - } - - updateIndices(); -} - - -void PluginList::changePluginPriority(std::vector rows, int newPriority) -{ - ChangeBracket layoutChange(this); - // sort rows to insert by their old priority (ascending) and insert them move them in that order - const std::vector &esp = m_ESPs; - std::sort(rows.begin(), rows.end(), - [&esp](const int &LHS, const int &RHS) { - return esp[LHS].m_Priority < esp[RHS].m_Priority; - }); - - // odd stuff: if any of the dragged sources has priority lower than the destination then the - // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? - for (std::vector::const_iterator iter = rows.begin(); - iter != rows.end(); ++iter) { - if (m_ESPs[*iter].m_Priority < newPriority) { - --newPriority; - break; - } - } - - for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - setPluginPriority(*iter, newPriority); - } - - layoutChange.finish(); - refreshLoadOrder(); - - startSaveTime(); -} - - -void PluginList::startSaveTime() -{ - testMasters(); - - if (!m_SaveTimer.isActive()) { - m_SaveTimer.start(2000); - } -} - - -bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - - std::vector sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { // only add each row once - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - int newPriority = 0; - - if ((row < 0) || - (row >= static_cast(m_ESPs.size()))) { - newPriority = m_ESPs.size(); - } else { - newPriority = m_ESPs[row].m_Priority; - } - changePluginPriority(sourceRows, newPriority); - - return false; -} - -QModelIndex PluginList::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - return createIndex(row, column, row); -} - -QModelIndex PluginList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - - -bool PluginList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QAbstractItemView *itemView = qobject_cast(obj); - - if (itemView == NULL) { - return QObject::eventFilter(obj, event); - } - - QKeyEvent *keyEvent = static_cast(event); - // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins - if ((keyEvent->modifiers() == Qt::ControlModifier) && - ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - foreach (QModelIndex idx, rows) { - if (proxyModel != NULL) { - idx = proxyModel->mapToSource(idx); - } - int newPriority = m_ESPs[idx.row()].m_Priority + diff; - if ((newPriority >= 0) && (newPriority < rowCount())) { - setPluginPriority(idx.row(), newPriority); - } - } - refreshLoadOrder(); - return true; - } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - - QModelIndex minRow, maxRow; - foreach (QModelIndex idx, selectionModel->selectedRows()) { - if (proxyModel != NULL) { - idx = proxyModel->mapToSource(idx); - } - if (!minRow.isValid() || (idx.row() < minRow.row())) { - minRow = idx; - } - if (!maxRow.isValid() || (idx.row() > maxRow.row())) { - maxRow = idx; - } - int oldState = idx.data(Qt::CheckStateRole).toInt(); - setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); - } - emit dataChanged(minRow, maxRow); - - return true; - } - } - return QObject::eventFilter(obj, event); -} - - -PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, - const QString &originName, const QString &fullPath, - bool hasIni) - : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), - m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) -{ - try { - ESP::File file(ToWString(fullPath)); - m_IsMaster = file.isMaster(); - m_IsDummy = file.isDummy(); - m_Author = QString::fromLatin1(file.author().c_str()); - m_Description = QString::fromLatin1(file.description().c_str()); - std::set masters = file.masters(); - for (auto iter = masters.begin(); iter != masters.end(); ++iter) { - m_Masters.insert(QString(iter->c_str())); - } - } catch (const std::exception &e) { - qCritical("failed to parse esp file %s: %s", qPrintable(fullPath), e.what()); - m_IsMaster = false; - m_IsDummy = false; - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "pluginlist.h" +#include "report.h" +#include "inject.h" +#include "settings.h" +#include "safewritefile.h" +#include "scopeguard.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); +} + +bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + if (LHS.m_IsMaster && !RHS.m_IsMaster) { + return true; + } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { + return false; + } else { + return LHS.m_Priority < RHS.m_Priority; + } +} + +bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { + QString lhsExtension = LHS.m_Name.right(3).toLower(); + QString rhsExtension = RHS.m_Name.right(3).toLower(); + if (lhsExtension != rhsExtension) { + return lhsExtension == "esm"; + } + + return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0; +} + +PluginList::PluginList(QObject *parent) + : QAbstractItemModel(parent) + , m_FontMetrics(QFont()) + , m_SaveTimer(this) +{ + m_SaveTimer.setSingleShot(true); + connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); + + m_Utf8Codec = QTextCodec::codecForName("utf-8"); + m_LocalCodec = QTextCodec::codecForName("Windows-1252"); + + if (m_LocalCodec == NULL) { + qCritical("required 8-bit string-encoding not supported."); + m_LocalCodec = m_Utf8Codec; + } + +} + +PluginList::~PluginList() +{ +} + + +QString PluginList::getColumnName(int column) +{ + switch (column) { + case COL_NAME: return tr("Name"); + case COL_PRIORITY: return tr("Priority"); + case COL_MODINDEX: return tr("Mod Index"); + case COL_FLAGS: return tr("Flags"); + default: return tr("unknown"); + } +} + + +QString PluginList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: return tr("Name of your mods"); + case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " + "overwrites data from plugins with lower priority."); + case COL_MODINDEX: return tr("The modindex determins the formids of objects originating from this mods."); + default: return tr("unknown"); + } +} + + +void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory, + const QString &pluginsFile, const QString &loadOrderFile, + const QString &lockedOrderFile) +{ + ChangeBracket layoutChange(this); + + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + m_ESPs.clear(); + std::vector primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + + m_CurrentProfile = profileName; + + std::vector files = baseDirectory.getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + FileEntry::Ptr current = *iter; + if (current.get() == NULL) { + continue; + } + QString filename = ToQString(current->getName()); + QString extension = filename.right(3).toLower(); + + if ((extension == "esp") || (extension == "esm")) { + bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end(); + + bool archive = false; + try { + FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); + + QString iniPath = QFileInfo(filename).baseName() + ".ini"; + bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != NULL; + + m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni)); + } catch (const std::exception &e) { + reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); + } + } + } + + if (readLoadOrder(loadOrderFile)) { + int maxPriority = 0; + // assign known load orders + for (std::vector::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { + std::map::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower()); + if (priorityIter != m_ESPLoadOrder.end()) { + if (priorityIter->second > maxPriority) { + maxPriority = priorityIter->second; + } + espIter->m_Priority = priorityIter->second; + } else { + espIter->m_Priority = -1; + } + } + + ++maxPriority; + + // assign maximum priorities for plugins with unknown priority + for (std::vector::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { + if (espIter->m_Priority == -1) { + espIter->m_Priority = maxPriority++; + } + } + } else { + // no load order stored, determine by date + std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate); + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + m_ESPs[i].m_Priority = i; + } + } + + std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority + // remove gaps from the priorities so we can use them as array indices without overflow + for (int i = 0; i < static_cast(m_ESPs.size()); ++i) { + m_ESPs[i].m_Priority = i; + } + + std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works + + updateIndices(); + + readEnabledFrom(pluginsFile); + + readLockedOrderFrom(lockedOrderFile); + + layoutChange.finish(); + + refreshLoadOrder(); + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); + + m_Refreshed(); +} + + +void PluginList::enableESP(const QString &name, bool enable) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = enable; + startSaveTime(); + } else { + reportError(tr("esp not found: %1").arg(name)); + } +} + + +void PluginList::enableAll() +{ + if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + iter->m_Enabled = true; + } + startSaveTime(); + } +} + + +void PluginList::disableAll() +{ + if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + if (!iter->m_ForceEnabled) { + iter->m_Enabled = false; + } + } + startSaveTime(); + } +} + + +bool PluginList::isEnabled(const QString &name) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + return m_ESPs[iter->second].m_Enabled; + } else { + return false; + } +} + +void PluginList::clearInformation(const QString &name) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name.toLower()].m_Messages.clear(); + } +} + +void PluginList::clearAdditionalInformation() +{ + m_AdditionalInfo.clear(); +} + +void PluginList::addInformation(const QString &name, const QString &message) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name.toLower()].m_Messages.append(message); + } +} + + +bool PluginList::isEnabled(int index) +{ + return m_ESPs.at(index).m_Enabled; +} + + +bool PluginList::readLoadOrder(const QString &fileName) +{ + std::set availableESPs; + for (std::vector::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + availableESPs.insert(iter->m_Name.toLower()); + } + + m_ESPLoadOrder.clear(); + + int priority = 0; + + std::vector primaryPlugins = GameInfo::instance().getPrimaryPlugins(); + for (std::vector::iterator iter = primaryPlugins.begin(); + iter != primaryPlugins.end(); ++iter) { + if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) { + m_ESPLoadOrder[ToQString(*iter)] = priority++; + } + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + QString modName; + if ((line.size() > 0) && (line.at(0) != '#')) { + modName = QString::fromUtf8(line.constData()).toLower(); + } + + if ((modName.size() > 0) && + (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) && + (availableESPs.find(modName) != availableESPs.end())) { + m_ESPLoadOrder[modName] = priority++; + } + } + + file.close(); + return true; +} + + +void PluginList::readEnabledFrom(const QString &fileName) +{ + for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + if (!iter->m_ForceEnabled) { + iter->m_Enabled = false; + } + iter->m_LoadOrder = -1; + } + + QFile file(fileName); + if (!file.exists()) { + throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData()); + } + + file.open(QIODevice::ReadOnly); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + QString modName; + if ((line.size() > 0) && (line.at(0) != '#')) { + modName = m_LocalCodec->toUnicode(line.trimmed().constData()); + } + if (modName.size() > 0) { + std::map::iterator iter = m_ESPsByName.find(modName.toLower()); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = true; + } else { + qWarning("plugin %s not found", modName.toUtf8().constData()); + startSaveTime(); + } + } + } + + file.close(); + + testMasters(); +} + + +void PluginList::readLockedOrderFrom(const QString &fileName) +{ + m_LockedOrder.clear(); + + QFile file(fileName); + if (!file.exists()) { + // no locked load order, that's ok + return; + } + + file.open(QIODevice::ReadOnly); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + if ((line.size() > 0) && (line.at(0) != '#')) { + QList fields = line.split('|'); + if (fields.count() == 2) { + m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt(); + } else { + reportError(tr("The file containing locked plugin indices is broken")); + break; + } + } + } + file.close(); +} + + + +void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const +{ + SafeWriteFile file(fileName); + + QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; + + file->resize(0); + + file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); + + QStringList saveList; + + bool invalidFileNames = false; + int writtenCount = 0; + for (size_t i = 0; i < m_ESPs.size(); ++i) { + int priority = m_ESPsByPriority[i]; + if (m_ESPs[priority].m_Enabled || writeUnchecked) { + //file.write(m_ESPs[priority].m_Name.toUtf8()); + if (!textCodec->canEncode(m_ESPs[priority].m_Name)) { + invalidFileNames = true; + qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); + } else { + saveList << m_ESPs[priority].m_Name; + file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); + } + file->write("\r\n"); + ++writtenCount; + } + } + + if (invalidFileNames) { + reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " + "Please see mo_interface.log for a list of affected plugins and rename them.")); + } + + if (file.commitIfDifferent(m_LastSaveHash[fileName])) { + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); + } +} + + +void PluginList::writeLockedOrder(const QString &fileName) const +{ + SafeWriteFile file(fileName); + + file->resize(0); + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) { + file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8()); + } + file.commit(); +} + + +void PluginList::saveTo(const QString &pluginFileName + , const QString &loadOrderFileName + , const QString &lockedOrderFileName + , const QString& deleterFileName + , bool hideUnchecked) const +{ + writePlugins(pluginFileName, false); + writePlugins(loadOrderFileName, true); + writeLockedOrder(lockedOrderFileName); + + if (hideUnchecked) { + SafeWriteFile deleterFile(deleterFileName); + deleterFile->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + int priority = m_ESPsByPriority[i]; + if (!m_ESPs[priority].m_Enabled) { + deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); + deleterFile->write("\r\n"); + } + } + if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); + } + } else { + shellDelete(QStringList() << deleterFileName); + } + + m_SaveTimer.stop(); +} + + +bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) +{ + if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) { + // nothing to do + return true; + } + + for (std::vector::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + std::wstring espName = ToWString(iter->m_Name); + const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName); + if (fileEntry.get() != NULL) { + QString fileName; + bool archive = false; + int originid = fileEntry->getOrigin(archive); + fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name); + + HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) { + if (::GetLastError() == ERROR_SHARING_VIOLATION) { + // file is locked, probably the game is running + return false; + } else { + throw windows_error(QObject::tr("failed to access %1").arg(fileName).toUtf8().constData()); + } + } + + ULONGLONG temp = 0; + temp = (145731ULL + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL; + + FILETIME newWriteTime; + + newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); + newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); + iter->m_Time = newWriteTime; + fileEntry->setFileTime(newWriteTime); + if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) { + throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); + } + + CloseHandle(file); + } + } + return true; +} + +int PluginList::enabledCount() const +{ + int enabled = 0; + foreach (auto info, m_ESPs) { + if (info.m_Enabled) { + ++enabled; + } + } + return enabled; +} + +bool PluginList::isESPLocked(int index) const +{ + return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); +} + + +void PluginList::lockESPIndex(int index, bool lock) +{ + if (lock) { + m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; + } else { + auto iter = m_LockedOrder.find(getName(index).toLower()); + if (iter != m_LockedOrder.end()) { + m_LockedOrder.erase(iter); + } + } + startSaveTime(); +} + + +void PluginList::syncLoadOrder() +{ + int loadOrder = 0; + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + int index = m_ESPsByPriority[i]; + + if (m_ESPs[index].m_Enabled) { + m_ESPs[index].m_LoadOrder = loadOrder++; + } else { + m_ESPs[index].m_LoadOrder = -1; + } + } +} + +void PluginList::refreshLoadOrder() +{ + ChangeBracket layoutChange(this); + syncLoadOrder(); + // set priorities according to locked load order + std::map lockedLoadOrder; + std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), + [&lockedLoadOrder] (const std::pair &ele) { lockedLoadOrder[ele.second] = ele.first; }); + + int targetPrio = 0; + // this is guaranteed to iterate from lowest key (load order) to highest + for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { + auto nameIter = m_ESPsByName.find(iter->second); + if (nameIter != m_ESPsByName.end()) { + // locked esp exists + + // find the location to insert at + while ((targetPrio < static_cast(m_ESPs.size() - 1)) && + (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) { + ++targetPrio; + } + + if (static_cast(targetPrio) >= m_ESPs.size()) { + continue; + } + + int temp = targetPrio; + int index = nameIter->second; + if (m_ESPs[index].m_Priority != temp) { + setPluginPriority(index, temp); + m_ESPs[index].m_LoadOrder = iter->first; + syncLoadOrder(); + startSaveTime(); + } + } + } +} + + + + +IPluginList::PluginState PluginList::state(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return IPluginList::STATE_MISSING; + } else { + return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; + } +} + +int PluginList::priority(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].m_Priority; + } +} + +int PluginList::loadOrder(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].m_LoadOrder; + } +} + +bool PluginList::isMaster(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].m_IsMaster; + } +} + +QString PluginList::origin(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].m_OriginName; + } +} + +bool PluginList::onRefreshed(const std::function &callback) +{ + auto conn = m_Refreshed.connect(callback); + return conn.connected(); +} + + +bool PluginList::onPluginMoved(const std::function &func) +{ + auto conn = m_PluginMoved.connect(func); + return conn.connected(); +} + + +void PluginList::updateIndices() +{ + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + m_ESPsByPriority.resize(m_ESPs.size()); + + for (unsigned int i = 0; i < m_ESPs.size(); ++i) { + m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; + m_ESPsByPriority[m_ESPs[i].m_Priority] = i; + } +} + + +int PluginList::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return m_ESPs.size(); + } else { + return 0; + } +} + +int PluginList::columnCount(const QModelIndex &) const +{ + return COL_LASTCOLUMN + 1; +} + + +void PluginList::testMasters() +{ +// emit layoutAboutToBeChanged(); + + std::set enabledMasters; + for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + if (iter->m_Enabled) { + enabledMasters.insert(iter->m_Name.toLower()); + } + } + + for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { + iter->m_MasterUnset.clear(); + if (iter->m_Enabled) { + for (auto master = iter->m_Masters.begin(); master != iter->m_Masters.end(); ++master) { + if (enabledMasters.find(master->toLower()) == enabledMasters.end()) { + iter->m_MasterUnset.insert(*master); + } + } + } + } + +#pragma message("emitting this seems to cause a crash!") +// emit layoutChanged(); +} + + +QVariant PluginList::data(const QModelIndex &modelIndex, int role) const +{ + int index = modelIndex.row(); + + if ((role == Qt::DisplayRole) + || (role == Qt::EditRole)) { + switch (modelIndex.column()) { + case COL_NAME: { + return m_ESPs[index].m_Name; + } break; + case COL_PRIORITY: { + return m_ESPs[index].m_Priority; + } break; + case COL_MODINDEX: { + if (m_ESPs[index].m_LoadOrder == -1) { + return QString(); + } else { + return QString("%1").arg(m_ESPs[index].m_LoadOrder, 2, 16, QChar('0')).toUpper(); + } + } break; + default: { + return QVariant(); + } break; + } + } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { + if (m_ESPs[index].m_ForceEnabled) { + return QVariant(); + } else { + return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; + } + } else if (role == Qt::ForegroundRole) { + if ((modelIndex.column() == COL_NAME) && + m_ESPs[index].m_ForceEnabled) { + return QBrush(Qt::gray); + } + } else if (role == Qt::FontRole) { + QFont result; + if (m_ESPs[index].m_IsMaster) { + result.setItalic(true); + result.setWeight(QFont::Bold); + } else if (m_ESPs[index].m_IsDummy) { + result.setItalic(true); + } + return result; + } else if (role == Qt::TextAlignmentRole) { + if (modelIndex.column() == 0) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } + } else if (role == Qt::ToolTipRole) { + QString name = m_ESPs[index].m_Name.toLower(); + auto addInfoIter = m_AdditionalInfo.find(name); + QString toolTip; + if (addInfoIter != m_AdditionalInfo.end()) { + if (!addInfoIter->second.m_Messages.isEmpty()) { + toolTip += addInfoIter->second.m_Messages.join("
            ") + "

            "; + } + } + if (m_ESPs[index].m_ForceEnabled) { + toolTip += tr("This plugin can't be disabled (enforced by the game)"); + } else { + QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + if (m_ESPs[index].m_Author.size() > 0) { + text += "
            " + tr("Author") + ": " + m_ESPs[index].m_Author; + } + if (m_ESPs[index].m_Description.size() > 0) { + text += "
            " + tr("Description") + ": " + m_ESPs[index].m_Description; + } + if (m_ESPs[index].m_MasterUnset.size() > 0) { + text += "
            " + tr("Missing Masters") + ": " + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + ""; + } + std::set enabledMasters; + std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), + m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), + std::inserter(enabledMasters, enabledMasters.end())); + if (enabledMasters.size() > 0) { + 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."; + } else if (m_ESPs[index].m_IsDummy) { + text += "
            This file is a dummy! It exists only so the bsa with the same name gets loaded. If you let MO manage archives you " + "don't need this: Enable the archive with the same name in the \"Archive\" tab and disable this plugin."; + } + toolTip += text; + } + return toolTip; + } else if (role == Qt::UserRole + 1) { + QVariantList result; + QString nameLower = m_ESPs[index].m_Name.toLower(); + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(QIcon(":/MO/gui/warning")); + } + if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { + result.append(QIcon(":/MO/gui/locked")); + } + auto bossInfoIter = m_AdditionalInfo.find(nameLower); + if (bossInfoIter != m_AdditionalInfo.end()) { + if (!bossInfoIter->second.m_Messages.isEmpty()) { + result.append(QIcon(":/MO/gui/information")); + } + /*if (bossInfoIter->second.m_LOOTUnrecognized) { + result.append(QIcon(":/MO/gui/help")); + }*/ + } + if (m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/attachment")); + } + if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { + result.append(QIcon(":/MO/gui/edit_clear")); + } + return result; + } + return QVariant(); +} + + +bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) +{ + bool result = false; + if (role == Qt::CheckStateRole) { + m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; + emit dataChanged(modIndex, modIndex); + + refreshLoadOrder(); + startSaveTime(); + + result = true; + } else if (role == Qt::EditRole) { + if (modIndex.column() == COL_PRIORITY) { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + setPluginPriority(modIndex.row(), newPriority); + result = true; + } + refreshLoadOrder(); + } + } + return result; +} + + +QVariant PluginList::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + if (role == Qt::DisplayRole) { + return getColumnName(section); + } else if (role == Qt::ToolTipRole) { + return getColumnToolTip(section); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 25; + temp.rheight() += 12; + return temp; + } + } + return QAbstractItemModel::headerData(section, orientation, role); +} + + +Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const +{ + int index = modelIndex.row(); + Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); + + if (modelIndex.isValid()) { + if (!m_ESPs[index].m_ForceEnabled) { + result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; + } + if (modelIndex.column() == COL_PRIORITY) { + result |= Qt::ItemIsEditable; + } + } else { + result |= Qt::ItemIsDropEnabled; + } + + return result; +} + + +void PluginList::setPluginPriority(int row, int &newPriority) +{ + int newPriorityTemp = newPriority; + + if (!m_ESPs[row].m_IsMaster) { + // don't allow esps to be moved above esms + while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && + m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { + ++newPriorityTemp; + } + } else { + // don't allow esms to be moved below esps + while ((newPriorityTemp > 0) && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { + --newPriorityTemp; + } + // also don't allow "regular" esms to be moved above primary plugins + while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_ForceEnabled)) { + ++newPriorityTemp; + } + } + + // enforce valid range + if (newPriorityTemp < 0) newPriorityTemp = 0; + else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) newPriorityTemp = m_ESPsByPriority.size() - 1; + + try { + int oldPriority = m_ESPs.at(row).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + } + emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount())); + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + } + emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); + ++newPriority; + } + + m_ESPs.at(row).m_Priority = newPriorityTemp; + emit dataChanged(index(row, 0), index(row, columnCount())); + m_PluginMoved(m_ESPs[row].m_Name, oldPriority, newPriorityTemp); + } catch (const std::out_of_range&) { + reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name)); + } + + updateIndices(); +} + + +void PluginList::changePluginPriority(std::vector rows, int newPriority) +{ + ChangeBracket layoutChange(this); + // sort rows to insert by their old priority (ascending) and insert them move them in that order + const std::vector &esp = m_ESPs; + std::sort(rows.begin(), rows.end(), + [&esp](const int &LHS, const int &RHS) { + return esp[LHS].m_Priority < esp[RHS].m_Priority; + }); + + // odd stuff: if any of the dragged sources has priority lower than the destination then the + // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? + for (std::vector::const_iterator iter = rows.begin(); + iter != rows.end(); ++iter) { + if (m_ESPs[*iter].m_Priority < newPriority) { + --newPriority; + break; + } + } + + for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { + setPluginPriority(*iter, newPriority); + } + + layoutChange.finish(); + refreshLoadOrder(); + + startSaveTime(); +} + + +void PluginList::startSaveTime() +{ + testMasters(); + + if (!m_SaveTimer.isActive()) { + m_SaveTimer.start(2000); + } +} + + +bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); + + std::vector sourceRows; + + while (!stream.atEnd()) { + int sourceRow, col; + QMap roleDataMap; + stream >> sourceRow >> col >> roleDataMap; + if (col == 0) { // only add each row once + sourceRows.push_back(sourceRow); + } + } + + if (row == -1) { + row = parent.row(); + } + + int newPriority = 0; + + if ((row < 0) || + (row >= static_cast(m_ESPs.size()))) { + newPriority = m_ESPs.size(); + } else { + newPriority = m_ESPs[row].m_Priority; + } + changePluginPriority(sourceRows, newPriority); + + return false; +} + +QModelIndex PluginList::index(int row, int column, const QModelIndex&) const +{ + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + return createIndex(row, column, row); +} + +QModelIndex PluginList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + + +bool PluginList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QAbstractItemView *itemView = qobject_cast(obj); + + if (itemView == NULL) { + return QObject::eventFilter(obj, event); + } + + QKeyEvent *keyEvent = static_cast(event); + // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins + if ((keyEvent->modifiers() == Qt::ControlModifier) && + ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); + int diff = -1; + if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { + diff = 1; + } + QModelIndexList rows = selectionModel->selectedRows(); + if (keyEvent->key() == Qt::Key_Down) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + foreach (QModelIndex idx, rows) { + if (proxyModel != NULL) { + idx = proxyModel->mapToSource(idx); + } + int newPriority = m_ESPs[idx.row()].m_Priority + diff; + if ((newPriority >= 0) && (newPriority < rowCount())) { + setPluginPriority(idx.row(), newPriority); + } + } + refreshLoadOrder(); + return true; + } else if (keyEvent->key() == Qt::Key_Space) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); + + QModelIndex minRow, maxRow; + foreach (QModelIndex idx, selectionModel->selectedRows()) { + if (proxyModel != NULL) { + idx = proxyModel->mapToSource(idx); + } + if (!minRow.isValid() || (idx.row() < minRow.row())) { + minRow = idx; + } + if (!maxRow.isValid() || (idx.row() > maxRow.row())) { + maxRow = idx; + } + int oldState = idx.data(Qt::CheckStateRole).toInt(); + setData(idx, oldState == Qt::Unchecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); + } + emit dataChanged(minRow, maxRow); + + return true; + } + } + return QObject::eventFilter(obj, event); +} + + +PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, + const QString &originName, const QString &fullPath, + bool hasIni) + : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), + m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) +{ + try { + ESP::File file(ToWString(fullPath)); + m_IsMaster = file.isMaster(); + m_IsDummy = file.isDummy(); + m_Author = QString::fromLatin1(file.author().c_str()); + m_Description = QString::fromLatin1(file.description().c_str()); + std::set masters = file.masters(); + for (auto iter = masters.begin(); iter != masters.end(); ++iter) { + m_Masters.insert(QString(iter->c_str())); + } + } catch (const std::exception &e) { + qCritical("failed to parse esp file %s: %s", qPrintable(fullPath), e.what()); + m_IsMaster = false; + m_IsDummy = false; + } +} diff --git a/src/pluginlist.h b/src/pluginlist.h index d4a245f0..b8e141fc 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -1,344 +1,344 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef PLUGINLIST_H -#define PLUGINLIST_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "pdll.h" -#include - - - -template -class ChangeBracket { -public: - ChangeBracket(C *model) - : m_Model(nullptr) - { - QVariant var = model->property("__aboutToChange"); - bool aboutToChange = var.isValid() && var.toBool(); - if (!aboutToChange) { - model->layoutAboutToBeChanged(); - model->setProperty("__aboutToChange", true); - m_Model = model; - } - } - ~ChangeBracket() { - finish(); - } - - void finish() { - if (m_Model != nullptr) { - m_Model->layoutChanged(); - m_Model->setProperty("__aboutToChange", false); - m_Model = nullptr; - } - } - -private: - C *m_Model; -}; - - - -/** - * @brief model representing the plugins (.esp/.esm) in the current virtual data folder - **/ -class PluginList : public QAbstractItemModel, public MOBase::IPluginList -{ - Q_OBJECT - friend class ChangeBracket; -public: - - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_PRIORITY, - COL_MODINDEX, - - COL_LASTCOLUMN = COL_MODINDEX - }; - - typedef boost::signals2::signal SignalRefreshed; - typedef boost::signals2::signal SignalPluginMoved; - -public: - - /** - * @brief constructor - * - * @param parent parent object - **/ - PluginList(QObject *parent = NULL); - - ~PluginList(); - - /** - * @brief does a complete refresh of the list - * - * @param profileName name of the current profile - * @param baseDirectory the root directory structure representing the virtual data directory - * @param pluginsFile file that stores the list of enabled plugins - * @param loadOrderFile file that stored the load order (not an official file but used by many tools for skyrim) - * @param lockedOrderFile list of plugins that shouldn't change load order - * @todo the profile is not used? If it was, we should pass the Profile-object instead - **/ - void refresh(const QString &profileName - , const MOShared::DirectoryEntry &baseDirectory - , const QString &pluginsFile - , const QString &loadOrderFile - , const QString &lockedOrderFile); - - /** - * @brief enable a plugin based on its name - * - * @param name name of the plugin to enable - * @param enable set to true to enable the esp, false to disable it - **/ - void enableESP(const QString &name, bool enable = true); - - /** - * @brief test if a plugin is enabled - * - * @param name name of the plugin to look up - * @return true if the plugin is enabled, false otherwise - **/ - bool isEnabled(const QString &name); - - /** - * @brief clear all additional information we stored on plugins - */ - void clearAdditionalInformation(); - - /** - * @brief reset additional information on a mod - * @param name name of the plugin to clear the information of - */ - void clearInformation(const QString &name); - - /** - * @brief add additional information on a mod (i.e. from loot) - * @param name name of the plugin to add information about - * @param message the message to add to the plugin - */ - void addInformation(const QString &name, const QString &message); - - /** - * @brief test if a plugin is enabled - * - * @param index index of the plugin to look up - * @return true if the plugin is enabled, false otherwise - * @throws std::out_of_range exception is thrown if index is invalid - **/ - bool isEnabled(int index); - - /** - * @brief update the plugin status (enabled/disabled) from the specified file - * - * @param fileName path of the file to load. the filename should be "plugin.txt" - * @todo it would make sense to move this into the Profile-class - **/ - void readEnabledFrom(const QString &fileName); - - /** - * @brief save the plugin status to the specified file - * - * @param pluginFileName path of the plugin.txt to write to - * @param loadOrderFileName path of the loadorder.txt to write to - * @param lockedOrderFileName path of the lockedorder.txt to write to - * @param deleterFileName file to receive a list of files to hide from the virtual data tree. This is used to hide unchecked plugins if "hideUnchecked" is true - * @param hideUnchecked if true, plugins that aren't enabled will be hidden from the virtual data directory - **/ - void saveTo(const QString &pluginFileName - , const QString &loadOrderFileName - , const QString &lockedOrderFileName - , const QString &deleterFileName - , bool hideUnchecked) const; - - /** - * @brief save the current load order - * - * the load order used by the game is defined by the last modification time which this - * function sets. An exception is newer version of skyrim where the load order is defined - * by the order of files in plugins.txt - * @param directoryStructure the root directory structure representing the virtual data directory - * @return true on success or if there was nothing to save, false if the load order can't be saved, i.e. because files are locked - * @todo since this works on actual files the load order can't be configured per-profile. Files of the same name - * in different mods can also have different load orders which makes this very intransparent - * @note also stores to disk the list of locked esps - **/ - bool saveLoadOrder(MOShared::DirectoryEntry &directoryStructure); - - /** - * @return number of enabled plugins in the list - */ - int enabledCount() const; - - QString getName(int index) const { return m_ESPs.at(index).m_Name; } - int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } - bool isESPLocked(int index) const; - void lockESPIndex(int index, bool lock); - - bool eventFilter(QObject *obj, QEvent *event); - - static QString getColumnName(int column); - static QString getColumnToolTip(int column); - - void refreshLoadOrder(); - -public: - - virtual PluginState state(const QString &name) const; - virtual int priority(const QString &name) const; - virtual int loadOrder(const QString &name) const; - virtual bool isMaster(const QString &name) const; - virtual QString origin(const QString &name) const; - virtual bool onRefreshed(const std::function &callback); - virtual bool onPluginMoved(const std::function &func); - -public: // implementation of the QAbstractTableModel interface - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - -public slots: - - /** - * @brief enables ALL plugins - **/ - void enableAll(); - - /** - * @brief disables ALL plugins - **/ - void disableAll(); - -signals: - - /** - * @brief emitted when the plugin list changed, i.e. the load order was modified or a plugin was checked/unchecked - * @note this is currently only used to signal that there are changes that can be saved, it does - * not immediately cause anything to be written to disc - **/ - void esplist_changed(); - - /** - * @brief emitted when the plugin list should be saved - */ - void saveTimer(); - -private: - - struct ESPInfo { - - ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); - QString m_Name; - bool m_Enabled; - bool m_ForceEnabled; - int m_Priority; - int m_LoadOrder; - FILETIME m_Time; - QString m_OriginName; - bool m_IsMaster; - bool m_IsDummy; - QString m_Author; - QString m_Description; - bool m_HasIni; - std::set m_Masters; - mutable std::set m_MasterUnset; - }; - - struct AdditionalInfo { - QStringList m_Messages; - }; - - friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); - friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); - friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); - -private: - - void syncLoadOrder(); - void updateIndices(); - - void writePlugins(const QString &fileName, bool writeUnchecked) const; - void writeLockedOrder(const QString &fileName) const; - - bool readLoadOrder(const QString &fileName); - void readLockedOrderFrom(const QString &fileName); - void setPluginPriority(int row, int &newPriority); - void changePluginPriority(std::vector rows, int newPriority); - - void startSaveTime(); - - void testMasters(); - -private: - - std::vector m_ESPs; - mutable std::map m_LastSaveHash; - - std::map m_ESPsByName; - std::vector m_ESPsByPriority; - - // maps esp names to the priority specified in loadorder.txt. The esp names are - // all lowercase!! This is to work around the fact that BOSS for some reason writes some file with - // capitalization that doesn't match the actual name - std::map m_ESPLoadOrder; - std::map m_LockedOrder; - - std::map m_AdditionalInfo; // maps esp names to boss information - - QString m_CurrentProfile; - QFontMetrics m_FontMetrics; - - QTextCodec *m_Utf8Codec; - QTextCodec *m_LocalCodec; - - mutable QTimer m_SaveTimer; - - SignalRefreshed m_Refreshed; - - QTemporaryFile m_TempFile; - - SignalPluginMoved m_PluginMoved; - -}; - - - -#endif // PLUGINLIST_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef PLUGINLIST_H +#define PLUGINLIST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pdll.h" +#include + + + +template +class ChangeBracket { +public: + ChangeBracket(C *model) + : m_Model(nullptr) + { + QVariant var = model->property("__aboutToChange"); + bool aboutToChange = var.isValid() && var.toBool(); + if (!aboutToChange) { + model->layoutAboutToBeChanged(); + model->setProperty("__aboutToChange", true); + m_Model = model; + } + } + ~ChangeBracket() { + finish(); + } + + void finish() { + if (m_Model != nullptr) { + m_Model->layoutChanged(); + m_Model->setProperty("__aboutToChange", false); + m_Model = nullptr; + } + } + +private: + C *m_Model; +}; + + + +/** + * @brief model representing the plugins (.esp/.esm) in the current virtual data folder + **/ +class PluginList : public QAbstractItemModel, public MOBase::IPluginList +{ + Q_OBJECT + friend class ChangeBracket; +public: + + enum EColumn { + COL_NAME, + COL_FLAGS, + COL_PRIORITY, + COL_MODINDEX, + + COL_LASTCOLUMN = COL_MODINDEX + }; + + typedef boost::signals2::signal SignalRefreshed; + typedef boost::signals2::signal SignalPluginMoved; + +public: + + /** + * @brief constructor + * + * @param parent parent object + **/ + PluginList(QObject *parent = NULL); + + ~PluginList(); + + /** + * @brief does a complete refresh of the list + * + * @param profileName name of the current profile + * @param baseDirectory the root directory structure representing the virtual data directory + * @param pluginsFile file that stores the list of enabled plugins + * @param loadOrderFile file that stored the load order (not an official file but used by many tools for skyrim) + * @param lockedOrderFile list of plugins that shouldn't change load order + * @todo the profile is not used? If it was, we should pass the Profile-object instead + **/ + void refresh(const QString &profileName + , const MOShared::DirectoryEntry &baseDirectory + , const QString &pluginsFile + , const QString &loadOrderFile + , const QString &lockedOrderFile); + + /** + * @brief enable a plugin based on its name + * + * @param name name of the plugin to enable + * @param enable set to true to enable the esp, false to disable it + **/ + void enableESP(const QString &name, bool enable = true); + + /** + * @brief test if a plugin is enabled + * + * @param name name of the plugin to look up + * @return true if the plugin is enabled, false otherwise + **/ + bool isEnabled(const QString &name); + + /** + * @brief clear all additional information we stored on plugins + */ + void clearAdditionalInformation(); + + /** + * @brief reset additional information on a mod + * @param name name of the plugin to clear the information of + */ + void clearInformation(const QString &name); + + /** + * @brief add additional information on a mod (i.e. from loot) + * @param name name of the plugin to add information about + * @param message the message to add to the plugin + */ + void addInformation(const QString &name, const QString &message); + + /** + * @brief test if a plugin is enabled + * + * @param index index of the plugin to look up + * @return true if the plugin is enabled, false otherwise + * @throws std::out_of_range exception is thrown if index is invalid + **/ + bool isEnabled(int index); + + /** + * @brief update the plugin status (enabled/disabled) from the specified file + * + * @param fileName path of the file to load. the filename should be "plugin.txt" + * @todo it would make sense to move this into the Profile-class + **/ + void readEnabledFrom(const QString &fileName); + + /** + * @brief save the plugin status to the specified file + * + * @param pluginFileName path of the plugin.txt to write to + * @param loadOrderFileName path of the loadorder.txt to write to + * @param lockedOrderFileName path of the lockedorder.txt to write to + * @param deleterFileName file to receive a list of files to hide from the virtual data tree. This is used to hide unchecked plugins if "hideUnchecked" is true + * @param hideUnchecked if true, plugins that aren't enabled will be hidden from the virtual data directory + **/ + void saveTo(const QString &pluginFileName + , const QString &loadOrderFileName + , const QString &lockedOrderFileName + , const QString &deleterFileName + , bool hideUnchecked) const; + + /** + * @brief save the current load order + * + * the load order used by the game is defined by the last modification time which this + * function sets. An exception is newer version of skyrim where the load order is defined + * by the order of files in plugins.txt + * @param directoryStructure the root directory structure representing the virtual data directory + * @return true on success or if there was nothing to save, false if the load order can't be saved, i.e. because files are locked + * @todo since this works on actual files the load order can't be configured per-profile. Files of the same name + * in different mods can also have different load orders which makes this very intransparent + * @note also stores to disk the list of locked esps + **/ + bool saveLoadOrder(MOShared::DirectoryEntry &directoryStructure); + + /** + * @return number of enabled plugins in the list + */ + int enabledCount() const; + + QString getName(int index) const { return m_ESPs.at(index).m_Name; } + int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } + bool isESPLocked(int index) const; + void lockESPIndex(int index, bool lock); + + bool eventFilter(QObject *obj, QEvent *event); + + static QString getColumnName(int column); + static QString getColumnToolTip(int column); + + void refreshLoadOrder(); + +public: + + virtual PluginState state(const QString &name) const; + virtual int priority(const QString &name) const; + virtual int loadOrder(const QString &name) const; + virtual bool isMaster(const QString &name) const; + virtual QString origin(const QString &name) const; + virtual bool onRefreshed(const std::function &callback); + virtual bool onPluginMoved(const std::function &func); + +public: // implementation of the QAbstractTableModel interface + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual Qt::ItemFlags flags(const QModelIndex &index) const; + virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + +public slots: + + /** + * @brief enables ALL plugins + **/ + void enableAll(); + + /** + * @brief disables ALL plugins + **/ + void disableAll(); + +signals: + + /** + * @brief emitted when the plugin list changed, i.e. the load order was modified or a plugin was checked/unchecked + * @note this is currently only used to signal that there are changes that can be saved, it does + * not immediately cause anything to be written to disc + **/ + void esplist_changed(); + + /** + * @brief emitted when the plugin list should be saved + */ + void saveTimer(); + +private: + + struct ESPInfo { + + ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); + QString m_Name; + bool m_Enabled; + bool m_ForceEnabled; + int m_Priority; + int m_LoadOrder; + FILETIME m_Time; + QString m_OriginName; + bool m_IsMaster; + bool m_IsDummy; + QString m_Author; + QString m_Description; + bool m_HasIni; + std::set m_Masters; + mutable std::set m_MasterUnset; + }; + + struct AdditionalInfo { + QStringList m_Messages; + }; + + friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); + friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); + friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); + +private: + + void syncLoadOrder(); + void updateIndices(); + + void writePlugins(const QString &fileName, bool writeUnchecked) const; + void writeLockedOrder(const QString &fileName) const; + + bool readLoadOrder(const QString &fileName); + void readLockedOrderFrom(const QString &fileName); + void setPluginPriority(int row, int &newPriority); + void changePluginPriority(std::vector rows, int newPriority); + + void startSaveTime(); + + void testMasters(); + +private: + + std::vector m_ESPs; + mutable std::map m_LastSaveHash; + + std::map m_ESPsByName; + std::vector m_ESPsByPriority; + + // maps esp names to the priority specified in loadorder.txt. The esp names are + // all lowercase!! This is to work around the fact that BOSS for some reason writes some file with + // capitalization that doesn't match the actual name + std::map m_ESPLoadOrder; + std::map m_LockedOrder; + + std::map m_AdditionalInfo; // maps esp names to boss information + + QString m_CurrentProfile; + QFontMetrics m_FontMetrics; + + QTextCodec *m_Utf8Codec; + QTextCodec *m_LocalCodec; + + mutable QTimer m_SaveTimer; + + SignalRefreshed m_Refreshed; + + QTemporaryFile m_TempFile; + + SignalPluginMoved m_PluginMoved; + +}; + + + +#endif // PLUGINLIST_H diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 8412fa27..02d99b36 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -1,152 +1,152 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "pluginlistsortproxy.h" -#include "pluginlist.h" -#include -#include -#include - - -PluginListSortProxy::PluginListSortProxy(QObject *parent) - : QSortFilterProxyModel(parent), - m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) -{ - m_EnabledColumns.set(PluginList::COL_NAME); - m_EnabledColumns.set(PluginList::COL_PRIORITY); - m_EnabledColumns.set(PluginList::COL_MODINDEX); - this->setDynamicSortFilter(true); -} - - -unsigned int PluginListSortProxy::getEnabledColumns() const -{ - unsigned int result = 0; - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - if (m_EnabledColumns.test(i)) { - result |= 1 << i; - } - } - return result; -} - - -void PluginListSortProxy::setEnabledColumns(unsigned int columns) -{ - emit layoutAboutToBeChanged(); - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - m_EnabledColumns.set(i, (columns & (1 << i)) != 0); - } - emit layoutChanged(); -} - - -Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); - if (sortColumn() == PluginList::COL_NAME) { - flags &= ~Qt::ItemIsDragEnabled; - } - return flags; -} - - -void PluginListSortProxy::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(PluginList::getColumnName(i)); - checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - int i = 0; - - emit layoutAboutToBeChanged(); - m_EnabledColumns.reset(); - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); - if (checkBox != NULL) { - m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); - } - } - ++i; - } - emit layoutChanged(); -} - - -void PluginListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - invalidateFilter(); -} - - -bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const -{ - return m_CurrentFilter.isEmpty() || - sourceModel()->data(sourceModel()->index(row, 0)).toString().contains(m_CurrentFilter, Qt::CaseInsensitive); -} - - -bool PluginListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - PluginList *plugins = qobject_cast(sourceModel()); - switch (left.column()) { - case PluginList::COL_NAME: { - return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), Qt::CaseInsensitive) < 0; - } break; - case PluginList::COL_PRIORITY: - case PluginList::COL_MODINDEX: { - return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); - } break; - default: { - return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); - } break; - } -} - - -bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - if ((row == -1) && (column == -1)) { - return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering. - // This should fix that - if (sortOrder() == Qt::DescendingOrder) { - --row; - } - - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "pluginlistsortproxy.h" +#include "pluginlist.h" +#include +#include +#include + + +PluginListSortProxy::PluginListSortProxy(QObject *parent) + : QSortFilterProxyModel(parent), + m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) +{ + m_EnabledColumns.set(PluginList::COL_NAME); + m_EnabledColumns.set(PluginList::COL_PRIORITY); + m_EnabledColumns.set(PluginList::COL_MODINDEX); + this->setDynamicSortFilter(true); +} + + +unsigned int PluginListSortProxy::getEnabledColumns() const +{ + unsigned int result = 0; + for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { + if (m_EnabledColumns.test(i)) { + result |= 1 << i; + } + } + return result; +} + + +void PluginListSortProxy::setEnabledColumns(unsigned int columns) +{ + emit layoutAboutToBeChanged(); + for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { + m_EnabledColumns.set(i, (columns & (1 << i)) != 0); + } + emit layoutChanged(); +} + + +Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); + if (sortColumn() == PluginList::COL_NAME) { + flags &= ~Qt::ItemIsDragEnabled; + } + return flags; +} + + +void PluginListSortProxy::displayColumnSelection(const QPoint &pos) +{ + QMenu menu; + + for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { + QCheckBox *checkBox = new QCheckBox(&menu); + checkBox->setText(PluginList::getColumnName(i)); + checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); + QWidgetAction *checkableAction = new QWidgetAction(&menu); + checkableAction->setDefaultWidget(checkBox); + menu.addAction(checkableAction); + } + menu.exec(pos); + int i = 0; + + emit layoutAboutToBeChanged(); + m_EnabledColumns.reset(); + foreach (const QAction *action, menu.actions()) { + const QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != NULL) { + const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); + if (checkBox != NULL) { + m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); + } + } + ++i; + } + emit layoutChanged(); +} + + +void PluginListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + invalidateFilter(); +} + + +bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const +{ + return m_CurrentFilter.isEmpty() || + sourceModel()->data(sourceModel()->index(row, 0)).toString().contains(m_CurrentFilter, Qt::CaseInsensitive); +} + + +bool PluginListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + PluginList *plugins = qobject_cast(sourceModel()); + switch (left.column()) { + case PluginList::COL_NAME: { + return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), Qt::CaseInsensitive) < 0; + } break; + case PluginList::COL_PRIORITY: + case PluginList::COL_MODINDEX: { + return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); + } break; + default: { + return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); + } break; + } +} + + +bool PluginListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if ((row == -1) && (column == -1)) { + return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); + } + // in the regular model, when dropping between rows, the row-value passed to + // the sourceModel is inconsistent between ascending and descending ordering. + // This should fix that + if (sortOrder() == Qt::DescendingOrder) { + --row; + } + + QModelIndex proxyIndex = index(row, column, parent); + QModelIndex sourceIndex = mapToSource(proxyIndex); + return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), + sourceIndex.parent()); +} + diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index d5c71858..bc8a005b 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -1,71 +1,71 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef PLUGINLISTSORTPROXY_H -#define PLUGINLISTSORTPROXY_H - - -#include -#include -#include "pluginlist.h" - - -class PluginListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT -public: - - enum ESorting { - SORT_ASCENDING, - SORT_DESCENDING - }; - -public: - - explicit PluginListSortProxy(QObject *parent = 0); - - unsigned int getEnabledColumns() const; - - void setEnabledColumns(unsigned int columns); - - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - -public slots: - - void displayColumnSelection(const QPoint &pos); - void updateFilter(const QString &filter); - -protected: - - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - -private: - - int m_SortIndex; - Qt::SortOrder m_SortOrder; - - std::bitset m_EnabledColumns; - QString m_CurrentFilter; - -}; - -#endif // PLUGINLISTSORTPROXY_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef PLUGINLISTSORTPROXY_H +#define PLUGINLISTSORTPROXY_H + + +#include +#include +#include "pluginlist.h" + + +class PluginListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT +public: + + enum ESorting { + SORT_ASCENDING, + SORT_DESCENDING + }; + +public: + + explicit PluginListSortProxy(QObject *parent = 0); + + unsigned int getEnabledColumns() const; + + void setEnabledColumns(unsigned int columns); + + virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; + + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + +public slots: + + void displayColumnSelection(const QPoint &pos); + void updateFilter(const QString &filter); + +protected: + + virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + +private: + + int m_SortIndex; + Qt::SortOrder m_SortOrder; + + std::bitset m_EnabledColumns; + QString m_CurrentFilter; + +}; + +#endif // PLUGINLISTSORTPROXY_H diff --git a/src/profile.cpp b/src/profile.cpp index 2bbdea66..fbfb3266 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -1,782 +1,782 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "profile.h" -#include "report.h" -#include "gameinfo.h" -#include "windows_error.h" -#include "dummybsa.h" -#include "modinfo.h" -#include "safewritefile.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -Profile::Profile() - : m_SaveTimer(NULL) -{ - initTimer(); -} - -Profile::Profile(const QString &name, bool useDefaultSettings) - : m_SaveTimer(NULL) -{ - initTimer(); - QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); - QDir profileBase(profilesDir); - - QString fixedName = name; - if (!fixDirectoryName(fixedName)) { - throw MyException(tr("invalid profile name %1").arg(name)); - } - - if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { - throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); - } - QString fullPath = profilesDir + "/" + fixedName; - m_Directory = QDir(fullPath); - QFile modList(m_Directory.filePath("modlist.txt")); - if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(fixedName); - throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); - } - modList.close(); - - try { - GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); - } catch (...) { - // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); - throw; - } - refreshModStatus(); -} - - -Profile::Profile(const QDir& directory) - : m_Directory(directory), m_SaveTimer(NULL) -{ - initTimer(); - if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qPrintable(directory.path())); - } - - GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); - - if (!QFile::exists(getIniFileName())) { - reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); - } - refreshModStatus(); -} - - -Profile::Profile(const Profile& reference) - : m_Directory(reference.m_Directory), m_SaveTimer(NULL) -{ - initTimer(); - refreshModStatus(); -} - - -Profile::~Profile() -{ - writeModlistNow(); -} - - -void Profile::initTimer() -{ - m_SaveTimer = new QTimer(this); - m_SaveTimer->setSingleShot(true); - connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); -} - - -bool Profile::exists() const -{ - return m_Directory.exists(); -} - - -void Profile::writeModlist() const -{ - if (!m_SaveTimer->isActive()) { - m_SaveTimer->start(2000); - } -} - - -void Profile::cancelWriteModlist() const -{ - m_SaveTimer->stop(); -} - - -void Profile::writeModlistNow(bool onlyOnTimer) const -{ - if (onlyOnTimer && !m_SaveTimer->isActive()) return; - - m_SaveTimer->stop(); - if (!m_Directory.exists()) return; - - try { - QString fileName = getModlistFileName(); - SafeWriteFile file(fileName); - - file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - if (m_ModStatus.empty()) { - return; - } - - for (int i = m_ModStatus.size() - 1; i >= 0; --i) { - // the priority order was inverted on load so it has to be inverted again - unsigned int index = m_ModIndexByPriority[i]; - if (index != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - std::vector flags = modInfo->getFlags(); - if ((modInfo->getFixedPriority() == INT_MIN)) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - file->write("*"); - } else if (m_ModStatus[index].m_Enabled) { - file->write("+"); - } else { - file->write("-"); - } - file->write(modInfo->name().toUtf8()); - file->write("\r\n"); - } - } - } - - if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); - } - } catch (const std::exception &e) { - reportError(tr("failed to write mod list: %1").arg(e.what())); - return; - } -} - - -void Profile::createTweakedIniFile() -{ - QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); - - if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); - return; - } - - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - if (m_ModStatus[i].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - mergeTweaks(modInfo, tweakedIni); - } - } - - mergeTweak(getProfileTweaks(), tweakedIni); - - bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str())) { - error = true; - } - } - - if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); - } - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); -} - - -void Profile::refreshModStatus() -{ - QFile file(getModlistFileName()); - if (!file.exists()) { - throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); - } - - bool modStatusModified = false; - m_ModStatus.clear(); - m_ModStatus.resize(ModInfo::getNumMods()); - - std::set namesRead; - - // load mods from file and update enabled state and priority for them - file.open(QIODevice::ReadOnly); - int index = 0; - while (!file.atEnd()) { - QByteArray line = file.readLine(); - bool enabled = true; - QString modName; - if (line.length() == 0) { - // empty line - continue; - } else if (line.at(0) == '#') { - // comment line - continue; - } else if (line.at(0) == '-') { - enabled = false; - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else if ((line.at(0) == '+') - || (line.at(0) == '*')) { - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else { - modName = QString::fromUtf8(line.trimmed().constData()); - } - if (modName.size() > 0) { - if (namesRead.find(modName) != namesRead.end()) { - continue; - } else { - namesRead.insert(modName); - } - unsigned int modindex = ModInfo::getIndex(modName); - if (modindex != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(modindex); - if ((modindex < m_ModStatus.size()) - && (info->getFixedPriority() == INT_MIN)) { - m_ModStatus[modindex].m_Enabled = enabled || info->alwaysEnabled(); - if (m_ModStatus[modindex].m_Priority == -1) { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - m_ModStatus[modindex].m_Priority = index++; - } - } else { - qDebug("mod \"%s\" (profile \"%s\") not found", - modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; - } - } - } else { - // line was empty after trimming - } - } - - int numKnownMods = index; - - int topInsert = 0; - - // invert priority order to match that of the pluginlist. Also - // give priorities to mods not referenced in the profile - for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - if (m_ModStatus[i].m_Priority != -1) { - m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; - } else { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - m_ModStatus[i].m_Priority = --topInsert; - } else { - m_ModStatus[i].m_Priority = index++; - } - // also, mark the mod-list as changed - modStatusModified = true; - } - } - // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up - // to align priority with 0 - if (topInsert < 0) { - int offset = topInsert * -1; - for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - m_ModStatus[i].m_Priority += offset; - } - } - - file.close(); - updateIndices(); - if (modStatusModified) { - writeModlist(); - } -} - - -void Profile::dumpModStatus() const -{ - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); - } -} - - -void Profile::updateIndices() -{ - m_NumRegularMods = 0; - m_ModIndexByPriority.clear(); - m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - int priority = m_ModStatus[i].m_Priority; - if (priority < 0) { - // don't assign this to mapping at all, it's probably the overwrite mod - continue; - } else if (priority >= static_cast(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } - } -} - - -std::vector > Profile::getActiveMods() -{ - std::vector > result; - for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); - iter != m_ModIndexByPriority.end(); ++iter) { - if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); - result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); - } - } - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - if (overwriteIndex != UINT_MAX) { - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); - } else { - reportError(tr("Overwrite directory couldn't be parsed")); - } - return result; -} - - -unsigned int Profile::modIndexByPriority(unsigned int priority) const -{ - if (priority >= m_ModStatus.size()) { - throw MyException(tr("invalid priority %1").arg(priority)); - } - - return m_ModIndexByPriority[priority]; -} - - -void Profile::setModEnabled(unsigned int index, bool enabled) -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - // we could quit in the following case, this shouldn't be a change anyway, - // but at least this allows the situation to be fixed in case of an error - if (modInfo->alwaysEnabled()) { - enabled = true; - } - - if (enabled != m_ModStatus[index].m_Enabled) { - m_ModStatus[index].m_Enabled = enabled; - emit modStatusChanged(index); - } -} - - -bool Profile::modEnabled(unsigned int index) const -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - return m_ModStatus[index].m_Enabled; -} - - -int Profile::getModPriority(unsigned int index) const -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - return m_ModStatus[index].m_Priority; -} - - -void Profile::setModPriority(unsigned int index, int &newPriority) -{ - if (m_ModStatus.at(index).m_Overwrite) { - // can't change priority of the overwrite - return; - } - - int newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); - - // don't try to place below overwrite - while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || - m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { - --newPriorityTemp; - } - - int oldPriority = m_ModStatus.at(index).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - } else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - ++newPriority; - } - - m_ModStatus.at(index).m_Priority = newPriorityTemp; - - updateIndices(); - writeModlist(); -} - - -Profile Profile::createFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return Profile(QDir(profileDirectory)); -} - - -Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory)); -} - - -void Profile::copyFilesTo(QString &target) const -{ - copyDir(m_Directory.absolutePath(), target, false); -} - - -std::vector Profile::splitDZString(const wchar_t *buffer) const -{ - std::vector result; - const wchar_t *pos = buffer; - size_t length = wcslen(pos); - while (length != 0U) { - result.push_back(pos); - pos += length + 1; - length = wcslen(pos); - } - return result; -} - - -void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const -{ - static const int bufferSize = 32768; - - std::wstring tweakNameW = ToWString(tweakName); - std::wstring tweakedIniW = ToWString(tweakedIni); - QScopedArrayPointer buffer(new wchar_t[bufferSize]); - - // retrieve a list of sections - DWORD size = ::GetPrivateProfileSectionNamesW( - buffer.data(), bufferSize, tweakNameW.c_str()); - - if (size == bufferSize - 2) { - // unfortunately there is no good way to find the required size - // of the buffer - throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); - } - - std::vector sections = splitDZString(buffer.data()); - - // now iterate over all sections and retrieve a list of keys in each - for (std::vector::iterator iter = sections.begin(); - iter != sections.end(); ++iter) { - // retrieve the names of all keys - size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(), - bufferSize, tweakNameW.c_str()); - if (size == bufferSize - 2) { - throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); - } - - std::vector keys = splitDZString(buffer.data()); - - for (std::vector::iterator keyIter = keys.begin(); - keyIter != keys.end(); ++keyIter) { - //TODO this treats everything as strings but how could I differentiate the type? - ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), - NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); - ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), - buffer.data(), tweakedIniW.c_str()); - } - } -} - -void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const -{ - std::vector iniTweaks = modInfo->getIniTweaks(); - for (std::vector::iterator iter = iniTweaks.begin(); - iter != iniTweaks.end(); ++iter) { - mergeTweak(*iter, tweakedIni); - } -} - - -bool Profile::invalidationActive(bool *supported) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - *supported = true; - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value - // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno != 0x02) { - if (supported != NULL) { - *supported = false; - } - return false; - } else { - QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } - QStringList archives = ToQString(buffer).split(','); - - for (int i = 0; i < archives.count(); ++i) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - return true; - } - } - } else { - *supported = false; - } - return false; -} - - -void Profile::deactivateInvalidation() const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); - throw windows_error(errorMessage.toUtf8().constData()); - } else { - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - for (int i = 0; i < archives.count();) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - archives.removeAt(i); - } else { - ++i; - } - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } -} - - -void Profile::activateInvalidation(const QString& dataDirectory) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - throw windows_error("failed to parse ini file"); - } else { - // ignore. shouldn't have gotten here anyway - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); - - if (!archives.contains(invalidationBSA)) { - archives.insert(0, invalidationBSA); - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - - QString bsaFile = dataDirectory + "/" + invalidationBSA; - if (!QFile::exists(bsaFile)) { - DummyBSA bsa; - bsa.write(bsaFile); - } - } -} - - -bool Profile::localSavesEnabled() const -{ - return m_Directory.exists("saves"); -} - - -bool Profile::enableLocalSaves(bool enable) -{ - if (enable) { - if (m_Directory.exists("_saves")) { - m_Directory.rename("_saves", "saves"); - } else { - m_Directory.mkdir("saves"); - } - } else { - QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), - tr("Do you want to delete local savegames? (If you select \"No\", the save games " - "will show up again if you re-enable local savegames)"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel); - if (res == QMessageBox::Yes) { - shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); - } else if (res == QMessageBox::No) { - m_Directory.rename("saves", "_saves"); - } else { - return false; - } - } - - // default: assume success - return true; -} - - -QString Profile::getModlistFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); -} - -QString Profile::getPluginsFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); -} - -QString Profile::getLoadOrderFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); -} - -QString Profile::getLockedOrderFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); -} - -QString Profile::getArchivesFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); -} - -QString Profile::getDeleterFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); -} - -QString Profile::getIniFileName() const -{ - std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); - return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); -} - -QString Profile::getProfileTweaks() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); -} - -QString Profile::getPath() const -{ - return QDir::cleanPath(m_Directory.absolutePath()); -} - -void Profile::rename(const QString &newName) -{ - QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profileDir.rename(getName(), newName); - m_Directory = profileDir.absoluteFilePath(newName); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "profile.h" +#include "report.h" +#include "gameinfo.h" +#include "windows_error.h" +#include "dummybsa.h" +#include "modinfo.h" +#include "safewritefile.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +Profile::Profile() + : m_SaveTimer(NULL) +{ + initTimer(); +} + +Profile::Profile(const QString &name, bool useDefaultSettings) + : m_SaveTimer(NULL) +{ + initTimer(); + QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); + QDir profileBase(profilesDir); + + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(name)); + } + + if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { + throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + fixedName; + m_Directory = QDir(fullPath); + QFile modList(m_Directory.filePath("modlist.txt")); + if (!modList.open(QIODevice::ReadWrite)) { + profileBase.rmdir(fixedName); + throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); + } + modList.close(); + + try { + GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); + } catch (...) { + // clean up in case of an error + shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); + throw; + } + refreshModStatus(); +} + + +Profile::Profile(const QDir& directory) + : m_Directory(directory), m_SaveTimer(NULL) +{ + initTimer(); + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { + qWarning("missing modlist.txt in %s", qPrintable(directory.path())); + } + + GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); + + if (!QFile::exists(getIniFileName())) { + reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); + } + refreshModStatus(); +} + + +Profile::Profile(const Profile& reference) + : m_Directory(reference.m_Directory), m_SaveTimer(NULL) +{ + initTimer(); + refreshModStatus(); +} + + +Profile::~Profile() +{ + writeModlistNow(); +} + + +void Profile::initTimer() +{ + m_SaveTimer = new QTimer(this); + m_SaveTimer->setSingleShot(true); + connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); +} + + +bool Profile::exists() const +{ + return m_Directory.exists(); +} + + +void Profile::writeModlist() const +{ + if (!m_SaveTimer->isActive()) { + m_SaveTimer->start(2000); + } +} + + +void Profile::cancelWriteModlist() const +{ + m_SaveTimer->stop(); +} + + +void Profile::writeModlistNow(bool onlyOnTimer) const +{ + if (onlyOnTimer && !m_SaveTimer->isActive()) return; + + m_SaveTimer->stop(); + if (!m_Directory.exists()) return; + + try { + QString fileName = getModlistFileName(); + SafeWriteFile file(fileName); + + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + if (m_ModStatus.empty()) { + return; + } + + for (int i = m_ModStatus.size() - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + if (index != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + std::vector flags = modInfo->getFlags(); + if ((modInfo->getFixedPriority() == INT_MIN)) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { + file->write("*"); + } else if (m_ModStatus[index].m_Enabled) { + file->write("+"); + } else { + file->write("-"); + } + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); + } + } + } + + if (file.commitIfDifferent(m_LastModlistHash)) { + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); + } + } catch (const std::exception &e) { + reportError(tr("failed to write mod list: %1").arg(e.what())); + return; + } +} + + +void Profile::createTweakedIniFile() +{ + QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); + + if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { + reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + return; + } + + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + if (m_ModStatus[i].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + mergeTweaks(modInfo, tweakedIni); + } + } + + mergeTweak(getProfileTweaks(), tweakedIni); + + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (localSavesEnabled()) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str())) { + error = true; + } + } + + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); +} + + +void Profile::refreshModStatus() +{ + QFile file(getModlistFileName()); + if (!file.exists()) { + throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); + } + + bool modStatusModified = false; + m_ModStatus.clear(); + m_ModStatus.resize(ModInfo::getNumMods()); + + std::set namesRead; + + // load mods from file and update enabled state and priority for them + file.open(QIODevice::ReadOnly); + int index = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine(); + bool enabled = true; + QString modName; + if (line.length() == 0) { + // empty line + continue; + } else if (line.at(0) == '#') { + // comment line + continue; + } else if (line.at(0) == '-') { + enabled = false; + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else if ((line.at(0) == '+') + || (line.at(0) == '*')) { + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else { + modName = QString::fromUtf8(line.trimmed().constData()); + } + if (modName.size() > 0) { + if (namesRead.find(modName) != namesRead.end()) { + continue; + } else { + namesRead.insert(modName); + } + unsigned int modindex = ModInfo::getIndex(modName); + if (modindex != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(modindex); + if ((modindex < m_ModStatus.size()) + && (info->getFixedPriority() == INT_MIN)) { + m_ModStatus[modindex].m_Enabled = enabled || info->alwaysEnabled(); + if (m_ModStatus[modindex].m_Priority == -1) { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + m_ModStatus[modindex].m_Priority = index++; + } + } else { + qDebug("mod \"%s\" (profile \"%s\") not found", + modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; + } + } + } else { + // line was empty after trimming + } + } + + int numKnownMods = index; + + int topInsert = 0; + + // invert priority order to match that of the pluginlist. Also + // give priorities to mods not referenced in the profile + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + if (m_ModStatus[i].m_Priority != -1) { + m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; + } else { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + m_ModStatus[i].m_Priority = --topInsert; + } else { + m_ModStatus[i].m_Priority = index++; + } + // also, mark the mod-list as changed + modStatusModified = true; + } + } + // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up + // to align priority with 0 + if (topInsert < 0) { + int offset = topInsert * -1; + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + m_ModStatus[i].m_Priority += offset; + } + } + + file.close(); + updateIndices(); + if (modStatusModified) { + writeModlist(); + } +} + + +void Profile::dumpModStatus() const +{ + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + } +} + + +void Profile::updateIndices() +{ + m_NumRegularMods = 0; + m_ModIndexByPriority.clear(); + m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + int priority = m_ModStatus[i].m_Priority; + if (priority < 0) { + // don't assign this to mapping at all, it's probably the overwrite mod + continue; + } else if (priority >= static_cast(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; + } + } +} + + +std::vector > Profile::getActiveMods() +{ + std::vector > result; + for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); + iter != m_ModIndexByPriority.end(); ++iter) { + if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); + result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); + } + } + + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + if (overwriteIndex != UINT_MAX) { + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); + } else { + reportError(tr("Overwrite directory couldn't be parsed")); + } + return result; +} + + +unsigned int Profile::modIndexByPriority(unsigned int priority) const +{ + if (priority >= m_ModStatus.size()) { + throw MyException(tr("invalid priority %1").arg(priority)); + } + + return m_ModIndexByPriority[priority]; +} + + +void Profile::setModEnabled(unsigned int index, bool enabled) +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + // we could quit in the following case, this shouldn't be a change anyway, + // but at least this allows the situation to be fixed in case of an error + if (modInfo->alwaysEnabled()) { + enabled = true; + } + + if (enabled != m_ModStatus[index].m_Enabled) { + m_ModStatus[index].m_Enabled = enabled; + emit modStatusChanged(index); + } +} + + +bool Profile::modEnabled(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Enabled; +} + + +int Profile::getModPriority(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Priority; +} + + +void Profile::setModPriority(unsigned int index, int &newPriority) +{ + if (m_ModStatus.at(index).m_Overwrite) { + // can't change priority of the overwrite + return; + } + + int newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); + + // don't try to place below overwrite + while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || + m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { + --newPriorityTemp; + } + + int oldPriority = m_ModStatus.at(index).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + ++newPriority; + } + + m_ModStatus.at(index).m_Priority = newPriorityTemp; + + updateIndices(); + writeModlist(); +} + + +Profile Profile::createFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return Profile(QDir(profileDirectory)); +} + + +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory)); +} + + +void Profile::copyFilesTo(QString &target) const +{ + copyDir(m_Directory.absolutePath(), target, false); +} + + +std::vector Profile::splitDZString(const wchar_t *buffer) const +{ + std::vector result; + const wchar_t *pos = buffer; + size_t length = wcslen(pos); + while (length != 0U) { + result.push_back(pos); + pos += length + 1; + length = wcslen(pos); + } + return result; +} + + +void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const +{ + static const int bufferSize = 32768; + + std::wstring tweakNameW = ToWString(tweakName); + std::wstring tweakedIniW = ToWString(tweakedIni); + QScopedArrayPointer buffer(new wchar_t[bufferSize]); + + // retrieve a list of sections + DWORD size = ::GetPrivateProfileSectionNamesW( + buffer.data(), bufferSize, tweakNameW.c_str()); + + if (size == bufferSize - 2) { + // unfortunately there is no good way to find the required size + // of the buffer + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector sections = splitDZString(buffer.data()); + + // now iterate over all sections and retrieve a list of keys in each + for (std::vector::iterator iter = sections.begin(); + iter != sections.end(); ++iter) { + // retrieve the names of all keys + size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(), + bufferSize, tweakNameW.c_str()); + if (size == bufferSize - 2) { + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector keys = splitDZString(buffer.data()); + + for (std::vector::iterator keyIter = keys.begin(); + keyIter != keys.end(); ++keyIter) { + //TODO this treats everything as strings but how could I differentiate the type? + ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), + NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); + ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), + buffer.data(), tweakedIniW.c_str()); + } + } +} + +void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const +{ + std::vector iniTweaks = modInfo->getIniTweaks(); + for (std::vector::iterator iter = iniTweaks.begin(); + iter != iniTweaks.end(); ++iter) { + mergeTweak(*iter, tweakedIni); + } +} + + +bool Profile::invalidationActive(bool *supported) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + *supported = true; + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value + // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno != 0x02) { + if (supported != NULL) { + *supported = false; + } + return false; + } else { + QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } + QStringList archives = ToQString(buffer).split(','); + + for (int i = 0; i < archives.count(); ++i) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + return true; + } + } + } else { + *supported = false; + } + return false; +} + + +void Profile::deactivateInvalidation() const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); + throw windows_error(errorMessage.toUtf8().constData()); + } else { + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + for (int i = 0; i < archives.count();) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + archives.removeAt(i); + } else { + ++i; + } + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } +} + + +void Profile::activateInvalidation(const QString& dataDirectory) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + throw windows_error("failed to parse ini file"); + } else { + // ignore. shouldn't have gotten here anyway + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); + + if (!archives.contains(invalidationBSA)) { + archives.insert(0, invalidationBSA); + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + + QString bsaFile = dataDirectory + "/" + invalidationBSA; + if (!QFile::exists(bsaFile)) { + DummyBSA bsa; + bsa.write(bsaFile); + } + } +} + + +bool Profile::localSavesEnabled() const +{ + return m_Directory.exists("saves"); +} + + +bool Profile::enableLocalSaves(bool enable) +{ + if (enable) { + if (m_Directory.exists("_saves")) { + m_Directory.rename("_saves", "saves"); + } else { + m_Directory.mkdir("saves"); + } + } else { + QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), + tr("Do you want to delete local savegames? (If you select \"No\", the save games " + "will show up again if you re-enable local savegames)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); + } else if (res == QMessageBox::No) { + m_Directory.rename("saves", "_saves"); + } else { + return false; + } + } + + // default: assume success + return true; +} + + +QString Profile::getModlistFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); +} + +QString Profile::getPluginsFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); +} + +QString Profile::getLoadOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); +} + +QString Profile::getLockedOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); +} + +QString Profile::getArchivesFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); +} + +QString Profile::getDeleterFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); +} + +QString Profile::getIniFileName() const +{ + std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); + return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); +} + +QString Profile::getProfileTweaks() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +} + +QString Profile::getPath() const +{ + return QDir::cleanPath(m_Directory.absolutePath()); +} + +void Profile::rename(const QString &newName) +{ + QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profileDir.rename(getName(), newName); + m_Directory = profileDir.absoluteFilePath(newName); +} diff --git a/src/profile.h b/src/profile.h index d2f66ca0..fa34ab9e 100644 --- a/src/profile.h +++ b/src/profile.h @@ -1,330 +1,330 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef PROFILE_H -#define PROFILE_H - - -#include "modinfo.h" - -#include -#include -#include -#include -#include -#include - - -/** - * @brief represents a profile - **/ -class Profile : public QObject -{ - - Q_OBJECT - -public: - - typedef boost::shared_ptr Ptr; - -public: - - /** - * @brief default constructor - * @todo This constructor initialised nothing, the resulting object is not usable - **/ - Profile(); - - /** - * @brief constructor - * - * This constructor is used to create a new profile so it is to be assumed a profile - * by this name does not yet exist - * @param name name of the new profile - * @param filter save game filter. Defaults to <no filter>. - **/ - Profile(const QString &name, bool useDefaultSettings); - /** - * @brief constructor - * - * This constructor is used to open an existing profile though it will also try to repair - * the profile if important files are missing (including the directory itself) so technically, - * invoking this should always produce a working profile - * @param directory directory to read the profile from - **/ - Profile(const QDir &directory); - - Profile(const Profile &reference); - - ~Profile(); - - /** - * @return true if this profile (still) exists on disc - */ - bool exists() const; - - /** - * @param name of the new profile - * @param reference profile to copy from - **/ - static Profile createFrom(const QString &name, const Profile &reference); - - /** - * @param name of the new profile - * @param reference profile to copy from - **/ - static Profile *createPtrFrom(const QString &name, const Profile &reference); - - /** - * @brief write out the modlist.txt - **/ - void writeModlist() const; - - /** - * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is - * saved while it's being modified - */ - void cancelWriteModlist() const; - - /** - * @brief test if this profile uses archive invalidation - * - * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile - * @return true if archive invalidation is active - * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist - **/ - bool invalidationActive(bool *supported) const; - - /** - * @brief deactivate archive invalidation if it was active - **/ - void deactivateInvalidation() const; - - /** - * @brief activate archive invalidation - * - * @param dataDirectory data directory of the game - * @todo passing the data directory as a parameter is useless, the function should - * be able to query it from GameInfo - **/ - void activateInvalidation(const QString &dataDirectory) const; - - /** - * @return true if this profile uses local save games - */ - bool localSavesEnabled() const; - - /** - * @brief enables or disables the use of local save games for this profile - * disabling this does not delete exising local saves but they will not be visible - * in the game - * @param enable if true, local saves are enabled, otherewise they are disabled - */ - bool enableLocalSaves(bool enable); - - /** - * @return name of the profile (this is identical to its directory name) - **/ - QString getName() const { return m_Directory.dirName(); } - - /** - * @return the path of the plugins file in this profile - * @todo is this required? can the functionality using this function be moved to the Profile-class? - **/ - QString getPluginsFileName() const; - - /** - * @return the path of the loadorder file in this profile - **/ - QString getLoadOrderFileName() const; - - /** - * @return the path of the file containing locked mod indices - */ - QString getLockedOrderFileName() const; - - /** - * @return the path of the modlist file in this profile - */ - QString getModlistFileName() const; - - /** - * @return path of the archives file in this profile - */ - QString getArchivesFileName() const; - - /** - * @return the path of the delete file in this profile - * @note the deleter file lists plugins that should be hidden from the game and other tools - **/ - QString getDeleterFileName() const; - - /** - * @return the path of the ini file in this profile - * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) - * the concept of this function is somewhat broken - **/ - QString getIniFileName() const; - - /** - * @return the path of the tweak ini in this profile - */ - QString getProfileTweaks() const; - - /** - * @return path to this profile - **/ - QString getPath() const; - - void rename(const QString &newName); - - /** - * @brief create the ini file to be used by the game - * - * the tweaked ini file constructed by this file is a merger - * of the game-ini of this profile with ini tweaks applied */ - void createTweakedIniFile(); - - /** - * @brief re-read the modlist.txt and update the mod status from it - **/ - void refreshModStatus(); - - /** - * @brief retrieve a list of mods that are enabled in this profile - * - * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path - **/ - std::vector > getActiveMods(); - - /** - * retrieve the number of mods for which this object has status information. - * This is usually the same as ModInfo::getNumMods() except between - * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() - * - * @return number of mods for which the profile has status information - **/ - unsigned int numMods() const { return m_ModStatus.size(); } - - /** - * @return the number of mods that can be enabled and where the priority can be modified - */ - unsigned int numRegularMods() const { return m_NumRegularMods; } - - /** - * @brief retrieve the mod index based on the priority - * - * @param priority priority to look up - * @return the index of the mod - * @throw std::exception an exception is thrown if there is no mod with the specified priority - **/ - unsigned int modIndexByPriority(unsigned int priority) const; - - /** - * @brief enable or disable a mod - * - * @param index index of the mod to enable/disable - * @param enabled true if the mod is to be enabled, false if it is to be disabled - **/ - void setModEnabled(unsigned int index, bool enabled); - - /** - * change the priority of a mod. Of course this also changes the priority of other mods. - * The priority of the mods in the range ]old, new priority] are shifted so that no gaps - * are possible. - * - * @param index index of the mod to change - * @param newPriority the new priority value - * - * @todo what happens if the new priority is outside the range? - **/ - void setModPriority(unsigned int index, int &newPriority); - - /** - * @brief determine if a mod is enabled - * - * @param index index of the mod to look up - * @return true if the mod is enabled, false otherwise - **/ - bool modEnabled(unsigned int index) const; - - /** - * @brief query the priority of a mod - * - * @param index index of the mod to look up - * @return priority of the specified mod - **/ - int getModPriority(unsigned int index) const; - - void dumpModStatus() const; - -signals: - - /** - * @brief emitted whenever the status (enabled/disabled) of a mod changed - * - * @param index index of the mod that changed - **/ - void modStatusChanged(unsigned int index); - -public slots: - - void writeModlistNow(bool onlyOnTimer = false) const; - -private: - - class ModStatus { - friend class Profile; - public: - ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} - private: - bool m_Overwrite; - bool m_Enabled; - int m_Priority; - }; - -private: - Profile& operator=(const Profile &reference); // not implemented - - void initTimer(); - - void updateIndices(); - - void copyFilesTo(QString &target) const; - - std::vector splitDZString(const wchar_t *buffer) const; - void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; - void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; - -private: - - QDir m_Directory; - - mutable QByteArray m_LastModlistHash; - std::vector m_ModStatus; - std::vector m_ModIndexByPriority; - unsigned int m_NumRegularMods; - - QTimer *m_SaveTimer; -}; - -Q_DECLARE_METATYPE(Profile) - - -#endif // PROFILE_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef PROFILE_H +#define PROFILE_H + + +#include "modinfo.h" + +#include +#include +#include +#include +#include +#include + + +/** + * @brief represents a profile + **/ +class Profile : public QObject +{ + + Q_OBJECT + +public: + + typedef boost::shared_ptr Ptr; + +public: + + /** + * @brief default constructor + * @todo This constructor initialised nothing, the resulting object is not usable + **/ + Profile(); + + /** + * @brief constructor + * + * This constructor is used to create a new profile so it is to be assumed a profile + * by this name does not yet exist + * @param name name of the new profile + * @param filter save game filter. Defaults to <no filter>. + **/ + Profile(const QString &name, bool useDefaultSettings); + /** + * @brief constructor + * + * This constructor is used to open an existing profile though it will also try to repair + * the profile if important files are missing (including the directory itself) so technically, + * invoking this should always produce a working profile + * @param directory directory to read the profile from + **/ + Profile(const QDir &directory); + + Profile(const Profile &reference); + + ~Profile(); + + /** + * @return true if this profile (still) exists on disc + */ + bool exists() const; + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile createFrom(const QString &name, const Profile &reference); + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile *createPtrFrom(const QString &name, const Profile &reference); + + /** + * @brief write out the modlist.txt + **/ + void writeModlist() const; + + /** + * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is + * saved while it's being modified + */ + void cancelWriteModlist() const; + + /** + * @brief test if this profile uses archive invalidation + * + * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile + * @return true if archive invalidation is active + * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist + **/ + bool invalidationActive(bool *supported) const; + + /** + * @brief deactivate archive invalidation if it was active + **/ + void deactivateInvalidation() const; + + /** + * @brief activate archive invalidation + * + * @param dataDirectory data directory of the game + * @todo passing the data directory as a parameter is useless, the function should + * be able to query it from GameInfo + **/ + void activateInvalidation(const QString &dataDirectory) const; + + /** + * @return true if this profile uses local save games + */ + bool localSavesEnabled() const; + + /** + * @brief enables or disables the use of local save games for this profile + * disabling this does not delete exising local saves but they will not be visible + * in the game + * @param enable if true, local saves are enabled, otherewise they are disabled + */ + bool enableLocalSaves(bool enable); + + /** + * @return name of the profile (this is identical to its directory name) + **/ + QString getName() const { return m_Directory.dirName(); } + + /** + * @return the path of the plugins file in this profile + * @todo is this required? can the functionality using this function be moved to the Profile-class? + **/ + QString getPluginsFileName() const; + + /** + * @return the path of the loadorder file in this profile + **/ + QString getLoadOrderFileName() const; + + /** + * @return the path of the file containing locked mod indices + */ + QString getLockedOrderFileName() const; + + /** + * @return the path of the modlist file in this profile + */ + QString getModlistFileName() const; + + /** + * @return path of the archives file in this profile + */ + QString getArchivesFileName() const; + + /** + * @return the path of the delete file in this profile + * @note the deleter file lists plugins that should be hidden from the game and other tools + **/ + QString getDeleterFileName() const; + + /** + * @return the path of the ini file in this profile + * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) + * the concept of this function is somewhat broken + **/ + QString getIniFileName() const; + + /** + * @return the path of the tweak ini in this profile + */ + QString getProfileTweaks() const; + + /** + * @return path to this profile + **/ + QString getPath() const; + + void rename(const QString &newName); + + /** + * @brief create the ini file to be used by the game + * + * the tweaked ini file constructed by this file is a merger + * of the game-ini of this profile with ini tweaks applied */ + void createTweakedIniFile(); + + /** + * @brief re-read the modlist.txt and update the mod status from it + **/ + void refreshModStatus(); + + /** + * @brief retrieve a list of mods that are enabled in this profile + * + * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path + **/ + std::vector > getActiveMods(); + + /** + * retrieve the number of mods for which this object has status information. + * This is usually the same as ModInfo::getNumMods() except between + * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() + * + * @return number of mods for which the profile has status information + **/ + unsigned int numMods() const { return m_ModStatus.size(); } + + /** + * @return the number of mods that can be enabled and where the priority can be modified + */ + unsigned int numRegularMods() const { return m_NumRegularMods; } + + /** + * @brief retrieve the mod index based on the priority + * + * @param priority priority to look up + * @return the index of the mod + * @throw std::exception an exception is thrown if there is no mod with the specified priority + **/ + unsigned int modIndexByPriority(unsigned int priority) const; + + /** + * @brief enable or disable a mod + * + * @param index index of the mod to enable/disable + * @param enabled true if the mod is to be enabled, false if it is to be disabled + **/ + void setModEnabled(unsigned int index, bool enabled); + + /** + * change the priority of a mod. Of course this also changes the priority of other mods. + * The priority of the mods in the range ]old, new priority] are shifted so that no gaps + * are possible. + * + * @param index index of the mod to change + * @param newPriority the new priority value + * + * @todo what happens if the new priority is outside the range? + **/ + void setModPriority(unsigned int index, int &newPriority); + + /** + * @brief determine if a mod is enabled + * + * @param index index of the mod to look up + * @return true if the mod is enabled, false otherwise + **/ + bool modEnabled(unsigned int index) const; + + /** + * @brief query the priority of a mod + * + * @param index index of the mod to look up + * @return priority of the specified mod + **/ + int getModPriority(unsigned int index) const; + + void dumpModStatus() const; + +signals: + + /** + * @brief emitted whenever the status (enabled/disabled) of a mod changed + * + * @param index index of the mod that changed + **/ + void modStatusChanged(unsigned int index); + +public slots: + + void writeModlistNow(bool onlyOnTimer = false) const; + +private: + + class ModStatus { + friend class Profile; + public: + ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} + private: + bool m_Overwrite; + bool m_Enabled; + int m_Priority; + }; + +private: + Profile& operator=(const Profile &reference); // not implemented + + void initTimer(); + + void updateIndices(); + + void copyFilesTo(QString &target) const; + + std::vector splitDZString(const wchar_t *buffer) const; + void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; + void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; + +private: + + QDir m_Directory; + + mutable QByteArray m_LastModlistHash; + std::vector m_ModStatus; + std::vector m_ModIndexByPriority; + unsigned int m_NumRegularMods; + + QTimer *m_SaveTimer; +}; + +Q_DECLARE_METATYPE(Profile) + + +#endif // PROFILE_H diff --git a/src/profileinputdialog.cpp b/src/profileinputdialog.cpp index af33dcdd..5a1fd03c 100644 --- a/src/profileinputdialog.cpp +++ b/src/profileinputdialog.cpp @@ -1,48 +1,48 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "profileinputdialog.h" -#include "ui_profileinputdialog.h" -#include - -ProfileInputDialog::ProfileInputDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::ProfileInputDialog) -{ - ui->setupUi(this); -} - -ProfileInputDialog::~ProfileInputDialog() -{ - delete ui; -} - -QString ProfileInputDialog::getName() const -{ - QString result = ui->nameEdit->text(); - MOBase::fixDirectoryName(result); - return result; -} - - -bool ProfileInputDialog::getPreferDefaultSettings() const -{ - return ui->defaultSettingsBox->checkState() == Qt::Checked; -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "profileinputdialog.h" +#include "ui_profileinputdialog.h" +#include + +ProfileInputDialog::ProfileInputDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ProfileInputDialog) +{ + ui->setupUi(this); +} + +ProfileInputDialog::~ProfileInputDialog() +{ + delete ui; +} + +QString ProfileInputDialog::getName() const +{ + QString result = ui->nameEdit->text(); + MOBase::fixDirectoryName(result); + return result; +} + + +bool ProfileInputDialog::getPreferDefaultSettings() const +{ + return ui->defaultSettingsBox->checkState() == Qt::Checked; +} + diff --git a/src/profileinputdialog.h b/src/profileinputdialog.h index 5ee1d600..8a132666 100644 --- a/src/profileinputdialog.h +++ b/src/profileinputdialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef PROFILEINPUTDIALOG_H #define PROFILEINPUTDIALOG_H diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 69872a24..3fdad880 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -1,320 +1,320 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "profilesdialog.h" -#include "ui_profilesdialog.h" -#include "profile.h" -#include "report.h" -#include "utility.h" -#include "transfersavesdialog.h" -#include "profileinputdialog.h" -#include "mainwindow.h" -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - -Q_DECLARE_METATYPE(Profile::Ptr) - - -ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) - : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) -{ - ui->setupUi(this); - - QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - m_ProfilesList = findChild("profilesList"); - - QDirIterator profileIter(profilesDir); - - while (profileIter.hasNext()) { - profileIter.next(); - addItem(profileIter.filePath()); - } - - QCheckBox *invalidationBox = findChild("invalidationBox"); - if (!GameInfo::instance().requiresBSAInvalidation()) { - invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); - invalidationBox->setEnabled(false); - } -} - -ProfilesDialog::~ProfilesDialog() -{ - delete ui; -} - -void ProfilesDialog::showEvent(QShowEvent *event) -{ - TutorableDialog::showEvent(event); - - if (m_ProfilesList->count() == 0) { - QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0)); - pos.rx() += m_ProfilesList->width() / 2; - pos.ry() += (m_ProfilesList->height() / 2) - 20; - QWhatsThis::showText(pos, - QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. " - "ATTENTION: Run the game at least once before creating a profile!"), m_ProfilesList); - } -} - -void ProfilesDialog::on_closeButton_clicked() -{ - close(); -} - - -void ProfilesDialog::addItem(const QString &name) -{ - QDir profileDir(name); - QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); - try { - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir)))); - m_FailState = false; - } catch (const std::exception& e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - } -} - - -void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) -{ - try { - QListWidget *profilesList = findChild("profilesList"); - QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings)))); - profilesList->addItem(newItem); - m_FailState = false; - } catch (const std::exception&) { - m_FailState = true; - throw; - } -} - - -void ProfilesDialog::createProfile(const QString &name, const Profile &reference) -{ - try { - QListWidget *profilesList = findChild("profilesList"); - QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference)))); - profilesList->addItem(newItem); - m_FailState = false; - } catch (const std::exception&) { - m_FailState = true; - throw; - } -} - - -void ProfilesDialog::on_addProfileButton_clicked() -{ - ProfileInputDialog dialog(this); - bool okClicked = dialog.exec(); - QString name = dialog.getName(); - - if (okClicked && (name.size() > 0)) { - try { - createProfile(name, dialog.getPreferDefaultSettings()); - } catch (const std::exception &e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - } - } -} - -void ProfilesDialog::on_copyProfileButton_clicked() -{ - bool okClicked; - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked); - fixDirectoryName(name); - if (okClicked) { - if (name.size() > 0) { - QListWidget *profilesList = findChild("profilesList"); - - try { - const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - createProfile(name, *currentProfile); - } catch (const std::exception &e) { - reportError(tr("failed to copy profile: %1").arg(e.what())); - } - } else { - QMessageBox::warning(this, tr("Invalid name"), tr("Invalid profile name")); - } - } -} - -void ProfilesDialog::on_removeProfileButton_clicked() -{ - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including local savegames if any)?"), - QMessageBox::Yes | QMessageBox::No); - - if (confirmBox.exec() == QMessageBox::Yes) { - QListWidget *profilesList = findChild("profilesList"); - - Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); - QString profilePath; - if (currentProfile.get() == NULL) { - profilePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + "/" + profilesList->currentItem()->text(); - if (QMessageBox::question(this, tr("Profile broken"), - tr("This profile you're about to delete seems to be broken or the path is invalid. " - "I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - return; - } - } else { - // on destruction, the profile object would write the profile.ini file again, so - // we have to get rid of the it before deleting the directory - profilePath = currentProfile->getPath(); - } - QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); - if (item != NULL) { - delete item; - } - if (!shellDelete(QStringList(profilePath))) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(profilePath), ::GetLastError()); - if (!removeDir(profilePath)) { - qWarning("regular delete failed too"); - } - } - } -} - - -void ProfilesDialog::on_renameButton_clicked() -{ - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - - bool valid = false; - QString name; - - while (!valid) { - bool ok = false; - name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), - QLineEdit::Normal, currentProfile->getName(), - &ok); - valid = fixDirectoryName(name); - if (!ok) { - return; - } - } - - ui->profilesList->currentItem()->setText(name); - currentProfile->rename(name); -} - - -void ProfilesDialog::on_invalidationBox_stateChanged(int state) -{ - QListWidget *profilesList = findChild("profilesList"); - - QListWidgetItem *currentItem = profilesList->currentItem(); - if (currentItem == NULL) { - return; - } - if (!ui->invalidationBox->isEnabled()) { - return; - } - try { - QVariant currentProfileVariant = currentItem->data(Qt::UserRole); - if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { - return; - } - const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value(); - if (state == Qt::Unchecked) { - currentProfile->deactivateInvalidation(); - } else { - currentProfile->activateInvalidation(m_GamePath + "/data"); - } - } catch (const std::exception &e) { - reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); - } -} - - -void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - QCheckBox *invalidationBox = findChild("invalidationBox"); - QCheckBox *localSavesBox = findChild("localSavesBox"); - QPushButton *copyButton = findChild("copyProfileButton"); - QPushButton *removeButton = findChild("removeProfileButton"); - QPushButton *transferButton = findChild("transferButton"); - QPushButton *renameButton = findChild("renameButton"); - - if (current != NULL) { - if (!current->data(Qt::UserRole).isValid()) return; - const Profile::Ptr currentProfile = current->data(Qt::UserRole).value(); - - try { - bool invalidationSupported = false; - invalidationBox->blockSignals(true); - invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); - invalidationBox->setEnabled(invalidationSupported); - invalidationBox->blockSignals(false); - - bool localSaves = currentProfile->localSavesEnabled(); - transferButton->setEnabled(localSaves); - // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves - // were disabled and delete the files/rename the dir - localSavesBox->blockSignals(true); - localSavesBox->setChecked(localSaves); - localSavesBox->blockSignals(false); - - copyButton->setEnabled(true); - removeButton->setEnabled(true); - renameButton->setEnabled(true); - } catch (const std::exception& E) { - reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); - copyButton->setEnabled(false); - removeButton->setEnabled(false); - renameButton->setEnabled(false); - invalidationBox->setChecked(false); - } - } else { - invalidationBox->setChecked(false); - copyButton->setEnabled(false); - removeButton->setEnabled(false); - renameButton->setEnabled(false); - } -} - -void ProfilesDialog::on_localSavesBox_stateChanged(int state) -{ - Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); - - if (currentProfile->enableLocalSaves(state == Qt::Checked)) { - ui->transferButton->setEnabled(state == Qt::Checked); - } else { - // revert checkbox-state - ui->localSavesBox->setChecked(state != Qt::Checked); - } -} - -void ProfilesDialog::on_transferButton_clicked() -{ - const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); - TransferSavesDialog transferDialog(*currentProfile, this); - transferDialog.exec(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "profilesdialog.h" +#include "ui_profilesdialog.h" +#include "profile.h" +#include "report.h" +#include "utility.h" +#include "transfersavesdialog.h" +#include "profileinputdialog.h" +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + +Q_DECLARE_METATYPE(Profile::Ptr) + + +ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) + : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) +{ + ui->setupUi(this); + + QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + m_ProfilesList = findChild("profilesList"); + + QDirIterator profileIter(profilesDir); + + while (profileIter.hasNext()) { + profileIter.next(); + addItem(profileIter.filePath()); + } + + QCheckBox *invalidationBox = findChild("invalidationBox"); + if (!GameInfo::instance().requiresBSAInvalidation()) { + invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); + invalidationBox->setEnabled(false); + } +} + +ProfilesDialog::~ProfilesDialog() +{ + delete ui; +} + +void ProfilesDialog::showEvent(QShowEvent *event) +{ + TutorableDialog::showEvent(event); + + if (m_ProfilesList->count() == 0) { + QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0)); + pos.rx() += m_ProfilesList->width() / 2; + pos.ry() += (m_ProfilesList->height() / 2) - 20; + QWhatsThis::showText(pos, + QObject::tr("Before you can use ModOrganizer, you need to create at least one profile. " + "ATTENTION: Run the game at least once before creating a profile!"), m_ProfilesList); + } +} + +void ProfilesDialog::on_closeButton_clicked() +{ + close(); +} + + +void ProfilesDialog::addItem(const QString &name) +{ + QDir profileDir(name); + QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); + try { + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir)))); + m_FailState = false; + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } +} + + +void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) +{ + try { + QListWidget *profilesList = findChild("profilesList"); + QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings)))); + profilesList->addItem(newItem); + m_FailState = false; + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + + +void ProfilesDialog::createProfile(const QString &name, const Profile &reference) +{ + try { + QListWidget *profilesList = findChild("profilesList"); + QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference)))); + profilesList->addItem(newItem); + m_FailState = false; + } catch (const std::exception&) { + m_FailState = true; + throw; + } +} + + +void ProfilesDialog::on_addProfileButton_clicked() +{ + ProfileInputDialog dialog(this); + bool okClicked = dialog.exec(); + QString name = dialog.getName(); + + if (okClicked && (name.size() > 0)) { + try { + createProfile(name, dialog.getPreferDefaultSettings()); + } catch (const std::exception &e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + } + } +} + +void ProfilesDialog::on_copyProfileButton_clicked() +{ + bool okClicked; + QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), QLineEdit::Normal, QString(), &okClicked); + fixDirectoryName(name); + if (okClicked) { + if (name.size() > 0) { + QListWidget *profilesList = findChild("profilesList"); + + try { + const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + createProfile(name, *currentProfile); + } catch (const std::exception &e) { + reportError(tr("failed to copy profile: %1").arg(e.what())); + } + } else { + QMessageBox::warning(this, tr("Invalid name"), tr("Invalid profile name")); + } + } +} + +void ProfilesDialog::on_removeProfileButton_clicked() +{ + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including local savegames if any)?"), + QMessageBox::Yes | QMessageBox::No); + + if (confirmBox.exec() == QMessageBox::Yes) { + QListWidget *profilesList = findChild("profilesList"); + + Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value(); + QString profilePath; + if (currentProfile.get() == NULL) { + profilePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + "/" + profilesList->currentItem()->text(); + if (QMessageBox::question(this, tr("Profile broken"), + tr("This profile you're about to delete seems to be broken or the path is invalid. " + "I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { + return; + } + } else { + // on destruction, the profile object would write the profile.ini file again, so + // we have to get rid of the it before deleting the directory + profilePath = currentProfile->getPath(); + } + QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); + if (item != NULL) { + delete item; + } + if (!shellDelete(QStringList(profilePath))) { + qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(profilePath), ::GetLastError()); + if (!removeDir(profilePath)) { + qWarning("regular delete failed too"); + } + } + } +} + + +void ProfilesDialog::on_renameButton_clicked() +{ + Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + bool valid = false; + QString name; + + while (!valid) { + bool ok = false; + name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), + QLineEdit::Normal, currentProfile->getName(), + &ok); + valid = fixDirectoryName(name); + if (!ok) { + return; + } + } + + ui->profilesList->currentItem()->setText(name); + currentProfile->rename(name); +} + + +void ProfilesDialog::on_invalidationBox_stateChanged(int state) +{ + QListWidget *profilesList = findChild("profilesList"); + + QListWidgetItem *currentItem = profilesList->currentItem(); + if (currentItem == NULL) { + return; + } + if (!ui->invalidationBox->isEnabled()) { + return; + } + try { + QVariant currentProfileVariant = currentItem->data(Qt::UserRole); + if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { + return; + } + const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value(); + if (state == Qt::Unchecked) { + currentProfile->deactivateInvalidation(); + } else { + currentProfile->activateInvalidation(m_GamePath + "/data"); + } + } catch (const std::exception &e) { + reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); + } +} + + +void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) +{ + QCheckBox *invalidationBox = findChild("invalidationBox"); + QCheckBox *localSavesBox = findChild("localSavesBox"); + QPushButton *copyButton = findChild("copyProfileButton"); + QPushButton *removeButton = findChild("removeProfileButton"); + QPushButton *transferButton = findChild("transferButton"); + QPushButton *renameButton = findChild("renameButton"); + + if (current != NULL) { + if (!current->data(Qt::UserRole).isValid()) return; + const Profile::Ptr currentProfile = current->data(Qt::UserRole).value(); + + try { + bool invalidationSupported = false; + invalidationBox->blockSignals(true); + invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); + invalidationBox->setEnabled(invalidationSupported); + invalidationBox->blockSignals(false); + + bool localSaves = currentProfile->localSavesEnabled(); + transferButton->setEnabled(localSaves); + // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves + // were disabled and delete the files/rename the dir + localSavesBox->blockSignals(true); + localSavesBox->setChecked(localSaves); + localSavesBox->blockSignals(false); + + copyButton->setEnabled(true); + removeButton->setEnabled(true); + renameButton->setEnabled(true); + } catch (const std::exception& E) { + reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); + copyButton->setEnabled(false); + removeButton->setEnabled(false); + renameButton->setEnabled(false); + invalidationBox->setChecked(false); + } + } else { + invalidationBox->setChecked(false); + copyButton->setEnabled(false); + removeButton->setEnabled(false); + renameButton->setEnabled(false); + } +} + +void ProfilesDialog::on_localSavesBox_stateChanged(int state) +{ + Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); + + if (currentProfile->enableLocalSaves(state == Qt::Checked)) { + ui->transferButton->setEnabled(state == Qt::Checked); + } else { + // revert checkbox-state + ui->localSavesBox->setChecked(state != Qt::Checked); + } +} + +void ProfilesDialog::on_transferButton_clicked() +{ + const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value(); + TransferSavesDialog transferDialog(*currentProfile, this); + transferDialog.exec(); +} diff --git a/src/profilesdialog.h b/src/profilesdialog.h index f864a7e0..905d49b1 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -1,96 +1,96 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef PROFILESDIALOG_H -#define PROFILESDIALOG_H - -#include "tutorabledialog.h" -#include -#include "profile.h" - - -namespace Ui { - class ProfilesDialog; -} - - -/** - * @brief Dialog that can be used to create/delete/modify profiles - **/ -class ProfilesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param gamePath the path to the game directory - * @param parent parent widget - * @todo the game path could be retrieved from GameInfo just as easily - **/ - explicit ProfilesDialog(const QString &gamePath, QWidget *parent = 0); - ~ProfilesDialog(); - - /** - * @return true if creation of a new profile failed - * @todo the notion of a fail state makes little sense in the current dialog - **/ - bool failed() const { return m_FailState; } - -protected: - - virtual void showEvent(QShowEvent *event); - -private: - - void addItem(const QString &name); - void createProfile(const QString &name, bool useDefaultSettings); - void createProfile(const QString &name, const Profile &reference); - -private slots: - - void on_closeButton_clicked(); - - void on_addProfileButton_clicked(); - - void on_invalidationBox_stateChanged(int arg1); - - void on_copyProfileButton_clicked(); - - void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void on_removeProfileButton_clicked(); - - void on_localSavesBox_stateChanged(int arg1); - - void on_transferButton_clicked(); - - void on_renameButton_clicked(); - -private: - Ui::ProfilesDialog *ui; - QString m_GamePath; - QListWidget *m_ProfilesList; - bool m_FailState; - -}; - -#endif // PROFILESDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef PROFILESDIALOG_H +#define PROFILESDIALOG_H + +#include "tutorabledialog.h" +#include +#include "profile.h" + + +namespace Ui { + class ProfilesDialog; +} + + +/** + * @brief Dialog that can be used to create/delete/modify profiles + **/ +class ProfilesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param gamePath the path to the game directory + * @param parent parent widget + * @todo the game path could be retrieved from GameInfo just as easily + **/ + explicit ProfilesDialog(const QString &gamePath, QWidget *parent = 0); + ~ProfilesDialog(); + + /** + * @return true if creation of a new profile failed + * @todo the notion of a fail state makes little sense in the current dialog + **/ + bool failed() const { return m_FailState; } + +protected: + + virtual void showEvent(QShowEvent *event); + +private: + + void addItem(const QString &name); + void createProfile(const QString &name, bool useDefaultSettings); + void createProfile(const QString &name, const Profile &reference); + +private slots: + + void on_closeButton_clicked(); + + void on_addProfileButton_clicked(); + + void on_invalidationBox_stateChanged(int arg1); + + void on_copyProfileButton_clicked(); + + void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + + void on_removeProfileButton_clicked(); + + void on_localSavesBox_stateChanged(int arg1); + + void on_transferButton_clicked(); + + void on_renameButton_clicked(); + +private: + Ui::ProfilesDialog *ui; + QString m_GamePath; + QListWidget *m_ProfilesList; + bool m_FailState; + +}; + +#endif // PROFILESDIALOG_H diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp index df604f5e..1d7aea0a 100644 --- a/src/queryoverwritedialog.cpp +++ b/src/queryoverwritedialog.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #include "queryoverwritedialog.h" #include "ui_queryoverwritedialog.h" diff --git a/src/queryoverwritedialog.h b/src/queryoverwritedialog.h index e6821c38..1a22a60e 100644 --- a/src/queryoverwritedialog.h +++ b/src/queryoverwritedialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef QUERYOVERWRITEDIALOG_H #define QUERYOVERWRITEDIALOG_H diff --git a/src/report.cpp b/src/report.cpp index ca7e1dc9..5cf36078 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1,53 +1,53 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "report.h" -#include "utility.h" -#include -#include -#include - - -using namespace MOBase; - - -void reportError(QString message) -{ - if (QApplication::topLevelWidgets().count() != 0) { - QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok); - messageBox.exec(); - } else { - ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK); - } -} - - -std::tstring toTString(const QString& source) -{ -#ifdef UNICODE - wchar_t* temp = new wchar_t[source.size() + 1]; - source.toWCharArray(temp); - temp[source.size()] = '\0'; - std::tstring result(temp); - delete[] temp; - return result; -#else // UNICODE - return source.toAscii(); -#endif // UNICODE -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "report.h" +#include "utility.h" +#include +#include +#include + + +using namespace MOBase; + + +void reportError(QString message) +{ + if (QApplication::topLevelWidgets().count() != 0) { + QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok); + messageBox.exec(); + } else { + ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK); + } +} + + +std::tstring toTString(const QString& source) +{ +#ifdef UNICODE + wchar_t* temp = new wchar_t[source.size() + 1]; + source.toWCharArray(temp); + temp[source.size()] = '\0'; + std::tstring result(temp); + delete[] temp; + return result; +#else // UNICODE + return source.toAscii(); +#endif // UNICODE +} diff --git a/src/report.h b/src/report.h index 97b8b607..b319f9a0 100644 --- a/src/report.h +++ b/src/report.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef REPORT_H #define REPORT_H diff --git a/src/savegame.cpp b/src/savegame.cpp index 06e028e2..1cdabb2d 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -1,106 +1,106 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "savegame.h" -#include -#include -#include -#include -#include -#include -#include -#include "gameinfo.h" - - -SaveGame::SaveGame(QObject *parent) - : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(QObject *parent, const QString &filename) - : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(const SaveGame& reference) - : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel), - m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber), - m_Screenshot(reference.m_Screenshot) -{ -} - - -SaveGame& SaveGame::operator=(const SaveGame &reference) -{ - if (&reference != this) { - m_FileName = reference.m_FileName; - m_PCName = reference.m_PCName; - m_PCLevel = reference.m_PCLevel; - m_PCLocation = reference.m_PCLocation; - m_SaveNumber = reference.m_SaveNumber; - m_Screenshot = reference.m_Screenshot; - } - return *this; -} - - -SaveGame::~SaveGame() -{ -} - -QStringList SaveGame::attachedFiles() const -{ - QStringList result; - foreach (const std::wstring &ext, MOShared::GameInfo::instance().getSavegameAttachmentExtensions()) { - QFileInfo fi(fileName()); - fi.setFile(fi.canonicalPath() + "/" + fi.completeBaseName() + "." + MOBase::ToQString(ext)); - if (fi.exists()) { - result.append(fi.filePath()); - } - } - - return result; -} - -QStringList SaveGame::saveFiles() const -{ - QStringList result = attachedFiles(); - result.append(fileName()); - return result; -} - - -void SaveGame::setCreationTime(const QString &fileName) -{ - QFileInfo creationTime(fileName); - QDateTime modified = creationTime.lastModified(); - memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); - - m_CreationTime.wDay = static_cast(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast(modified.date().month()); - m_CreationTime.wYear =static_cast( modified.date().year()); - - m_CreationTime.wHour = static_cast(modified.time().hour()); - m_CreationTime.wMinute = static_cast(modified.time().minute()); - m_CreationTime.wSecond = static_cast(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "savegame.h" +#include +#include +#include +#include +#include +#include +#include +#include "gameinfo.h" + + +SaveGame::SaveGame(QObject *parent) + : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() +{ +} + + +SaveGame::SaveGame(QObject *parent, const QString &filename) + : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() +{ +} + + +SaveGame::SaveGame(const SaveGame& reference) + : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel), + m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber), + m_Screenshot(reference.m_Screenshot) +{ +} + + +SaveGame& SaveGame::operator=(const SaveGame &reference) +{ + if (&reference != this) { + m_FileName = reference.m_FileName; + m_PCName = reference.m_PCName; + m_PCLevel = reference.m_PCLevel; + m_PCLocation = reference.m_PCLocation; + m_SaveNumber = reference.m_SaveNumber; + m_Screenshot = reference.m_Screenshot; + } + return *this; +} + + +SaveGame::~SaveGame() +{ +} + +QStringList SaveGame::attachedFiles() const +{ + QStringList result; + foreach (const std::wstring &ext, MOShared::GameInfo::instance().getSavegameAttachmentExtensions()) { + QFileInfo fi(fileName()); + fi.setFile(fi.canonicalPath() + "/" + fi.completeBaseName() + "." + MOBase::ToQString(ext)); + if (fi.exists()) { + result.append(fi.filePath()); + } + } + + return result; +} + +QStringList SaveGame::saveFiles() const +{ + QStringList result = attachedFiles(); + result.append(fileName()); + return result; +} + + +void SaveGame::setCreationTime(const QString &fileName) +{ + QFileInfo creationTime(fileName); + QDateTime modified = creationTime.lastModified(); + memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); + + m_CreationTime.wDay = static_cast(modified.date().day()); + m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); + m_CreationTime.wMonth = static_cast(modified.date().month()); + m_CreationTime.wYear =static_cast( modified.date().year()); + + m_CreationTime.wHour = static_cast(modified.time().hour()); + m_CreationTime.wMinute = static_cast(modified.time().minute()); + m_CreationTime.wSecond = static_cast(modified.time().second()); + m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); +} diff --git a/src/savegame.h b/src/savegame.h index 9a5f7036..1fd2f7ab 100644 --- a/src/savegame.h +++ b/src/savegame.h @@ -1,131 +1,131 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SAVEGAME_H -#define SAVEGAME_H - - -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - - -/** - * @brief represents a single save game - **/ -class SaveGame : public QObject { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGame(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGame(QObject *parent, const QString &filename); - - SaveGame(const SaveGame& reference); - - SaveGame& operator=(const SaveGame &reference); - - ~SaveGame(); - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString) { } - - /** - * @return filename of this savegame - */ - const QString &fileName() const { return m_FileName; } - - /** - * @return a list of additional files that belong to this savegame - */ - virtual QStringList attachedFiles() const; - - /** - * @return a list of all files that belong to this savegame - */ - virtual QStringList saveFiles() const; - - /** - * @return name of the player character - **/ - const QString &pcName() const { return m_PCName; } - - /** - * @return level of the player character - **/ - unsigned short pcLevel() const { return m_PCLevel; } - - /** - * @return location of the player character - **/ - const QString &pcLocation() const { return m_PCLocation; } - - /** - * @return index of the save game - **/ - unsigned long saveNumber() const { return m_SaveNumber; } - - /** - * @return creation time of the save game - **/ - SYSTEMTIME creationTime() const { return m_CreationTime; } - - /** - * @return screenshot in the savegame - **/ - const QImage &screenshot() const { return m_Screenshot; } - -private: - - void setCreationTime(const QString &fileName); - -protected: - - QString m_FileName; - QString m_PCName; - unsigned short m_PCLevel; - QString m_PCLocation; - unsigned long m_SaveNumber; - SYSTEMTIME m_CreationTime; - QImage m_Screenshot; - -}; - - -#endif // SAVEGAME_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SAVEGAME_H +#define SAVEGAME_H + + +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + + +/** + * @brief represents a single save game + **/ +class SaveGame : public QObject { + +Q_OBJECT + +public: + + /** + * @brief construct an empty object + **/ + SaveGame(QObject *parent = 0); + + /** + * @brief construct a save game and immediately read out information from the file + * + * @param filename absolute path of the save game file + **/ + SaveGame(QObject *parent, const QString &filename); + + SaveGame(const SaveGame& reference); + + SaveGame& operator=(const SaveGame &reference); + + ~SaveGame(); + + /** + * @brief read out information from a savegame + * + * @param fileName absolute path of the save game file + **/ + virtual void readFile(const QString) { } + + /** + * @return filename of this savegame + */ + const QString &fileName() const { return m_FileName; } + + /** + * @return a list of additional files that belong to this savegame + */ + virtual QStringList attachedFiles() const; + + /** + * @return a list of all files that belong to this savegame + */ + virtual QStringList saveFiles() const; + + /** + * @return name of the player character + **/ + const QString &pcName() const { return m_PCName; } + + /** + * @return level of the player character + **/ + unsigned short pcLevel() const { return m_PCLevel; } + + /** + * @return location of the player character + **/ + const QString &pcLocation() const { return m_PCLocation; } + + /** + * @return index of the save game + **/ + unsigned long saveNumber() const { return m_SaveNumber; } + + /** + * @return creation time of the save game + **/ + SYSTEMTIME creationTime() const { return m_CreationTime; } + + /** + * @return screenshot in the savegame + **/ + const QImage &screenshot() const { return m_Screenshot; } + +private: + + void setCreationTime(const QString &fileName); + +protected: + + QString m_FileName; + QString m_PCName; + unsigned short m_PCLevel; + QString m_PCLocation; + unsigned long m_SaveNumber; + SYSTEMTIME m_CreationTime; + QImage m_Screenshot; + +}; + + +#endif // SAVEGAME_H diff --git a/src/savegamegamebryo.cpp b/src/savegamegamebryo.cpp index bf113254..f593ef5a 100644 --- a/src/savegamegamebryo.cpp +++ b/src/savegamegamebryo.cpp @@ -1,338 +1,338 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "savegamegamebyro.h" -#include -#include -#include -#include "gameinfo.h" -#include -#include -#include - - -using namespace MOShared; - - -template -void FileRead(QFile &file, T &value) -{ - int read = file.read(reinterpret_cast(&value), sizeof(T)); - if (read != sizeof(T)) { - throw std::runtime_error("unexpected end of file"); - } -} - - -template -void FileSkip(QFile &file, int count = 1) -{ - char ignore[sizeof(T)]; - for (int i = 0; i < count; ++i) { - if (file.read(ignore, sizeof(T)) != sizeof(T)) { - throw std::runtime_error("unexpected end of file"); - } - } -} - - -QString ReadBString(QFile &file) -{ - char buffer[256]; - file.read(buffer, 1); // size including zero termination - unsigned char size = buffer[0]; - file.read(buffer, size); - return QString::fromLatin1(buffer, size); -} - - -QString ReadFOSString(QFile &file, bool delimiter) -{ - union { - char lengthBuffer[2]; - unsigned short length; - }; - - file.read(lengthBuffer, 2); - if (delimiter) { - FileSkip(file); // 0x7c - } - char *buffer = new char[length]; - file.read(buffer, length); - - QString result = QString::fromLatin1(buffer, length); - delete [] buffer; - - return result; -} - - -SaveGameGamebryo::SaveGameGamebryo(QObject *parent) - : SaveGame(parent), m_Plugins() -{ -} - - -SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName) - : SaveGame(parent, fileName), m_Plugins() -{ - readFile(fileName); -} - - -SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference) - : SaveGame(reference), m_Plugins(reference.m_Plugins) -{ -} - - -SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference) -{ - if (&reference != this) { - SaveGame::operator =(reference); - m_Plugins = reference.m_Plugins; - } - return *this; -} - - -SaveGameGamebryo::~SaveGameGamebryo() -{ -} - - - - - -void SaveGameGamebryo::readSkyrimFile(QFile &saveFile) -{ - char fileID[14]; - - saveFile.read(fileID, 13); - fileID[13] = '\0'; - if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - FileSkip(saveFile); // header size - FileSkip(saveFile); // header version, -> 8 - FileRead(saveFile, m_SaveNumber); - - m_PCName = ReadFOSString(saveFile, false); - - unsigned long temp; - FileRead(saveFile, temp); // player level - m_PCLevel = static_cast(temp); - - m_PCLocation = ReadFOSString(saveFile, false); - ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss - ReadFOSString(saveFile, false); // race name (i.e. BretonRace) - - - FileSkip(saveFile); // ??? - FileSkip(saveFile, 2); // ??? - FileSkip(saveFile, 8); // filetime - -// FileSkip(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe - - unsigned long width, height; - FileRead(saveFile, width); // 320 - FileRead(saveFile, height); // 192 - - QScopedArrayPointer buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); - - FileSkip(saveFile); // form version - FileSkip(saveFile); // plugin info size - - unsigned char pluginCount; - FileRead(saveFile, pluginCount); - - for (int i = 0; i < pluginCount; ++i) { - m_Plugins.push_back(ReadFOSString(saveFile, false)); - } -} - - -void SaveGameGamebryo::readESSFile(QFile &saveFile) -{ - char fileID[13]; - unsigned char versionMinor; - unsigned long headerVersion, saveHeaderSize; -// *** format is different for fallout! - saveFile.read(fileID, 12); - fileID[12] = '\0'; - FileSkip(saveFile); FileRead(saveFile, versionMinor); - FileSkip(saveFile); // modified time - FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize); - - if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - FileRead(saveFile, m_SaveNumber); - - m_PCName = ReadBString(saveFile); - - FileRead(saveFile, m_PCLevel); - m_PCLocation = ReadBString(saveFile); - FileSkip(saveFile); // game days - FileSkip(saveFile); // game ticks - FileRead(saveFile, m_CreationTime); - - unsigned long size; - FileRead(saveFile, size); // screenshot size - - unsigned long width, height; - FileRead(saveFile, width); FileRead(saveFile, height); - QScopedArrayPointer buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); - - unsigned char pluginCount; - FileRead(saveFile, pluginCount); - - for (int i = 0; i < pluginCount; ++i) { - QString name = ReadBString(saveFile); - m_Plugins.push_back(name); - } -} - - -void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas) -{ - char fileID[13]; - saveFile.read(fileID, 12); - // the signature is only 11 characters, the 12th is random? - fileID[11] = '\0'; - - if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - char ignore = 0x00; - while (ignore != 0x7c) { - FileRead(saveFile, ignore); // unknown - } - if (newVegas) { - ignore = 0x00; - // in new vegas there is another block of uninteresting (?) information - FileSkip(saveFile); // 0x7c - while (ignore != 0x7c) { - FileRead(saveFile, ignore); // unknown - } - } - - unsigned long width, height; - FileRead(saveFile, width); - FileSkip(saveFile); // 0x7c - FileRead(saveFile, height); - FileSkip(saveFile); // 0x7c - - FileRead(saveFile, m_SaveNumber); - FileSkip(saveFile); // 0x7c - - m_PCName = ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - long Level; - FileRead(saveFile, Level); - m_PCLevel = Level; - FileSkip(saveFile); // 0x7c - - m_PCLocation = ReadFOSString(saveFile, true); - FileSkip(saveFile); // 0x7c - - ReadFOSString(saveFile, true); // playtime - - FileSkip(saveFile); - - QScopedArrayPointer buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256); - - FileSkip(saveFile, 5); // unknown - - unsigned char pluginCount = 0; - FileRead(saveFile, pluginCount); - FileSkip(saveFile); // 0x7c - - for (int i = 0; i < pluginCount; ++i) { - QString name = ReadFOSString(saveFile, true); - m_Plugins.push_back(name); - FileSkip(saveFile); // 0x7c - } -} - - -void SaveGameGamebryo::setCreationTime(const QString &fileName) -{ - QFileInfo creationTime(fileName); - QDateTime modified = creationTime.lastModified(); - memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); - - m_CreationTime.wDay = static_cast(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast(modified.date().month()); - m_CreationTime.wYear =static_cast( modified.date().year()); - - m_CreationTime.wHour = static_cast(modified.time().hour()); - m_CreationTime.wMinute = static_cast(modified.time().minute()); - m_CreationTime.wSecond = static_cast(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); -} - - -void SaveGameGamebryo::readFile(const QString &fileName) -{ - m_FileName = fileName; - QFile saveFile(fileName); - if (!saveFile.open(QIODevice::ReadOnly)) { - throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData()); - } - switch (GameInfo::instance().getType()) { - case GameInfo::TYPE_FALLOUT3: { - setCreationTime(fileName); - readFOSFile(saveFile, false); - } break; - case GameInfo::TYPE_FALLOUTNV: { - setCreationTime(fileName); - readFOSFile(saveFile, true); - } break; - case GameInfo::TYPE_OBLIVION: { - readESSFile(saveFile); - } break; - case GameInfo::TYPE_SKYRIM: { - setCreationTime(fileName); - readSkyrimFile(saveFile); - } break; - } - - saveFile.close(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "savegamegamebyro.h" +#include +#include +#include +#include "gameinfo.h" +#include +#include +#include + + +using namespace MOShared; + + +template +void FileRead(QFile &file, T &value) +{ + int read = file.read(reinterpret_cast(&value), sizeof(T)); + if (read != sizeof(T)) { + throw std::runtime_error("unexpected end of file"); + } +} + + +template +void FileSkip(QFile &file, int count = 1) +{ + char ignore[sizeof(T)]; + for (int i = 0; i < count; ++i) { + if (file.read(ignore, sizeof(T)) != sizeof(T)) { + throw std::runtime_error("unexpected end of file"); + } + } +} + + +QString ReadBString(QFile &file) +{ + char buffer[256]; + file.read(buffer, 1); // size including zero termination + unsigned char size = buffer[0]; + file.read(buffer, size); + return QString::fromLatin1(buffer, size); +} + + +QString ReadFOSString(QFile &file, bool delimiter) +{ + union { + char lengthBuffer[2]; + unsigned short length; + }; + + file.read(lengthBuffer, 2); + if (delimiter) { + FileSkip(file); // 0x7c + } + char *buffer = new char[length]; + file.read(buffer, length); + + QString result = QString::fromLatin1(buffer, length); + delete [] buffer; + + return result; +} + + +SaveGameGamebryo::SaveGameGamebryo(QObject *parent) + : SaveGame(parent), m_Plugins() +{ +} + + +SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName) + : SaveGame(parent, fileName), m_Plugins() +{ + readFile(fileName); +} + + +SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference) + : SaveGame(reference), m_Plugins(reference.m_Plugins) +{ +} + + +SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference) +{ + if (&reference != this) { + SaveGame::operator =(reference); + m_Plugins = reference.m_Plugins; + } + return *this; +} + + +SaveGameGamebryo::~SaveGameGamebryo() +{ +} + + + + + +void SaveGameGamebryo::readSkyrimFile(QFile &saveFile) +{ + char fileID[14]; + + saveFile.read(fileID, 13); + fileID[13] = '\0'; + if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + FileSkip(saveFile); // header size + FileSkip(saveFile); // header version, -> 8 + FileRead(saveFile, m_SaveNumber); + + m_PCName = ReadFOSString(saveFile, false); + + unsigned long temp; + FileRead(saveFile, temp); // player level + m_PCLevel = static_cast(temp); + + m_PCLocation = ReadFOSString(saveFile, false); + ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss + ReadFOSString(saveFile, false); // race name (i.e. BretonRace) + + + FileSkip(saveFile); // ??? + FileSkip(saveFile, 2); // ??? + FileSkip(saveFile, 8); // filetime + +// FileSkip(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe + + unsigned long width, height; + FileRead(saveFile, width); // 320 + FileRead(saveFile, height); // 192 + + QScopedArrayPointer buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); + + FileSkip(saveFile); // form version + FileSkip(saveFile); // plugin info size + + unsigned char pluginCount; + FileRead(saveFile, pluginCount); + + for (int i = 0; i < pluginCount; ++i) { + m_Plugins.push_back(ReadFOSString(saveFile, false)); + } +} + + +void SaveGameGamebryo::readESSFile(QFile &saveFile) +{ + char fileID[13]; + unsigned char versionMinor; + unsigned long headerVersion, saveHeaderSize; +// *** format is different for fallout! + saveFile.read(fileID, 12); + fileID[12] = '\0'; + FileSkip(saveFile); FileRead(saveFile, versionMinor); + FileSkip(saveFile); // modified time + FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize); + + if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + FileRead(saveFile, m_SaveNumber); + + m_PCName = ReadBString(saveFile); + + FileRead(saveFile, m_PCLevel); + m_PCLocation = ReadBString(saveFile); + FileSkip(saveFile); // game days + FileSkip(saveFile); // game ticks + FileRead(saveFile, m_CreationTime); + + unsigned long size; + FileRead(saveFile, size); // screenshot size + + unsigned long width, height; + FileRead(saveFile, width); FileRead(saveFile, height); + QScopedArrayPointer buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); + + unsigned char pluginCount; + FileRead(saveFile, pluginCount); + + for (int i = 0; i < pluginCount; ++i) { + QString name = ReadBString(saveFile); + m_Plugins.push_back(name); + } +} + + +void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas) +{ + char fileID[13]; + saveFile.read(fileID, 12); + // the signature is only 11 characters, the 12th is random? + fileID[11] = '\0'; + + if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) { + throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); + } + + char ignore = 0x00; + while (ignore != 0x7c) { + FileRead(saveFile, ignore); // unknown + } + if (newVegas) { + ignore = 0x00; + // in new vegas there is another block of uninteresting (?) information + FileSkip(saveFile); // 0x7c + while (ignore != 0x7c) { + FileRead(saveFile, ignore); // unknown + } + } + + unsigned long width, height; + FileRead(saveFile, width); + FileSkip(saveFile); // 0x7c + FileRead(saveFile, height); + FileSkip(saveFile); // 0x7c + + FileRead(saveFile, m_SaveNumber); + FileSkip(saveFile); // 0x7c + + m_PCName = ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + long Level; + FileRead(saveFile, Level); + m_PCLevel = Level; + FileSkip(saveFile); // 0x7c + + m_PCLocation = ReadFOSString(saveFile, true); + FileSkip(saveFile); // 0x7c + + ReadFOSString(saveFile, true); // playtime + + FileSkip(saveFile); + + QScopedArrayPointer buffer(new unsigned char[width * height * 3]); + saveFile.read(reinterpret_cast(buffer.data()), width * height * 3); + // why do I have to copy here? without the copy, the buffer seems to get deleted after the + // temporary vanishes, but Qts implicit sharing should handle that? + m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256); + + FileSkip(saveFile, 5); // unknown + + unsigned char pluginCount = 0; + FileRead(saveFile, pluginCount); + FileSkip(saveFile); // 0x7c + + for (int i = 0; i < pluginCount; ++i) { + QString name = ReadFOSString(saveFile, true); + m_Plugins.push_back(name); + FileSkip(saveFile); // 0x7c + } +} + + +void SaveGameGamebryo::setCreationTime(const QString &fileName) +{ + QFileInfo creationTime(fileName); + QDateTime modified = creationTime.lastModified(); + memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); + + m_CreationTime.wDay = static_cast(modified.date().day()); + m_CreationTime.wDayOfWeek = static_cast(modified.date().dayOfWeek()); + m_CreationTime.wMonth = static_cast(modified.date().month()); + m_CreationTime.wYear =static_cast( modified.date().year()); + + m_CreationTime.wHour = static_cast(modified.time().hour()); + m_CreationTime.wMinute = static_cast(modified.time().minute()); + m_CreationTime.wSecond = static_cast(modified.time().second()); + m_CreationTime.wMilliseconds = static_cast(modified.time().msec()); +} + + +void SaveGameGamebryo::readFile(const QString &fileName) +{ + m_FileName = fileName; + QFile saveFile(fileName); + if (!saveFile.open(QIODevice::ReadOnly)) { + throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData()); + } + switch (GameInfo::instance().getType()) { + case GameInfo::TYPE_FALLOUT3: { + setCreationTime(fileName); + readFOSFile(saveFile, false); + } break; + case GameInfo::TYPE_FALLOUTNV: { + setCreationTime(fileName); + readFOSFile(saveFile, true); + } break; + case GameInfo::TYPE_OBLIVION: { + readESSFile(saveFile); + } break; + case GameInfo::TYPE_SKYRIM: { + setCreationTime(fileName); + readSkyrimFile(saveFile); + } break; + } + + saveFile.close(); +} diff --git a/src/savegamegamebyro.h b/src/savegamegamebyro.h index 70400636..eedda6c2 100644 --- a/src/savegamegamebyro.h +++ b/src/savegamegamebyro.h @@ -1,102 +1,102 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SAVEGAMEGAMEBRYO_H -#define SAVEGAMEGAMEBRYO_H - - -#include "savegame.h" -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include - - -/** - * @brief represents a single save game - **/ -class SaveGameGamebryo : public SaveGame { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGameGamebryo(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGameGamebryo(QObject *parent, const QString &filename); - - - SaveGameGamebryo(const SaveGameGamebryo &reference); - - SaveGameGamebryo &operator=(const SaveGameGamebryo &reference); - - ~SaveGameGamebryo(); - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString &fileName); - - /** - * @return number of plugins that were enabled when the save game was created - **/ - int numPlugins() const { return m_Plugins.size(); } - - /** - * retrieve the name of one of the plugins that were enabled when the save game - * was created. valid indices are in the range between [0, numPlugins()[ - * @param index plugin index - * @return name of the plugin - **/ - const QString &plugin(int index) const { return m_Plugins.at(index); } - - -private: - - void readESSFile(QFile &saveFile); - void readFOSFile(QFile &saveFile, bool newVegas); - void readSkyrimFile(QFile &saveFile); - - void setCreationTime(const QString &fileName); - -private: - - std::vector m_Plugins; - -}; - -Q_DECLARE_METATYPE(SaveGameGamebryo) -Q_DECLARE_METATYPE(SaveGameGamebryo*) - -#endif // SAVEGAMEGAMEBRYO_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SAVEGAMEGAMEBRYO_H +#define SAVEGAMEGAMEBRYO_H + + +#include "savegame.h" +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include + + +/** + * @brief represents a single save game + **/ +class SaveGameGamebryo : public SaveGame { + +Q_OBJECT + +public: + + /** + * @brief construct an empty object + **/ + SaveGameGamebryo(QObject *parent = 0); + + /** + * @brief construct a save game and immediately read out information from the file + * + * @param filename absolute path of the save game file + **/ + SaveGameGamebryo(QObject *parent, const QString &filename); + + + SaveGameGamebryo(const SaveGameGamebryo &reference); + + SaveGameGamebryo &operator=(const SaveGameGamebryo &reference); + + ~SaveGameGamebryo(); + + /** + * @brief read out information from a savegame + * + * @param fileName absolute path of the save game file + **/ + virtual void readFile(const QString &fileName); + + /** + * @return number of plugins that were enabled when the save game was created + **/ + int numPlugins() const { return m_Plugins.size(); } + + /** + * retrieve the name of one of the plugins that were enabled when the save game + * was created. valid indices are in the range between [0, numPlugins()[ + * @param index plugin index + * @return name of the plugin + **/ + const QString &plugin(int index) const { return m_Plugins.at(index); } + + +private: + + void readESSFile(QFile &saveFile); + void readFOSFile(QFile &saveFile, bool newVegas); + void readSkyrimFile(QFile &saveFile); + + void setCreationTime(const QString &fileName); + +private: + + std::vector m_Plugins; + +}; + +Q_DECLARE_METATYPE(SaveGameGamebryo) +Q_DECLARE_METATYPE(SaveGameGamebryo*) + +#endif // SAVEGAMEGAMEBRYO_H diff --git a/src/savegameinfowidget.cpp b/src/savegameinfowidget.cpp index f07a9694..093b794c 100644 --- a/src/savegameinfowidget.cpp +++ b/src/savegameinfowidget.cpp @@ -1,62 +1,62 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "savegameinfowidget.h" -#include "ui_savegameinfowidget.h" -#include "utility.h" -#include - - -SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent) - : QWidget(parent), ui(new Ui::SaveGameInfoWidget) -{ - ui->setupUi(this); - this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget); - setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0)); -// installEventFilter(this); -} - -SaveGameInfoWidget::~SaveGameInfoWidget() -{ - delete ui; -} - - -void SaveGameInfoWidget::setSave(const SaveGame *saveGame) -{ - ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber())); - ui->characterLabel->setText(saveGame->pcName()); - ui->locationLabel->setText(saveGame->pcLocation()); - ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel())); - ui->dateLabel->setText(MOBase::ToString(saveGame->creationTime())); - ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot())); - if (ui->gameFrame->layout() != NULL) { - QLayoutItem *item = NULL; - while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) { - delete item->widget(); - delete item; - } - ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize); - } -} - -QFrame *SaveGameInfoWidget::getGameFrame() -{ - return ui->gameFrame; -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "savegameinfowidget.h" +#include "ui_savegameinfowidget.h" +#include "utility.h" +#include + + +SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent) + : QWidget(parent), ui(new Ui::SaveGameInfoWidget) +{ + ui->setupUi(this); + this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget); + setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0)); +// installEventFilter(this); +} + +SaveGameInfoWidget::~SaveGameInfoWidget() +{ + delete ui; +} + + +void SaveGameInfoWidget::setSave(const SaveGame *saveGame) +{ + ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber())); + ui->characterLabel->setText(saveGame->pcName()); + ui->locationLabel->setText(saveGame->pcLocation()); + ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel())); + ui->dateLabel->setText(MOBase::ToString(saveGame->creationTime())); + ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot())); + if (ui->gameFrame->layout() != NULL) { + QLayoutItem *item = NULL; + while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) { + delete item->widget(); + delete item; + } + ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize); + } +} + +QFrame *SaveGameInfoWidget::getGameFrame() +{ + return ui->gameFrame; +} diff --git a/src/savegameinfowidget.h b/src/savegameinfowidget.h index 318108c4..d1cd2b98 100644 --- a/src/savegameinfowidget.h +++ b/src/savegameinfowidget.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef SAVEGAMEINFOWIDGET_H #define SAVEGAMEINFOWIDGET_H diff --git a/src/savegameinfowidgetgamebryo.cpp b/src/savegameinfowidgetgamebryo.cpp index deaa3e01..8a7cf1ba 100644 --- a/src/savegameinfowidgetgamebryo.cpp +++ b/src/savegameinfowidgetgamebryo.cpp @@ -1,73 +1,73 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "savegameinfowidgetgamebryo.h" -#include -#include - - -SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent) - : SaveGameInfoWidget(parent), m_PluginList(pluginList) -{ - QVBoxLayout *gameLayout = new QVBoxLayout(); - gameLayout->setMargin(0); - gameLayout->setSpacing(2); - getGameFrame()->setLayout(gameLayout); - setSave(saveGame); -} - - -void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame) -{ - SaveGameInfoWidget::setSave(saveGame); - const SaveGameGamebryo *gamebryoSave = qobject_cast(saveGame); - QLayout *layout = getGameFrame()->layout(); - QLabel *header = new QLabel(tr("Missing ESPs")); - QFont headerFont = header->font(); - QFont contentFont = headerFont; - headerFont.setItalic(true); - contentFont.setBold(true); - contentFont.setPointSize(7); - header->setFont(headerFont); - layout->addWidget(header); - int count = 0; - for (int i = 0; i < gamebryoSave->numPlugins(); ++i) { - const QString &pluginName = gamebryoSave->plugin(i); - if (m_PluginList->isEnabled(pluginName)) { - continue; - } else { - ++count; - } - - if (count > 10) { - break; - } - - QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i)); - pluginLabel->setIndent(10); - pluginLabel->setFont(contentFont); - layout->addWidget(pluginLabel); - } - if (count > 10) { - QLabel *dotDotLabel = new QLabel("..."); - dotDotLabel->setIndent(10); - dotDotLabel->setFont(contentFont); - layout->addWidget(dotDotLabel); - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "savegameinfowidgetgamebryo.h" +#include +#include + + +SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent) + : SaveGameInfoWidget(parent), m_PluginList(pluginList) +{ + QVBoxLayout *gameLayout = new QVBoxLayout(); + gameLayout->setMargin(0); + gameLayout->setSpacing(2); + getGameFrame()->setLayout(gameLayout); + setSave(saveGame); +} + + +void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame) +{ + SaveGameInfoWidget::setSave(saveGame); + const SaveGameGamebryo *gamebryoSave = qobject_cast(saveGame); + QLayout *layout = getGameFrame()->layout(); + QLabel *header = new QLabel(tr("Missing ESPs")); + QFont headerFont = header->font(); + QFont contentFont = headerFont; + headerFont.setItalic(true); + contentFont.setBold(true); + contentFont.setPointSize(7); + header->setFont(headerFont); + layout->addWidget(header); + int count = 0; + for (int i = 0; i < gamebryoSave->numPlugins(); ++i) { + const QString &pluginName = gamebryoSave->plugin(i); + if (m_PluginList->isEnabled(pluginName)) { + continue; + } else { + ++count; + } + + if (count > 10) { + break; + } + + QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i)); + pluginLabel->setIndent(10); + pluginLabel->setFont(contentFont); + layout->addWidget(pluginLabel); + } + if (count > 10) { + QLabel *dotDotLabel = new QLabel("..."); + dotDotLabel->setIndent(10); + dotDotLabel->setFont(contentFont); + layout->addWidget(dotDotLabel); + } +} diff --git a/src/savegameinfowidgetgamebryo.h b/src/savegameinfowidgetgamebryo.h index b60de8ea..1f5fd7ee 100644 --- a/src/savegameinfowidgetgamebryo.h +++ b/src/savegameinfowidgetgamebryo.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef SAVEGAMEINFOWIDGETGAMEBRYO_H #define SAVEGAMEINFOWIDGETGAMEBRYO_H diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 902b4d9c..b4df8a1f 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -1,83 +1,83 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "selectiondialog.h" -#include "ui_selectiondialog.h" - -#include - -SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) - : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) -{ - ui->setupUi(this); - - ui->descriptionLabel->setText(description); -} - -SelectionDialog::~SelectionDialog() -{ - delete ui; -} - - -void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) -{ - QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - -int SelectionDialog::numChoices() const -{ - return ui->buttonBox->findChildren(QString()).count(); -} - - -QVariant SelectionDialog::getChoiceData() -{ - return m_Choice->property("data"); -} - - -QString SelectionDialog::getChoiceString() -{ - if ((m_Choice == NULL) || - (m_ValidateByData && !m_Choice->property("data").isValid())) { - return QString(); - } else { - return m_Choice->text(); - } -} - - -void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) -{ - m_Choice = button; - if (!m_ValidateByData || m_Choice->property("data").isValid()) { - this->accept(); - } else { - this->reject(); - } -} - -void SelectionDialog::on_cancelButton_clicked() -{ - this->reject(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "selectiondialog.h" +#include "ui_selectiondialog.h" + +#include + +SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) + : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) +{ + ui->setupUi(this); + + ui->descriptionLabel->setText(description); +} + +SelectionDialog::~SelectionDialog() +{ + delete ui; +} + + +void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) +{ + QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); + button->setProperty("data", data); + ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); + if (data.isValid()) m_ValidateByData = true; +} + +int SelectionDialog::numChoices() const +{ + return ui->buttonBox->findChildren(QString()).count(); +} + + +QVariant SelectionDialog::getChoiceData() +{ + return m_Choice->property("data"); +} + + +QString SelectionDialog::getChoiceString() +{ + if ((m_Choice == NULL) || + (m_ValidateByData && !m_Choice->property("data").isValid())) { + return QString(); + } else { + return m_Choice->text(); + } +} + + +void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) +{ + m_Choice = button; + if (!m_ValidateByData || m_Choice->property("data").isValid()) { + this->accept(); + } else { + this->reject(); + } +} + +void SelectionDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/selectiondialog.h b/src/selectiondialog.h index fc04291e..fe148859 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -1,68 +1,68 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include -#include - -namespace Ui { -class SelectionDialog; -} - -class SelectionDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit SelectionDialog(const QString &description, QWidget *parent = 0); - - ~SelectionDialog(); - - /** - * @brief add a choice to the dialog - * @param buttonText the text to be displayed on the button - * @param description the description that shows up under in small letters inside the button - * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) - * all buttons that contain no data will be treated as "cancel" buttons - */ - void addChoice(const QString &buttonText, const QString &description, const QVariant &data); - - int numChoices() const; - - QVariant getChoiceData(); - QString getChoiceString(); - -private slots: - - void on_buttonBox_clicked(QAbstractButton *button); - - void on_cancelButton_clicked(); - -private: - - Ui::SelectionDialog *ui; - QAbstractButton *m_Choice; - bool m_ValidateByData; - -}; - -#endif // SELECTIONDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SELECTIONDIALOG_H +#define SELECTIONDIALOG_H + +#include +#include + +namespace Ui { +class SelectionDialog; +} + +class SelectionDialog : public QDialog +{ + Q_OBJECT + +public: + + explicit SelectionDialog(const QString &description, QWidget *parent = 0); + + ~SelectionDialog(); + + /** + * @brief add a choice to the dialog + * @param buttonText the text to be displayed on the button + * @param description the description that shows up under in small letters inside the button + * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) + * all buttons that contain no data will be treated as "cancel" buttons + */ + void addChoice(const QString &buttonText, const QString &description, const QVariant &data); + + int numChoices() const; + + QVariant getChoiceData(); + QString getChoiceString(); + +private slots: + + void on_buttonBox_clicked(QAbstractButton *button); + + void on_cancelButton_clicked(); + +private: + + Ui::SelectionDialog *ui; + QAbstractButton *m_Choice; + bool m_ValidateByData; + +}; + +#endif // SELECTIONDIALOG_H diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 650e650c..6037422c 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -1,466 +1,466 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "selfupdater.h" -#include "utility.h" -#include "installationmanager.h" -#include "report.h" -#include "messagedialog.h" -#include "downloadmanager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -typedef Archive* (*CreateArchiveType)(); - - -template T resolveFunction(QLibrary &lib, const char *name) -{ - T temp = reinterpret_cast(lib.resolve(name)); - if (temp == NULL) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; -} - - -SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) - : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), - m_Reply(NULL), m_Progress(parent), m_Attempts(3) -{ - m_Progress.setMaximum(100); - - QLibrary archiveLib("dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - - CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); - - m_CurrentArchive = CreateArchiveFunc(); - if (!m_CurrentArchive->isValid()) { - throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError())); - } - - connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); - - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16); -} - - -SelfUpdater::~SelfUpdater() -{ - delete m_CurrentArchive; -} - - -void SelfUpdater::testForUpdate() -{ - if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { - emit updateAvailable(); - return; - } - - if (m_UpdateRequestID == -1) { - m_UpdateRequestID = m_Interface->requestDescription( - SkyrimInfo::getNexusModIDStatic(), this, QVariant(), - QString(), ToQString(SkyrimInfo::getNexusInfoUrlStatic()), - SkyrimInfo::getNexusGameIDStatic()); - } -} - -void SelfUpdater::startUpdate() -{ - if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { - m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z"); - installUpdate(); - return; - } - - if ((m_UpdateRequestID == -1) && - (!m_NewestVersion.isEmpty())) { - if (QMessageBox::question(m_Parent, tr("Update"), - tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(), - this, m_NewestVersion, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } - } -} - - -void SelfUpdater::download(const QString &downloadLink, const QString &fileName) -{ - QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); - QUrl dlUrl(downloadLink); - QNetworkRequest request(dlUrl); - m_Canceled = false; - m_Reply = accessManager->get(request); - m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName))); - m_UpdateFile.open(QIODevice::WriteOnly); - m_Progress.setModal(true); - m_Progress.show(); - m_Progress.setValue(0); - m_Progress.setWindowTitle(tr("Update")); - m_Progress.setLabelText(tr("Download in progress")); - - connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); -} - - -void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - if (m_Reply != NULL) { - if (m_Canceled) { - m_Reply->abort(); - } else { - if (bytesTotal != 0) { - m_Progress.setValue((bytesReceived * 100) / bytesTotal); - } - } - } -} - - -void SelfUpdater::downloadReadyRead() -{ - if (m_Reply != NULL) { - m_UpdateFile.write(m_Reply->readAll()); - } -} - - -void SelfUpdater::downloadFinished() -{ - int error = QNetworkReply::NoError; - - if (m_Reply != NULL) { - m_UpdateFile.write(m_Reply->readAll()); - - error = m_Reply->error(); - - if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { - m_Canceled = true; - } - - m_Progress.hide(); - m_Reply->close(); - m_Reply->deleteLater(); - m_Reply = NULL; - } - - m_UpdateFile.close(); - - if ((m_UpdateFile.size() == 0) || - (error != QNetworkReply::NoError) || - m_Canceled) { - if (!m_Canceled) { - reportError(tr("Download failed: %1").arg(error)); - } - m_UpdateFile.remove(); - return; - } - - qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); - - try { - installUpdate(); - } catch (const std::exception &e) { - reportError(tr("Failed to install update: %1").arg(e.what())); - } -} - - -void SelfUpdater::downloadCancel() -{ - m_Canceled = true; -} - - -void SelfUpdater::installUpdate() -{ - const QString mopath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())); - - QString backupPath = mopath.mid(0).append("/update_backup"); - QDir().mkdir(backupPath); - - // rename files that are currently open so we can unpack the update - if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(), - new MethodCallback(this, &SelfUpdater::queryPassword))) { - throw MyException(tr("failed to open archive \"%1\": %2") - .arg(m_UpdateFile.fileName()) - .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError()))); - } - - // move all files contained in the archive out of the way, - // otherwise we can't overwrite everything - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - for (size_t i = 0; i < size; ++i) { - QString outputName = ToQString(data[i]->getFileName()); - if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) { - outputName = outputName.mid(13); - data[i]->setOutputFileName(ToWString(outputName).c_str()); - } else if (outputName == "ModOrganizer") { - data[i]->setSkip(true); - } - QFileInfo file(mopath.mid(0).append("/").append(outputName)); - if (file.exists() && file.isFile()) { - if (!shellMove(QStringList(mopath.mid(0).append("/").append(outputName)), - QStringList(backupPath.mid(0).append("/").append(outputName)))) { - reportError(tr("failed to move outdated files: %1. Please update manually.").arg(windowsErrorString(::GetLastError()))); - return; - } - } - } - - // now unpack the archive into the mo directory - if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(), - new MethodCallback(this, &SelfUpdater::updateProgress), - new MethodCallback(this, &SelfUpdater::updateProgressFile), - new MethodCallback(this, &SelfUpdater::report7ZipError))) { - throw std::runtime_error("extracting failed"); - } - - m_CurrentArchive->close(); - - m_UpdateFile.remove(); - - QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted.")); - - QProcess newProcess; - if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) { - newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update")); - } else { - newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update")); - } - emit restart(); -} - -void SelfUpdater::queryPassword(LPSTR) -{ - // nop -} - -void SelfUpdater::updateProgress(float) -{ - // nop -} - -void SelfUpdater::updateProgressFile(LPCWSTR) -{ - // nop -} - -void SelfUpdater::report7ZipError(LPCWSTR errorMessage) -{ - QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage)); -} - - -QString SelfUpdater::retrieveNews(const QString &description) -{ - QStringList temp = description.split("[s][/s]"); - if (temp.length() < 2) { - return QString(); - } else { - return temp.at(1); - } -} - - -void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - - QVariantMap result = resultData.toMap(); - QString motd = retrieveNews(result["description"].toString()).trimmed(); - if (motd.length() != 0) { - emit motdAvailable(motd); - } - - m_NewestVersion = result["version"].toString(); - if (m_NewestVersion.isEmpty()) { - QTimer::singleShot(5000, this, SLOT(testForUpdate())); - } - VersionInfo currentVersion(m_MOVersion); - VersionInfo newestVersion(m_NewestVersion); - - if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) { - emit updateAvailable(); - } else if (newestVersion < currentVersion) { - qDebug("this version is newer than the current version on nexus (%s vs %s)", - currentVersion.canonicalString().toUtf8().constData(), - newestVersion.canonicalString().toUtf8().constData()); - } - } -} - - -void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) -{ - if (requestID != m_UpdateRequestID) { - return; - } - QString version = userData.toString(); - - m_UpdateRequestID = -1; - - if (!resultData.canConvert()) { - qCritical("invalid files result: %s", resultData.toString().toUtf8().constData()); - reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log.")); - return; - } - - QVariantList result = resultData.toList(); - - QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version)); - QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version)); - int updateFileID = -1; - QString updateFileName; - int mainFileID = -1; - QString mainFileName; - int mainFileSize = 0; - - foreach(QVariant file, result) { - QVariantMap fileInfo = file.toMap(); - if (!fileInfo["uri"].toString().endsWith(".7z")) { - continue; - } - - if (fileInfo["version"].toString() == version) { - if (fileInfo["category_id"].toInt() == 2) { - QString description = fileInfo["description"].toString(); - // update - if (updateExpList.indexIn(description) != -1) { - // there is an update for the newest version of MO, but does - // it apply to the current version? - QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts); - if (supportedVersions.contains(m_MOVersion.canonicalString())) { - updateFileID = fileInfo["id"].toInt(); - updateFileName = fileInfo["uri"].toString(); - } else { - qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); - } - } else if (updateExpRange.indexIn(description) != -1) { - VersionInfo rangeLowEnd(updateExpRange.cap(1)); - VersionInfo rangeHighEnd(updateExpRange.cap(2)); - if ((rangeLowEnd <= m_MOVersion) && - (m_MOVersion <= rangeHighEnd)) { - updateFileID = fileInfo["id"].toInt(); - updateFileName = fileInfo["uri"].toString(); - break; - } else { - qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); - } - } else { - qWarning("invalid update description: %s", - description.toUtf8().constData()); - } - } else if (fileInfo["category_id"].toInt() == 1) { - mainFileID = fileInfo["id"].toInt(); - mainFileName = fileInfo["uri"].toString(); - mainFileSize = fileInfo["size"].toInt(); - } - } - } - - if (updateFileID != -1) { - qDebug("update available: %d", updateFileID); - m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), - updateFileID, this, updateFileName, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } else if (mainFileID != -1) { - qDebug("full download required: %d", mainFileID); - if (QMessageBox::question(m_Parent, tr("Update"), - tr("No incremental update available for this version, " - "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), - mainFileID, this, mainFileName, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } - } else { - qCritical("no file for update found"); - MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent); - m_Progress.hide(); - } -} - - -void SelfUpdater::nxmRequestFailed(int, int, QVariant, int requestID, const QString &errorMessage) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - if (m_Attempts > 0) { - QTimer::singleShot(60000, this, SLOT(testForUpdate())); - --m_Attempts; - } else { - qWarning("Failed to retrieve update information: %s", qPrintable(errorMessage)); - MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent, false); - } - } -} - - -void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - QVariantList serverList = resultData.toList(); - if (serverList.count() != 0) { - std::map dummy; - qSort(serverList.begin(), serverList.end(), boost::bind(&DownloadManager::ServerByPreference, dummy, _1, _2)); - - - QVariantMap dlServer = serverList.first().toMap(); - - download(dlServer["URI"].toString(), userData.toString()); - } else { - MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent); - m_Progress.hide(); - } - } -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "selfupdater.h" +#include "utility.h" +#include "installationmanager.h" +#include "report.h" +#include "messagedialog.h" +#include "downloadmanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +typedef Archive* (*CreateArchiveType)(); + + +template T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(lib.resolve(name)); + if (temp == NULL) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); + } + return temp; +} + + +SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) + : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), + m_Reply(NULL), m_Progress(parent), m_Attempts(3) +{ + m_Progress.setMaximum(100); + + QLibrary archiveLib("dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); + } + + CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); + + m_CurrentArchive = CreateArchiveFunc(); + if (!m_CurrentArchive->isValid()) { + throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError())); + } + + connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); + + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16); +} + + +SelfUpdater::~SelfUpdater() +{ + delete m_CurrentArchive; +} + + +void SelfUpdater::testForUpdate() +{ + if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + emit updateAvailable(); + return; + } + + if (m_UpdateRequestID == -1) { + m_UpdateRequestID = m_Interface->requestDescription( + SkyrimInfo::getNexusModIDStatic(), this, QVariant(), + QString(), ToQString(SkyrimInfo::getNexusInfoUrlStatic()), + SkyrimInfo::getNexusGameIDStatic()); + } +} + +void SelfUpdater::startUpdate() +{ + if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { + m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z"); + installUpdate(); + return; + } + + if ((m_UpdateRequestID == -1) && + (!m_NewestVersion.isEmpty())) { + if (QMessageBox::question(m_Parent, tr("Update"), + tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(), + this, m_NewestVersion, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } +} + + +void SelfUpdater::download(const QString &downloadLink, const QString &fileName) +{ + QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); + QUrl dlUrl(downloadLink); + QNetworkRequest request(dlUrl); + m_Canceled = false; + m_Reply = accessManager->get(request); + m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName))); + m_UpdateFile.open(QIODevice::WriteOnly); + m_Progress.setModal(true); + m_Progress.show(); + m_Progress.setValue(0); + m_Progress.setWindowTitle(tr("Update")); + m_Progress.setLabelText(tr("Download in progress")); + + connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); + connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); +} + + +void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + if (m_Reply != NULL) { + if (m_Canceled) { + m_Reply->abort(); + } else { + if (bytesTotal != 0) { + m_Progress.setValue((bytesReceived * 100) / bytesTotal); + } + } + } +} + + +void SelfUpdater::downloadReadyRead() +{ + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + } +} + + +void SelfUpdater::downloadFinished() +{ + int error = QNetworkReply::NoError; + + if (m_Reply != NULL) { + m_UpdateFile.write(m_Reply->readAll()); + + error = m_Reply->error(); + + if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { + m_Canceled = true; + } + + m_Progress.hide(); + m_Reply->close(); + m_Reply->deleteLater(); + m_Reply = NULL; + } + + m_UpdateFile.close(); + + if ((m_UpdateFile.size() == 0) || + (error != QNetworkReply::NoError) || + m_Canceled) { + if (!m_Canceled) { + reportError(tr("Download failed: %1").arg(error)); + } + m_UpdateFile.remove(); + return; + } + + qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + + try { + installUpdate(); + } catch (const std::exception &e) { + reportError(tr("Failed to install update: %1").arg(e.what())); + } +} + + +void SelfUpdater::downloadCancel() +{ + m_Canceled = true; +} + + +void SelfUpdater::installUpdate() +{ + const QString mopath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())); + + QString backupPath = mopath.mid(0).append("/update_backup"); + QDir().mkdir(backupPath); + + // rename files that are currently open so we can unpack the update + if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(), + new MethodCallback(this, &SelfUpdater::queryPassword))) { + throw MyException(tr("failed to open archive \"%1\": %2") + .arg(m_UpdateFile.fileName()) + .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError()))); + } + + // move all files contained in the archive out of the way, + // otherwise we can't overwrite everything + FileData* const *data; + size_t size; + m_CurrentArchive->getFileList(data, size); + + for (size_t i = 0; i < size; ++i) { + QString outputName = ToQString(data[i]->getFileName()); + if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) { + outputName = outputName.mid(13); + data[i]->setOutputFileName(ToWString(outputName).c_str()); + } else if (outputName == "ModOrganizer") { + data[i]->setSkip(true); + } + QFileInfo file(mopath.mid(0).append("/").append(outputName)); + if (file.exists() && file.isFile()) { + if (!shellMove(QStringList(mopath.mid(0).append("/").append(outputName)), + QStringList(backupPath.mid(0).append("/").append(outputName)))) { + reportError(tr("failed to move outdated files: %1. Please update manually.").arg(windowsErrorString(::GetLastError()))); + return; + } + } + } + + // now unpack the archive into the mo directory + if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(), + new MethodCallback(this, &SelfUpdater::updateProgress), + new MethodCallback(this, &SelfUpdater::updateProgressFile), + new MethodCallback(this, &SelfUpdater::report7ZipError))) { + throw std::runtime_error("extracting failed"); + } + + m_CurrentArchive->close(); + + m_UpdateFile.remove(); + + QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted.")); + + QProcess newProcess; + if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) { + newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update")); + } else { + newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update")); + } + emit restart(); +} + +void SelfUpdater::queryPassword(LPSTR) +{ + // nop +} + +void SelfUpdater::updateProgress(float) +{ + // nop +} + +void SelfUpdater::updateProgressFile(LPCWSTR) +{ + // nop +} + +void SelfUpdater::report7ZipError(LPCWSTR errorMessage) +{ + QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage)); +} + + +QString SelfUpdater::retrieveNews(const QString &description) +{ + QStringList temp = description.split("[s][/s]"); + if (temp.length() < 2) { + return QString(); + } else { + return temp.at(1); + } +} + + +void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + + QVariantMap result = resultData.toMap(); + QString motd = retrieveNews(result["description"].toString()).trimmed(); + if (motd.length() != 0) { + emit motdAvailable(motd); + } + + m_NewestVersion = result["version"].toString(); + if (m_NewestVersion.isEmpty()) { + QTimer::singleShot(5000, this, SLOT(testForUpdate())); + } + VersionInfo currentVersion(m_MOVersion); + VersionInfo newestVersion(m_NewestVersion); + + if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) { + emit updateAvailable(); + } else if (newestVersion < currentVersion) { + qDebug("this version is newer than the current version on nexus (%s vs %s)", + currentVersion.canonicalString().toUtf8().constData(), + newestVersion.canonicalString().toUtf8().constData()); + } + } +} + + +void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID != m_UpdateRequestID) { + return; + } + QString version = userData.toString(); + + m_UpdateRequestID = -1; + + if (!resultData.canConvert()) { + qCritical("invalid files result: %s", resultData.toString().toUtf8().constData()); + reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log.")); + return; + } + + QVariantList result = resultData.toList(); + + QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version)); + QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version)); + int updateFileID = -1; + QString updateFileName; + int mainFileID = -1; + QString mainFileName; + int mainFileSize = 0; + + foreach(QVariant file, result) { + QVariantMap fileInfo = file.toMap(); + if (!fileInfo["uri"].toString().endsWith(".7z")) { + continue; + } + + if (fileInfo["version"].toString() == version) { + if (fileInfo["category_id"].toInt() == 2) { + QString description = fileInfo["description"].toString(); + // update + if (updateExpList.indexIn(description) != -1) { + // there is an update for the newest version of MO, but does + // it apply to the current version? + QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts); + if (supportedVersions.contains(m_MOVersion.canonicalString())) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else if (updateExpRange.indexIn(description) != -1) { + VersionInfo rangeLowEnd(updateExpRange.cap(1)); + VersionInfo rangeHighEnd(updateExpRange.cap(2)); + if ((rangeLowEnd <= m_MOVersion) && + (m_MOVersion <= rangeHighEnd)) { + updateFileID = fileInfo["id"].toInt(); + updateFileName = fileInfo["uri"].toString(); + break; + } else { + qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); + } + } else { + qWarning("invalid update description: %s", + description.toUtf8().constData()); + } + } else if (fileInfo["category_id"].toInt() == 1) { + mainFileID = fileInfo["id"].toInt(); + mainFileName = fileInfo["uri"].toString(); + mainFileSize = fileInfo["size"].toInt(); + } + } + } + + if (updateFileID != -1) { + qDebug("update available: %d", updateFileID); + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + updateFileID, this, updateFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } else if (mainFileID != -1) { + qDebug("full download required: %d", mainFileID); + if (QMessageBox::question(m_Parent, tr("Update"), + tr("No incremental update available for this version, " + "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { + m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), + mainFileID, this, mainFileName, + ToQString(SkyrimInfo::getNexusInfoUrlStatic())); + } + } else { + qCritical("no file for update found"); + MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent); + m_Progress.hide(); + } +} + + +void SelfUpdater::nxmRequestFailed(int, int, QVariant, int requestID, const QString &errorMessage) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + if (m_Attempts > 0) { + QTimer::singleShot(60000, this, SLOT(testForUpdate())); + --m_Attempts; + } else { + qWarning("Failed to retrieve update information: %s", qPrintable(errorMessage)); + MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent, false); + } + } +} + + +void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID) +{ + if (requestID == m_UpdateRequestID) { + m_UpdateRequestID = -1; + QVariantList serverList = resultData.toList(); + if (serverList.count() != 0) { + std::map dummy; + qSort(serverList.begin(), serverList.end(), boost::bind(&DownloadManager::ServerByPreference, dummy, _1, _2)); + + + QVariantMap dlServer = serverList.first().toMap(); + + download(dlServer["URI"].toString(), userData.toString()); + } else { + MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent); + m_Progress.hide(); + } + } +} + diff --git a/src/selfupdater.h b/src/selfupdater.h index 14f7e90a..9648f15a 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -1,145 +1,145 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SELFUPDATER_H -#define SELFUPDATER_H - - -#include "nexusinterface.h" -#include -#include - -#include -#include -#include -#include - - -/** - * @brief manages updates for Mod Organizer itself - * This class is used to update the Mod Organizer - * The process looks like this: - * 1. call testForUpdate() to determine is available - * 2. if the updateAvailable() signal is received, allow the user to start the update - * 3. if the user start the update, call startUpdate() - * 4. startUpdate() will first query a list of files, try to determine if there is an - * incremental update. If not, the user will have to confirm the download of a full download. - * Once the correct file is selected, it is downloaded. - * 5. before the downloaded file is extracted, existing files that are going to be replaced are - * moved to "update_backup" on because files that are currently open can't be replaced. - * 6. the update is extracted and then deleted - * 7. finally, a restart is requested via signal. - * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files - * should now be open - * - * @todo use NexusBridge - **/ -class SelfUpdater : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param nexusInterface interface to query information from nexus - * @param parent parent widget - * @todo passing the nexus interface is unneccessary - **/ - SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); - ~SelfUpdater(); - - /** - * @brief start the update process - * @note this should not be called if there is no update available - **/ - void startUpdate(); - - /** - * @return current version of Mod Organizer - **/ - MOBase::VersionInfo getVersion() const { return m_MOVersion; } - -public slots: - - /** - * @brief request information about the current version - **/ - void testForUpdate(); - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - -signals: - - /** - * @brief emitted if a restart of the client is necessary to complete the update - **/ - void restart(); - - /** - * @brief emitted if an update is available - **/ - void updateAvailable(); - - /** - * @brief emitted if a message of the day was received - **/ - void motdAvailable(const QString &motd); - -private: - - void download(const QString &downloadLink, const QString &fileName); - void installUpdate(); - void queryPassword(LPSTR password); - void updateProgress(float percentage); - void updateProgressFile(LPCWSTR fileName); - void report7ZipError(LPCWSTR errorMessage); - QString retrieveNews(const QString &description); - -private slots: - - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void downloadReadyRead(); - void downloadFinished(); - void downloadCancel(); - -private: - - QWidget *m_Parent; - MOBase::VersionInfo m_MOVersion; - NexusInterface *m_Interface; - int m_UpdateRequestID; - QString m_NewestVersion; - QFile m_UpdateFile; - QNetworkReply *m_Reply; - QProgressDialog m_Progress; - bool m_Canceled; - int m_Attempts; - - Archive *m_CurrentArchive; - -}; - - -#endif // SELFUPDATER_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SELFUPDATER_H +#define SELFUPDATER_H + + +#include "nexusinterface.h" +#include +#include + +#include +#include +#include +#include + + +/** + * @brief manages updates for Mod Organizer itself + * This class is used to update the Mod Organizer + * The process looks like this: + * 1. call testForUpdate() to determine is available + * 2. if the updateAvailable() signal is received, allow the user to start the update + * 3. if the user start the update, call startUpdate() + * 4. startUpdate() will first query a list of files, try to determine if there is an + * incremental update. If not, the user will have to confirm the download of a full download. + * Once the correct file is selected, it is downloaded. + * 5. before the downloaded file is extracted, existing files that are going to be replaced are + * moved to "update_backup" on because files that are currently open can't be replaced. + * 6. the update is extracted and then deleted + * 7. finally, a restart is requested via signal. + * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files + * should now be open + * + * @todo use NexusBridge + **/ +class SelfUpdater : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + * + * @param nexusInterface interface to query information from nexus + * @param parent parent widget + * @todo passing the nexus interface is unneccessary + **/ + SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); + ~SelfUpdater(); + + /** + * @brief start the update process + * @note this should not be called if there is no update available + **/ + void startUpdate(); + + /** + * @return current version of Mod Organizer + **/ + MOBase::VersionInfo getVersion() const { return m_MOVersion; } + +public slots: + + /** + * @brief request information about the current version + **/ + void testForUpdate(); + + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + +signals: + + /** + * @brief emitted if a restart of the client is necessary to complete the update + **/ + void restart(); + + /** + * @brief emitted if an update is available + **/ + void updateAvailable(); + + /** + * @brief emitted if a message of the day was received + **/ + void motdAvailable(const QString &motd); + +private: + + void download(const QString &downloadLink, const QString &fileName); + void installUpdate(); + void queryPassword(LPSTR password); + void updateProgress(float percentage); + void updateProgressFile(LPCWSTR fileName); + void report7ZipError(LPCWSTR errorMessage); + QString retrieveNews(const QString &description); + +private slots: + + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadReadyRead(); + void downloadFinished(); + void downloadCancel(); + +private: + + QWidget *m_Parent; + MOBase::VersionInfo m_MOVersion; + NexusInterface *m_Interface; + int m_UpdateRequestID; + QString m_NewestVersion; + QFile m_UpdateFile; + QNetworkReply *m_Reply; + QProgressDialog m_Progress; + bool m_Canceled; + int m_Attempts; + + Archive *m_CurrentArchive; + +}; + + +#endif // SELFUPDATER_H diff --git a/src/settings.cpp b/src/settings.cpp index 3c78ed62..1cc168dd 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,771 +1,771 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "settings.h" - -#include "settingsdialog.h" -#include "utility.h" -#include "helper.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -template -class QListWidgetItemEx : public QListWidgetItem { -public: - QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) - : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} - - virtual bool operator< ( const QListWidgetItem & other ) const { - return this->data(m_SortRole).value() < other.data(m_SortRole).value(); - } -private: - int m_SortRole; -}; - - -static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, - 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; - -Settings *Settings::s_Instance = NULL; - - -Settings::Settings() - : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) -{ - if (s_Instance != NULL) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } -} - - -Settings::~Settings() -{ - s_Instance = NULL; -} - - -Settings &Settings::instance() -{ - if (s_Instance == NULL) { - throw std::runtime_error("no instance of \"Settings\""); - } - return *s_Instance; -} - -void Settings::clearPlugins() -{ - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); - int count = m_Settings.beginReadArray("pluginBlacklist"); - for (int i = 0; i < count; ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } - m_Settings.endArray(); -} - -bool Settings::pluginBlacklisted(const QString &fileName) const -{ - return m_PluginBlacklist.contains(fileName); -} - -void Settings::registerAsNXMHandler(bool force) -{ - std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); - std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); - std::wstring mode = force ? L"forcereg" : L"reg"; - ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), - (mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\"").c_str(), NULL, SW_SHOWNORMAL); -} - -void Settings::registerPlugin(IPlugin *plugin) -{ - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QMap()); - foreach (const PluginSetting &setting, plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); - temp = setting.defaultValue; - } - m_PluginSettings[plugin->name()][setting.key] = temp; - } -} - - -QString Settings::obfuscate(const QString &password) const -{ - QByteArray temp = password.toUtf8(); - - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return buffer.toBase64(); -} - - -QString Settings::deObfuscate(const QString &password) const -{ - QByteArray temp(QByteArray::fromBase64(password.toUtf8())); - - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return QString::fromUtf8(buffer.constData()); -} - - -bool Settings::hideUncheckedPlugins() const -{ - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); -} - -bool Settings::forceEnableCoreFiles() const -{ - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); -} - -bool Settings::automaticLoginEnabled() const -{ - return m_Settings.value("Settings/nexus_login", false).toBool(); -} - -QString Settings::getSteamAppID() const -{ - return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); -} - -QString Settings::getDownloadDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); -} - - -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.beginGroup("Servers"); - - foreach (const QString &serverKey, m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - foreach (const QString &serverKey, m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - -QString Settings::getCacheDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); -} - -QString Settings::getModDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); -} - -QString Settings::getNMMVersion() const -{ - static const QString MIN_NMM_VERSION = "0.47.0"; - QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); - if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { - result = MIN_NMM_VERSION; - } - return result; -} - -bool Settings::getNexusLogin(QString &username, QString &password) const -{ - if (m_Settings.value("Settings/nexus_login", false).toBool()) { - username = m_Settings.value("Settings/nexus_username", "").toString(); - password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); - return true; - } else { - return false; - } -} - -bool Settings::compactDownloads() const -{ - return m_Settings.value("Settings/compact_downloads", false).toBool(); -} - -bool Settings::metaDownloads() const -{ - return m_Settings.value("Settings/meta_downloads", false).toBool(); -} - -bool Settings::offlineMode() const -{ - return m_Settings.value("Settings/offline_mode", false).toBool(); -} - -int Settings::logLevel() const -{ - return m_Settings.value("Settings/log_level", 0).toInt(); -} - - -void Settings::setNexusLogin(QString username, QString password) -{ - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", username); - m_Settings.setValue("Settings/nexus_password", obfuscate(password)); -} - - -LoadMechanism::EMechanism Settings::getLoadMechanism() const -{ - switch (m_Settings.value("Settings/load_mechanism").toInt()) { - case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; - case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; - case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; - } - throw std::runtime_error("invalid load mechanism"); -} - - -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); -} - - -bool Settings::useProxy() -{ - return m_Settings.value("Settings/use_proxy", false).toBool(); -} - -bool Settings::displayForeign() -{ - return m_Settings.value("Settings/display_foreign", true).toBool(); -} - -void Settings::setMotDHash(uint hash) -{ - m_Settings.setValue("motd_hash", hash); -} - -uint Settings::getMotDHash() const -{ - return m_Settings.value("motd_hash", 0).toUInt(); -} - -QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } - - return *iterSetting; -} - -void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); -} - -QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); -} - -void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); - if (sync) { - m_Settings.sync(); - } -} - -QString Settings::language() -{ - QString result = m_Settings.value("Settings/language", "").toString(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); - } - } - return result; -} - -void Settings::updateServers(const QList &servers) -{ - m_Settings.beginGroup("Servers"); - QStringList oldServerKeys = m_Settings.childKeys(); - - foreach (const ServerInfo &server, servers) { - if (!oldServerKeys.contains(server.name)) { - // not yet known server - QVariantMap newVal; - newVal["premium"] = server.premium; - newVal["preferred"] = server.preferred ? 1 : 0; - newVal["lastSeen"] = server.lastSeen; - newVal["downloadCount"] = 0; - newVal["downloadSpeed"] = 0.0; - - m_Settings.setValue(server.name, newVal); - } else { - QVariantMap data = m_Settings.value(server.name).toMap(); - data["lastSeen"] = server.lastSeen; - data["premium"] = server.premium; - - m_Settings.setValue(server.name, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -void Settings::addBlacklistPlugin(const QString &fileName) -{ - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); -} - -void Settings::writePluginBlacklist() -{ - m_Settings.beginWriteArray("pluginBlacklist"); - int idx = 0; - foreach (const QString &plugin, m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); - } - - m_Settings.endArray(); -} - -void Settings::addLanguages(QComboBox *languageBox) -{ - languageBox->addItem("English", "en_US"); - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); - QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; - QRegExp exp(pattern); - while (langIter.hasNext()) { - langIter.next(); - QString file = langIter.fileName(); - if (exp.exactMatch(file)) { - QString languageCode = exp.cap(1); - QLocale locale(languageCode); - QString languageString = QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } - } - languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); - } - } -} - -void Settings::addStyles(QComboBox *styleBox) -{ - styleBox->addItem("None", ""); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - styleBox->addItem("Fusion", "Fusion"); -#else - styleBox->addItem("Plastique", "Plastique"); - styleBox->addItem("Cleanlooks", "Cleanlooks"); -#endif - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - styleBox->addItem(style, style); - } -} - -bool Settings::isNXMHandler(bool *modifyable) -{ - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command", - QSettings::NativeFormat); - - QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - if (modifyable != NULL) { - handlerReg.setValue("Default", currentExe); - handlerReg.sync(); - - *modifyable = handlerReg.status() == QSettings::NoError; - // QSettings::isWritable returns wrong results... - } - return currentExe == myExe; -} - - -void Settings::setNXMHandlerActive(bool active, bool writable) -{ -// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat); - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); - - if (writable) { - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - handlerReg.setValue("Default", "URL:NXM Protocol"); - handlerReg.setValue("URL Protocol", ""); - handlerReg.setValue("shell/open/command/Default", active ? myExe : ""); - handlerReg.sync(); - } else { - Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active); - } -} - - -void Settings::resetDialogs() -{ - m_Settings.beginGroup("DialogChoices"); - QStringList keys = m_Settings.childKeys(); - foreach (QString key, keys) { - m_Settings.remove(key); - } - - m_Settings.endGroup(); -} - - -void Settings::query(QWidget *parent) -{ - SettingsDialog dialog(parent); - - connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - - // General Page - QComboBox *languageBox = dialog.findChild("languageBox"); - QComboBox *styleBox = dialog.findChild("styleBox"); - QComboBox *logLevelBox = dialog.findChild("logLevelBox"); - QCheckBox *compactBox = dialog.findChild("compactBox"); - QCheckBox *showMetaBox = dialog.findChild("showMetaBox"); - - QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); - QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); - QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); - - // nexus page - QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); - QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); - QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); - QCheckBox *offlineBox = dialog.findChild("offlineBox"); - QCheckBox *proxyBox = dialog.findChild("proxyBox"); - - QListWidget *knownServersList = dialog.findChild("knownServersList"); - QListWidget *preferredServersList = dialog.findChild("preferredServersList"); - - // plugis page - QListWidget *pluginsList = dialog.findChild("pluginsList"); - QListWidget *pluginBlacklistList = dialog.findChild("pluginBlacklist"); - - // workarounds page - QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); - QComboBox *mechanismBox = dialog.findChild("mechanismBox"); - QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); - QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); - QCheckBox *hideUncheckedBox = dialog.findChild("hideUncheckedBox"); - QCheckBox *displayForeignBox = dialog.findChild("displayForeignBox"); - - - // - // set up current settings - // - LoadMechanism::EMechanism mechanismID = getLoadMechanism(); - int index = 0; - - if (m_LoadMechanism.isDirectLoadingSupported()) { - mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); - if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isScriptExtenderSupported()) { - mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isProxyDLLSupported()) { - mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = mechanismBox->count() - 1; - } - } - - mechanismBox->setCurrentIndex(index); - - { - addLanguages(languageBox); - QString languageCode = language(); - int currentID = languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both variants - if (currentID == -1) { - currentID = languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - languageBox->setCurrentIndex(currentID); - } - } - - { - addStyles(styleBox); - int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - styleBox->setCurrentIndex(currentID); - } - } - - compactBox->setChecked(compactDownloads()); - showMetaBox->setChecked(metaDownloads()); - - hideUncheckedBox->setChecked(hideUncheckedPlugins()); - displayForeignBox->setChecked(displayForeign()); - forceEnableBox->setChecked(forceEnableCoreFiles()); - - appIDEdit->setText(getSteamAppID()); - - if (automaticLoginEnabled()) { - loginCheckBox->setChecked(true); - usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); - passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); - } - - downloadDirEdit->setText(getDownloadDirectory()); - modDirEdit->setText(getModDirectory()); - cacheDirEdit->setText(getCacheDirectory()); - offlineBox->setChecked(offlineMode()); - proxyBox->setChecked(useProxy()); - nmmVersionEdit->setText(getNMMVersion()); - logLevelBox->setCurrentIndex(logLevel()); - - // display plugin settings - foreach (IPlugin *plugin, m_Plugins) { - QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); - listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); - pluginsList->addItem(listItem); - } - - // display plugin blacklist - foreach (const QString &pluginName, m_PluginBlacklist) { - pluginBlacklistList->addItem(pluginName); - } - - // display server preferences - m_Settings.beginGroup("Servers"); - foreach (const QString &key, m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - QString type = val["premium"].toBool() ? "(premium)" : "(free)"; - - QString descriptor = key + " " + type; - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); - } - - QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); - - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { - preferredServersList->addItem(newItem); - } else { - knownServersList->addItem(newItem); - } - preferredServersList->sortItems(Qt::DescendingOrder); - } - m_Settings.endGroup(); - - if (dialog.exec() == QDialog::Accepted) { - // - // transfer modified settings to configuration file - // - - m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); - m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); - m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); - m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); - - - { // advanced settings - if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && - (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " - "Mods not present (or named differently) in the new location will be disabled in all profiles. " - "There is no way to undo this unless you backed up your profiles manually. Proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - modDirEdit->setText(getModDirectory()); - } - - if (!QDir(downloadDirEdit->text()).exists()) { - QDir().mkpath(downloadDirEdit->text()); - } - if (!QDir(cacheDirEdit->text()).exists()) { - QDir().mkpath(cacheDirEdit->text()); - } - if (!QDir(modDirEdit->text()).exists()) { - QDir().mkpath(modDirEdit->text()); - } - - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); - } - - - QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); - QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); - if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit languageChanged(newLanguage); - } - - QString oldStyle = m_Settings.value("Settings/style", "").toString(); - QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); - if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit styleChanged(newStyle); - } - - m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); - - if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { - m_Settings.setValue("Settings/app_id", appIDEdit->text()); - } else { - m_Settings.remove("Settings/app_id"); - } - if (loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text())); - } else { - m_Settings.setValue("Settings/nexus_login", false); - m_Settings.remove("Settings/nexus_username"); - m_Settings.remove("Settings/nexus_password"); - } - m_Settings.setValue("Settings/offline_mode", offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked()); - - m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); - - // transfer plugin settings to in-memory structure - for (int i = 0; i < pluginsList->count(); ++i) { - QListWidgetItem *item = pluginsList->item(i); - m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); - } - } - - // store plugin blacklist - m_PluginBlacklist.clear(); - foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { - m_PluginBlacklist.insert(item->text()); - } - writePluginBlacklist(); - - // store server preference - m_Settings.beginGroup("Servers"); - for (int i = 0; i < knownServersList->count(); ++i) { - QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = 0; - m_Settings.setValue(key, val); - } - int count = preferredServersList->count(); - for (int i = 0; i < count; ++i) { - QString key = preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = count - i; - m_Settings.setValue(key, val); - } - m_Settings.endGroup(); - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "settings.h" + +#include "settingsdialog.h" +#include "utility.h" +#include "helper.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +template +class QListWidgetItemEx : public QListWidgetItem { +public: + QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) + : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} + + virtual bool operator< ( const QListWidgetItem & other ) const { + return this->data(m_SortRole).value() < other.data(m_SortRole).value(); + } +private: + int m_SortRole; +}; + + +static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, + 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; + +Settings *Settings::s_Instance = NULL; + + +Settings::Settings() + : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) +{ + if (s_Instance != NULL) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } +} + + +Settings::~Settings() +{ + s_Instance = NULL; +} + + +Settings &Settings::instance() +{ + if (s_Instance == NULL) { + throw std::runtime_error("no instance of \"Settings\""); + } + return *s_Instance; +} + +void Settings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + int count = m_Settings.beginReadArray("pluginBlacklist"); + for (int i = 0; i < count; ++i) { + m_Settings.setArrayIndex(i); + m_PluginBlacklist.insert(m_Settings.value("name").toString()); + } + m_Settings.endArray(); +} + +bool Settings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); +} + +void Settings::registerAsNXMHandler(bool force) +{ + std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); + std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); + std::wstring mode = force ? L"forcereg" : L"reg"; + ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), + (mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\"").c_str(), NULL, SW_SHOWNORMAL); +} + +void Settings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QMap()); + foreach (const PluginSetting &setting, plugin->settings()) { + QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { + qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", + qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); + temp = setting.defaultValue; + } + m_PluginSettings[plugin->name()][setting.key] = temp; + } +} + + +QString Settings::obfuscate(const QString &password) const +{ + QByteArray temp = password.toUtf8(); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return buffer.toBase64(); +} + + +QString Settings::deObfuscate(const QString &password) const +{ + QByteArray temp(QByteArray::fromBase64(password.toUtf8())); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return QString::fromUtf8(buffer.constData()); +} + + +bool Settings::hideUncheckedPlugins() const +{ + return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); +} + +bool Settings::forceEnableCoreFiles() const +{ + return m_Settings.value("Settings/force_enable_core_files", true).toBool(); +} + +bool Settings::automaticLoginEnabled() const +{ + return m_Settings.value("Settings/nexus_login", false).toBool(); +} + +QString Settings::getSteamAppID() const +{ + return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); +} + +QString Settings::getDownloadDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); +} + + +void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.beginGroup("Servers"); + + foreach (const QString &serverKey, m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + if (serverKey == serverName) { + data["downloadCount"] = data["downloadCount"].toInt() + 1; + data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); + m_Settings.setValue(serverKey, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +std::map Settings::getPreferredServers() +{ + std::map result; + m_Settings.beginGroup("Servers"); + + foreach (const QString &serverKey, m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + int preference = data["preferred"].toInt(); + if (preference > 0) { + result[serverKey] = preference; + } + } + m_Settings.endGroup(); + + return result; +} + +QString Settings::getCacheDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); +} + +QString Settings::getModDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); +} + +QString Settings::getNMMVersion() const +{ + static const QString MIN_NMM_VERSION = "0.47.0"; + QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); + if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { + result = MIN_NMM_VERSION; + } + return result; +} + +bool Settings::getNexusLogin(QString &username, QString &password) const +{ + if (m_Settings.value("Settings/nexus_login", false).toBool()) { + username = m_Settings.value("Settings/nexus_username", "").toString(); + password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); + return true; + } else { + return false; + } +} + +bool Settings::compactDownloads() const +{ + return m_Settings.value("Settings/compact_downloads", false).toBool(); +} + +bool Settings::metaDownloads() const +{ + return m_Settings.value("Settings/meta_downloads", false).toBool(); +} + +bool Settings::offlineMode() const +{ + return m_Settings.value("Settings/offline_mode", false).toBool(); +} + +int Settings::logLevel() const +{ + return m_Settings.value("Settings/log_level", 0).toInt(); +} + + +void Settings::setNexusLogin(QString username, QString password) +{ + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", username); + m_Settings.setValue("Settings/nexus_password", obfuscate(password)); +} + + +LoadMechanism::EMechanism Settings::getLoadMechanism() const +{ + switch (m_Settings.value("Settings/load_mechanism").toInt()) { + case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; + case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; + case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; + } + throw std::runtime_error("invalid load mechanism"); +} + + +void Settings::setupLoadMechanism() +{ + m_LoadMechanism.activate(getLoadMechanism()); +} + + +bool Settings::useProxy() +{ + return m_Settings.value("Settings/use_proxy", false).toBool(); +} + +bool Settings::displayForeign() +{ + return m_Settings.value("Settings/display_foreign", true).toBool(); +} + +void Settings::setMotDHash(uint hash) +{ + m_Settings.setValue("motd_hash", hash); +} + +uint Settings::getMotDHash() const +{ + return m_Settings.value("motd_hash", 0).toUInt(); +} + +QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); + } + + return *iterSetting; +} + +void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); +} + +QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); +} + +void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + if (!m_PluginSettings.contains(pluginName)) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + if (sync) { + m_Settings.sync(); + } +} + +QString Settings::language() +{ + QString result = m_Settings.value("Settings/language", "").toString(); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); + } else { + // fallback system locale + result = QLocale::system().name(); + } + } + return result; +} + +void Settings::updateServers(const QList &servers) +{ + m_Settings.beginGroup("Servers"); + QStringList oldServerKeys = m_Settings.childKeys(); + + foreach (const ServerInfo &server, servers) { + if (!oldServerKeys.contains(server.name)) { + // not yet known server + QVariantMap newVal; + newVal["premium"] = server.premium; + newVal["preferred"] = server.preferred ? 1 : 0; + newVal["lastSeen"] = server.lastSeen; + newVal["downloadCount"] = 0; + newVal["downloadSpeed"] = 0.0; + + m_Settings.setValue(server.name, newVal); + } else { + QVariantMap data = m_Settings.value(server.name).toMap(); + data["lastSeen"] = server.lastSeen; + data["premium"] = server.premium; + + m_Settings.setValue(server.name, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +void Settings::addBlacklistPlugin(const QString &fileName) +{ + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); +} + +void Settings::writePluginBlacklist() +{ + m_Settings.beginWriteArray("pluginBlacklist"); + int idx = 0; + foreach (const QString &plugin, m_PluginBlacklist) { + m_Settings.setArrayIndex(idx++); + m_Settings.setValue("name", plugin); + } + + m_Settings.endArray(); +} + +void Settings::addLanguages(QComboBox *languageBox) +{ + languageBox->addItem("English", "en_US"); + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); + QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + QRegExp exp(pattern); + while (langIter.hasNext()) { + langIter.next(); + QString file = langIter.fileName(); + if (exp.exactMatch(file)) { + QString languageCode = exp.cap(1); + QLocale locale(languageCode); + QString languageString = QLocale::languageToString(locale.language()); + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (traditional)"; + } else { + languageString = "Chinese (simplified)"; + } + } + languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); + } + } +} + +void Settings::addStyles(QComboBox *styleBox) +{ + styleBox->addItem("None", ""); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + styleBox->addItem("Fusion", "Fusion"); +#else + styleBox->addItem("Plastique", "Plastique"); + styleBox->addItem("Cleanlooks", "Cleanlooks"); +#endif + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); + while (langIter.hasNext()) { + langIter.next(); + QString style = langIter.fileName(); + styleBox->addItem(style, style); + } +} + +bool Settings::isNXMHandler(bool *modifyable) +{ + QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command", + QSettings::NativeFormat); + + QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); + QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); + if (modifyable != NULL) { + handlerReg.setValue("Default", currentExe); + handlerReg.sync(); + + *modifyable = handlerReg.status() == QSettings::NoError; + // QSettings::isWritable returns wrong results... + } + return currentExe == myExe; +} + + +void Settings::setNXMHandlerActive(bool active, bool writable) +{ +// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat); + QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); + + if (writable) { + QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); + handlerReg.setValue("Default", "URL:NXM Protocol"); + handlerReg.setValue("URL Protocol", ""); + handlerReg.setValue("shell/open/command/Default", active ? myExe : ""); + handlerReg.sync(); + } else { + Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active); + } +} + + +void Settings::resetDialogs() +{ + m_Settings.beginGroup("DialogChoices"); + QStringList keys = m_Settings.childKeys(); + foreach (QString key, keys) { + m_Settings.remove(key); + } + + m_Settings.endGroup(); +} + + +void Settings::query(QWidget *parent) +{ + SettingsDialog dialog(parent); + + connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); + + // General Page + QComboBox *languageBox = dialog.findChild("languageBox"); + QComboBox *styleBox = dialog.findChild("styleBox"); + QComboBox *logLevelBox = dialog.findChild("logLevelBox"); + QCheckBox *compactBox = dialog.findChild("compactBox"); + QCheckBox *showMetaBox = dialog.findChild("showMetaBox"); + + QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); + QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); + QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); + + // nexus page + QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); + QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); + QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); + QCheckBox *offlineBox = dialog.findChild("offlineBox"); + QCheckBox *proxyBox = dialog.findChild("proxyBox"); + + QListWidget *knownServersList = dialog.findChild("knownServersList"); + QListWidget *preferredServersList = dialog.findChild("preferredServersList"); + + // plugis page + QListWidget *pluginsList = dialog.findChild("pluginsList"); + QListWidget *pluginBlacklistList = dialog.findChild("pluginBlacklist"); + + // workarounds page + QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); + QComboBox *mechanismBox = dialog.findChild("mechanismBox"); + QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); + QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); + QCheckBox *hideUncheckedBox = dialog.findChild("hideUncheckedBox"); + QCheckBox *displayForeignBox = dialog.findChild("displayForeignBox"); + + + // + // set up current settings + // + LoadMechanism::EMechanism mechanismID = getLoadMechanism(); + int index = 0; + + if (m_LoadMechanism.isDirectLoadingSupported()) { + mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); + if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isScriptExtenderSupported()) { + mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); + if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isProxyDLLSupported()) { + mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); + if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { + index = mechanismBox->count() - 1; + } + } + + mechanismBox->setCurrentIndex(index); + + { + addLanguages(languageBox); + QString languageCode = language(); + int currentID = languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both variants + if (currentID == -1) { + currentID = languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + languageBox->setCurrentIndex(currentID); + } + } + + { + addStyles(styleBox); + int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + styleBox->setCurrentIndex(currentID); + } + } + + compactBox->setChecked(compactDownloads()); + showMetaBox->setChecked(metaDownloads()); + + hideUncheckedBox->setChecked(hideUncheckedPlugins()); + displayForeignBox->setChecked(displayForeign()); + forceEnableBox->setChecked(forceEnableCoreFiles()); + + appIDEdit->setText(getSteamAppID()); + + if (automaticLoginEnabled()) { + loginCheckBox->setChecked(true); + usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); + passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); + } + + downloadDirEdit->setText(getDownloadDirectory()); + modDirEdit->setText(getModDirectory()); + cacheDirEdit->setText(getCacheDirectory()); + offlineBox->setChecked(offlineMode()); + proxyBox->setChecked(useProxy()); + nmmVersionEdit->setText(getNMMVersion()); + logLevelBox->setCurrentIndex(logLevel()); + + // display plugin settings + foreach (IPlugin *plugin, m_Plugins) { + QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); + listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); + listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); + pluginsList->addItem(listItem); + } + + // display plugin blacklist + foreach (const QString &pluginName, m_PluginBlacklist) { + pluginBlacklistList->addItem(pluginName); + } + + // display server preferences + m_Settings.beginGroup("Servers"); + foreach (const QString &key, m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QString type = val["premium"].toBool() ? "(premium)" : "(free)"; + + QString descriptor = key + " " + type; + if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { + int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + descriptor += QString(" (%1 kbps)").arg(bps / 1024); + } + + QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); + + newItem->setData(Qt::UserRole, key); + newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); + if (val["preferred"].toInt() > 0) { + preferredServersList->addItem(newItem); + } else { + knownServersList->addItem(newItem); + } + preferredServersList->sortItems(Qt::DescendingOrder); + } + m_Settings.endGroup(); + + if (dialog.exec() == QDialog::Accepted) { + // + // transfer modified settings to configuration file + // + + m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); + m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); + m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); + m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); + + + { // advanced settings + if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && + (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " + "Mods not present (or named differently) in the new location will be disabled in all profiles. " + "There is no way to undo this unless you backed up your profiles manually. Proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + modDirEdit->setText(getModDirectory()); + } + + if (!QDir(downloadDirEdit->text()).exists()) { + QDir().mkpath(downloadDirEdit->text()); + } + if (!QDir(cacheDirEdit->text()).exists()) { + QDir().mkpath(cacheDirEdit->text()); + } + if (!QDir(modDirEdit->text()).exists()) { + QDir().mkpath(modDirEdit->text()); + } + + m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); + m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); + m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); + } + + + QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); + QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { + m_Settings.setValue("Settings/language", newLanguage); + emit languageChanged(newLanguage); + } + + QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { + m_Settings.setValue("Settings/style", newStyle); + emit styleChanged(newStyle); + } + + m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); + + if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { + m_Settings.setValue("Settings/app_id", appIDEdit->text()); + } else { + m_Settings.remove("Settings/app_id"); + } + if (loginCheckBox->isChecked()) { + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); + m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text())); + } else { + m_Settings.setValue("Settings/nexus_login", false); + m_Settings.remove("Settings/nexus_username"); + m_Settings.remove("Settings/nexus_password"); + } + m_Settings.setValue("Settings/offline_mode", offlineBox->isChecked()); + m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked()); + m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked()); + + m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); + + // transfer plugin settings to in-memory structure + for (int i = 0; i < pluginsList->count(); ++i) { + QListWidgetItem *item = pluginsList->item(i); + m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + } + // store plugin settings on disc + for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { + m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + } + } + + // store plugin blacklist + m_PluginBlacklist.clear(); + foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + m_PluginBlacklist.insert(item->text()); + } + writePluginBlacklist(); + + // store server preference + m_Settings.beginGroup("Servers"); + for (int i = 0; i < knownServersList->count(); ++i) { + QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = 0; + m_Settings.setValue(key, val); + } + int count = preferredServersList->count(); + for (int i = 0; i < count; ++i) { + QString key = preferredServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = count - i; + m_Settings.setValue(key, val); + } + m_Settings.endGroup(); + } +} diff --git a/src/settings.h b/src/settings.h index 0b051831..44e8088e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,313 +1,313 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef WORKAROUNDS_H -#define WORKAROUNDS_H - -#include "loadmechanism.h" -#include "serverinfo.h" -#include - -#include -#include -#include - - -/** - * manages the settings for Mod Organizer. The settings are not cached - * inside the class but read/written directly from/to disc - **/ -class Settings : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - **/ - Settings(); - - virtual ~Settings(); - - static Settings &instance(); - - /** - * unregister all plugins from settings - */ - void clearPlugins(); - - /** - * @brief register plugin to be configurable - * @param plugin the plugin to register - * @return true if the plugin may be registered, false if it is blacklisted - */ - void registerPlugin(MOBase::IPlugin *plugin); - - /** - * displays a SettingsDialog that allows the user to change settings. If the - * user accepts the changes, the settings are immediately written - **/ - void query(QWidget *parent); - - /** - * set up the settings for the specified plugins - **/ - void addPluginSettings(const std::vector &plugins); - - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - - /** - * retrieve the directory where downloads are stored (with native separators) - **/ - QString getDownloadDirectory() const; - - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - - /** - * retrieve the directory where mods are stored (with native separators) - **/ - QString getModDirectory() const; - - /** - * returns the version of nmm to impersonate when connecting to nexus - **/ - QString getNMMVersion() const; - - /** - * retrieve the directory where the web cache is stored (with native separators) - **/ - QString getCacheDirectory() const; - - /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; - - /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusLogin(QString &username, QString &password) const; - - /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - - /** - * @return true if the user chose compact downloads - */ - bool compactDownloads() const; - - /** - * @return true if the user chose meta downloads - */ - bool metaDownloads() const; - - /** - * @return the configured log level - */ - int logLevel() const; - - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusLogin(QString username, QString password); - - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); - - /** - * @return true if the user configured the use of a network proxy - */ - bool useProxy(); - - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign(); - - /** - * @brief sets the new motd hash - **/ - void setMotDHash(uint hash); - - /** - * @return hash of the last displayed message of the day - **/ - uint getMotDHash() const; - - /** - * @brief allows direct access to the wrapped QSettings object - * @return the wrapped QSettings object - */ - QSettings &directInterface() { return m_Settings; } - - /** - * @brief retrieve a setting for one of the installed plugins - * @param pluginName name of the plugin - * @param key name of the setting to retrieve - * @return the requested value as a QVariant - * @note an invalid QVariant is returned if the the plugin/setting is not declared - */ - QVariant pluginSetting(const QString &pluginName, const QString &key) const; - - /** - * @brief set a setting for one of the installed mods - * @param pluginName name of the plugin - * @param key name of the setting to change - * @param value the new value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - - /** - * @brief retrieve a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param def default value to return if the value is not set - * @return the requested value - */ - QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; - - /** - * @brief set a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param value value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - - /** - * @brief updates the list of known servers - * @param list of servers from a recent query - */ - void updateServers(const QList &servers); - - /** - * @brief add a plugin that is to be blacklisted - * @param fileName name of the plugin to blacklist - */ - void addBlacklistPlugin(const QString &fileName); - - /** - * @brief test if a plugin is blacklisted and shouldn't be loaded - * @param fileName name of the plugin - * @return true if the file is blacklisted - */ - bool pluginBlacklisted(const QString &fileName) const; - - /** - * @return all loaded MO plugins - */ - std::vector plugins() const { return m_Plugins; } - - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); - -private: - - QString obfuscate(const QString &password) const; - QString deObfuscate(const QString &password) const; - - void addLanguages(QComboBox *languageBox); - void addStyles(QComboBox *styleBox); - bool isNXMHandler(bool *modifyable); - void setNXMHandlerActive(bool active, bool writable); - void readPluginBlacklist(); - void writePluginBlacklist(); - -private slots: - - void resetDialogs(); - -signals: - - void languageChanged(const QString &newLanguage); - void styleChanged(const QString &newStyle); - -private: - - static Settings *s_Instance; - - QSettings m_Settings; - - LoadMechanism m_LoadMechanism; - - std::vector m_Plugins; - - QMap > m_PluginSettings; - - QSet m_PluginBlacklist; - -}; - -#endif // WORKAROUNDS_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef WORKAROUNDS_H +#define WORKAROUNDS_H + +#include "loadmechanism.h" +#include "serverinfo.h" +#include + +#include +#include +#include + + +/** + * manages the settings for Mod Organizer. The settings are not cached + * inside the class but read/written directly from/to disc + **/ +class Settings : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + **/ + Settings(); + + virtual ~Settings(); + + static Settings &instance(); + + /** + * unregister all plugins from settings + */ + void clearPlugins(); + + /** + * @brief register plugin to be configurable + * @param plugin the plugin to register + * @return true if the plugin may be registered, false if it is blacklisted + */ + void registerPlugin(MOBase::IPlugin *plugin); + + /** + * displays a SettingsDialog that allows the user to change settings. If the + * user accepts the changes, the settings are immediately written + **/ + void query(QWidget *parent); + + /** + * set up the settings for the specified plugins + **/ + void addPluginSettings(const std::vector &plugins); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual dat adirectory + **/ + bool hideUncheckedPlugins() const; + + /** + * @return true if files of the core game are forced-enabled so the user can't accidentally disable them + */ + bool forceEnableCoreFiles() const; + + /** + * @brief register download speed + * @param url complete download url + * @param bytesPerSecond download size in bytes per second + */ + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString getSteamAppID() const; + + /** + * retrieve the directory where downloads are stored (with native separators) + **/ + QString getDownloadDirectory() const; + + /** + * retrieve a sorted list of preferred servers + */ + std::map getPreferredServers(); + + /** + * retrieve the directory where mods are stored (with native separators) + **/ + QString getModDirectory() const; + + /** + * returns the version of nmm to impersonate when connecting to nexus + **/ + QString getNMMVersion() const; + + /** + * retrieve the directory where the web cache is stored (with native separators) + **/ + QString getCacheDirectory() const; + + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; + + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool getNexusLogin(QString &username, QString &password) const; + + /** + * @return true if the user disabled internet features + */ + bool offlineMode() const; + + /** + * @return true if the user chose compact downloads + */ + bool compactDownloads() const; + + /** + * @return true if the user chose meta downloads + */ + bool metaDownloads() const; + + /** + * @return the configured log level + */ + int logLevel() const; + + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + void setNexusLogin(QString username, QString password); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism getLoadMechanism() const; + + /** + * @brief activate the load mechanism selected by the user + **/ + void setupLoadMechanism(); + + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy(); + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign(); + + /** + * @brief sets the new motd hash + **/ + void setMotDHash(uint hash); + + /** + * @return hash of the last displayed message of the day + **/ + uint getMotDHash() const; + + /** + * @brief allows direct access to the wrapped QSettings object + * @return the wrapped QSettings object + */ + QSettings &directInterface() { return m_Settings; } + + /** + * @brief retrieve a setting for one of the installed plugins + * @param pluginName name of the plugin + * @param key name of the setting to retrieve + * @return the requested value as a QVariant + * @note an invalid QVariant is returned if the the plugin/setting is not declared + */ + QVariant pluginSetting(const QString &pluginName, const QString &key) const; + + /** + * @brief set a setting for one of the installed mods + * @param pluginName name of the plugin + * @param key name of the setting to change + * @param value the new value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + + /** + * @brief retrieve a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param def default value to return if the value is not set + * @return the requested value + */ + QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; + + /** + * @brief set a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param value value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + + /** + * @brief updates the list of known servers + * @param list of servers from a recent query + */ + void updateServers(const QList &servers); + + /** + * @brief add a plugin that is to be blacklisted + * @param fileName name of the plugin to blacklist + */ + void addBlacklistPlugin(const QString &fileName); + + /** + * @brief test if a plugin is blacklisted and shouldn't be loaded + * @param fileName name of the plugin + * @return true if the file is blacklisted + */ + bool pluginBlacklisted(const QString &fileName) const; + + /** + * @return all loaded MO plugins + */ + std::vector plugins() const { return m_Plugins; } + + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); + +private: + + QString obfuscate(const QString &password) const; + QString deObfuscate(const QString &password) const; + + void addLanguages(QComboBox *languageBox); + void addStyles(QComboBox *styleBox); + bool isNXMHandler(bool *modifyable); + void setNXMHandlerActive(bool active, bool writable); + void readPluginBlacklist(); + void writePluginBlacklist(); + +private slots: + + void resetDialogs(); + +signals: + + void languageChanged(const QString &newLanguage); + void styleChanged(const QString &newStyle); + +private: + + static Settings *s_Instance; + + QSettings m_Settings; + + LoadMechanism m_LoadMechanism; + + std::vector m_Plugins; + + QMap > m_PluginSettings; + + QSet m_PluginBlacklist; + +}; + +#endif // WORKAROUNDS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index bf1f95dc..a7d44d72 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,173 +1,173 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "settingsdialog.h" -#include "ui_settingsdialog.h" -#include "categoriesdialog.h" -#include "helper.h" -#include "noeditdelegate.h" -#include -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include "settings.h" - - -using namespace MOBase; -using namespace MOShared; - - -SettingsDialog::SettingsDialog(QWidget *parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) -{ - ui->setupUi(this); - - QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -void SettingsDialog::addPlugins(const std::vector &plugins) -{ - foreach (IPlugin *plugin, plugins) { - ui->pluginsList->addItem(plugin->name()); - } -} - -void SettingsDialog::accept() -{ - storeSettings(ui->pluginsList->currentItem()); - TutorableDialog::accept(); -} - - -void SettingsDialog::on_loginCheckBox_toggled(bool checked) -{ - QLineEdit *usernameEdit = findChild("usernameEdit"); - QLineEdit *passwordEdit = findChild("passwordEdit"); - if (checked) { - passwordEdit->setEnabled(true); - usernameEdit->setEnabled(true); - } else { - passwordEdit->setEnabled(false); - usernameEdit->setEnabled(false); - } -} - -void SettingsDialog::on_categoriesBtn_clicked() -{ - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - -void SettingsDialog::on_bsaDateBtn_clicked() -{ - Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); -} - -void SettingsDialog::on_browseDownloadDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->downloadDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseModDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->modDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseCacheDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); - if (!temp.isEmpty()) { - ui->cacheDirEdit->setText(temp); - } -} - -void SettingsDialog::on_resetDialogsButton_clicked() -{ - if (QMessageBox::question(this, tr("Confirm?"), - tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit resetDialogs(); - } -} - -void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) -{ - if (pluginItem != NULL) { - QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); - - for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { - const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); - settings[item->text(0)] = item->data(1, Qt::DisplayRole); - } - - pluginItem->setData(Qt::UserRole + 1, settings); - } -} - -void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - storeSettings(previous); - - ui->pluginSettingsList->clear(); - IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QMap settings = current->data(Qt::UserRole + 1).toMap(); - ui->pluginSettingsList->setEnabled(settings.count() != 0); - for (auto iter = settings.begin(); iter != settings.end(); ++iter) { - QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); - QVariant value = *iter; - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); - } -} - -void SettingsDialog::deleteBlacklistItem() -{ - ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); -} - -void SettingsDialog::on_associateButton_clicked() -{ - Settings::instance().registerAsNXMHandler(true); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "settingsdialog.h" +#include "ui_settingsdialog.h" +#include "categoriesdialog.h" +#include "helper.h" +#include "noeditdelegate.h" +#include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include "settings.h" + + +using namespace MOBase; +using namespace MOShared; + + +SettingsDialog::SettingsDialog(QWidget *parent) + : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::addPlugins(const std::vector &plugins) +{ + foreach (IPlugin *plugin, plugins) { + ui->pluginsList->addItem(plugin->name()); + } +} + +void SettingsDialog::accept() +{ + storeSettings(ui->pluginsList->currentItem()); + TutorableDialog::accept(); +} + + +void SettingsDialog::on_loginCheckBox_toggled(bool checked) +{ + QLineEdit *usernameEdit = findChild("usernameEdit"); + QLineEdit *passwordEdit = findChild("passwordEdit"); + if (checked) { + passwordEdit->setEnabled(true); + usernameEdit->setEnabled(true); + } else { + passwordEdit->setEnabled(false); + usernameEdit->setEnabled(false); + } +} + +void SettingsDialog::on_categoriesBtn_clicked() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void SettingsDialog::on_bsaDateBtn_clicked() +{ + Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); +} + +void SettingsDialog::on_browseDownloadDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->downloadDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseModDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->modDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseCacheDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); + if (!temp.isEmpty()) { + ui->cacheDirEdit->setText(temp); + } +} + +void SettingsDialog::on_resetDialogsButton_clicked() +{ + if (QMessageBox::question(this, tr("Confirm?"), + tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit resetDialogs(); + } +} + +void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) +{ + if (pluginItem != NULL) { + QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); + + for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { + const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); + settings[item->text(0)] = item->data(1, Qt::DisplayRole); + } + + pluginItem->setData(Qt::UserRole + 1, settings); + } +} + +void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + storeSettings(previous); + + ui->pluginSettingsList->clear(); + IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QMap settings = current->data(Qt::UserRole + 1).toMap(); + ui->pluginSettingsList->setEnabled(settings.count() != 0); + for (auto iter = settings.begin(); iter != settings.end(); ++iter) { + QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); + QVariant value = *iter; + + ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); + newItem->setData(1, Qt::DisplayRole, value); + newItem->setData(1, Qt::EditRole, value); + + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui->pluginSettingsList->addTopLevelItem(newItem); + } +} + +void SettingsDialog::deleteBlacklistItem() +{ + ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +} + +void SettingsDialog::on_associateButton_clicked() +{ + Settings::instance().registerAsNXMHandler(true); +} diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 0bd9fd23..e99fb3e3 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -1,84 +1,84 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef WORKAROUNDDIALOG_H -#define WORKAROUNDDIALOG_H - -#include "tutorabledialog.h" -#include -#include -#include - -namespace Ui { - class SettingsDialog; -} - -/** - * dialog used to change settings for Mod Organizer. On top of the - * settings managed by the "Settings" class, this offers a button to open the - * CategoriesDialog - **/ -class SettingsDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit SettingsDialog(QWidget *parent = 0); - ~SettingsDialog(); - - void addPlugins(const std::vector &plugins); - -public slots: - - virtual void accept(); - -signals: - - void resetDialogs(); - -private: - - void storeSettings(QListWidgetItem *pluginItem); - -private slots: - void on_loginCheckBox_toggled(bool checked); - - void on_categoriesBtn_clicked(); - - void on_bsaDateBtn_clicked(); - - void on_browseDownloadDirBtn_clicked(); - - void on_browseModDirBtn_clicked(); - - void on_browseCacheDirBtn_clicked(); - - void on_resetDialogsButton_clicked(); - - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void deleteBlacklistItem(); - - void on_associateButton_clicked(); - -private: - Ui::SettingsDialog *ui; -}; - -#endif // WORKAROUNDDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef WORKAROUNDDIALOG_H +#define WORKAROUNDDIALOG_H + +#include "tutorabledialog.h" +#include +#include +#include + +namespace Ui { + class SettingsDialog; +} + +/** + * dialog used to change settings for Mod Organizer. On top of the + * settings managed by the "Settings" class, this offers a button to open the + * CategoriesDialog + **/ +class SettingsDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(QWidget *parent = 0); + ~SettingsDialog(); + + void addPlugins(const std::vector &plugins); + +public slots: + + virtual void accept(); + +signals: + + void resetDialogs(); + +private: + + void storeSettings(QListWidgetItem *pluginItem); + +private slots: + void on_loginCheckBox_toggled(bool checked); + + void on_categoriesBtn_clicked(); + + void on_bsaDateBtn_clicked(); + + void on_browseDownloadDirBtn_clicked(); + + void on_browseModDirBtn_clicked(); + + void on_browseCacheDirBtn_clicked(); + + void on_resetDialogsButton_clicked(); + + void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + + void deleteBlacklistItem(); + + void on_associateButton_clicked(); + +private: + Ui::SettingsDialog *ui; +}; + +#endif // WORKAROUNDDIALOG_H diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 3f96bff1..f1bad828 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -30,4 +30,4 @@ namespace MOShared { #undef APPPARAM } -} // namespace MOShared +} // namespace MOShared diff --git a/src/shared/inject.h b/src/shared/inject.h index d5030363..7f4f2962 100644 --- a/src/shared/inject.h +++ b/src/shared/inject.h @@ -1,31 +1,31 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#pragma once - -#define WIN32_LEAN_AND_MEAN -#include - -#include - -namespace MOShared { - -void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); - -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +#include + +namespace MOShared { + +void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); + +} diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index 2befab70..a1928523 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -1,107 +1,107 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "singleinstance.h" -#include "report.h" -#include "utility.h" -#include - -static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; -static const int s_Timeout = 5000; - - -SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : - QObject(parent), m_PrimaryInstance(false) -{ - m_SharedMem.setKey(s_Key); - if (!m_SharedMem.create(1)) { - if (forcePrimary) { - while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - Sleep(500); - if (m_SharedMem.create(1)) { - m_PrimaryInstance = true; - break; - } - } - } - - if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - m_SharedMem.attach(); - m_PrimaryInstance = false; - } - if ((m_SharedMem.error() != QSharedMemory::NoError) && - (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { - throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); - } - } else { - m_PrimaryInstance = true; - } - if (m_PrimaryInstance) { - connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); - m_Server.listen(s_Key); - } -} - - -void SingleInstance::sendMessage(const QString &message) -{ - if (m_PrimaryInstance) { - // nobody there to receive the message - return; - } - QLocalSocket socket(this); - - bool connected = false; - for(int i = 0; i < 2 && !connected; ++i) { - if (i > 0) { - Sleep(250); - } - - // other instance may be just starting up - socket.connectToServer(s_Key, QIODevice::WriteOnly); - connected = socket.waitForConnected(s_Timeout); - } - - if (!connected) { - reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); - return; - } - - socket.write(message.toUtf8()); - if (!socket.waitForBytesWritten(s_Timeout)) { - reportError(tr("failed to communicate with running instance: %1").arg(socket.errorString())); - return; - } - - socket.disconnectFromServer(); -} - - -void SingleInstance::receiveMessage() -{ - QLocalSocket *socket = m_Server.nextPendingConnection(); - if (!socket->waitForReadyRead(s_Timeout)) { - reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); - return; - } - - QString message = QString::fromUtf8(socket->readAll().constData()); - emit messageSent(message); - socket->disconnectFromServer(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "singleinstance.h" +#include "report.h" +#include "utility.h" +#include + +static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; +static const int s_Timeout = 5000; + + +SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : + QObject(parent), m_PrimaryInstance(false) +{ + m_SharedMem.setKey(s_Key); + if (!m_SharedMem.create(1)) { + if (forcePrimary) { + while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + Sleep(500); + if (m_SharedMem.create(1)) { + m_PrimaryInstance = true; + break; + } + } + } + + if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + m_SharedMem.attach(); + m_PrimaryInstance = false; + } + if ((m_SharedMem.error() != QSharedMemory::NoError) && + (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { + throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); + } + } else { + m_PrimaryInstance = true; + } + if (m_PrimaryInstance) { + connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); + m_Server.listen(s_Key); + } +} + + +void SingleInstance::sendMessage(const QString &message) +{ + if (m_PrimaryInstance) { + // nobody there to receive the message + return; + } + QLocalSocket socket(this); + + bool connected = false; + for(int i = 0; i < 2 && !connected; ++i) { + if (i > 0) { + Sleep(250); + } + + // other instance may be just starting up + socket.connectToServer(s_Key, QIODevice::WriteOnly); + connected = socket.waitForConnected(s_Timeout); + } + + if (!connected) { + reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); + return; + } + + socket.write(message.toUtf8()); + if (!socket.waitForBytesWritten(s_Timeout)) { + reportError(tr("failed to communicate with running instance: %1").arg(socket.errorString())); + return; + } + + socket.disconnectFromServer(); +} + + +void SingleInstance::receiveMessage() +{ + QLocalSocket *socket = m_Server.nextPendingConnection(); + if (!socket->waitForReadyRead(s_Timeout)) { + reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + return; + } + + QString message = QString::fromUtf8(socket->readAll().constData()); + emit messageSent(message); + socket->disconnectFromServer(); +} diff --git a/src/singleinstance.h b/src/singleinstance.h index cc95bf48..14d49d6d 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + #ifndef SINGLEINSTANCE_H #define SINGLEINSTANCE_H diff --git a/src/spawn.cpp b/src/spawn.cpp index d314a326..8cc5abe1 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,211 +1,211 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "spawn.h" -#include "report.h" -#include "utility.h" -#include -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -static const int BUFSIZE = 4096; - -bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, - HANDLE stdOut, HANDLE stdErr, - HANDLE& processHandle, HANDLE& threadHandle) -{ - BOOL inheritHandles = FALSE; - STARTUPINFO si; - ::ZeroMemory(&si, sizeof(si)); - if (stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = stdOut; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - if (stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - si.cb = sizeof(si); - int length = wcslen(binary) + wcslen(arguments) + 4; - wchar_t *commandLine = NULL; - if (arguments[0] != L'\0') { - commandLine = new wchar_t[length]; - _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); - } else { - commandLine = new wchar_t[length]; - _snwprintf(commandLine, length, L"\"%ls\"", binary); - } - - QString moPath = QCoreApplication::applicationDirPath(); - - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } - - { - boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]); - _tcsncpy(newPath.get(), oldPath.get(), offset - 1); - newPath.get()[offset - 1] = L'\0'; - _tcsncat(newPath.get(), TEXT(";"), 1); - _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length()); - - ::SetEnvironmentVariable(TEXT("PATH"), newPath.get()); - } - - PROCESS_INFORMATION pi; - BOOL success = ::CreateProcess(NULL, - commandLine, - NULL, NULL, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL - NULL, // same environment as parent - currentDirectory, // current directory - &si, &pi // startup and process information - ); - - ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get()); - - delete [] commandLine; - - if (!success) { - throw windows_error("failed to start process"); - } - - processHandle = pi.hProcess; - threadHandle = pi.hThread; - return true; -} - - -HANDLE startBinary(const QFileInfo &binary, - const QString &arguments, - const QString& profileName, - int logLevel, - const QDir ¤tDirectory, - bool hooked, - HANDLE stdOut, - HANDLE stdErr) -{ - HANDLE jobObject = ::CreateJobObject(NULL, NULL); - - if (jobObject == NULL) { - qWarning("failed to create job object: %lu", ::GetLastError()); - } - - HANDLE processHandle, threadHandle; - std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); - std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); - - try { - if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), true, - stdOut, stdErr, processHandle, threadHandle)) { - reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - } catch (const windows_error &e) { - if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { - // TODO: check if this is really correct. Are all settings updated that the secondary instance may use? - - if (QMessageBox::question(NULL, QObject::tr("Elevation required"), - QObject::tr("This process requires elevation to run.\n" - "This is a potential security risk so I highly advice you to investigate if\n" - "\"%1\"\n" - "can be installed to work without elevation.\n\n" - "Start elevated anyway? " - "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)").arg( - QDir::toNativeSeparators(binary.absoluteFilePath())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - ::ShellExecuteW(NULL, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(), - (std::wstring(L"\"") + binaryName + L"\" " + ToWString(arguments)).c_str(), currentDirectoryName.c_str(), SW_SHOWNORMAL); - return INVALID_HANDLE_VALUE; - } else { - return INVALID_HANDLE_VALUE; - } - } else { - reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); - return INVALID_HANDLE_VALUE; - } - } - - if (hooked) { - try { - QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName())); - if (!dllInfo.exists()) { - reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName())); - return INVALID_HANDLE_VALUE; - } - injectDLL(processHandle, threadHandle, - QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(), - ToWString(profileName).c_str(), logLevel); - } catch (const windows_error& e) { - reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what())); - ::TerminateProcess(processHandle, 1); - return INVALID_HANDLE_VALUE; - } -#ifdef _DEBUG - reportError("ready?"); -#endif // DEBUG - } - - if (::AssignProcessToJobObject(jobObject, processHandle) == 0) { - qWarning("failed to assign to job object: %lu", ::GetLastError()); - ::CloseHandle(jobObject); - jobObject = processHandle; - } else { - ::CloseHandle(processHandle); - } - - if (::ResumeThread(threadHandle) == (DWORD)-1) { - reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - ::CloseHandle(threadHandle); - return jobObject; -} - -/* -ExitProxy *ExitProxy::s_Instance = NULL; - -ExitProxy *ExitProxy::instance() -{ - if (s_Instance == NULL) { - s_Instance = new ExitProxy(); - } - return s_Instance; -} - -void ExitProxy::emitExit() -{ - emit exit(); -}*/ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "spawn.h" +#include "report.h" +#include "utility.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +static const int BUFSIZE = 4096; + +bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, + HANDLE stdOut, HANDLE stdErr, + HANDLE& processHandle, HANDLE& threadHandle) +{ + BOOL inheritHandles = FALSE; + STARTUPINFO si; + ::ZeroMemory(&si, sizeof(si)); + if (stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + if (stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + si.cb = sizeof(si); + int length = wcslen(binary) + wcslen(arguments) + 4; + wchar_t *commandLine = NULL; + if (arguments[0] != L'\0') { + commandLine = new wchar_t[length]; + _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); + } else { + commandLine = new wchar_t[length]; + _snwprintf(commandLine, length, L"\"%ls\"", binary); + } + + QString moPath = QCoreApplication::applicationDirPath(); + + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); + DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); + if (offset > BUFSIZE) { + oldPath.reset(new TCHAR[offset]); + ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + } + + { + boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]); + _tcsncpy(newPath.get(), oldPath.get(), offset - 1); + newPath.get()[offset - 1] = L'\0'; + _tcsncat(newPath.get(), TEXT(";"), 1); + _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length()); + + ::SetEnvironmentVariable(TEXT("PATH"), newPath.get()); + } + + PROCESS_INFORMATION pi; + BOOL success = ::CreateProcess(NULL, + commandLine, + NULL, NULL, // no special process or thread attributes + inheritHandles, // inherit handles if we plan to use stdout or stderr reroute + CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL + NULL, // same environment as parent + currentDirectory, // current directory + &si, &pi // startup and process information + ); + + ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get()); + + delete [] commandLine; + + if (!success) { + throw windows_error("failed to start process"); + } + + processHandle = pi.hProcess; + threadHandle = pi.hThread; + return true; +} + + +HANDLE startBinary(const QFileInfo &binary, + const QString &arguments, + const QString& profileName, + int logLevel, + const QDir ¤tDirectory, + bool hooked, + HANDLE stdOut, + HANDLE stdErr) +{ + HANDLE jobObject = ::CreateJobObject(NULL, NULL); + + if (jobObject == NULL) { + qWarning("failed to create job object: %lu", ::GetLastError()); + } + + HANDLE processHandle, threadHandle; + std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); + std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); + + try { + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), true, + stdOut, stdErr, processHandle, threadHandle)) { + reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + } catch (const windows_error &e) { + if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { + // TODO: check if this is really correct. Are all settings updated that the secondary instance may use? + + if (QMessageBox::question(NULL, QObject::tr("Elevation required"), + QObject::tr("This process requires elevation to run.\n" + "This is a potential security risk so I highly advice you to investigate if\n" + "\"%1\"\n" + "can be installed to work without elevation.\n\n" + "Start elevated anyway? " + "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)").arg( + QDir::toNativeSeparators(binary.absoluteFilePath())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + ::ShellExecuteW(NULL, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(), + (std::wstring(L"\"") + binaryName + L"\" " + ToWString(arguments)).c_str(), currentDirectoryName.c_str(), SW_SHOWNORMAL); + return INVALID_HANDLE_VALUE; + } else { + return INVALID_HANDLE_VALUE; + } + } else { + reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); + return INVALID_HANDLE_VALUE; + } + } + + if (hooked) { + try { + QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName())); + if (!dllInfo.exists()) { + reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName())); + return INVALID_HANDLE_VALUE; + } + injectDLL(processHandle, threadHandle, + QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(), + ToWString(profileName).c_str(), logLevel); + } catch (const windows_error& e) { + reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what())); + ::TerminateProcess(processHandle, 1); + return INVALID_HANDLE_VALUE; + } +#ifdef _DEBUG + reportError("ready?"); +#endif // DEBUG + } + + if (::AssignProcessToJobObject(jobObject, processHandle) == 0) { + qWarning("failed to assign to job object: %lu", ::GetLastError()); + ::CloseHandle(jobObject); + jobObject = processHandle; + } else { + ::CloseHandle(processHandle); + } + + if (::ResumeThread(threadHandle) == (DWORD)-1) { + reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + ::CloseHandle(threadHandle); + return jobObject; +} + +/* +ExitProxy *ExitProxy::s_Instance = NULL; + +ExitProxy *ExitProxy::instance() +{ + if (s_Instance == NULL) { + s_Instance = new ExitProxy(); + } + return s_Instance; +} + +void ExitProxy::emitExit() +{ + emit exit(); +}*/ diff --git a/src/spawn.h b/src/spawn.h index 3f037119..63eda18d 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,68 +1,68 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SPAWN_H -#define SPAWN_H - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - - -/** - * @brief a dirty little trick so we can issue a clean restart from startBinary - * @note unused - */ -/*class ExitProxy : public QObject { - Q_OBJECT -public: - static ExitProxy *instance(); - void emitExit(); -signals: - void exit(); -private: - ExitProxy() {} -private: - static ExitProxy *s_Instance; -};*/ - - -/** - * @brief spawn a binary with Mod Organizer injected - * - * @param binary the binary to spawn - * @param arguments arguments to pass to the binary - * @param profileName name of the active profile - * @param currentDirectory the directory to use as the working directory to run in - * @param logLevel log level to be used by the hook library. Ignored if hooked is false - * @param hooked if set, the binary is started with mo injected - * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process - * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process - * @return the process handle - * @todo is the profile name even used any more? - * @todo is the hooked parameter used? - **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, - const QDir ¤tDirectory, bool hooked, - HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); - -#endif // SPAWN_H - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SPAWN_H +#define SPAWN_H + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + + +/** + * @brief a dirty little trick so we can issue a clean restart from startBinary + * @note unused + */ +/*class ExitProxy : public QObject { + Q_OBJECT +public: + static ExitProxy *instance(); + void emitExit(); +signals: + void exit(); +private: + ExitProxy() {} +private: + static ExitProxy *s_Instance; +};*/ + + +/** + * @brief spawn a binary with Mod Organizer injected + * + * @param binary the binary to spawn + * @param arguments arguments to pass to the binary + * @param profileName name of the active profile + * @param currentDirectory the directory to use as the working directory to run in + * @param logLevel log level to be used by the hook library. Ignored if hooked is false + * @param hooked if set, the binary is started with mo injected + * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process + * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process + * @return the process handle + * @todo is the profile name even used any more? + * @todo is the hooked parameter used? + **/ +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, + const QDir ¤tDirectory, bool hooked, + HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); + +#endif // SPAWN_H + diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 32b76c97..380719d8 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -1,166 +1,166 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "syncoverwritedialog.h" -#include "ui_syncoverwritedialog.h" -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent) - : TutorableDialog("SyncOverwrite", parent), - ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure) -{ - ui->setupUi(this); - refresh(path); - - QHeaderView *headerView = ui->syncTree->header(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); -#else - headerView->setResizeMode(0, QHeaderView::Stretch); - headerView->setResizeMode(1, QHeaderView::Interactive); -#endif -} - - -SyncOverwriteDialog::~SyncOverwriteDialog() -{ - delete ui; -} - - -static void addToComboBox(QComboBox *box, const QString &name, const QVariant &userData) -{ - if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) { - box->addItem(name, userData); - } -} - - -void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree) -{ - QDir overwrite(path); - overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); - QDirIterator dirIter(overwrite); - while (dirIter.hasNext()) { - dirIter.next(); - QFileInfo fileInfo = dirIter.fileInfo(); - - QString file = fileInfo.fileName(); - if (file == "meta.ini") { - continue; - } - - QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file)); - - if (fileInfo.isDir()) { - DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file)); - if (subDir != NULL) { - readTree(fileInfo.absoluteFilePath(), subDir, newItem); - } else { - qCritical("no directory structure for %s?", file.toUtf8().constData()); - delete newItem; - newItem = NULL; - } - } else { - const FileEntry::Ptr entry = directoryStructure->findFile(ToWString(file)); - QComboBox* combo = new QComboBox(ui->syncTree); - combo->addItem(tr(""), -1); - if (entry.get() != NULL) { - bool ignore; - int origin = entry->getOrigin(ignore); - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin); - const std::vector &alternatives = entry->getAlternatives(); - for (std::vector::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter); - } - combo->setCurrentIndex(combo->count() - 1); - } else { - combo->setCurrentIndex(0); - } - ui->syncTree->setItemWidget(newItem, 1, combo); - } - if (newItem != NULL) { - subTree->addChild(newItem); - } - } -} - - -void SyncOverwriteDialog::refresh(const QString &path) -{ - QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("")); - readTree(path, m_DirectoryStructure, rootItem); - ui->syncTree->addTopLevelItem(rootItem); - ui->syncTree->expandAll(); -} - - -void SyncOverwriteDialog::applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory) -{ - for (int i = 0; i < item->childCount(); ++i) { - QTreeWidgetItem *child = item->child(i); - QString filePath; - if (path.length() != 0) { - filePath = path.mid(0).append("/").append(child->text(0)); - } else { - filePath = child->text(0); - } - if (child->childCount() != 0) { - applyTo(child, filePath, modDirectory); - } else { - QComboBox *comboBox = qobject_cast(ui->syncTree->itemWidget(child, 1)); - if (comboBox != NULL) { - int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); - if (originID != -1) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); - QString source = m_SourcePath.mid(0).append("/").append(filePath); - QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath); - if (!QFile::remove(destination)) { - reportError(tr("failed to remove %1").arg(destination)); - } else if (!QFile::rename(source, destination)) { - reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); - } - } - } - } - } - - QDir dir(m_SourcePath + "/" + path); - if ((path.length() > 0) && (dir.count() == 2)) { - dir.rmpath("."); - } -} - - -void SyncOverwriteDialog::apply(const QString &modDirectory) -{ - applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "syncoverwritedialog.h" +#include "ui_syncoverwritedialog.h" +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent) + : TutorableDialog("SyncOverwrite", parent), + ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure) +{ + ui->setupUi(this); + refresh(path); + + QHeaderView *headerView = ui->syncTree->header(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + headerView->setSectionResizeMode(0, QHeaderView::Stretch); + headerView->setSectionResizeMode(1, QHeaderView::Interactive); +#else + headerView->setResizeMode(0, QHeaderView::Stretch); + headerView->setResizeMode(1, QHeaderView::Interactive); +#endif +} + + +SyncOverwriteDialog::~SyncOverwriteDialog() +{ + delete ui; +} + + +static void addToComboBox(QComboBox *box, const QString &name, const QVariant &userData) +{ + if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) { + box->addItem(name, userData); + } +} + + +void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree) +{ + QDir overwrite(path); + overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + QDirIterator dirIter(overwrite); + while (dirIter.hasNext()) { + dirIter.next(); + QFileInfo fileInfo = dirIter.fileInfo(); + + QString file = fileInfo.fileName(); + if (file == "meta.ini") { + continue; + } + + QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file)); + + if (fileInfo.isDir()) { + DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file)); + if (subDir != NULL) { + readTree(fileInfo.absoluteFilePath(), subDir, newItem); + } else { + qCritical("no directory structure for %s?", file.toUtf8().constData()); + delete newItem; + newItem = NULL; + } + } else { + const FileEntry::Ptr entry = directoryStructure->findFile(ToWString(file)); + QComboBox* combo = new QComboBox(ui->syncTree); + combo->addItem(tr(""), -1); + if (entry.get() != NULL) { + bool ignore; + int origin = entry->getOrigin(ignore); + addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin); + const std::vector &alternatives = entry->getAlternatives(); + for (std::vector::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter); + } + combo->setCurrentIndex(combo->count() - 1); + } else { + combo->setCurrentIndex(0); + } + ui->syncTree->setItemWidget(newItem, 1, combo); + } + if (newItem != NULL) { + subTree->addChild(newItem); + } + } +} + + +void SyncOverwriteDialog::refresh(const QString &path) +{ + QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("")); + readTree(path, m_DirectoryStructure, rootItem); + ui->syncTree->addTopLevelItem(rootItem); + ui->syncTree->expandAll(); +} + + +void SyncOverwriteDialog::applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory) +{ + for (int i = 0; i < item->childCount(); ++i) { + QTreeWidgetItem *child = item->child(i); + QString filePath; + if (path.length() != 0) { + filePath = path.mid(0).append("/").append(child->text(0)); + } else { + filePath = child->text(0); + } + if (child->childCount() != 0) { + applyTo(child, filePath, modDirectory); + } else { + QComboBox *comboBox = qobject_cast(ui->syncTree->itemWidget(child, 1)); + if (comboBox != NULL) { + int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); + if (originID != -1) { + FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); + QString source = m_SourcePath.mid(0).append("/").append(filePath); + QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath); + if (!QFile::remove(destination)) { + reportError(tr("failed to remove %1").arg(destination)); + } else if (!QFile::rename(source, destination)) { + reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); + } + } + } + } + } + + QDir dir(m_SourcePath + "/" + path); + if ((path.length() > 0) && (dir.count() == 2)) { + dir.rmpath("."); + } +} + + +void SyncOverwriteDialog::apply(const QString &modDirectory) +{ + applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); +} diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h index 7fbd876e..79b30a8d 100644 --- a/src/syncoverwritedialog.h +++ b/src/syncoverwritedialog.h @@ -1,55 +1,55 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef SYNCOVERWRITEDIALOG_H -#define SYNCOVERWRITEDIALOG_H - - -#include "tutorabledialog.h" -#include -#include - - -namespace Ui { -class SyncOverwriteDialog; -} - -class SyncOverwriteDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0); - ~SyncOverwriteDialog(); - - void apply(const QString &modDirectory); -private: - void refresh(const QString &path); - void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree); - void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory); - -private: - - Ui::SyncOverwriteDialog *ui; - QString m_SourcePath; - MOShared::DirectoryEntry *m_DirectoryStructure; - -}; - -#endif // SYNCOVERWRITEDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef SYNCOVERWRITEDIALOG_H +#define SYNCOVERWRITEDIALOG_H + + +#include "tutorabledialog.h" +#include +#include + + +namespace Ui { +class SyncOverwriteDialog; +} + +class SyncOverwriteDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0); + ~SyncOverwriteDialog(); + + void apply(const QString &modDirectory); +private: + void refresh(const QString &path); + void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree); + void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory); + +private: + + Ui::SyncOverwriteDialog *ui; + QString m_SourcePath; + MOShared::DirectoryEntry *m_DirectoryStructure; + +}; + +#endif // SYNCOVERWRITEDIALOG_H diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 8e2b9b59..b07b1673 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -1,328 +1,328 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "transfersavesdialog.h" -#include "ui_transfersavesdialog.h" -#include "utility.h" -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent) - : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile) -{ - ui->setupUi(this); - refreshGlobalSaves(); - refreshLocalSaves(); - refreshGlobalCharacters(); - refreshLocalCharacters(); -} - -TransferSavesDialog::~TransferSavesDialog() -{ - delete ui; -} - - -void TransferSavesDialog::refreshGlobalSaves() -{ - m_GlobalSaves.clear(); - - QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()))); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QStringList files = savesDir.entryList(QDir::Files, QDir::Time); - - foreach (const QString &filename, files) { - SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); - save->setParent(this); - m_GlobalSaves.push_back(save); - } -} - - -void TransferSavesDialog::refreshLocalSaves() -{ - m_LocalSaves.clear(); - - QDir savesDir(m_Profile.getPath().append("/saves")); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QStringList files = savesDir.entryList(QDir::Files, QDir::Time); - - foreach (const QString &filename, files) { - SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); - save->setParent(this); - m_LocalSaves.push_back(save); - } -} - - -void TransferSavesDialog::refreshGlobalCharacters() -{ - std::set characters; - for (std::vector::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->globalCharacterList->clear(); - for (std::set::const_iterator iter = characters.begin(); - iter != characters.end(); ++iter) { - ui->globalCharacterList->addItem(*iter); - } - if (ui->globalCharacterList->count() > 0) { - ui->globalCharacterList->setCurrentRow(0); - ui->copyToLocalBtn->setEnabled(true); - ui->moveToLocalBtn->setEnabled(true); - } else { - ui->copyToLocalBtn->setEnabled(false); - ui->moveToLocalBtn->setEnabled(false); - } -} - - -void TransferSavesDialog::refreshLocalCharacters() -{ - std::set characters; - for (std::vector::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->localCharacterList->clear(); - for (std::set::const_iterator iter = characters.begin(); - iter != characters.end(); ++iter) { - ui->localCharacterList->addItem(*iter); - } - if (ui->localCharacterList->count() > 0) { - ui->localCharacterList->setCurrentRow(0); - ui->copyToGlobalBtn->setEnabled(true); - ui->moveToGlobalBtn->setEnabled(true); - } else { - ui->copyToGlobalBtn->setEnabled(false); - ui->moveToGlobalBtn->setEnabled(false); - } -} - - -bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile) -{ - QMessageBox::StandardButton res = overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No; - if (overwriteMode == OVERWRITE_ASK) { - res = QMessageBox::question(this, tr("Overwrite"), - tr("Overwrite the file \"%1\"").arg(destinationFile), - QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll); - if (res == QMessageBox::YesToAll) { - overwriteMode = OVERWRITE_YES; - res = QMessageBox::Yes; - } else if (res == QMessageBox::NoToAll) { - overwriteMode = OVERWRITE_NO; - res = QMessageBox::No; - } - } - return res == QMessageBox::Yes; -} - -void TransferSavesDialog::on_moveToLocalBtn_clicked() -{ - QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QString destination = m_Profile.getPath().append("/saves"); - OverwriteMode overwriteMode = OVERWRITE_ASK; - - for (std::vector::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to move %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_copyToLocalBtn_clicked() -{ - QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QString destination = m_Profile.getPath().append("/saves"); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to copy %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_moveToGlobalBtn_clicked() -{ - QString selectedCharacter = ui->localCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Move all save games of character \"%1\" to the global location? Please be aware " - "that this will mess up the running number of save games.").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to move %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_copyToGlobalBtn_clicked() -{ - QString selectedCharacter = ui->localCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the global location? Please be aware " - "that this will mess up the running number of save games.").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to copy %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); -} - -void TransferSavesDialog::on_doneButton_clicked() -{ - close(); -} - -void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->globalSavesList->clear(); - for (std::vector::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == currentText) { - ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); - } - } -} - -void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->localSavesList->clear(); - for (std::vector::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == currentText) { - ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); - } - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "transfersavesdialog.h" +#include "ui_transfersavesdialog.h" +#include "utility.h" +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent) + : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile) +{ + ui->setupUi(this); + refreshGlobalSaves(); + refreshLocalSaves(); + refreshGlobalCharacters(); + refreshLocalCharacters(); +} + +TransferSavesDialog::~TransferSavesDialog() +{ + delete ui; +} + + +void TransferSavesDialog::refreshGlobalSaves() +{ + m_GlobalSaves.clear(); + + QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()))); + + QStringList filters; + filters << ToQString(GameInfo::instance().getSaveGameExtension()); + savesDir.setNameFilters(filters); + + QStringList files = savesDir.entryList(QDir::Files, QDir::Time); + + foreach (const QString &filename, files) { + SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); + save->setParent(this); + m_GlobalSaves.push_back(save); + } +} + + +void TransferSavesDialog::refreshLocalSaves() +{ + m_LocalSaves.clear(); + + QDir savesDir(m_Profile.getPath().append("/saves")); + + QStringList filters; + filters << ToQString(GameInfo::instance().getSaveGameExtension()); + savesDir.setNameFilters(filters); + + QStringList files = savesDir.entryList(QDir::Files, QDir::Time); + + foreach (const QString &filename, files) { + SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); + save->setParent(this); + m_LocalSaves.push_back(save); + } +} + + +void TransferSavesDialog::refreshGlobalCharacters() +{ + std::set characters; + for (std::vector::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->globalCharacterList->clear(); + for (std::set::const_iterator iter = characters.begin(); + iter != characters.end(); ++iter) { + ui->globalCharacterList->addItem(*iter); + } + if (ui->globalCharacterList->count() > 0) { + ui->globalCharacterList->setCurrentRow(0); + ui->copyToLocalBtn->setEnabled(true); + ui->moveToLocalBtn->setEnabled(true); + } else { + ui->copyToLocalBtn->setEnabled(false); + ui->moveToLocalBtn->setEnabled(false); + } +} + + +void TransferSavesDialog::refreshLocalCharacters() +{ + std::set characters; + for (std::vector::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + characters.insert((*iter)->pcName()); + } + ui->localCharacterList->clear(); + for (std::set::const_iterator iter = characters.begin(); + iter != characters.end(); ++iter) { + ui->localCharacterList->addItem(*iter); + } + if (ui->localCharacterList->count() > 0) { + ui->localCharacterList->setCurrentRow(0); + ui->copyToGlobalBtn->setEnabled(true); + ui->moveToGlobalBtn->setEnabled(true); + } else { + ui->copyToGlobalBtn->setEnabled(false); + ui->moveToGlobalBtn->setEnabled(false); + } +} + + +bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile) +{ + QMessageBox::StandardButton res = overwriteMode == OVERWRITE_YES ? QMessageBox::Yes : QMessageBox::No; + if (overwriteMode == OVERWRITE_ASK) { + res = QMessageBox::question(this, tr("Overwrite"), + tr("Overwrite the file \"%1\"").arg(destinationFile), + QMessageBox::Yes | QMessageBox::No | QMessageBox::YesToAll | QMessageBox::NoToAll); + if (res == QMessageBox::YesToAll) { + overwriteMode = OVERWRITE_YES; + res = QMessageBox::Yes; + } else if (res == QMessageBox::NoToAll) { + overwriteMode = OVERWRITE_NO; + res = QMessageBox::No; + } + } + return res == QMessageBox::Yes; +} + +void TransferSavesDialog::on_moveToLocalBtn_clicked() +{ + QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QString destination = m_Profile.getPath().append("/saves"); + OverwriteMode overwriteMode = OVERWRITE_ASK; + + for (std::vector::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = (*iter)->saveFiles(); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to move %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_copyToLocalBtn_clicked() +{ + QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QString destination = m_Profile.getPath().append("/saves"); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = (*iter)->saveFiles(); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to copy %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_moveToGlobalBtn_clicked() +{ + QString selectedCharacter = ui->localCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Move all save games of character \"%1\" to the global location? Please be aware " + "that this will mess up the running number of save games.").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = (*iter)->saveFiles(); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to move %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); +} + +void TransferSavesDialog::on_copyToGlobalBtn_clicked() +{ + QString selectedCharacter = ui->localCharacterList->currentItem()->text(); + if (QMessageBox::question(this, tr("Confirm"), + tr("Copy all save games of character \"%1\" to the global location? Please be aware " + "that this will mess up the running number of save games.").arg(selectedCharacter), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + + QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); + OverwriteMode overwriteMode = OVERWRITE_ASK; + for (std::vector::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == selectedCharacter) { + QStringList files = (*iter)->saveFiles(); + foreach (const QString &file, files) { + QFileInfo fileInfo(file); + QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); + if (QFile::exists(destinationFile)) { + if (testOverwrite(overwriteMode, destinationFile)) { + QFile::remove(destinationFile); + } else { + continue; + } + } + if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { + qCritical("failed to copy %s to %s", + fileInfo.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + } + } + } + } + refreshGlobalSaves(); + refreshGlobalCharacters(); +} + +void TransferSavesDialog::on_doneButton_clicked() +{ + close(); +} + +void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText) +{ + ui->globalSavesList->clear(); + for (std::vector::const_iterator iter = m_GlobalSaves.begin(); + iter != m_GlobalSaves.end(); ++iter) { + if ((*iter)->pcName() == currentText) { + ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); + } + } +} + +void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText) +{ + ui->localSavesList->clear(); + for (std::vector::const_iterator iter = m_LocalSaves.begin(); + iter != m_LocalSaves.end(); ++iter) { + if ((*iter)->pcName() == currentText) { + ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); + } + } +} diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index 4306b960..37a85000 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -1,82 +1,82 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef TRANSFERSAVESDIALOG_H -#define TRANSFERSAVESDIALOG_H - -#include "tutorabledialog.h" -#include "savegamegamebyro.h" -#include "profile.h" - -namespace Ui { -class TransferSavesDialog; -} - -class TransferSavesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0); - ~TransferSavesDialog(); - -private slots: - - void on_moveToLocalBtn_clicked(); - - void on_doneButton_clicked(); - - void on_globalCharacterList_currentTextChanged(const QString ¤tText); - - void on_localCharacterList_currentTextChanged(const QString ¤tText); - - void on_copyToLocalBtn_clicked(); - - void on_moveToGlobalBtn_clicked(); - - void on_copyToGlobalBtn_clicked(); - -private: - - enum OverwriteMode { - OVERWRITE_ASK, - OVERWRITE_YES, - OVERWRITE_NO - }; - -private: - - void refreshGlobalCharacters(); - void refreshLocalCharacters(); - void refreshGlobalSaves(); - void refreshLocalSaves(); - bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile); - -private: - - Ui::TransferSavesDialog *ui; - - Profile m_Profile; - - std::vector m_GlobalSaves; - std::vector m_LocalSaves; - -}; - -#endif // TRANSFERSAVESDIALOG_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef TRANSFERSAVESDIALOG_H +#define TRANSFERSAVESDIALOG_H + +#include "tutorabledialog.h" +#include "savegamegamebyro.h" +#include "profile.h" + +namespace Ui { +class TransferSavesDialog; +} + +class TransferSavesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0); + ~TransferSavesDialog(); + +private slots: + + void on_moveToLocalBtn_clicked(); + + void on_doneButton_clicked(); + + void on_globalCharacterList_currentTextChanged(const QString ¤tText); + + void on_localCharacterList_currentTextChanged(const QString ¤tText); + + void on_copyToLocalBtn_clicked(); + + void on_moveToGlobalBtn_clicked(); + + void on_copyToGlobalBtn_clicked(); + +private: + + enum OverwriteMode { + OVERWRITE_ASK, + OVERWRITE_YES, + OVERWRITE_NO + }; + +private: + + void refreshGlobalCharacters(); + void refreshLocalCharacters(); + void refreshGlobalSaves(); + void refreshLocalSaves(); + bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile); + +private: + + Ui::TransferSavesDialog *ui; + + Profile m_Profile; + + std::vector m_GlobalSaves; + std::vector m_LocalSaves; + +}; + +#endif // TRANSFERSAVESDIALOG_H -- cgit v1.3.1 From 0050cb07409d775efe14e728c1cef4a2c33aa1db Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 21 Jul 2014 19:14:24 +0200 Subject: - download-list will no longer show a file as having incomplete data if there is no file version - added a new mod column with icons displaying the content of the mod - MO now differentiates between mods using an internal name that disambiguates between foreign and regular mods --- src/aboutdialog.cpp | 7 ++- src/downloadmanager.cpp | 3 +- src/icondelegate.cpp | 17 ------- src/icondelegate.h | 1 - src/mainwindow.cpp | 12 ++++- src/modflagicondelegate.cpp | 17 +++++++ src/modflagicondelegate.h | 1 + src/modinfo.cpp | 26 +++++++++- src/modinfo.h | 32 ++++++++++++ src/modlist.cpp | 74 ++++++++++++++++++++++++++-- src/modlist.h | 10 ++++ src/modlistsortproxy.cpp | 7 +++ src/pluginflagicondelegate.cpp | 27 +++++++--- src/pluginflagicondelegate.h | 27 ++++++++-- src/pluginlist.cpp | 3 -- src/profile.cpp | 21 ++++---- src/resources.qrc | 12 +++++ src/resources/contents/breastplate.png | Bin 0 -> 10578 bytes src/resources/contents/checkbox-tree.png | Bin 0 -> 1144 bytes src/resources/contents/conversation.png | Bin 0 -> 7980 bytes src/resources/contents/double-quaver.png | Bin 0 -> 5877 bytes src/resources/contents/empty-chessboard.png | Bin 0 -> 959 bytes src/resources/contents/hand-of-god.png | Bin 0 -> 7105 bytes src/resources/contents/jigsaw-piece.png | Bin 0 -> 5971 bytes src/resources/contents/lyre.png | Bin 0 -> 9368 bytes src/resources/contents/tinker.png | Bin 0 -> 6475 bytes src/resources/contents/usable.png | Bin 0 -> 9919 bytes 27 files changed, 241 insertions(+), 56 deletions(-) create mode 100644 src/resources/contents/breastplate.png create mode 100644 src/resources/contents/checkbox-tree.png create mode 100644 src/resources/contents/conversation.png create mode 100644 src/resources/contents/double-quaver.png create mode 100644 src/resources/contents/empty-chessboard.png create mode 100644 src/resources/contents/hand-of-god.png create mode 100644 src/resources/contents/jigsaw-piece.png create mode 100644 src/resources/contents/lyre.png create mode 100644 src/resources/contents/tinker.png create mode 100644 src/resources/contents/usable.png diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index a8f9b4db..b5bfeb04 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -39,13 +39,12 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("Qt 4.8.5", LICENSE_LGPL3); addLicense("Qt Json", LICENSE_GPL3); addLicense("Boost Library", LICENSE_BOOST); - addLicense("Tango Icon Theme", LICENSE_NONE); - addLicense("RRZE Icon Set", LICENSE_CCBY3); addLicense("7-zip", LICENSE_LGPL3); addLicense("ZLib", LICENSE_ZLIB); addLicense("NIF File Format Library", LICENSE_BSD3); - addLicense("BOSS (modified)", LICENSE_GPL3); - addLicense("Alphanum Algorithm", LICENSE_ZLIB); + 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); ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); #ifdef HGID diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 391ac7b9..856ca79c 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -831,7 +831,7 @@ bool DownloadManager::isInfoIncomplete(int index) const // other repositories currently don't support re-querying info anyway return false; } - return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0) || !info->m_FileInfo->version.isValid(); + return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0); } @@ -1181,7 +1181,6 @@ void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userD QVariantMap result = resultData.toMap(); info->name = result["name"].toString(); - qDebug("file info received for %s", qPrintable(info->name)); info->version.parse(result["version"].toString()); if (!info->version.isValid()) { info->version = info->newestVersion; diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index f7cf6977..8c2fe5cc 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -51,20 +51,3 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, painter->restore(); } - -QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const -{ - int count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); - QSize result; - if (index < ModInfo::getNumMods()) { - result = QSize(count * 40, 20); - } else { - result = QSize(1, 20); - } - if (option.rect.width() > 0) { - result.setWidth(std::min(option.rect.width(), result.width())); - } - return result; -} - diff --git a/src/icondelegate.h b/src/icondelegate.h index 072343bb..b24de102 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -33,7 +33,6 @@ public: explicit IconDelegate(QObject *parent = 0); virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; signals: diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c3182321..a3887a23 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -209,14 +209,22 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->setModel(m_ModListSortProxy); + GenericIconDelegate *contentDelegate = new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), contentDelegate, SLOT(columnResized(int,int,int))); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); + ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); //ui->modList->setAcceptDrops(true); ui->modList->header()->installEventFilter(&m_ModList); if (initSettings.contains("mod_list_state")) { ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); + + // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that + ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) + 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) - 1); } else { // hide these columns by default + ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); } @@ -230,7 +238,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->espList->setModel(m_PluginListSortProxy); ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); - ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); if (initSettings.contains("plugin_list_state")) { ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); } @@ -2758,7 +2766,7 @@ void MainWindow::modorder_changed() if (m_CurrentProfile->modEnabled(i)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); + m_DirectoryStructure->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1); } } refreshBSAList(); diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index 914503a1..96b2bd58 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -68,3 +68,20 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const } } + +QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +{ + int count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + QSize result; + if (index < ModInfo::getNumMods()) { + result = QSize(count * 40, 20); + } else { + result = QSize(1, 20); + } + if (option.rect.width() > 0) { + result.setWidth(std::min(option.rect.width(), result.width())); + } + return result; +} + diff --git a/src/modflagicondelegate.h b/src/modflagicondelegate.h index b64ca08f..cc652c06 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -7,6 +7,7 @@ class ModFlagIconDelegate : public IconDelegate { public: explicit ModFlagIconDelegate(QObject *parent = 0); + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; private: virtual QList getIcons(const QModelIndex &index) const; virtual size_t getNumIcons(const QModelIndex &index) const; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index b0351cf4..cb20567e 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -232,7 +232,7 @@ void ModInfo::updateIndices() QRegExp backupRegEx(".*backup[0-9]*$"); for (unsigned int i = 0; i < s_Collection.size(); ++i) { - QString modName = s_Collection[i]->name(); + QString modName = s_Collection[i]->internalName(); int modID = s_Collection[i]->getNexusID(); s_ModsByName[modName] = i; s_ModsByModID[modID].push_back(i); @@ -804,6 +804,28 @@ std::vector ModInfoRegular::getFlags() const } +std::vector ModInfoRegular::getContents() const +{ + std::vector result; + QDir dir(absolutePath()); + if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { + result.push_back(CONTENT_PLUGIN); + } + QString sePluginPath = ToQString(GameInfo::instance().getSEName()) + "/plugins"; + if (dir.exists("textures")) result.push_back(CONTENT_TEXTURE); + if (dir.exists("meshes")) result.push_back(CONTENT_MESH); + if (dir.exists("interface") + || dir.exists("menus")) result.push_back(CONTENT_INTERFACE); + if (dir.exists("music")) result.push_back(CONTENT_MUSIC); + if (dir.exists("sound")) result.push_back(CONTENT_SOUND); + if (dir.exists("scripts")) result.push_back(CONTENT_SCRIPT); + if (dir.exists(sePluginPath)) result.push_back(CONTENT_SKSE); + if (dir.exists("strings")) result.push_back(CONTENT_STRING); + if (dir.exists("SkyProc Patchers")) result.push_back(CONTENT_SKYPROC); + return result; +} + + int ModInfoRegular::getHighlight() const { return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; @@ -959,6 +981,8 @@ QStringList ModInfoOverwrite::archives() const return result; } +const char ModInfoForeign::INT_IDENTIFIER[] = "__int__foreign"; + QString ModInfoForeign::name() const { return m_Name; diff --git a/src/modinfo.h b/src/modinfo.h index 0c144936..e2121828 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -68,6 +68,19 @@ public: FLAG_CONFLICT_REDUNDANT }; + enum EContent { + CONTENT_PLUGIN, + CONTENT_TEXTURE, + CONTENT_MESH, + CONTENT_INTERFACE, + CONTENT_MUSIC, + CONTENT_SOUND, + CONTENT_SCRIPT, + CONTENT_SKSE, + CONTENT_SKYPROC, + CONTENT_STRING + }; + enum EHighlight { HIGHLIGHT_NONE = 0, HIGHLIGHT_INVALID = 1, @@ -303,6 +316,13 @@ public: **/ virtual QString name() const = 0; + /** + * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be + * this is used to distinguish between mods that have the same visible name + * @return internal mod name + */ + virtual QString internalName() const { return name(); } + /** * @brief getter for the mod path * @@ -368,6 +388,11 @@ public: */ virtual std::vector getFlags() const = 0; + /** + * @return a list of content types contained in a mod + */ + virtual std::vector getContents() const { return std::vector(); } + /** * @brief test if the specified flag is set for this mod * @param flag the flag to test @@ -798,6 +823,8 @@ public: */ virtual std::vector getFlags() const; + virtual std::vector getContents() const; + /** * @return an indicator if and how this mod should be highlighted by the UI */ @@ -974,6 +1001,10 @@ class ModInfoForeign : public ModInfoWithConflictInfo friend class ModInfo; +public: + + static const char INT_IDENTIFIER[]; + public: virtual bool updateAvailable() const { return false; } @@ -994,6 +1025,7 @@ public: virtual void endorse(bool) {} virtual bool isEmpty() const { return false; } virtual QString name() const; + virtual QString internalName() const { return name() + INT_IDENTIFIER; } virtual QString notes() const { return ""; } virtual QDateTime creationTime() const; virtual QString absolutePath() const; diff --git a/src/modlist.cpp b/src/modlist.cpp index ff126faa..ca173b18 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -52,15 +52,23 @@ ModList::ModList(QObject *parent) : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), m_FontMetrics(QFont()), m_DropOnItems(false) { + m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(QIcon(":/MO/gui/content/plugin"), ":/MO/gui/content/plugin", tr("Game plugins (esp/esm)")); + m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(QIcon(":/MO/gui/content/interface"), ":/MO/gui/content/interface", tr("Interface")); + m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(QIcon(":/MO/gui/content/mesh"), ":/MO/gui/content/mesh", tr("Meshes")); + m_ContentIcons[ModInfo::CONTENT_MUSIC] = std::make_tuple(QIcon(":/MO/gui/content/music"), ":/MO/gui/content/music", tr("Music")); + m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(QIcon(":/MO/gui/content/script"), ":/MO/gui/content/script", tr("Scripts (Papyrus)")); + m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(QIcon(":/MO/gui/content/skse"), ":/MO/gui/content/skse", tr("Script Extender Plugin")); + m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(QIcon(":/MO/gui/content/skyproc"), ":/MO/gui/content/skyproc", tr("SkyProc Patcher")); + m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(QIcon(":/MO/gui/content/sound"), ":/MO/gui/content/sound", tr("Sound")); + m_ContentIcons[ModInfo::CONTENT_STRING] = std::make_tuple(QIcon(":/MO/gui/content/string"), ":/MO/gui/content/string", tr("Strings")); + m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(QIcon(":/MO/gui/content/texture"), ":/MO/gui/content/texture", tr("Textures")); } - void ModList::setProfile(Profile *profile) { m_Profile = profile; } - int ModList::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { @@ -70,7 +78,6 @@ int ModList::rowCount(const QModelIndex &parent) const } } - bool ModList::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { @@ -129,6 +136,35 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const } +QVariantList ModList::contentsToIcons(const std::vector &contents) const +{ + QVariantList result; + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(std::get<0>(iter->second)); + } else { + result.append(QIcon()); + } + } + return result; +} + +QString ModList::contentsToToolTip(const std::vector &contents) const +{ + QString result(""); + + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(QString("").arg(std::get<1>(iter->second)).arg(std::get<2>(iter->second))); + } + } + result.append("
            %2
            "); + return result; +} + + QVariant ModList::data(const QModelIndex &modelIndex, int role) const { if (m_Profile == NULL) return QVariant(); @@ -139,7 +175,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { - if (column == COL_FLAGS) { + if ((column == COL_FLAGS) + || (column == COL_CONTENT)) { return QVariant(); } else if (column == COL_NAME) { return modInfo->name(); @@ -252,6 +289,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const case COL_PRIORITY: return QtGroupingProxy::AGGR_MIN; default: return QtGroupingProxy::AGGR_NONE; } + } else if (role == Qt::UserRole + 3) { + return contentsToIcons(modInfo->getContents()); } else if (role == Qt::FontRole) { QFont result; if (column == COL_NAME) { @@ -313,6 +352,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } return result; + } else if (column == COL_CONTENT) { + return contentsToToolTip(modInfo->getContents()); } else if (column == COL_NAME) { try { return modInfo->getDescription(); @@ -560,7 +601,6 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) break; } } - for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { int oldPriority = m_Profile->getModPriority(*iter); @@ -640,6 +680,17 @@ IModList::ModStates ModList::state(unsigned int modIndex) const return result; } +QString ModList::displayName(const QString &internalName) const +{ + unsigned int modIndex = ModInfo::getIndex(internalName); + if (modIndex == UINT_MAX) { + // might be better to throw an exception? + return internalName; + } else { + return ModInfo::getByIndex(modIndex)->name(); + } +} + IModList::ModStates ModList::state(const QString &name) const { unsigned int modIndex = ModInfo::getIndex(name); @@ -880,6 +931,7 @@ QString ModList::getColumnName(int column) { switch (column) { case COL_FLAGS: return tr("Flags"); + case COL_CONTENT: return tr("Content"); case COL_NAME: return tr("Mod Name"); case COL_VERSION: return tr("Version"); case COL_PRIORITY: return tr("Priority"); @@ -901,6 +953,18 @@ QString ModList::getColumnToolTip(int column) case COL_CATEGORY: return tr("Category of the mod."); case COL_MODID: return tr("Id of the mod as used on Nexus."); case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_CONTENT: return tr("Depicts the content of the mod:
            " + "Game plugins (esp/esm)
            " + "interface
            " + "Meshes
            " + "Textures
            " + "Sounds
            " + "Music
            " + "Strings
            " + "Scripts (Papyrus)
            " + "Script Extender plugins
            " + "SkyProc Patcher
            " + ); case COL_INSTALLTIME: return tr("Time this mod was installed"); default: return tr("unknown"); } diff --git a/src/modlist.h b/src/modlist.h index 2e8d6a84..6116a913 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -52,6 +52,7 @@ public: enum EColumn { COL_NAME, COL_FLAGS, + COL_CONTENT, COL_CATEGORY, COL_MODID, COL_VERSION, @@ -104,6 +105,9 @@ public: public: + /// \copydoc MOBase::IModList::displayName + virtual QString displayName(const QString &internalName) const; + /// \copydoc MOBase::IModList::state virtual ModStates state(const QString &name) const; @@ -234,6 +238,10 @@ private: static QString getColumnToolTip(int column); + QVariantList contentsToIcons(const std::vector &content) const; + + QString contentsToToolTip(const std::vector &contents) const; + ModList::EColumn getEnabledColumn(int index) const; QVariant categoryData(int categoryID, int column, int role) const; @@ -287,6 +295,8 @@ private: SignalModStateChanged m_ModStateChanged; SignalModMoved m_ModMoved; + std::map > m_ContentIcons; + }; #endif // MODLIST_H diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 77702689..d790e277 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -159,6 +159,13 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, if (leftMod->getFlags().size() != rightMod->getFlags().size()) lt = leftMod->getFlags().size() < rightMod->getFlags().size(); } break; + case ModList::COL_CONTENT: { + int lLen = leftMod->getContents().size(); + int rLen = rightMod->getContents().size(); + if (lLen != rLen) { + lt = lLen < rLen; + } + } break; case ModList::COL_NAME: { int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); if (comp != 0) diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp index 761555d7..a4f66c04 100644 --- a/src/pluginflagicondelegate.cpp +++ b/src/pluginflagicondelegate.cpp @@ -3,24 +3,37 @@ #include -PluginFlagIconDelegate::PluginFlagIconDelegate(QObject *parent) +GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) : IconDelegate(parent) + , m_Role(role) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) { } -QList PluginFlagIconDelegate::getIcons(const QModelIndex &index) const +void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } +} + +QList GenericIconDelegate::getIcons(const QModelIndex &index) const { QList result; if (index.isValid()) { - foreach (const QVariant &var, index.data(Qt::UserRole + 1).toList()) { - result.append(var.value()); + foreach (const QVariant &var, index.data(m_Role).toList()) { + QIcon icon = var.value(); + if (!m_Compact || !icon.isNull()) { + result.append(icon); + } } } return result; } -size_t PluginFlagIconDelegate::getNumIcons(const QModelIndex &index) const +size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const { - return index.data(Qt::UserRole + 1).toList().count(); + return index.data(m_Role).toList().count(); } - diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h index 554e968b..3c05db72 100644 --- a/src/pluginflagicondelegate.h +++ b/src/pluginflagicondelegate.h @@ -3,15 +3,34 @@ #include "icondelegate.h" -class PluginFlagIconDelegate : public IconDelegate +/** + * @brief an icon delegate that takes the list of icons from a user-defines data role + */ +class GenericIconDelegate : public IconDelegate { +Q_OBJECT public: - PluginFlagIconDelegate(QObject *parent = NULL); - - // IconDelegate interface + /** + * @brief constructor + * @param parent parent object + * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) + * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same + * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. + * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal + * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" + * @param compactSize see explanation of logicalIndex + */ + GenericIconDelegate(QObject *parent = NULL, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); +public slots: + void columnResized(int logicalIndex, int oldSize, int newSize); private: virtual QList getIcons(const QModelIndex &index) const; virtual size_t getNumIcons(const QModelIndex &index) const; +private: + int m_Role; + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; }; #endif // PLUGINFLAGICONDELEGATE_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 4794414a..5159871d 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -844,9 +844,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (!bossInfoIter->second.m_Messages.isEmpty()) { result.append(QIcon(":/MO/gui/information")); } - /*if (bossInfoIter->second.m_LOOTUnrecognized) { - result.append(QIcon(":/MO/gui/help")); - }*/ } if (m_ESPs[index].m_HasIni) { result.append(QIcon(":/MO/gui/attachment")); diff --git a/src/profile.cpp b/src/profile.cpp index fbfb3266..ca47d327 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -265,22 +265,23 @@ void Profile::refreshModStatus() modName = QString::fromUtf8(line.trimmed().constData()); } if (modName.size() > 0) { - if (namesRead.find(modName) != namesRead.end()) { + QString lookupName = modName + (line.at(0) == '*' ? ModInfoForeign::INT_IDENTIFIER : ""); + if (namesRead.find(lookupName) != namesRead.end()) { continue; } else { - namesRead.insert(modName); + namesRead.insert(lookupName); } - unsigned int modindex = ModInfo::getIndex(modName); - if (modindex != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(modindex); - if ((modindex < m_ModStatus.size()) + unsigned int modIndex = ModInfo::getIndex(lookupName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); + if ((modIndex < m_ModStatus.size()) && (info->getFixedPriority() == INT_MIN)) { - m_ModStatus[modindex].m_Enabled = enabled || info->alwaysEnabled(); - if (m_ModStatus[modindex].m_Priority == -1) { + m_ModStatus[modIndex].m_Enabled = enabled || info->alwaysEnabled(); + if (m_ModStatus[modIndex].m_Priority == -1) { if (static_cast(index) >= m_ModStatus.size()) { throw MyException(tr("invalid index %1").arg(index)); } - m_ModStatus[modindex].m_Priority = index++; + m_ModStatus[modIndex].m_Priority = index++; } } else { qDebug("mod \"%s\" (profile \"%s\") not found", @@ -381,7 +382,7 @@ std::vector > Profile::getActiveMods() iter != m_ModIndexByPriority.end(); ++iter) { if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); - result.push_back(std::make_tuple(modInfo->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); + result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); } } diff --git a/src/resources.qrc b/src/resources.qrc index 3287ad5d..20b0e3fe 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -66,4 +66,16 @@ resources/badge_9.png resources/badge_more.png + + resources/contents/jigsaw-piece.png + resources/contents/hand-of-god.png + resources/contents/empty-chessboard.png + resources/contents/double-quaver.png + resources/contents/lyre.png + resources/contents/usable.png + resources/contents/checkbox-tree.png + resources/contents/tinker.png + resources/contents/breastplate.png + resources/contents/conversation.png + diff --git a/src/resources/contents/breastplate.png b/src/resources/contents/breastplate.png new file mode 100644 index 00000000..e42d0888 Binary files /dev/null and b/src/resources/contents/breastplate.png differ diff --git a/src/resources/contents/checkbox-tree.png b/src/resources/contents/checkbox-tree.png new file mode 100644 index 00000000..ba6da286 Binary files /dev/null and b/src/resources/contents/checkbox-tree.png differ diff --git a/src/resources/contents/conversation.png b/src/resources/contents/conversation.png new file mode 100644 index 00000000..73e5980e Binary files /dev/null and b/src/resources/contents/conversation.png differ diff --git a/src/resources/contents/double-quaver.png b/src/resources/contents/double-quaver.png new file mode 100644 index 00000000..a7e06c72 Binary files /dev/null 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 new file mode 100644 index 00000000..1ccbbd12 Binary files /dev/null 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 new file mode 100644 index 00000000..9d55580e Binary files /dev/null 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 new file mode 100644 index 00000000..0f276ea0 Binary files /dev/null and b/src/resources/contents/jigsaw-piece.png differ diff --git a/src/resources/contents/lyre.png b/src/resources/contents/lyre.png new file mode 100644 index 00000000..1ee4765f Binary files /dev/null and b/src/resources/contents/lyre.png differ diff --git a/src/resources/contents/tinker.png b/src/resources/contents/tinker.png new file mode 100644 index 00000000..8ac7bfdd Binary files /dev/null and b/src/resources/contents/tinker.png differ diff --git a/src/resources/contents/usable.png b/src/resources/contents/usable.png new file mode 100644 index 00000000..dbbf6619 Binary files /dev/null and b/src/resources/contents/usable.png differ -- cgit v1.3.1 From 470b6ed0bf20525988d719d23725f7c9d19a9f36 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 27 Jul 2014 18:34:32 +0200 Subject: - browser dialog now has a hidden url-field (for testing only) - loot_cli is no longer part of this project. I will probably create a fork of loot that allows command-line usage instead - loot integration now works with such a modified loot version - integrated loot will now also integrate incompatibility messages in the MO UI - overwrite-markers are now updated as the list order is changed - fnis checker will now always allow the user to ignore fnis errors - plugin interface now has a function to wait for handles returned from startApplication (which can be job or process handles) - bugfix: non-mo mods sharing the name with regular mods now have a different internal name - bugfix: using hotkeys the vanilla game-plugins could be moved --- src/browserdialog.cpp | 30 ++++++++- src/browserdialog.h | 4 ++ src/browserdialog.ui | 29 ++++++++- src/browserview.cpp | 3 +- src/genericicondelegate.cpp | 39 ++++++++++++ src/genericicondelegate.h | 36 +++++++++++ src/mainwindow.cpp | 139 +++++++++++++++++++++++++++++++++++------ src/mainwindow.h | 4 +- src/modinfo.cpp | 103 +++++++++++++++--------------- src/modinfo.h | 13 ++-- src/modlist.cpp | 4 +- src/modlistsortproxy.cpp | 16 ++--- src/modlistsortproxy.h | 3 + src/modlistview.cpp | 1 - src/organizer.pro | 4 +- src/organizerproxy.cpp | 5 ++ src/organizerproxy.h | 1 + src/pluginflagicondelegate.cpp | 39 ------------ src/pluginflagicondelegate.h | 36 ----------- src/pluginlist.cpp | 7 +++ src/viewmarkingscrollbar.cpp | 3 + 21 files changed, 348 insertions(+), 171 deletions(-) create mode 100644 src/genericicondelegate.cpp create mode 100644 src/genericicondelegate.h delete mode 100644 src/pluginflagicondelegate.cpp delete mode 100644 src/pluginflagicondelegate.h diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 4897ea4f..521459d0 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -37,6 +37,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include @@ -57,9 +58,12 @@ BrowserDialog::BrowserDialog(QWidget *parent) m_Tabs = this->findChild("browserTabWidget"); + installEventFilter(this); + connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); -} + ui->urlEdit->setVisible(false); +} BrowserDialog::~BrowserDialog() { @@ -108,13 +112,14 @@ BrowserView *BrowserDialog::getCurrentView() } -void BrowserDialog::urlChanged(const QUrl&) +void BrowserDialog::urlChanged(const QUrl &url) { BrowserView *currentView = getCurrentView(); if (currentView != NULL) { ui->backBtn->setEnabled(currentView->history()->canGoBack()); ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); } + ui->urlEdit->setText(url.toString()); } @@ -268,3 +273,24 @@ void BrowserDialog::on_browserTabWidget_currentChanged(int index) ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); } } + +void BrowserDialog::on_urlEdit_returnPressed() +{ + QWebView *currentView = getCurrentView(); + if (currentView != NULL) { + currentView->setUrl(QUrl(ui->urlEdit->text())); + } +} + +bool BrowserDialog::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = reinterpret_cast(event); + if ((keyEvent->modifiers() & Qt::ControlModifier) + && (keyEvent->key() == Qt::Key_U)) { + ui->urlEdit->setVisible(!ui->urlEdit->isVisible()); + return true; + } + } + return QDialog::eventFilter(object, event); +} diff --git a/src/browserdialog.h b/src/browserdialog.h index c6e0aab6..10b44fac 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -63,6 +63,7 @@ public: **/ void openUrl(const QUrl &url); + virtual bool eventFilter(QObject *object, QEvent *event); signals: /** @@ -103,6 +104,8 @@ private slots: void on_browserTabWidget_currentChanged(int index); + void on_urlEdit_returnPressed(); + private: QString guessFileName(const QString &url); @@ -119,6 +122,7 @@ private: QTabWidget *m_Tabs; + }; #endif // BROWSERDIALOG_H diff --git a/src/browserdialog.ui b/src/browserdialog.ui index 7d154fbb..c682fc5d 100644 --- a/src/browserdialog.ui +++ b/src/browserdialog.ui @@ -17,7 +17,16 @@ 0 - + + 0 + + + 0 + + + 0 + + 0 @@ -180,7 +189,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -237,6 +255,13 @@ + + + + true + + + diff --git a/src/browserview.cpp b/src/browserview.cpp index bd43a18e..aeb16520 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -28,6 +28,7 @@ along with Mod Organizer. If not, see . #include #include "utility.h" + BrowserView::BrowserView(QWidget *parent) : QWebView(parent) { @@ -36,7 +37,6 @@ BrowserView::BrowserView(QWidget *parent) page()->settings()->setMaximumPagesInCache(10); } - QWebView *BrowserView::createWindow(QWebPage::WebWindowType) { BrowserView *newView = new BrowserView(parentWidget()); @@ -44,7 +44,6 @@ QWebView *BrowserView::createWindow(QWebPage::WebWindowType) return newView; } - bool BrowserView::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::ShortcutOverride) { diff --git a/src/genericicondelegate.cpp b/src/genericicondelegate.cpp new file mode 100644 index 00000000..c3471735 --- /dev/null +++ b/src/genericicondelegate.cpp @@ -0,0 +1,39 @@ +#include "genericicondelegate.h" +#include "pluginlist.h" +#include + + +GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) + : IconDelegate(parent) + , m_Role(role) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) +{ +} + +void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } +} + +QList GenericIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + if (index.isValid()) { + foreach (const QVariant &var, index.data(m_Role).toList()) { + QIcon icon = var.value(); + if (!m_Compact || !icon.isNull()) { + result.append(icon); + } + } + } + return result; +} + +size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const +{ + return index.data(m_Role).toList().count(); +} diff --git a/src/genericicondelegate.h b/src/genericicondelegate.h new file mode 100644 index 00000000..8bc75e27 --- /dev/null +++ b/src/genericicondelegate.h @@ -0,0 +1,36 @@ +#ifndef GENERICICONDELEGATE_H +#define GENERICICONDELEGATE_H + +#include "icondelegate.h" + +/** + * @brief an icon delegate that takes the list of icons from a user-defines data role + */ +class GenericIconDelegate : public IconDelegate +{ +Q_OBJECT +public: + /** + * @brief constructor + * @param parent parent object + * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) + * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same + * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. + * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal + * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" + * @param compactSize see explanation of logicalIndex + */ + GenericIconDelegate(QObject *parent = NULL, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); +public slots: + void columnResized(int logicalIndex, int oldSize, int newSize); +private: + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; +private: + int m_Role; + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; +}; + +#endif // GENERICICONDELEGATE_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a3887a23..4aea645f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -49,7 +49,7 @@ along with Mod Organizer. If not, see . #include "questionboxmemory.h" #include "tutorialmanager.h" #include "modflagicondelegate.h" -#include "pluginflagicondelegate.h" +#include "genericicondelegate.h" #include "credentialsdialog.h" #include "selectiondialog.h" #include "csvbuilder.h" @@ -728,6 +728,7 @@ void MainWindow::savePluginList() void MainWindow::modFilterActive(bool filterActive) { if (filterActive) { + m_ModList.setOverwriteMarkers(std::set(), std::set()); ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); } else if (ui->groupCombo->currentIndex() != 0) { ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); @@ -784,7 +785,6 @@ bool MainWindow::saveCurrentLists() return true; } - bool MainWindow::addProfile() { QComboBox *profileBox = findChild("profileBox"); @@ -807,7 +807,6 @@ bool MainWindow::addProfile() } } - void MainWindow::hookUpWindowTutorials() { QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); @@ -829,7 +828,6 @@ void MainWindow::hookUpWindowTutorials() } } - void MainWindow::showEvent(QShowEvent *event) { refreshFilters(); @@ -2773,6 +2771,19 @@ void MainWindow::modorder_changed() m_CurrentProfile->writeModlist(); saveArchiveList(); m_DirectoryStructure->getFileRegister()->sortOrigins(); + + { // refresh selection + QModelIndex current = ui->modList->currentIndex(); + if (current.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + modInfo->doConflictCheck(); + m_ModList.setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); + if (m_ModListSortProxy != NULL) { + m_ModListSortProxy->invalidate(); + } + ui->modList->verticalScrollBar()->repaint(); + } + } } void MainWindow::procError(QProcess::ProcessError error) @@ -2960,6 +2971,7 @@ void MainWindow::refreshFilters() { QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + QVariant currentIndexName = ui->modList->currentIndex().data(); ui->modList->setCurrentIndex(QModelIndex()); QStringList selectedItems; @@ -2998,8 +3010,11 @@ void MainWindow::refreshFilters() matches.at(0)->setSelected(true); } } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + QModelIndexList matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } } @@ -3069,9 +3084,14 @@ void MainWindow::modlistChanged(const QModelIndex&, int) void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) { - ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - if (m_ModListSortProxy != NULL) { + if (current.isValid()) { + ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + } else { + m_ModList.setOverwriteMarkers(std::set(), std::set()); + } + if ((m_ModListSortProxy != NULL) + && !m_ModListSortProxy->beingInvalidated()) { m_ModListSortProxy->invalidate(); } ui->modList->verticalScrollBar()->repaint(); @@ -5277,11 +5297,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU 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\\."); foreach (const std::string &line, lines) { if (line.length() > 0) { size_t progidx = line.find("[progress]"); - size_t reportidx = line.find("[report]"); + size_t reportidx = line.find("[Report]"); size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); @@ -5296,8 +5317,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); m_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else if (std::tr1::regex_match(line, match, exIncompatible)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + m_PluginList.addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { - qDebug("%s", line.c_str()); + qDebug("[loot] %s", line.c_str()); } } } @@ -5350,6 +5375,65 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID); } + +bool MainWindow::waitForProcessOrJob(HANDLE handle, LPDWORD exitCode) +{ + LockedDialog *dialog = new LockedDialog(this); + dialog->show(); + setEnabled(false); + ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); this->setEnabled(true); }); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + + bool isJobHandle = true; + + ULONG lastProcessID = ULONG_MAX; + HANDLE processHandle = handle; + + DWORD res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(handle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + // fake signaled state + res = WAIT_OBJECT_0; + break; + } else { + // this is indeed a job handle. Figure out one of the process handles as well. + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != handle) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + + res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE); + } + + if (exitCode != NULL) { + ::GetExitCodeProcess(processHandle, exitCode); + } + ::CloseHandle(processHandle); + + return res == WAIT_OBJECT_0; +} + void MainWindow::on_bossButton_clicked() { std::string reportURL; @@ -5368,18 +5452,21 @@ void MainWindow::on_bossButton_clicked() dialog.show(); QStringList parameters; - parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) + parameters << "--unattended" + << "--stdout" + << "--noreport" + << "--game" << ToQString(GameInfo::instance().getGameShortName()) << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); - if (!m_DidUpdateMasterList) { - parameters << "--updateMasterlist"; + if (m_DidUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } else { m_DidUpdateMasterList = true; } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; HANDLE stdOutRead = INVALID_HANDLE_VALUE; createStdoutPipe(&stdOutRead, &stdOutWrite); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/LOOT.exe"), parameters.join(" "), m_CurrentProfile->getName(), m_Settings.logLevel(), @@ -5395,6 +5482,8 @@ void MainWindow::on_bossButton_clicked() DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; bool isJobHandle = true; + ULONG lastProcessID; + HANDLE processHandle = loot; if (loot != INVALID_HANDLE_VALUE) { DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); @@ -5404,6 +5493,14 @@ void MainWindow::on_bossButton_clicked() if (info.NumberOfProcessIdsInList == 0) { qDebug("no more processes in job"); break; + } else { + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != loot) { + ::CloseHandle(processHandle); + } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } } } else { // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there @@ -5437,7 +5534,8 @@ void MainWindow::on_bossButton_clicked() processLOOTOut(remainder, reportURL, errorMessages, dialog); } DWORD exitCode = 0UL; - ::GetExitCodeProcess(loot, &exitCode); + ::GetExitCodeProcess(processHandle, &exitCode); + ::CloseHandle(processHandle); if (exitCode != 0UL) { reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); return; @@ -5460,11 +5558,12 @@ void MainWindow::on_bossButton_clicked() if (reportURL.length() > 0) { m_IntegratedBrowser.setWindowTitle("LOOT Report"); QString report(reportURL.c_str()); - if (QFile::exists(report)) { - m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report)); - } else { - qWarning("report file missing"); + QStringList temp = report.split("?"); + QUrl url = QUrl::fromLocalFile(temp.at(0)); + if (temp.size() > 1) { + url.setEncodedQuery(temp.at(1).toUtf8()); } + m_IntegratedBrowser.openUrl(url); } refreshESPList(); diff --git a/src/mainwindow.h b/src/mainwindow.h index 62503062..124dde5f 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -134,9 +134,11 @@ public: HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); + bool waitForProcessOrJob(HANDLE processHandle, LPDWORD exitCode = NULL); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - void waitForProcessOrJob(HANDLE processHandle); + public slots: void refreshLists(); diff --git a/src/modinfo.cpp b/src/modinfo.cpp index cb20567e..60df0137 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -359,67 +359,72 @@ std::vector ModInfoWithConflictInfo::getFlags() const } -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +void ModInfoWithConflictInfo::doConflictCheck() const { - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - bool regular = false; + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + bool regular = false; - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - // for all files in this origin - for (auto iter = files.begin(); iter != files.end(); ++iter) { - const std::vector &alternatives = (*iter)->getAlternatives(); - if ((alternatives.size() == 0) - || (alternatives[0] == dataID)) { - // no alternatives -> no conflict - regular = true; - } else { - if ((*iter)->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + // for all files in this origin + for (auto iter = files.begin(); iter != files.end(); ++iter) { + const std::vector &alternatives = (*iter)->getAlternatives(); + if ((alternatives.size() == 0) + || (alternatives[0] == dataID)) { + // no alternatives -> no conflict + regular = true; + } else { + if ((*iter)->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } + // for all non-providing alternative origins + for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { + if ((*altIter != dataID) && (*altIter != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } - // for all non-providing alternative origins - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - if ((*altIter != dataID) && (*altIter != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); + } else { + m_OverwrittenList.insert(altIndex); } } } } } + } - m_LastConflictCheck = QTime::currentTime(); + m_LastConflictCheck = QTime::currentTime(); - if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) { - if (!regular) { - m_CurrentConflictState = CONFLICT_REDUNDANT; - } else { - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } + if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) { + if (!regular) { + m_CurrentConflictState = CONFLICT_REDUNDANT; + } else { + m_CurrentConflictState = CONFLICT_OVERWRITTEN; } - else m_CurrentConflictState = CONFLICT_NONE; + } + else m_CurrentConflictState = CONFLICT_NONE; +} + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); } return m_CurrentConflictState; diff --git a/src/modinfo.h b/src/modinfo.h index e2121828..1097e5ba 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -498,17 +498,20 @@ public: virtual void saveMeta() {} /** - * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - * @return + * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed */ virtual std::set getModOverwrite() { return std::set(); } /** - * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed - * @return + * @return list of mods (as mod index) that overwrite this one. Updates may be delayed */ virtual std::set getModOverwritten() { return std::set(); } + /** + * @brief update conflict information + */ + virtual void doConflictCheck() const {} + signals: /** @@ -567,6 +570,8 @@ public: virtual std::set getModOverwritten() { return m_OverwrittenList; } + virtual void doConflictCheck() const; + private: enum EConflictType { diff --git a/src/modlist.cpp b/src/modlist.cpp index ca173b18..681d880c 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -224,7 +224,6 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const return QString(); } } else { - //return tr("None"); return QVariant(); } } @@ -887,6 +886,8 @@ void ModList::removeRow(int row, const QModelIndex&) void ModList::notifyChange(int rowStart, int rowEnd) { if (rowStart < 0) { + m_Overwrite.clear(); + m_Overwritten.clear(); beginResetModel(); endResetModel(); } else { @@ -1021,6 +1022,7 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) notifyChange(idx.row()); } } + emit modorder_changed(); return true; } else if (keyEvent->key() == Qt::Key_Delete) { QItemSelectionModel *selectionModel = itemView->selectionModel(); diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index d790e277..ecd1755d 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -34,6 +34,7 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) , m_CurrentFilter() , m_FilterActive(false) , m_FilterMode(FILTER_AND) + , m_BeingInvalidated(false) { m_EnabledColumns.set(ModList::COL_FLAGS); m_EnabledColumns.set(ModList::COL_NAME); @@ -43,7 +44,6 @@ ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) // but I don't know why. This should be necessary } - void ModListSortProxy::setProfile(Profile *profile) { m_Profile = profile; @@ -72,7 +72,6 @@ Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const return flags; } - void ModListSortProxy::displayColumnSelection(const QPoint &pos) { QMenu menu; @@ -103,7 +102,6 @@ void ModListSortProxy::displayColumnSelection(const QPoint &pos) emit layoutChanged(); } - void ModListSortProxy::enableAllVisible() { if (m_Profile == NULL) return; @@ -115,7 +113,6 @@ void ModListSortProxy::enableAllVisible() invalidate(); } - void ModListSortProxy::disableAllVisible() { if (m_Profile == NULL) return; @@ -127,7 +124,6 @@ void ModListSortProxy::disableAllVisible() invalidate(); } - bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { @@ -208,15 +204,16 @@ bool ModListSortProxy::lessThan(const QModelIndex &left, return lt; } - void ModListSortProxy::updateFilter(const QString &filter) { m_CurrentFilter = filter; updateFilterActive(); + // workaround because qt throws a fit if, as a result of this invalidation, another invalidate is called + m_BeingInvalidated = true; invalidateFilter(); + m_BeingInvalidated = false; } - bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const { foreach (ModInfo::EFlag flag, flags) { @@ -231,7 +228,6 @@ bool ModListSortProxy::hasConflictFlag(const std::vector &flags) return false; } - bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { @@ -306,8 +302,6 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const return false; } - - bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { if (!m_CurrentFilter.isEmpty() && @@ -330,7 +324,6 @@ void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) } } - bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const { if (m_Profile == NULL) { @@ -362,7 +355,6 @@ bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) cons } } - bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index fc7edcd5..cb474200 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -83,6 +83,7 @@ public: return rowCount(parent) > 0; } + bool beingInvalidated() const { return m_BeingInvalidated; } public slots: @@ -116,6 +117,8 @@ private: bool m_FilterActive; FilterMode m_FilterMode; + bool m_BeingInvalidated; + }; #endif // MODLISTSORTPROXY_H diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 2789afe1..50a13a8c 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -38,7 +38,6 @@ ModListView::ModListView(QWidget *parent) : QTreeView(parent) , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { -// setStyle(new ModListViewStyle(style(), indentation())); setVerticalScrollBar(m_Scrollbar); } diff --git a/src/organizer.pro b/src/organizer.pro index 19d3a3a0..994da396 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -88,7 +88,7 @@ SOURCES += \ json.cpp \ safewritefile.cpp \ modflagicondelegate.cpp \ - pluginflagicondelegate.cpp \ + genericicondelegate.cpp \ organizerproxy.cpp \ viewmarkingscrollbar.cpp @@ -168,7 +168,7 @@ HEADERS += \ safewritefile.h\ pdll.h \ modflagicondelegate.h \ - pluginflagicondelegate.h \ + genericicondelegate.h \ organizerproxy.h \ viewmarkingscrollbar.h diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index c48bc765..8adfda7e 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -104,6 +104,11 @@ HANDLE OrganizerProxy::startApplication(const QString &executable, const QString return m_Proxied->startApplication(executable, args, cwd, profile); } +bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + return m_Proxied->waitForProcessOrJob(handle, exitCode); +} + bool OrganizerProxy::onAboutToRun(const std::function &func) { auto conn = m_Proxied->m_AboutToRun.connect(func); diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 017908e5..7e49c99c 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -35,6 +35,7 @@ public: virtual MOBase::IPluginList *pluginList(); virtual MOBase::IModList *modList(); virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = NULL) const; virtual void refreshModList(bool saveChanges); virtual bool onAboutToRun(const std::function &func); diff --git a/src/pluginflagicondelegate.cpp b/src/pluginflagicondelegate.cpp deleted file mode 100644 index a4f66c04..00000000 --- a/src/pluginflagicondelegate.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "pluginflagicondelegate.h" -#include "pluginlist.h" -#include - - -GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) - : IconDelegate(parent) - , m_Role(role) - , m_LogicalIndex(logicalIndex) - , m_CompactSize(compactSize) - , m_Compact(false) -{ -} - -void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) -{ - if (logicalIndex == m_LogicalIndex) { - m_Compact = newSize < m_CompactSize; - } -} - -QList GenericIconDelegate::getIcons(const QModelIndex &index) const -{ - QList result; - if (index.isValid()) { - foreach (const QVariant &var, index.data(m_Role).toList()) { - QIcon icon = var.value(); - if (!m_Compact || !icon.isNull()) { - result.append(icon); - } - } - } - return result; -} - -size_t GenericIconDelegate::getNumIcons(const QModelIndex &index) const -{ - return index.data(m_Role).toList().count(); -} diff --git a/src/pluginflagicondelegate.h b/src/pluginflagicondelegate.h deleted file mode 100644 index 3c05db72..00000000 --- a/src/pluginflagicondelegate.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef PLUGINFLAGICONDELEGATE_H -#define PLUGINFLAGICONDELEGATE_H - -#include "icondelegate.h" - -/** - * @brief an icon delegate that takes the list of icons from a user-defines data role - */ -class GenericIconDelegate : public IconDelegate -{ -Q_OBJECT -public: - /** - * @brief constructor - * @param parent parent object - * @param role role of the itemmodel from which the icon list can be queried (as a QVariantList) - * @param logicalIndex logical index within the model. This is part of a "hack". Normally "empty" icons will be allocated the same - * space as a regular icon. This way the model can use empty icons as spacers and thus align same icons horizontally. - * Now, if you set the logical Index to a valid column and connect the columnResized slot to the sectionResized signal - * of the view, the delegate will turn off this behaviour if the column is smaller than "compactSize" - * @param compactSize see explanation of logicalIndex - */ - GenericIconDelegate(QObject *parent = NULL, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); -public slots: - void columnResized(int logicalIndex, int oldSize, int newSize); -private: - virtual QList getIcons(const QModelIndex &index) const; - virtual size_t getNumIcons(const QModelIndex &index) const; -private: - int m_Role; - int m_LogicalIndex; - int m_CompactSize; - bool m_Compact; -}; - -#endif // PLUGINFLAGICONDELEGATE_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 5159871d..3532397d 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1089,6 +1089,13 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) diff = 1; } QModelIndexList rows = selectionModel->selectedRows(); + // remove elements that aren't supposed to be movable + QMutableListIterator iter(rows); + while (iter.hasNext()) { + if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { + iter.remove(); + } + } if (keyEvent->key() == Qt::Key_Down) { for (int i = 0; i < rows.size() / 2; ++i) { rows.swap(i, rows.size() - i - 1); diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index eca37562..f1b1ba34 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -14,6 +14,9 @@ ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *p void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) { + if (m_Model == NULL) { + return; + } QScrollBar::paintEvent(event); QStyleOptionSlider styleOption; -- cgit v1.3.1 From 5d4a55c3a67caec2811a2b997753adc116403d77 Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 1 Aug 2014 12:10:49 +0200 Subject: - performance optimizations for detection of file changes in mod directories - added a workaround for download-link handling in chrome - MO will now create a profile even if the game-launcher has never been run - bugfix: files weren't cleanly removed from vfs file register --- src/ModOrganizer.pro | 2 +- src/organizer.pro | 8 +++-- src/settings.cpp | 45 +++++--------------------- src/settings.h | 3 -- src/shared/directoryentry.cpp | 74 +++++++++++++++++++++++++++++++++++++------ src/shared/directoryentry.h | 36 +++++++++++++-------- src/shared/shared.pro | 4 +-- src/shared/skyriminfo.cpp | 4 ++- src/shared/util.cpp | 18 +++++------ 9 files changed, 115 insertions(+), 79 deletions(-) diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 9907d086..1624c5d0 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -12,7 +12,7 @@ SUBDIRS = bsatk \ proxydll \ nxmhandler \ BossDummy \ - pythonRunner \ +# pythonRunner \ esptk plugins.depends = pythonRunner diff --git a/src/organizer.pro b/src/organizer.pro index 994da396..76f95955 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -235,10 +235,14 @@ OTHER_FILES += \ # leak detection with vld -#INCLUDEPATH += "E:/Visual Leak Detector/include" -#LIBS += -L"E:/Visual Leak Detector/lib/Win32" +INCLUDEPATH += "E:/Visual Leak Detector/include" +LIBS += -L"E:/Visual Leak Detector/lib/Win32" #DEFINES += LEAK_CHECK_WITH_VLD +# custom leak detection +LIBS += -lDbgHelp + +# model tests #SOURCES += modeltest.cpp #HEADERS += modeltest.h #DEFINES += TEST_MODELS diff --git a/src/settings.cpp b/src/settings.cpp index 1cc168dd..6bf13ee0 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include @@ -32,6 +33,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include using namespace MOBase; @@ -107,8 +109,12 @@ void Settings::registerAsNXMHandler(bool force) std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); std::wstring mode = force ? L"forcereg" : L"reg"; - ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), - (mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\"").c_str(), NULL, SW_SHOWNORMAL); + std::wstring parameters = mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\""; + HINSTANCE res = ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), parameters.c_str(), NULL, SW_SHOWNORMAL); + if ((int)res <= 32) { + QMessageBox::critical(NULL, tr("Failed"), + tr("Sorry, failed to start the helper application")); + } } void Settings::registerPlugin(IPlugin *plugin) @@ -460,41 +466,6 @@ void Settings::addStyles(QComboBox *styleBox) } } -bool Settings::isNXMHandler(bool *modifyable) -{ - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command", - QSettings::NativeFormat); - - QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - if (modifyable != NULL) { - handlerReg.setValue("Default", currentExe); - handlerReg.sync(); - - *modifyable = handlerReg.status() == QSettings::NoError; - // QSettings::isWritable returns wrong results... - } - return currentExe == myExe; -} - - -void Settings::setNXMHandlerActive(bool active, bool writable) -{ -// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat); - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); - - if (writable) { - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - handlerReg.setValue("Default", "URL:NXM Protocol"); - handlerReg.setValue("URL Protocol", ""); - handlerReg.setValue("shell/open/command/Default", active ? myExe : ""); - handlerReg.sync(); - } else { - Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active); - } -} - - void Settings::resetDialogs() { m_Settings.beginGroup("DialogChoices"); diff --git a/src/settings.h b/src/settings.h index 44e8088e..40fd2c5a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -272,7 +272,6 @@ public: * even if the user said earlier not to */ void registerAsNXMHandler(bool force); - private: QString obfuscate(const QString &password) const; @@ -280,8 +279,6 @@ private: void addLanguages(QComboBox *languageBox); void addStyles(QComboBox *styleBox); - bool isNXMHandler(bool *modifyable); - void setNXMHandlerActive(bool active, bool writable); void readPluginBlacklist(); void writePluginBlacklist(); diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index aead0cca..e0914870 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include #include +#include #include #include #include "error_report.h" @@ -28,6 +29,7 @@ along with Mod Organizer. If not, see . #include "windows_error.h" #include #include +#include #include "util.h" #include "leaktrace.h" @@ -133,13 +135,11 @@ private: // -void FilesOrigin::enable(bool enabled) +void FilesOrigin::enable(bool enabled, time_t notAfter) { if (!enabled) { std::set copy = m_Files; - for (auto iter = copy.begin(); iter != copy.end(); ++iter) { - m_FileRegister.lock()->removeOrigin(*iter, m_ID); - } + m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); m_Files.clear(); } m_Disabled = !enabled; @@ -233,6 +233,7 @@ std::vector FilesOrigin::getFiles() const void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive) { + m_LastAccessed = time(NULL); if (m_Parent != NULL) { m_Parent->propagateOrigin(origin); } @@ -324,13 +325,13 @@ static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS) FileEntry::FileEntry() - : m_Index(UINT_MAX), m_Name(), m_Parent(NULL) + : m_Index(UINT_MAX), m_Name(), m_Parent(NULL), m_LastAccessed(time(NULL)) { LEAK_TRACE; } FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) - : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"") + : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L""), m_LastAccessed(time(NULL)) { LEAK_TRACE; } @@ -654,6 +655,18 @@ void DirectoryEntry::removeFile(FileEntry::Index index) } +void DirectoryEntry::removeFiles(const std::set &indices) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end();) { + if (indices.find(iter->second) != indices.end()) { + m_Files.erase(iter++); + } else { + ++iter; + } + } +} + + int DirectoryEntry::anyOrigin() const { bool ignore; @@ -737,7 +750,7 @@ const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring &path, const if (len == std::string::npos) { // no more path components - auto iter = m_Files.find(path); + auto iter = m_Files.find(ToLower(path)); if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else if (directory != NULL) { @@ -777,7 +790,7 @@ DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &pa const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring &name) const { - auto iter = m_Files.find(name); + auto iter = m_Files.find(ToLower(name)); if (iter != m_Files.end()) { return m_FileRegister->getFile(iter->second); } else { @@ -865,7 +878,6 @@ FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const return FileEntry::Ptr(); } - void FileRegister::unregisterFile(FileEntry::Ptr file) { bool ignore; @@ -890,6 +902,8 @@ void FileRegister::removeFile(FileEntry::Index index) if (iter != m_Files.end()) { unregisterFile(iter->second); m_Files.erase(index); + } else { + log("invalid file index for remove: %lu", index); } } @@ -899,7 +913,49 @@ void FileRegister::removeOrigin(FileEntry::Index index, int originID) if (iter != m_Files.end()) { if (iter->second->removeOrigin(originID)) { unregisterFile(iter->second); + m_Files.erase(iter); } + } else { + log("invalid file index for remove (for origin): %lu", index); + } +} + +void FileRegister::removeOriginMulti(std::set indices, int originID, time_t notAfter) +{ + std::vector removedFiles; + for (auto iter = indices.begin(); iter != indices.end();) { + auto pos = m_Files.find(*iter); + if (pos != m_Files.end() + && (pos->second->lastAccessed() < notAfter) + && pos->second->removeOrigin(originID)) { + removedFiles.push_back(pos->second); + m_Files.erase(pos); + ++iter; + } else { + indices.erase(iter++); + } + } + + if (removedFiles.size() > 0) { + log("%d files actually removed", removedFiles.size()); + } + + // optimization: this is only called when disabling an origin and in this case we don't have + // to remove the file from the origin + + // need to remove files from their parent directories. multiple ways to go about this: + // a) for each file, search its parents file-list (preferably by name) and remove what is found + // b) gather the parent directories, go through the file list for each once and remove all files that have been removed + // the latter should be faster when there are many files in few directories. since this is called + // only when disabling an origin that is probably frequently the case + std::set parents; + BOOST_FOREACH (const FileEntry::Ptr &file, removedFiles) { + if (file->getParent() != NULL) { + parents.insert(file->getParent()); + } + } + BOOST_FOREACH (DirectoryEntry *parent, parents) { + parent->removeFiles(indices); } } diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 7b656b05..1e904311 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -34,6 +34,11 @@ along with Mod Organizer. If not, see . #include "util.h" + +#include "error_report.h" + + + namespace MOShared { @@ -60,6 +65,8 @@ public: Index getIndex() const { return m_Index; } + time_t lastAccessed() const { return m_LastAccessed; } + void addOrigin(int origin, FILETIME fileTime, const std::wstring &archive); // remove the specified origin from the list of origins that contain this file. if no origin is left, // the file is effectively deleted and true is returned. otherwise, false is returned @@ -98,6 +105,8 @@ private: DirectoryEntry *m_Parent; mutable FILETIME m_FileTime; + time_t m_LastAccessed; + friend bool operator<(const FileEntry &lhs, const FileEntry &rhs) { return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) < 0; } @@ -130,7 +139,7 @@ public: std::vector getFiles() const; - void enable(bool enabled); + void enable(bool enabled, time_t notAfter = LONG_MAX); bool isDisabled() const { return m_Disabled; } void addFile(FileEntry::Index index) { m_Files.insert(index); } @@ -171,8 +180,11 @@ public: FileEntry::Ptr createFile(const std::wstring &name, DirectoryEntry *parent); FileEntry::Ptr getFile(FileEntry::Index index) const; + size_t size() const { return m_Files.size(); } + void removeFile(FileEntry::Index index); void removeOrigin(FileEntry::Index index, int originID); + void removeOriginMulti(std::set indices, int originID, time_t notAfter); void sortOrigins(); @@ -258,7 +270,7 @@ public: void removeDir(const std::wstring &path); void remove(const std::wstring &fileName, int *origin) { - auto iter = m_Files.find(fileName); + auto iter = m_Files.find(ToLower(fileName)); if (iter != m_Files.end()) { if (origin != NULL) { FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); @@ -268,6 +280,8 @@ public: } } m_FileRegister->removeFile(iter->second); + } else { + log ("failed to remove %ls from %ls", fileName.c_str(), m_Name.c_str()); } } @@ -275,20 +289,23 @@ public: FilesOrigin &createOrigin(const std::wstring &originName, const std::wstring &directory, int priority); + void removeFiles(const std::set &indices); + private: DirectoryEntry(const DirectoryEntry &reference); DirectoryEntry &operator=(const DirectoryEntry &reference); void insert(const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive) { - auto iter = m_Files.find(fileName); + std::wstring fileNameLower = ToLower(fileName); + auto iter = m_Files.find(fileNameLower); FileEntry::Ptr file; if (iter != m_Files.end()) { file = m_FileRegister->getFile(iter->second); } else { file = m_FileRegister->createFile(fileName, this); // TODO this has been observed to cause a crash, no clue why - m_Files[fileName] = file->getIndex(); + m_Files[fileNameLower] = file->getIndex(); } file->addOrigin(origin.getID(), fileTime, archive); origin.addFile(file->getIndex()); @@ -305,22 +322,13 @@ private: void removeDirRecursive(); -private: - - struct WStrLess { - bool operator()(const std::wstring &LHS, const std::wstring &RHS) const { - return _wcsicmp(LHS.c_str(), RHS.c_str()) < 0; - } - }; - - private: boost::shared_ptr m_FileRegister; boost::shared_ptr m_OriginConnection; std::wstring m_Name; - std::map m_Files; + std::map m_Files; std::vector m_SubDirectories; DirectoryEntry *m_Parent; diff --git a/src/shared/shared.pro b/src/shared/shared.pro index c5925c24..84a9c274 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -15,8 +15,8 @@ INCLUDEPATH += ../bsatk "$(BOOSTPATH)" # only for custom leak detection -#DEFINES += TRACE_LEAKS -#LIBS += -lDbgHelp +DEFINES += TRACE_LEAKS +LIBS += -lDbgHelp CONFIG(debug, debug|release) { diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 5017da38..72452255 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -273,7 +273,9 @@ void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) std::wostringstream source; source << getMyGamesDirectory() << L"\\Skyrim\\skyrimprefs.ini"; if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { - if (::GetLastError() != ERROR_FILE_EXISTS) { + log("failed to copy ini file %ls", source.str().c_str()); + // create empty + if (::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) == INVALID_HANDLE_VALUE) { std::ostringstream stream; stream << "failed to copy ini file: " << ToString(source.str(), false); throw windows_error(stream.str()); diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 22657e6c..61ca9cdc 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -86,30 +86,28 @@ std::wstring ToWString(const std::string &source, bool utf8) std::string &ToLower(std::string &text) { - std::transform(text.begin(), text.end(), text.begin(), tolower); + std::transform(text.begin(), text.end(), text.begin(), ::tolower); return text; } std::string ToLower(const std::string &text) { - std::string temp = text; - - std::transform(temp.begin(), temp.end(), temp.begin(), tolower); - return temp; + std::string result = text; + std::transform(result.begin(), result.end(), result.begin(), ::tolower); + return result; } std::wstring &ToLower(std::wstring &text) { - std::transform(text.begin(), text.end(), text.begin(), towlower); + std::transform(text.begin(), text.end(), text.begin(), ::towlower); return text; } std::wstring ToLower(const std::wstring &text) { - std::wstring temp = text; - - std::transform(temp.begin(), temp.end(), temp.begin(), towlower); - return temp; + std::wstring result = text; + std::transform(result.begin(), result.end(), result.begin(), ::towlower); + return result; } VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) -- cgit v1.3.1 From 06701856f8eaed0f6bf802308c07952f0f0dd92e Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 18 Aug 2014 22:57:00 +0200 Subject: - login package now also uses a "proper" user-agent --- src/nexusinterface.cpp | 12 ++++++------ src/nexusinterface.h | 2 -- src/nxmaccessmanager.cpp | 12 ++++++++++-- src/nxmaccessmanager.h | 7 ++++++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index c7e2df9e..d844cd39 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -146,17 +146,16 @@ NexusInterface::NexusInterface() : m_NMMVersion() { atexit(&cleanup); - m_AccessManager = new NXMAccessManager(this); - - m_DiskCache = new QNetworkDiskCache(this); - connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, version.dwFileVersionLS >> 16); + + m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString()); + + m_DiskCache = new QNetworkDiskCache(this); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); } @@ -188,6 +187,7 @@ void NexusInterface::setCacheDirectory(const QString &directory) void NexusInterface::setNMMVersion(const QString &nmmVersion) { m_NMMVersion = nmmVersion; + m_AccessManager->setNMMVersion(nmmVersion); } void NexusInterface::loginCompleted() diff --git a/src/nexusinterface.h b/src/nexusinterface.h index db61b0bf..cc54daa9 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -305,8 +305,6 @@ private: private: -// static NexusInterface *s_Instance; - QNetworkDiskCache *m_DiskCache; NXMAccessManager *m_AccessManager; diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index a238d1c8..18b47707 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -41,10 +41,11 @@ using namespace MOBase; using namespace MOShared; -NXMAccessManager::NXMAccessManager(QObject *parent) +NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) , m_LoginReply(NULL) , m_ProgressDialog() + , m_MOVersion(moVersion) , m_LoginAttempted(false) { setCookieJar(new PersistentCookieJar( @@ -59,6 +60,10 @@ NXMAccessManager::~NXMAccessManager() } } +void NXMAccessManager::setNMMVersion(const QString &nmmVersion) +{ + m_NMMVersion = nmmVersion; +} QNetworkReply *NXMAccessManager::createRequest( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, @@ -122,7 +127,7 @@ void NXMAccessManager::login(const QString &username, const QString &password) void NXMAccessManager::pageLogin() { - QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/sessions/?Login&uri=%1") + QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/Sessions/?Login&uri=%1") .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage())))); QNetworkRequest request(requestString); @@ -141,6 +146,9 @@ void NXMAccessManager::pageLogin() postDataQuery = postData.encodedQuery(); #endif + QString userAgent = QString("Mod Organizer v%1 (compatible to Nexus Client v%2)").arg(m_MOVersion).arg(m_NMMVersion); + request.setRawHeader("User-Agent", userAgent.toUtf8()); + m_ProgressDialog.setLabelText(tr("Logging into Nexus")); QList buttons = m_ProgressDialog.findChildren(); buttons.at(0)->setEnabled(false); diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 525394c9..9d692bba 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -35,10 +35,12 @@ class NXMAccessManager : public QNetworkAccessManager Q_OBJECT public: - explicit NXMAccessManager(QObject *parent); + explicit NXMAccessManager(QObject *parent, const QString &moVersion); ~NXMAccessManager(); + void setNMMVersion(const QString &nmmVersion); + bool loggedIn() const; bool loginAttempted() const { return m_LoginAttempted; } @@ -93,6 +95,9 @@ private: QNetworkReply *m_LoginReply; QProgressDialog m_ProgressDialog; + QString m_MOVersion; + QString m_NMMVersion; + QString m_Username; QString m_Password; -- cgit v1.3.1 From 4b1e6683d258412dd6e02c55618ff158c749ab4a Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 18 Aug 2014 23:00:25 +0200 Subject: - can now filter by mod content - all files/directories that require write access can now be placed in AppData instead of the MO directory --- .hgignore | 1 + src/Doxyfile | 2639 +++++++++++++++++++++++++----------------- src/ModOrganizer.pro | 2 +- src/categories.cpp | 2 +- src/main.cpp | 54 +- src/mainwindow.cpp | 51 +- src/mainwindow.h | 3 +- src/modinfo.cpp | 23 +- src/modinfo.h | 16 + src/modlistsortproxy.cpp | 21 +- src/modlistsortproxy.h | 9 + src/organizer.pro | 4 +- src/organizer_cs.ts | 1052 +++++++++-------- src/organizer_de.ts | 896 +++++++------- src/organizer_en_US.ts | 731 ++++++------ src/organizer_es.ts | 879 +++++++------- src/organizer_fr.ts | 900 +++++++------- src/organizer_ru.ts | 985 ++++++++-------- src/organizer_tr.ts | 882 +++++++------- src/organizer_zh_CN.ts | 972 ++++++++-------- src/organizer_zh_TW.ts | 972 ++++++++-------- src/pluginlist.cpp | 1 + src/shared/directoryentry.h | 2 - src/shared/fallout3info.cpp | 4 +- src/shared/fallout3info.h | 2 +- src/shared/falloutnvinfo.cpp | 4 +- src/shared/falloutnvinfo.h | 2 +- src/shared/gameinfo.cpp | 83 +- src/shared/gameinfo.h | 7 +- src/shared/oblivioninfo.cpp | 4 +- src/shared/oblivioninfo.h | 2 +- src/shared/skyriminfo.cpp | 4 +- src/shared/skyriminfo.h | 2 +- src/spawn.cpp | 16 - src/version.rc | 4 +- 35 files changed, 6171 insertions(+), 5060 deletions(-) diff --git a/.hgignore b/.hgignore index e8142d4b..d1092a1d 100644 --- a/.hgignore +++ b/.hgignore @@ -31,5 +31,6 @@ html *.log *.filters *.lib +source/organizer/resources/contents/icons syntax: regexp Makefile\.(Debug|Release) diff --git a/src/Doxyfile b/src/Doxyfile index 15fd698b..34c4979a 100644 --- a/src/Doxyfile +++ b/src/Doxyfile @@ -1,358 +1,399 @@ -# Doxyfile 1.8.1.1 +# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project +# doxygen (www.doxygen.org) for a project. # -# All text after a hash (#) is considered a comment and will be ignored +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. # The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# http://www.gnu.org/software/libiconv for the list of possible encodings. +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or sequence of words) that should -# identify the project. Note that if you do not use Doxywizard you need -# to put quotes around the project name if it contains spaces. +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. -PROJECT_NAME = "Mod Organizer UI" +PROJECT_NAME = "Mod Organizer" -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. -PROJECT_NUMBER = 0.12.0 +PROJECT_NUMBER = 1.2.0 -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer -# a quick idea about the purpose of the project. Keep the description short. +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = "The user interface to mod organizer" +PROJECT_BRIEF = "Mod Manager for various computer games" -# With the PROJECT_LOGO tag one can specify an logo or icon that is -# included in the documentation. The maximum height of the logo should not -# exceed 55 pixels and the maximum width should not exceed 200 pixels. -# Doxygen will copy the logo to the output directory. +# With the PROJECT_LOGO tag one can specify an logo or icon that is included in +# the documentation. The maximum height of the logo should not exceed 55 pixels +# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo +# to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = E:/Documents/Projects/ModOrganizer_sf/source/organizer/splash.png -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. -OUTPUT_DIRECTORY = docs +OUTPUT_DIRECTORY = ../ -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would -# otherwise cause performance problems for the file system. +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. CREATE_SUBDIRS = NO -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, -# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English -# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, -# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, -# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. +# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. +# The default value is: YES. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" -# "represents" "a" "an" "the" - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief # description. +# The default value is: NO. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. +# The default value is: NO. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. +# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. FULL_PATH_NAMES = YES -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the -# path to strip. +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that -# are normally passed to the compiler using the -I flag. +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful if your file system -# doesn't support long names like on DOS, Mac, or CD-ROM. +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments -# (thus requiring an explicit @brief command for a brief description.) +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. JAVADOC_AUTOBRIEF = NO -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring -# an explicit \brief command for a brief description.) +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# re-implements. +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will -# be part of the file/class/namespace that contains it. +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a +# new page for each member. If set to NO, the documentation of a member will be +# part of the file/class/namespace that contains it. +# The default value is: NO. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. -TAB_SIZE = 8 +TAB_SIZE = 4 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding -# "class=itcl::class" will allow you to use the command class in the -# itcl::class meaning. +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. TCL_SUBST = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list -# of all members will be omitted, etc. +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for -# Java. For instance, namespaces will be presented as packages, qualified -# scopes will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources only. Doxygen will then generate output that is more tailored for -# Fortran. +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for -# VHDL. +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given extension. -# Doxygen has a built-in mapping, but you can override or extend it using this -# tag. The format is ext=language, where ext is a file extension, and language -# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, -# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions -# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. EXTENSION_MAPPING = -# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all -# comments according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you -# can mix doxygen, HTML, and XML commands with Markdown formatting. -# Disable only in case of backward compatibilities issues. +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. MARKDOWN_SUPPORT = YES -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also makes the inheritance and collaboration +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by by putting a % sign in front of the word +# or globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. +# The default value is: NO. BUILTIN_STL_SUPPORT = NO -# If you use Microsoft's C++/CLI language, you should set this option to YES to +# If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. +# The default value is: NO. CPP_CLI_SUPPORT = NO -# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. -# Doxygen will parse them like normal C++ but will assume all classes use public -# instead of private inheritance when no explicit protection keyword is present. +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. SIP_SUPPORT = NO -# For Microsoft's IDL there are propget and propput attributes to indicate getter -# and setter methods for a property. Setting this option to YES (the default) -# will make doxygen replace the get and set methods by a property in the -# documentation. This will only work if the methods are indeed getting or -# setting a simple type. If this is not the case, or you want to show the -# methods anyway, you should set this option to NO. +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. IDL_PROPERTY_SUPPORT = YES -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. +# The default value is: NO. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. SUBGROUPING = YES -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and -# unions are shown inside the group in which they are included (e.g. using -# @ingroup) instead of on a separate page (for HTML and Man pages) or -# section (for LaTeX and RTF). +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. INLINE_GROUPED_CLASSES = NO -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and -# unions with only public data fields will be shown inline in the documentation -# of the scope in which they are defined (i.e. file, namespace, or group -# documentation), provided this scope is documented. If set to NO (the default), -# structs, classes, and unions are shown on a separate page (for HTML and Man -# pages) or section (for LaTeX and RTF). +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. INLINE_SIMPLE_STRUCTS = NO -# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum -# is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically -# be useful for C code in case the coding convention dictates that all compound +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. TYPEDEF_HIDES_STRUCT = NO -# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to -# determine which symbols to keep in memory and which to flush to disk. -# When the cache is full, less often used symbols will be written to disk. -# For small to medium size projects (<1000 input files) the default value is -# probably good enough. For larger projects a too small cache size can cause -# doxygen to be busy swapping symbols to and from disk most of the time -# causing a significant performance penalty. -# If the system has enough physical memory increasing the cache will improve the -# performance by keeping more symbols in memory. Note that the value works on -# a logarithmic scale so increasing the size by one will roughly double the -# memory usage. The cache size is given by this formula: -# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. - -SYMBOL_CACHE_SIZE = 0 - -# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be -# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given -# their name and scope. Since this can be an expensive process and often the -# same symbol appear multiple times in the code, doxygen keeps a cache of -# pre-resolved symbols. If the cache is too small doxygen will become slower. -# If the cache is too large, memory is wasted. The cache size is given by this -# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, -# corresponding to a cache size of 2^16 = 65536 symbols. +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 @@ -360,1137 +401,1508 @@ LOOKUP_CACHE_SIZE = 0 # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. EXTRACT_ALL = NO -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class -# will be included in the documentation. +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will +# be included in the documentation. +# The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. +# The default value is: NO. EXTRACT_PACKAGE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. +# If the EXTRACT_STATIC tag is set to YES all static members of a file will be +# included in the documentation. +# The default value is: NO. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. EXTRACT_LOCAL_CLASSES = YES -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. -# If set to NO (the default) only methods in the interface are included. +# This flag is only useful for Objective-C code. When set to YES local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO only methods in the interface are +# included. +# The default value is: NO. EXTRACT_LOCAL_METHODS = NO -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base -# name of the file that contains the anonymous namespace. By default -# anonymous namespaces are hidden. +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO these classes will be included in the various overviews. This option has +# no effect if EXTRACT_ALL is enabled. +# The default value is: NO. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO these declarations will be +# included in the documentation. +# The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. +# The default value is: system dependent. CASE_SENSE_NAMES = NO -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES the +# scope will be hidden. +# The default value is: NO. HIDE_SCOPE_NAMES = NO -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. SHOW_INCLUDE_FILES = YES -# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen -# will list include files with double quotes in the documentation -# rather than with sharp brackets. +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. FORCE_LOCAL_INCLUDES = NO -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. +# The default value is: YES. SORT_MEMBER_DOCS = YES -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in -# declaration order. +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. SORT_BRIEF_DOCS = NO -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen -# will sort the (brief and detailed) documentation of class members so that -# constructors and destructors are listed first. If set to NO (the default) -# the constructors will appear in the respective orders defined by -# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. -# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO -# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the -# hierarchy of group names into alphabetical order. If set to NO (the default) -# the group names will appear in their defined order. +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. SORT_GROUP_NAMES = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the -# alphabetical list. +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. SORT_BY_SCOPE_NAME = NO -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to -# do proper type resolution of all parameters of a function it will reject a -# match between the prototype and the implementation of a member function even -# if there is only one candidate or it is obvious which candidate to choose -# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen -# will still accept a match between prototype and implementation in such cases. +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. STRICT_PROTO_MATCHING = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. +# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the +# todo list. This list is created by putting \todo commands in the +# documentation. +# The default value is: YES. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. +# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the +# test list. This list is created by putting \test commands in the +# documentation. +# The default value is: YES. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. +# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. +# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or macro consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and macros in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES the list +# will mention the files that were used to generate the documentation. +# The default value is: YES. SHOW_USED_FILES = YES -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. -# This will remove the Files entry from the Quick Index and from the -# Folder Tree View (if specified). The default is YES. +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. SHOW_FILES = YES -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the -# Namespaces page. This will remove the Namespaces entry from the Quick Index -# and from the Folder Tree View (if specified). The default is YES. +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. SHOW_NAMESPACES = YES -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output -# is used as the file version. See the manual for examples. +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. FILE_VERSION_FILTER = -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. -# You can optionally specify a file name after the option, if omitted -# DoxygenLayout.xml will be used as the name of the layout file. +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. LAYOUT_FILE = -# The CITE_BIB_FILES tag can be used to specify one or more bib files -# containing the references data. This must be a list of .bib files. The -# .bib extension is automatically appended if omitted. Using this command -# requires the bibtex tool to be installed. See also -# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style -# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this -# feature you need bibtex and perl available in the search path. +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. Do not use file names with spaces, bibtex cannot handle them. See +# also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- -# configuration options related to warning and progress messages +# Configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. QUIET = NO -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. +# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. WARN_IF_UNDOCUMENTED = YES -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. WARN_IF_DOC_ERROR = YES -# The WARN_NO_PARAMDOC option can be enabled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of -# documentation. +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO doxygen will only warn about wrong or incomplete parameter +# documentation, but not about the absence of documentation. +# The default value is: NO. WARN_NO_PARAMDOC = NO -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could -# be obtained via FILE_VERSION_FILTER) +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- -# configuration options related to the input files +# Configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. +# Note: If this tag is empty the current directory is searched. INPUT = . -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is -# also the default input encoding. Doxygen uses libiconv (or the iconv built -# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for -# the list of possible encodings. +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. INPUT_ENCODING = UTF-8 -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh -# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py -# *.f90 *.f *.for *.vhd *.vhdl - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.d \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.idl \ - *.odl \ - *.cs \ - *.php \ - *.php3 \ - *.inc \ - *.m \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.for \ - *.vhd \ - *.vhdl - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = NO - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# Note that relative paths are relative to the directory from which doxygen is +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank the +# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, +# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, +# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, +# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, +# *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = NCC \ + boss_modified \ + BossDummy -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded # from the input. +# The default value is: NO. EXCLUDE_SYMLINKS = NO -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories -# for example use the pattern */test/* +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). EXAMPLE_PATH = -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. -EXAMPLE_PATTERNS = * +EXAMPLE_PATTERNS = -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be -# ignored. +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. INPUT_FILTER = -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty or if -# non of the patterns match the file name, INPUT_FILTER is applied. +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER ) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. FILTER_SOURCE_FILES = NO -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) -# and it is also possible to disable source filtering for a specific pattern -# using *.ext= (so without naming a filter). This option only has effect when -# FILTER_SOURCE_FILES is enabled. +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + #--------------------------------------------------------------------------- -# configuration options related to source browsing +# Configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. SOURCE_BROWSER = NO -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C, C++ and Fortran comments will always remain visible. +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES -# then for each documented function all documented -# functions referencing it will be listed. +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. REFERENCED_BY_RELATION = NO -# If the REFERENCES_RELATION tag is set to YES -# then for each documented function all documented entities -# called/used by that function will be listed. +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. REFERENCES_RELATION = NO -# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) -# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from -# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentation. +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES, then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You -# will need version 4.8.6 or higher. +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. VERBATIM_HEADERS = YES +# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# compiled with the --with-libclang option. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + #--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index +# Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. ALPHABETICAL_INDEX = YES -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- -# configuration options related to the HTML output +# Configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. +# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output +# The default value is: YES. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. Note that when using a custom header you are responsible -# for the proper inclusion of any scripts and style sheets that doxygen -# needs, which is dependent on the configuration options used. -# It is advised to generate a default header using "doxygen -w html -# header.html footer.html stylesheet.css YourConfigFile" and then modify -# that header. Note that the header is subject to change so you typically -# have to redo this when upgrading to a newer version of doxygen or when -# changing the value of configuration settings such as GENERATE_TREEVIEW! +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own -# style sheet in the HTML output directory as well, or it will be erased! +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that -# the files will be copied as-is; there are no commands or markers available. +# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- +# defined cascading style sheet that is included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefor more robust against future updates. +# Doxygen will copy the style sheet file to the output directory. For an example +# see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. -# Doxygen will adjust the colors in the style sheet and background images -# according to this color. Hue is specified as an angle on a colorwheel, -# see http://en.wikipedia.org/wiki/Hue for more information. -# For instance the value 0 represents red, 60 is yellow, 120 is green, -# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. -# The allowed range is 0 to 359. +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the stylesheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of -# the colors in the HTML output. For a value of 0 the output will use -# grayscales only. A value of 255 will produce the most vivid colors. +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to -# the luminance component of the colors in the HTML output. Values below -# 100 gradually make the output lighter, whereas values above 100 make -# the output darker. The value divided by 100 is the actual gamma applied, -# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, -# and 100 does not change the gamma. +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting -# this to NO can help when comparing the output of multiple runs. +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the # page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of -# entries shown in the various tree structured indices initially; the user -# can expand and collapse entries dynamically later on. Doxygen will expand -# the tree to such a level that at most the specified number of entries are -# visible (unless a fully collapsed tree already exceeds this amount). -# So setting the number of entries 1 will produce a full collapsed tree by -# default. 0 is a special value representing an infinite number of entries -# and will result in a full expanded tree by default. +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 -# If the GENERATE_DOCSET tag is set to YES, additional index files -# will be generated that can be used as input for Apple's Xcode 3 -# integrated development environment, introduced with OSX 10.5 (Leopard). -# To create a documentation set, doxygen will generate a Makefile in the -# HTML output directory. Running make will produce the docset in that -# directory and running "make install" will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find -# it at startup. -# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO -# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the -# feed. A documentation feed provides an umbrella under which multiple -# documentation sets from a single provider (such as a company or product suite) -# can be grouped. +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" -# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that -# should uniquely identify the documentation set bundle. This should be a -# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen -# will append .docset to the name. +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project -# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher -# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) -# of the generated HTML documentation. +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be # written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler ( hhc.exe). If non-empty +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). +# The GENERATE_CHI flag controls if a separate .chi index file is generated ( +# YES) or that it should be included in the master .chm file ( NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING -# is used to encode HtmlHelp index (hhk), content (hhc) and project file -# content. +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. +# The BINARY_TOC flag controls whether a binary table of contents is generated ( +# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated -# that can be used as input for Qt's qhelpgenerator to generate a -# Qt Compressed Help (.qch) of the generated HTML documentation. +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_QHP = NO +GENERATE_QHP = YES -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can -# be used to specify the file name of the resulting .qch file. -# The path specified is relative to the HTML output folder. +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = -# The QHP_NAMESPACE tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#namespace +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating -# Qt Help Project output. For more information please see -# http://doc.trolltech.com/qthelpproject.html#virtual-folders +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc -# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to -# add. For more information please see -# http://doc.trolltech.com/qthelpproject.html#custom-filters +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = -# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see -# -# Qt Help Project / Custom Filters. +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's -# filter section matches. -# -# Qt Help Project / Filter Attributes. +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can -# be used to specify the location of Qt's qhelpgenerator. -# If non-empty doxygen will try to run qhelpgenerator on the generated -# .qhp file. +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files -# will be generated, which together with the HTML files, form an Eclipse help -# plugin. To install this plugin and make it available under the help contents -# menu in Eclipse, the contents of the directory containing the HTML and XML -# files needs to be copied into the plugins directory of eclipse. The name of -# the directory within the plugins directory should be the same as -# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before -# the help appears. +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO -# A unique identifier for the eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have -# this name. +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project -# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) -# at top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. Since the tabs have the same information as the -# navigation tree you can set this option to NO if you already set -# GENERATE_TREEVIEW to YES. +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. -# If the tag value is set to YES, a side panel will be generated -# containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). -# Windows users are probably better off using the HTML help feature. -# Since the tree basically has the same information as the tab index you -# could consider to set DISABLE_INDEX to NO when enabling this option. - -GENERATE_TREEVIEW = YES - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values -# (range [0,1..20]) that doxygen will group on one line in the generated HTML -# documentation. Note that a value of 0 will completely suppress the enum -# values from appearing in the overview section. +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open -# links to external symbols imported via tag files in a separate window. +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO -# Use this tag to change the font size of Latex formulas included -# as images in the HTML documentation. The default is 10. Note that -# when you change the font size after a successful doxygen run you need -# to manually remove any form_*.png images from the HTML output directory -# to force them to be regenerated. +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are -# not supported properly for IE 6.0, but are supported on all modern browsers. -# Note that when changing this option you need to delete any form_*.png files -# in the HTML output before the changes have effect. +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax -# (see http://www.mathjax.org) which uses client side Javascript for the -# rendering instead of using prerendered bitmaps. Use this if you do not -# have LaTeX installed or if you want to formulas look prettier in the HTML -# output. When enabled you may also need to install MathJax separately and -# configure the path to it using the MATHJAX_RELPATH option. +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using prerendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO -# When MathJax is enabled you need to specify the location relative to the -# HTML output directory using the MATHJAX_RELPATH option. The destination -# directory should contain the MathJax.js script. For instance, if the mathjax -# directory is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to -# the MathJax Content Delivery Network so you can quickly see the result without -# installing MathJax. However, it is strongly recommended to install a local -# copy of MathJax from http://www.mathjax.org before deployment. - -MATHJAX_RELPATH = http://www.mathjax.org/mathjax - -# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension -# names that should be enabled during MathJax rendering. +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = -# When the SEARCHENGINE tag is enabled doxygen will generate a search box -# for the HTML output. The underlying search engine uses javascript -# and DHTML and should work on any modern browser. Note that when using -# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets -# (GENERATE_DOCSET) there is already a search function so this one should -# typically be disabled. For large projects the javascript based search engine -# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /
          )", Qt::CaseInsensitive), 3); + // leave closeTagLength at 0 because we don't want to "eat" the next bullet point + } else if (tagName == "line") { + // ends immediately after the tag + closeTagPos = 6; + // leave closeTagLength at 0 because there is no close tag to skip over + } else { + QString closeTag = QString("[/%1]").arg(tagName); + closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); + if (closeTagPos == -1) { + // workaround to improve compatibility: add fake closing tag + input.append(closeTag); + closeTagPos = input.size() - closeTag.size(); + } + closeTagLength = closeTag.size(); + } if (closeTagPos > -1) { - length = closeTagPos + closeTag.length(); + length = closeTagPos + closeTagLength; QString temp = input.mid(0, length); if (tagIter->second.first.indexIn(temp) == 0) { if (tagIter->second.second.isEmpty()) { @@ -73,6 +89,9 @@ public: qWarning("don't know how to deal with tag %s", qPrintable(tagName)); } } else { + if (tagName == "*") { + temp.remove(QRegExp("(\\[/\\*\\])?(
          )?$")); + } return temp.replace(tagIter->second.first, tagIter->second.second); } } else { @@ -123,6 +142,8 @@ private: "
          \\1
          "); m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), "

          \\1

          "); + m_TagMap["line"] = std::make_pair(QRegExp("\\[line\\]"), + "
          "); // lists m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), @@ -135,8 +156,6 @@ private: "
            \\1
          "); m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), "
        • \\1
        • "); - m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)
          "), - "
        • \\1
        • "); // tables m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), @@ -153,13 +172,26 @@ private: "\\1"); m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " "); - m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " "); + m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + "\\1"); + m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), + "\\2"); m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), "\\2"); m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), "http://www.youtube.com/v/\\1"); + + // make all patterns non-greedy and case-insensitive + for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { + iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); + iter->second.first.setMinimal(true); + } + + // this tag is in fact greedy + m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)"), + "
        • \\1
        • "); + m_ColorMap.insert(std::make_pair("red", "FF0000")); m_ColorMap.insert(std::make_pair("green", "00FF00")); m_ColorMap.insert(std::make_pair("blue", "0000FF")); @@ -170,13 +202,13 @@ private: m_ColorMap.insert(std::make_pair("cyan", "00FFFF")); m_ColorMap.insert(std::make_pair("magenta", "FF00FF")); m_ColorMap.insert(std::make_pair("brown", "A52A2A")); - m_ColorMap.insert(std::make_pair("orange", "FFCC00")); - - // make all patterns non-greedy and case-insensitive - for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { - iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); - iter->second.first.setMinimal(true); - } + m_ColorMap.insert(std::make_pair("orange", "FFA500")); + m_ColorMap.insert(std::make_pair("gold", "FFD700")); + m_ColorMap.insert(std::make_pair("deepskyblue", "00BFFF")); + m_ColorMap.insert(std::make_pair("salmon", "FA8072")); + m_ColorMap.insert(std::make_pair("dodgerblue", "1E90FF")); + m_ColorMap.insert(std::make_pair("greenyellow", "ADFF2F")); + m_ColorMap.insert(std::make_pair("peru", "CD853F")); } private: @@ -209,18 +241,23 @@ QString convertToHTML(const QString &inputParam) // append everything between the previous tag-block and the current one result.append(input.midRef(lastBlock, pos - lastBlock)); - // convert the tag and content if necessary - int length = -1; - QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); - if (length != 0) { - QString temp = convertToHTML(replacement); - result.append(temp); - // length contains the number of characters in the original tag - pos += length; + if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) { + // skip invalid end tag + int tagEnd = input.indexOf(']', pos) + 1; + pos = tagEnd; } else { - // nothing replaced - result.append('['); - ++pos; + // convert the tag and content if necessary + int length = -1; + QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); + if (length != 0) { + result.append(convertToHTML(replacement)); + // length contains the number of characters in the original tag + pos += length; + } else { + // nothing replaced + result.append('['); + ++pos; + } } lastBlock = pos; } diff --git a/src/categories.cpp b/src/categories.cpp index 4c851338..28b1f4a2 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -104,7 +104,11 @@ void CategoryFactory::reset() { m_Categories.clear(); m_IDMap.clear(); - addCategory(0, "None", MakeVector(2, 28, 87), 0); + // 28 = + // 43 = Savegames (makes no sense to install them through MO) + // 45 = Videos and trailers + // 87 = Miscelanous + addCategory(0, "None", MakeVector(4, 28, 43, 45, 87), 0); } @@ -189,24 +193,38 @@ void CategoryFactory::loadDefaultCategories() // mods appear in the combo box addCategory(1, "Animations", MakeVector(1, 51), 0); addCategory(2, "Armour", MakeVector(1, 54), 0); - addCategory(3, "Audio", MakeVector(1, 61), 0); + addCategory(3, "Sound & Music", MakeVector(1, 61), 0); addCategory(5, "Clothing", MakeVector(1, 60), 0); addCategory(6, "Collectables", MakeVector(1, 92), 0); - addCategory(7, "Creatures", MakeVector(2, 83, 65), 0); + addCategory(28, "Companions", MakeVector(2, 66, 96), 0); + addCategory(7, "Creatures & Mounts", MakeVector(2, 83, 65), 0); addCategory(8, "Factions", MakeVector(1, 25), 0); addCategory(9, "Gameplay", MakeVector(1, 24), 0); addCategory(10, "Hair", MakeVector(1, 26), 0); addCategory(11, "Items", MakeVector(2, 27, 85), 0); - addCategory(19, "Weapons", MakeVector(2, 36, 55), 11); + addCategory(32, "Mercantile", MakeVector(1, 69), 0); + addCategory(19, "Weapons", MakeVector(1, 55), 11); + addCategory(36, "Weapon & Armour Sets", MakeVector(1, 39), 11); addCategory(12, "Locations", MakeVector(7, 22, 30, 70, 88, 89, 90, 91), 0); + addCategory(31, "Landscape Changes", MakeVector(1, 58), 0); addCategory(4, "Cities", MakeVector(1, 53), 12); + addCategory(29, "Environment", MakeVector(1, 74), 0); + addCategory(30, "Immersion", MakeVector(1, 78), 0); + addCategory(25, "Castles & Mansions", MakeVector(1, 68), 23); addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); addCategory(21, "Models & Textures", MakeVector(1, 29), 0); + addCategory(33, "Modders resources", MakeVector(1, 82), 0); addCategory(13, "NPCs", MakeVector(1, 33), 0); addCategory(14, "Patches", MakeVector(2, 79, 84), 0); + addCategory(24, "Bugfixes", MakeVector(1, 95), 0); + addCategory(35, "Utilities", MakeVector(1, 39), 0); + addCategory(26, "Cheats", MakeVector(1, 40), 0); + addCategory(23, "Player Homes", MakeVector(1, 67), 0); addCategory(15, "Quests", MakeVector(1, 35), 0); addCategory(16, "Races & Classes", MakeVector(1, 34), 0); + addCategory(27, "Combat", MakeVector(1, 77), 0); addCategory(22, "Skills", MakeVector(1, 73), 0); + addCategory(34, "Stealth", MakeVector(1, 76), 0); addCategory(17, "UI", MakeVector(1, 42), 0); addCategory(18, "Visuals", MakeVector(1, 62), 0); } diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 856ca79c..bc31adf4 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -685,7 +685,7 @@ void DownloadManager::queryInfo(int index) return; } - if (info->m_FileInfo->modID == 0UL) { + if (info->m_FileInfo->modID <= 0) { QString fileName = getFileName(index); QString ignore; NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true); diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 02abf30e..0e3aa55b 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -132,7 +132,7 @@ void EditExecutablesDialog::on_browseButton_clicked() if (::FindExecutableW(binaryNameW.c_str(), NULL, buffer) > (HINSTANCE)32) { DWORD binaryType = 0UL; if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::GetLastError()); + qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); } else if (binaryType == SCS_32BIT_BINARY) { binaryPath = ToQString(buffer); } diff --git a/src/main.cpp b/src/main.cpp index a21f41e4..3198208a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -311,9 +311,19 @@ int main(int argc, char *argv[]) } QString dataPath = instanceID.isEmpty() ? application.applicationDirPath() - : QDir::fromNativeSeparators(QDesktopServices::storageLocation(QDesktopServices::DataLocation)) + "/" + instanceID; + : QDir::fromNativeSeparators( +#if QT_VERSION >= 0x050000 + QStandardPaths::writableLocation(QStandardPaths::DataLocation) +#else + QDesktopServices::storageLocation(QDesktopServices::DataLocation) +#endif + ) + "/" + instanceID; application.setProperty("dataPath", dataPath); +#if QT_VERSION >= 0x050000 + qDebug("ssl support: %d", QSslSocket::supportsSsl()); +#endif + qDebug("data path: %s", qPrintable(dataPath)); if (!QDir(dataPath).exists()) { if (!QDir().mkpath(dataPath)) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0d9fad5d..984d8cfd 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -97,9 +97,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include -#include -#include #include #include #include @@ -114,8 +111,13 @@ along with Mod Organizer. If not, see . #include #include #include +#ifndef Q_MOC_RUN #include #include +#include +#include +#include +#endif #include #ifdef TEST_MODELS @@ -313,8 +315,6 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); -// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); @@ -367,6 +367,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget MainWindow::~MainWindow() { + m_AboutToRun.disconnect_all_slots(); + m_ModInstalled.disconnect_all_slots(); m_RefresherThread.exit(); m_RefresherThread.wait(); m_IntegratedBrowser.close(); @@ -888,6 +890,8 @@ void MainWindow::closeEvent(QCloseEvent* event) storeSettings(); +// unloadPlugins(); + // profile has to be cleaned up before the modinfo-buffer is cleared delete m_CurrentProfile; m_CurrentProfile = NULL; @@ -895,6 +899,7 @@ void MainWindow::closeEvent(QCloseEvent* event) ModInfo::clear(); LogBuffer::cleanQuit(); m_ModList.setProfile(NULL); + NexusInterface::instance()->cleanup(); } @@ -1175,7 +1180,9 @@ bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) IPluginDiagnose *diagnose = qobject_cast(plugin); if (diagnose != NULL) { m_DiagnosisPlugins.push_back(diagnose); - diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }); + m_DiagnosisConnections.push_back( + diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }) + ); } } { // mod page plugin @@ -1204,6 +1211,7 @@ bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) IPluginPreview *preview = qobject_cast(plugin); if (verifyPlugin(preview)) { m_PreviewGenerator.registerPlugin(preview); + return true; } } { // proxy plugins @@ -1242,13 +1250,41 @@ bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) return false; } - -void MainWindow::loadPlugins() +void MainWindow::unloadPlugins() { + // disconnect all slots before unloading plugins so plugins don't have to take care of that + m_AboutToRun.disconnect_all_slots(); + m_ModInstalled.disconnect_all_slots(); + m_ModList.disconnectSlots(); + m_PluginList.disconnectSlots(); + m_DiagnosisPlugins.clear(); + foreach (const boost::signals2::connection &connection, m_DiagnosisConnections) { + connection.disconnect(); + } + m_DiagnosisConnections.clear(); + m_Settings.clearPlugins(); + if (ui->actionTool->menu() != NULL) { + ui->actionTool->menu()->clear(); + } + + foreach (QPluginLoader *loader, m_PluginLoaders) { + qDebug("unloading %s", qPrintable(loader->fileName())); + if (!loader->unload()) { + qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); + } + delete loader; + } + m_PluginLoaders.clear(); +} + +void MainWindow::loadPlugins() +{ + unloadPlugins(); + foreach (QObject *plugin, QPluginLoader::staticInstances()) { registerPlugin(plugin, ""); } @@ -1287,14 +1323,15 @@ void MainWindow::loadPlugins() loadCheck.flush(); QString pluginName = iter.filePath(); if (QLibrary::isLibrary(pluginName)) { - QPluginLoader pluginLoader(pluginName); - if (pluginLoader.instance() == NULL) { + QPluginLoader *pluginLoader = new QPluginLoader(pluginName, this); + if (pluginLoader->instance() == NULL) { m_UnloadedPlugins.push_back(pluginName); qCritical("failed to load plugin %s: %s", - qPrintable(pluginName), qPrintable(pluginLoader.errorString())); + qPrintable(pluginName), qPrintable(pluginLoader->errorString())); } else { - if (registerPlugin(pluginLoader.instance(), pluginName)) { + if (registerPlugin(pluginLoader->instance(), pluginName)) { qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); + m_PluginLoaders.push_back(pluginLoader); } else { m_UnloadedPlugins.push_back(pluginName); qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); @@ -2214,6 +2251,8 @@ void MainWindow::storeSettings() void MainWindow::on_btnRefreshData_clicked() { if (!m_DirectoryUpdate) { + // save the mod list so changes don't get lost + m_CurrentProfile->writeModlistNow(true); refreshDirectoryStructure(); } else { qDebug("directory update"); @@ -4315,13 +4354,10 @@ void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QStrin void MainWindow::installTranslator(const QString &name) { -/* if (m_CurrentLanguage == "en_US") { - return; - }*/ QTranslator *translator = new QTranslator(this); QString fileName = name + "_" + m_CurrentLanguage; if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage != "en-US") { + if ((m_CurrentLanguage != "en-US") && (m_CurrentLanguage != "en_US")) { qWarning("localization file %s not found", qPrintable(fileName)); } // we don't actually expect localization files for english } @@ -4485,7 +4521,7 @@ int MainWindow::getBinaryExecuteInfo(const QFileInfo &targetInfo, if (::FindExecutableW(targetPathW.c_str(), NULL, buffer) > (HINSTANCE)32) { DWORD binaryType = 0UL; if (!::GetBinaryTypeW(targetPathW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::GetLastError()); + qDebug("failed to determine binary type of \"%ls\": %lu", targetPathW.c_str(), ::GetLastError()); } else if (binaryType == SCS_32BIT_BINARY) { binaryPath = ToQString(buffer); } @@ -5088,9 +5124,6 @@ void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) void MainWindow::on_actionProblems_triggered() { -// QString problemDescription; -// checkForProblems(problemDescription); -// QMessageBox::information(this, tr("Problems"), problemDescription); ProblemsDialog problems(m_DiagnosisPlugins, this); if (problems.hasProblems()) { problems.exec(); @@ -5594,7 +5627,11 @@ void MainWindow::on_bossButton_clicked() QStringList temp = report.split("?"); QUrl url = QUrl::fromLocalFile(temp.at(0)); if (temp.size() > 1) { +#if QT_VERSION >= 0x050000 + url.setQuery(temp.at(1).toUtf8()); +#else url.setEncodedQuery(temp.at(1).toUtf8()); +#endif } m_IntegratedBrowser.openUrl(url); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 22152f1c..8bd663ac 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include "executableslist.h" #include "modlist.h" #include "pluginlist.h" @@ -53,7 +54,9 @@ along with Mod Organizer. If not, see . #include "browserdialog.h" #include #include +#ifndef Q_MOC_RUN #include +#endif namespace Ui { class MainWindow; @@ -139,6 +142,8 @@ public: void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + QString getOriginDisplayName(int originID); + void unloadPlugins(); public slots: void refreshLists(); @@ -191,6 +196,7 @@ private: void registerPluginTool(MOBase::IPluginTool *tool); void registerModPage(MOBase::IPluginModPage *modPage); bool registerPlugin(QObject *pluginObj, const QString &fileName); + bool unregisterPlugin(QObject *pluginObj, const QString &fileName); void updateToolBar(); void activateSelectedProfile(); @@ -373,8 +379,10 @@ private: MOBase::IGameInfo *m_GameInfo; std::vector m_DiagnosisPlugins; + std::vector m_DiagnosisConnections; std::vector m_ModPages; std::vector m_UnloadedPlugins; + std::vector m_PluginLoaders; QFile m_PluginsCheck; diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 9558e03b..4ab6d142 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -569,9 +569,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); metaFile.setValue("version", m_Version.canonicalString()); - if (m_NexusID != -1) { - metaFile.setValue("modid", m_NexusID); - } + metaFile.setValue("modid", m_NexusID); metaFile.setValue("notes", m_Notes); metaFile.setValue("nexusDescription", m_NexusDescription); metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); diff --git a/src/modlist.cpp b/src/modlist.cpp index 681d880c..eedf1ec6 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -64,6 +64,12 @@ ModList::ModList(QObject *parent) m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(QIcon(":/MO/gui/content/texture"), ":/MO/gui/content/texture", tr("Textures")); } +ModList::~ModList() +{ + m_ModStateChanged.disconnect_all_slots(); + m_ModMoved.disconnect_all_slots(); +} + void ModList::setProfile(Profile *profile) { m_Profile = profile; @@ -653,6 +659,11 @@ void ModList::modInfoChanged(ModInfo::Ptr info) } } +void ModList::disconnectSlots() { + m_ModMoved.disconnect_all_slots(); + m_ModStateChanged.disconnect_all_slots(); +} + IModList::ModStates ModList::state(unsigned int modIndex) const { IModList::ModStates result; diff --git a/src/modlist.h b/src/modlist.h index 6116a913..632689c6 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -32,7 +32,9 @@ along with Mod Organizer. If not, see . #include #include #include +#ifndef Q_MOC_RUN #include +#endif #include #include #include @@ -73,6 +75,8 @@ public: **/ ModList(QObject *parent = NULL); + ~ModList(); + /** * @brief set the profile used for status information * @@ -103,6 +107,8 @@ public: void modInfoAboutToChange(ModInfo::Ptr info); void modInfoChanged(ModInfo::Ptr info); + void disconnectSlots(); + public: /// \copydoc MOBase::IModList::displayName diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index d844cd39..30221f4b 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -145,30 +145,25 @@ QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); NexusInterface::NexusInterface() : m_NMMVersion() { - atexit(&cleanup); - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, version.dwFileVersionLS >> 16); m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString()); - m_DiskCache = new QNetworkDiskCache(this); connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); } - -void NexusInterface::cleanup() -{ -} - - NXMAccessManager *NexusInterface::getAccessManager() { return m_AccessManager; } +NexusInterface::~NexusInterface() +{ + cleanup(); +} NexusInterface *NexusInterface::instance() { @@ -176,14 +171,12 @@ NexusInterface *NexusInterface::instance() return &s_Instance; } - void NexusInterface::setCacheDirectory(const QString &directory) { m_DiskCache->setCacheDirectory(directory); m_AccessManager->setCache(m_DiskCache); } - void NexusInterface::setNMMVersion(const QString &nmmVersion) { m_NMMVersion = nmmVersion; @@ -378,6 +371,14 @@ bool NexusInterface::requiresLogin(const NXMRequestInfo &info) || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); } +void NexusInterface::cleanup() +{ +// delete m_AccessManager; +// delete m_DiskCache; + m_AccessManager = nullptr; + m_DiskCache = nullptr; +} + void NexusInterface::nextRequest() { if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) diff --git a/src/nexusinterface.h b/src/nexusinterface.h index cc54daa9..af2f8c75 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -130,6 +130,8 @@ class NexusInterface : public QObject public: + ~NexusInterface(); + static NexusInterface *instance(); /** @@ -137,6 +139,11 @@ public: **/ NXMAccessManager *getAccessManager(); + /** + * @brief cleanup this interface. this is destructive, afterwards it can't be used again + */ + void cleanup(); + /** * @brief request description for a mod * @@ -301,7 +308,6 @@ private: void nextRequest(); void requestFinished(std::list::iterator iter); bool requiresLogin(const NXMRequestInfo &info); - static void cleanup(); private: diff --git a/src/organizer.pro b/src/organizer.pro index bffd1e16..b24586e6 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -9,11 +9,15 @@ TARGET = ModOrganizer TEMPLATE = app greaterThan(QT_MAJOR_VERSION, 4) { - QT += core gui widgets network xml sql xmlpatterns qml quick script webkit + QT += core gui widgets network xml sql xmlpatterns qml declarative script webkit webkitwidgets } else { QT += core gui network xml declarative script sql xmlpatterns webkit } +!include(../LocalPaths.pri) { + message("paths to required libraries need to be set up in LocalPaths.pri") +} + SOURCES += \ transfersavesdialog.cpp \ syncoverwritedialog.cpp \ @@ -248,9 +252,9 @@ LIBS += -lDbgHelp #DEFINES += TEST_MODELS -INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$(BOOSTPATH)" +INCLUDEPATH += ../shared ../archive ../uibase ../bsatk ../esptk ../boss_modified/boss-api "$${BOOSTPATH}" -LIBS += -L"$(BOOSTPATH)/stage/lib" +LIBS += -L"$${BOOSTPATH}/stage/lib" CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../shared/debug @@ -258,7 +262,8 @@ CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../uibase/debug LIBS += -L$$OUT_PWD/../boss_modified/debug LIBS += -lDbgHelp - PRE_TARGETDEPS += $$OUT_PWD/../shared/debug/mo_shared.lib \ + PRE_TARGETDEPS += \ + $$OUT_PWD/../shared/debug/mo_shared.lib \ $$OUT_PWD/../bsatk/debug/bsatk.lib } else { LIBS += -L$$OUT_PWD/../shared/release @@ -266,20 +271,19 @@ CONFIG(debug, debug|release) { LIBS += -L$$OUT_PWD/../uibase/release LIBS += -L$$OUT_PWD/../boss_modified/release QMAKE_CXXFLAGS += /Zi# /GL -# QMAKE_CXXFLAGS -= -O2 QMAKE_LFLAGS += /DEBUG# /LTCG /OPT:REF /OPT:ICF - PRE_TARGETDEPS += $$OUT_PWD/../shared/release/mo_shared.lib \ + PRE_TARGETDEPS += \ + $$OUT_PWD/../shared/release/mo_shared.lib \ $$OUT_PWD/../bsatk/release/bsatk.lib } #QMAKE_CXXFLAGS_WARN_ON -= -W3 #QMAKE_CXXFLAGS_WARN_ON += -W4 -QMAKE_CXXFLAGS += -wd4127 -wd4512 -wd4189 +QMAKE_CXXFLAGS += -wd4100 -wd4127 -wd4512 -wd4189 CONFIG += embed_manifest_exe # QMAKE_CXXFLAGS += /analyze - # QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'highestAvailable\' uiAccess=\'false\'\" TRANSLATIONS = organizer_de.ts \ @@ -313,9 +317,9 @@ TRANSLATIONS = organizer_de.ts \ LIBS += -lmo_shared -luibase -lshell32 -lole32 -luser32 -ladvapi32 -lgdi32 -lPsapi -lVersion -lbsatk -lshlwapi -LIBS += -L"$(ZLIBPATH)/build" -lzlibstatic +LIBS += -L"$${ZLIBPATH}/build" -lzlibstatic -DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS NOMINMAX +DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS NOMINMAX DEFINES += BOOST_DISABLE_ASSERTS NDEBUG #DEFINES += QMLJSDEBUGGER @@ -324,40 +328,40 @@ HGID = $$system(hg id -i) DEFINES += HGID=\\\"$${HGID}\\\" CONFIG(debug, debug|release) { - OUTDIR = $$OUT_PWD/debug + SRCDIR = $$OUT_PWD/debug DSTDIR = $$PWD/../../outputd } else { - OUTDIR = $$OUT_PWD/release + SRCDIR = $$OUT_PWD/release DSTDIR = $$PWD/../../output } -SRCDIR = $$PWD +BASEDIR = $$PWD +BASEDIR ~= s,/,$$QMAKE_DIR_SEP,g SRCDIR ~= s,/,$$QMAKE_DIR_SEP,g -OUTDIR ~= s,/,$$QMAKE_DIR_SEP,g DSTDIR ~= s,/,$$QMAKE_DIR_SEP,g -QMAKE_POST_LINK += xcopy /y /I $$quote($$OUTDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n) -QMAKE_POST_LINK += xcopy /y /I $$quote($$OUTDIR\\ModOrganizer*.pdb) $$quote($$DSTDIR) $$escape_expand(\\n) -QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\stylesheets) $$quote($$DSTDIR)\\stylesheets $$escape_expand(\\n) -QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\tutorials) $$quote($$DSTDIR)\\tutorials $$escape_expand(\\n) -QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /I $$quote($$SRCDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /I $$quote($$SRCDIR\\ModOrganizer*.pdb) $$quote($$DSTDIR) $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\stylesheets) $$quote($$DSTDIR)\\stylesheets $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\tutorials) $$quote($$DSTDIR)\\tutorials $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n) CONFIG(debug, debug|release) { greaterThan(QT_MAJOR_VERSION, 4) { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\..\\dlls.*manifest.debug.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) QMAKE_POST_LINK += copy /y $$quote($$DSTDIR\\dlls\\dlls.manifest.debug.qt5) $$quote($$DSTDIR\\dlls\\dlls.manifest) $$escape_expand(\\n) QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug.qt5 $$escape_expand(\\n) } else { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\..\\dlls.*manifest.debug) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) QMAKE_POST_LINK += copy /y $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$quote($$DSTDIR)\\dlls\\dlls.manifest $$escape_expand(\\n) QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$escape_expand(\\n) } } else { greaterThan(QT_MAJOR_VERSION, 4) { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\..\\dlls.*manifest.qt5) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) QMAKE_POST_LINK += copy /y $$quote($$DSTDIR\\dlls\\dlls.manifest.qt5) $$quote($$DSTDIR\\dlls\\dlls.manifest) $$escape_expand(\\n) QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.qt5 $$escape_expand(\\n) } else { - QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) + QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n) } } diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index f37f9e3a..43246b55 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -33,6 +33,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -95,6 +96,8 @@ PluginList::PluginList(QObject *parent) PluginList::~PluginList() { + m_Refreshed.disconnect_all_slots(); + m_PluginMoved.disconnect_all_slots(); } @@ -484,7 +487,7 @@ void PluginList::saveTo(const QString &pluginFileName if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); } - } else { + } else if (QFile::exists(deleterFileName)) { shellDelete(QStringList() << deleterFileName); } diff --git a/src/pluginlist.h b/src/pluginlist.h index 67d1b640..8c06fefd 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -26,15 +26,16 @@ along with Mod Organizer. If not, see . #include #include #include +#ifndef Q_MOC_RUN #include #include +#endif #include #include #include "pdll.h" #include - template class ChangeBracket { public: @@ -212,6 +213,11 @@ public: void refreshLoadOrder(); + void disconnectSlots() { + m_PluginMoved.disconnect_all_slots(); + m_Refreshed.disconnect_all_slots(); + } + public: virtual PluginState state(const QString &name) const; @@ -333,13 +339,11 @@ private: mutable QTimer m_SaveTimer; SignalRefreshed m_Refreshed; + SignalPluginMoved m_PluginMoved; QTemporaryFile m_TempFile; - SignalPluginMoved m_PluginMoved; }; - - #endif // PLUGINLIST_H diff --git a/src/settings.cpp b/src/settings.cpp index 6bf13ee0..65ea3a27 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -132,7 +132,6 @@ void Settings::registerPlugin(IPlugin *plugin) } } - QString Settings::obfuscate(const QString &password) const { QByteArray temp = password.toUtf8(); diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index e0914870..5d785822 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -936,10 +936,6 @@ void FileRegister::removeOriginMulti(std::set indices, int ori } } - if (removedFiles.size() > 0) { - log("%d files actually removed", removedFiles.size()); - } - // optimization: this is only called when disabling an origin and in this case we don't have // to remove the file from the origin diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 7836d719..096f373e 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -29,8 +29,10 @@ along with Mod Organizer. If not, see . #define WIN32_MEAN_AND_LEAN #include #include +#ifndef Q_MOC_RUN #include #include +#endif #include "util.h" diff --git a/src/shared/shared.pro b/src/shared/shared.pro index 84a9c274..69f6f2e6 100644 --- a/src/shared/shared.pro +++ b/src/shared/shared.pro @@ -11,33 +11,11 @@ TARGET = mo_shared TEMPLATE = lib CONFIG += staticlib -INCLUDEPATH += ../bsatk "$(BOOSTPATH)" - -# only for custom leak detection -DEFINES += TRACE_LEAKS -LIBS += -lDbgHelp - - -CONFIG(debug, debug|release) { - LIBS += -L$$OUT_PWD/../bsatk/debug - LIBS += -lDbgHelp - QMAKE_CXXFLAGS_DEBUG -= -Zi - QMAKE_CXXFLAGS += -Z7 - PRE_TARGETDEPS += $$OUT_PWD/../bsatk/debug/bsatk.lib -} else { - LIBS += -L$$OUT_PWD/../bsatk/release - PRE_TARGETDEPS += $$OUT_PWD/../bsatk/release/bsatk.lib +!include(../LocalPaths.pri) { + message("paths to required libraries need to be set up in LocalPaths.pri") } -LIBS += -lbsatk - -DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS - -DEFINES += BOOST_DISABLE_ASSERTS NDEBUG - -# QMAKE_CXXFLAGS += /analyze - SOURCES += \ inject.cpp \ windows_error.cpp \ @@ -66,3 +44,30 @@ HEADERS += \ appconfig.h \ appconfig.inc \ leaktrace.h + + +# only for custom leak detection +DEFINES += TRACE_LEAKS +LIBS += -lDbgHelp + + +CONFIG(debug, debug|release) { + LIBS += -L$$OUT_PWD/../bsatk/debug + LIBS += -lDbgHelp + QMAKE_CXXFLAGS_DEBUG -= -Zi + QMAKE_CXXFLAGS += -Z7 + PRE_TARGETDEPS += $$OUT_PWD/../bsatk/debug/bsatk.lib +} else { + LIBS += -L$$OUT_PWD/../bsatk/release + PRE_TARGETDEPS += $$OUT_PWD/../bsatk/release/bsatk.lib +} + +LIBS += -lbsatk + +DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS + +DEFINES += BOOST_DISABLE_ASSERTS NDEBUG + +# QMAKE_CXXFLAGS += /analyze + +INCLUDEPATH += ../bsatk "$${BOOSTPATH}" diff --git a/src/tutorials/TutorialOverlay.qml b/src/tutorials/TutorialOverlay.qml index 34959d7d..f2aad027 100644 --- a/src/tutorials/TutorialOverlay.qml +++ b/src/tutorials/TutorialOverlay.qml @@ -1,4 +1,3 @@ -// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 import QtQuick 1.1 import "tutorials.js" as Logic @@ -21,10 +20,11 @@ Rectangle { function enableBackground(enabled) { disabledBackground.visible = enabled } - +/* signal nextStep - onNextStep: { + onNextStep: {*/ + function nextStep() { if (step == 0) { Logic.init() } diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index 4f9c1a74..5ee4e790 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -15,19 +15,24 @@ function getTutorialSteps() function() { tutorial.text = qsTr("The highlighted button provides hints on solving problems MO recognized automatically.") - if (!tutorialControl.waitForAction("actionProblems")) { - highlightAction("actionProblems", false) - waitForClick() - } else { + if (tutorialControl.waitForAction("actionProblems")) { tutorial.text += qsTr("\nThere IS a problem now but you may want to hold off on fixing it until after completing the tutorial.") highlightAction("actionProblems", true) + } else { + highlightAction("actionProblems", false) + waitForClick() } }, function() { + console.log("next") tutorial.text = qsTr("This button provides multiple sources of information and further tutorials.") - highlightItem("actionHelp", true) - tutorialControl.waitForButton("actionHelp") + if (tutorialControl.waitForButton("actionHelp")) { + highlightItem("actionHelp", true) + } else { + console.error("help button broken") + waitForClick() + } }, function() { @@ -47,12 +52,16 @@ function getTutorialSteps() function() { tutorial.text = qsTr("Before we start installing mods, let's have a quick look at the settings.") manager.activateTutorial("SettingsDialog", "tutorial_firststeps_settings.js") - highlightAction("actionSettings", true) - tutorialControl.waitForAction("actionSettings") + if (tutorialControl.waitForAction("actionSettings")) { + highlightAction("actionSettings", true) + } else { + console.error("settings action broken") + waitForClick() + } }, function() { - tutorial.text = qsTr("Now it's time to install a few mods!" + tutorial.text = qsTr("Now it's time to install a few mods!" + "Please go along with this because we need a few mods installed to demonstrate other features") waitForClick() }, @@ -61,8 +70,12 @@ function getTutorialSteps() tutorial.text = qsTr("There are a few ways to get mods into ModOrganizer. " + "If you associated MO with NXM links in the settings you can now use your regular browser to send downloads from Nexus to MO. " + "Click on \"Nexus\" to open nexus, find a mod and click the green download buttons on Nexus saying \"Download with Manager\".") - highlightAction("actionNexus", true) - tutorialControl.waitForAction("actionNexus") + if (tutorialControl.waitForAction("actionNexus")) { + highlightAction("actionNexus", true) + } else { + console.error("browser action broken") + waitForClick() + } }, function() { -- cgit v1.3.1 From 12a57d7f6dfbff8a5de618fc7f09066b83efa78f Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 10 Sep 2014 20:34:31 +0200 Subject: - descriptions for plugin settings are now displayed --- src/settings.cpp | 1486 ++++++++++++++++++++++++------------------------ src/settings.h | 621 ++++++++++---------- src/settingsdialog.cpp | 355 ++++++------ 3 files changed, 1238 insertions(+), 1224 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 65ea3a27..04a5f279 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,741 +1,745 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "settings.h" - -#include "settingsdialog.h" -#include "utility.h" -#include "helper.h" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -template -class QListWidgetItemEx : public QListWidgetItem { -public: - QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) - : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} - - virtual bool operator< ( const QListWidgetItem & other ) const { - return this->data(m_SortRole).value() < other.data(m_SortRole).value(); - } -private: - int m_SortRole; -}; - - -static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, - 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; - -Settings *Settings::s_Instance = NULL; - - -Settings::Settings() - : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) -{ - if (s_Instance != NULL) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } -} - - -Settings::~Settings() -{ - s_Instance = NULL; -} - - -Settings &Settings::instance() -{ - if (s_Instance == NULL) { - throw std::runtime_error("no instance of \"Settings\""); - } - return *s_Instance; -} - -void Settings::clearPlugins() -{ - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); - int count = m_Settings.beginReadArray("pluginBlacklist"); - for (int i = 0; i < count; ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } - m_Settings.endArray(); -} - -bool Settings::pluginBlacklisted(const QString &fileName) const -{ - return m_PluginBlacklist.contains(fileName); -} - -void Settings::registerAsNXMHandler(bool force) -{ - std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); - std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); - std::wstring mode = force ? L"forcereg" : L"reg"; - std::wstring parameters = mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\""; - HINSTANCE res = ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), parameters.c_str(), NULL, SW_SHOWNORMAL); - if ((int)res <= 32) { - QMessageBox::critical(NULL, tr("Failed"), - tr("Sorry, failed to start the helper application")); - } -} - -void Settings::registerPlugin(IPlugin *plugin) -{ - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QMap()); - foreach (const PluginSetting &setting, plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); - temp = setting.defaultValue; - } - m_PluginSettings[plugin->name()][setting.key] = temp; - } -} - -QString Settings::obfuscate(const QString &password) const -{ - QByteArray temp = password.toUtf8(); - - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return buffer.toBase64(); -} - - -QString Settings::deObfuscate(const QString &password) const -{ - QByteArray temp(QByteArray::fromBase64(password.toUtf8())); - - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return QString::fromUtf8(buffer.constData()); -} - - -bool Settings::hideUncheckedPlugins() const -{ - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); -} - -bool Settings::forceEnableCoreFiles() const -{ - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); -} - -bool Settings::automaticLoginEnabled() const -{ - return m_Settings.value("Settings/nexus_login", false).toBool(); -} - -QString Settings::getSteamAppID() const -{ - return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); -} - -QString Settings::getDownloadDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); -} - - -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.beginGroup("Servers"); - - foreach (const QString &serverKey, m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -std::map Settings::getPreferredServers() -{ - std::map result; - m_Settings.beginGroup("Servers"); - - foreach (const QString &serverKey, m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - -QString Settings::getCacheDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); -} - -QString Settings::getModDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); -} - -QString Settings::getNMMVersion() const -{ - static const QString MIN_NMM_VERSION = "0.47.0"; - QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); - if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { - result = MIN_NMM_VERSION; - } - return result; -} - -bool Settings::getNexusLogin(QString &username, QString &password) const -{ - if (m_Settings.value("Settings/nexus_login", false).toBool()) { - username = m_Settings.value("Settings/nexus_username", "").toString(); - password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); - return true; - } else { - return false; - } -} - -bool Settings::compactDownloads() const -{ - return m_Settings.value("Settings/compact_downloads", false).toBool(); -} - -bool Settings::metaDownloads() const -{ - return m_Settings.value("Settings/meta_downloads", false).toBool(); -} - -bool Settings::offlineMode() const -{ - return m_Settings.value("Settings/offline_mode", false).toBool(); -} - -int Settings::logLevel() const -{ - return m_Settings.value("Settings/log_level", 0).toInt(); -} - - -void Settings::setNexusLogin(QString username, QString password) -{ - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", username); - m_Settings.setValue("Settings/nexus_password", obfuscate(password)); -} - - -LoadMechanism::EMechanism Settings::getLoadMechanism() const -{ - switch (m_Settings.value("Settings/load_mechanism").toInt()) { - case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; - case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; - case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; - } - throw std::runtime_error("invalid load mechanism"); -} - - -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); -} - - -bool Settings::useProxy() -{ - return m_Settings.value("Settings/use_proxy", false).toBool(); -} - -bool Settings::displayForeign() -{ - return m_Settings.value("Settings/display_foreign", true).toBool(); -} - -void Settings::setMotDHash(uint hash) -{ - m_Settings.setValue("motd_hash", hash); -} - -uint Settings::getMotDHash() const -{ - return m_Settings.value("motd_hash", 0).toUInt(); -} - -QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } - - return *iterSetting; -} - -void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); -} - -QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); -} - -void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); - if (sync) { - m_Settings.sync(); - } -} - -QString Settings::language() -{ - QString result = m_Settings.value("Settings/language", "").toString(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); - } - } - return result; -} - -void Settings::updateServers(const QList &servers) -{ - m_Settings.beginGroup("Servers"); - QStringList oldServerKeys = m_Settings.childKeys(); - - foreach (const ServerInfo &server, servers) { - if (!oldServerKeys.contains(server.name)) { - // not yet known server - QVariantMap newVal; - newVal["premium"] = server.premium; - newVal["preferred"] = server.preferred ? 1 : 0; - newVal["lastSeen"] = server.lastSeen; - newVal["downloadCount"] = 0; - newVal["downloadSpeed"] = 0.0; - - m_Settings.setValue(server.name, newVal); - } else { - QVariantMap data = m_Settings.value(server.name).toMap(); - data["lastSeen"] = server.lastSeen; - data["premium"] = server.premium; - - m_Settings.setValue(server.name, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -void Settings::addBlacklistPlugin(const QString &fileName) -{ - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); -} - -void Settings::writePluginBlacklist() -{ - m_Settings.beginWriteArray("pluginBlacklist"); - int idx = 0; - foreach (const QString &plugin, m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); - } - - m_Settings.endArray(); -} - -void Settings::addLanguages(QComboBox *languageBox) -{ - languageBox->addItem("English", "en_US"); - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); - QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; - QRegExp exp(pattern); - while (langIter.hasNext()) { - langIter.next(); - QString file = langIter.fileName(); - if (exp.exactMatch(file)) { - QString languageCode = exp.cap(1); - QLocale locale(languageCode); - QString languageString = QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } - } - languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); - } - } -} - -void Settings::addStyles(QComboBox *styleBox) -{ - styleBox->addItem("None", ""); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - styleBox->addItem("Fusion", "Fusion"); -#else - styleBox->addItem("Plastique", "Plastique"); - styleBox->addItem("Cleanlooks", "Cleanlooks"); -#endif - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - styleBox->addItem(style, style); - } -} - -void Settings::resetDialogs() -{ - m_Settings.beginGroup("DialogChoices"); - QStringList keys = m_Settings.childKeys(); - foreach (QString key, keys) { - m_Settings.remove(key); - } - - m_Settings.endGroup(); -} - - -void Settings::query(QWidget *parent) -{ - SettingsDialog dialog(parent); - - connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - - // General Page - QComboBox *languageBox = dialog.findChild("languageBox"); - QComboBox *styleBox = dialog.findChild("styleBox"); - QComboBox *logLevelBox = dialog.findChild("logLevelBox"); - QCheckBox *compactBox = dialog.findChild("compactBox"); - QCheckBox *showMetaBox = dialog.findChild("showMetaBox"); - - QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); - QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); - QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); - - // nexus page - QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); - QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); - QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); - QCheckBox *offlineBox = dialog.findChild("offlineBox"); - QCheckBox *proxyBox = dialog.findChild("proxyBox"); - - QListWidget *knownServersList = dialog.findChild("knownServersList"); - QListWidget *preferredServersList = dialog.findChild("preferredServersList"); - - // plugis page - QListWidget *pluginsList = dialog.findChild("pluginsList"); - QListWidget *pluginBlacklistList = dialog.findChild("pluginBlacklist"); - - // workarounds page - QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); - QComboBox *mechanismBox = dialog.findChild("mechanismBox"); - QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); - QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); - QCheckBox *hideUncheckedBox = dialog.findChild("hideUncheckedBox"); - QCheckBox *displayForeignBox = dialog.findChild("displayForeignBox"); - - - // - // set up current settings - // - LoadMechanism::EMechanism mechanismID = getLoadMechanism(); - int index = 0; - - if (m_LoadMechanism.isDirectLoadingSupported()) { - mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); - if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isScriptExtenderSupported()) { - mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isProxyDLLSupported()) { - mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = mechanismBox->count() - 1; - } - } - - mechanismBox->setCurrentIndex(index); - - { - addLanguages(languageBox); - QString languageCode = language(); - int currentID = languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both variants - if (currentID == -1) { - currentID = languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - languageBox->setCurrentIndex(currentID); - } - } - - { - addStyles(styleBox); - int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - styleBox->setCurrentIndex(currentID); - } - } - - compactBox->setChecked(compactDownloads()); - showMetaBox->setChecked(metaDownloads()); - - hideUncheckedBox->setChecked(hideUncheckedPlugins()); - displayForeignBox->setChecked(displayForeign()); - forceEnableBox->setChecked(forceEnableCoreFiles()); - - appIDEdit->setText(getSteamAppID()); - - if (automaticLoginEnabled()) { - loginCheckBox->setChecked(true); - usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); - passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); - } - - downloadDirEdit->setText(getDownloadDirectory()); - modDirEdit->setText(getModDirectory()); - cacheDirEdit->setText(getCacheDirectory()); - offlineBox->setChecked(offlineMode()); - proxyBox->setChecked(useProxy()); - nmmVersionEdit->setText(getNMMVersion()); - logLevelBox->setCurrentIndex(logLevel()); - - // display plugin settings - foreach (IPlugin *plugin, m_Plugins) { - QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); - listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); - pluginsList->addItem(listItem); - } - - // display plugin blacklist - foreach (const QString &pluginName, m_PluginBlacklist) { - pluginBlacklistList->addItem(pluginName); - } - - // display server preferences - m_Settings.beginGroup("Servers"); - foreach (const QString &key, m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - QString type = val["premium"].toBool() ? "(premium)" : "(free)"; - - QString descriptor = key + " " + type; - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); - } - - QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); - - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { - preferredServersList->addItem(newItem); - } else { - knownServersList->addItem(newItem); - } - preferredServersList->sortItems(Qt::DescendingOrder); - } - m_Settings.endGroup(); - - if (dialog.exec() == QDialog::Accepted) { - // - // transfer modified settings to configuration file - // - - m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); - m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); - m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); - m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); - - - { // advanced settings - if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && - (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " - "Mods not present (or named differently) in the new location will be disabled in all profiles. " - "There is no way to undo this unless you backed up your profiles manually. Proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - modDirEdit->setText(getModDirectory()); - } - - if (!QDir(downloadDirEdit->text()).exists()) { - QDir().mkpath(downloadDirEdit->text()); - } - if (!QDir(cacheDirEdit->text()).exists()) { - QDir().mkpath(cacheDirEdit->text()); - } - if (!QDir(modDirEdit->text()).exists()) { - QDir().mkpath(modDirEdit->text()); - } - - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); - } - - - QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); - QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); - if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit languageChanged(newLanguage); - } - - QString oldStyle = m_Settings.value("Settings/style", "").toString(); - QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); - if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit styleChanged(newStyle); - } - - m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); - - if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { - m_Settings.setValue("Settings/app_id", appIDEdit->text()); - } else { - m_Settings.remove("Settings/app_id"); - } - if (loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text())); - } else { - m_Settings.setValue("Settings/nexus_login", false); - m_Settings.remove("Settings/nexus_username"); - m_Settings.remove("Settings/nexus_password"); - } - m_Settings.setValue("Settings/offline_mode", offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked()); - - m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); - - // transfer plugin settings to in-memory structure - for (int i = 0; i < pluginsList->count(); ++i) { - QListWidgetItem *item = pluginsList->item(i); - m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); - } - } - - // store plugin blacklist - m_PluginBlacklist.clear(); - foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { - m_PluginBlacklist.insert(item->text()); - } - writePluginBlacklist(); - - // store server preference - m_Settings.beginGroup("Servers"); - for (int i = 0; i < knownServersList->count(); ++i) { - QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = 0; - m_Settings.setValue(key, val); - } - int count = preferredServersList->count(); - for (int i = 0; i < count; ++i) { - QString key = preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = count - i; - m_Settings.setValue(key, val); - } - m_Settings.endGroup(); - } -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "settings.h" + +#include "settingsdialog.h" +#include "utility.h" +#include "helper.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +template +class QListWidgetItemEx : public QListWidgetItem { +public: + QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) + : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} + + virtual bool operator< ( const QListWidgetItem & other ) const { + return this->data(m_SortRole).value() < other.data(m_SortRole).value(); + } +private: + int m_SortRole; +}; + + +static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, + 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; + +Settings *Settings::s_Instance = NULL; + + +Settings::Settings() + : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) +{ + if (s_Instance != NULL) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } +} + + +Settings::~Settings() +{ + s_Instance = NULL; +} + + +Settings &Settings::instance() +{ + if (s_Instance == NULL) { + throw std::runtime_error("no instance of \"Settings\""); + } + return *s_Instance; +} + +void Settings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); + + m_PluginBlacklist.clear(); + int count = m_Settings.beginReadArray("pluginBlacklist"); + for (int i = 0; i < count; ++i) { + m_Settings.setArrayIndex(i); + m_PluginBlacklist.insert(m_Settings.value("name").toString()); + } + m_Settings.endArray(); +} + +bool Settings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); +} + +void Settings::registerAsNXMHandler(bool force) +{ + std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); + std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); + std::wstring mode = force ? L"forcereg" : L"reg"; + std::wstring parameters = mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\""; + HINSTANCE res = ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), parameters.c_str(), NULL, SW_SHOWNORMAL); + if ((int)res <= 32) { + QMessageBox::critical(NULL, tr("Failed"), + tr("Sorry, failed to start the helper application")); + } +} + +void Settings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QMap()); + m_PluginDescriptions.insert(plugin->name(), QMap()); + foreach (const PluginSetting &setting, plugin->settings()) { + QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { + qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", + qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); + temp = setting.defaultValue; + } + m_PluginSettings[plugin->name()][setting.key] = temp; + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); + } +} + + +QString Settings::obfuscate(const QString &password) const +{ + QByteArray temp = password.toUtf8(); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return buffer.toBase64(); +} + + +QString Settings::deObfuscate(const QString &password) const +{ + QByteArray temp(QByteArray::fromBase64(password.toUtf8())); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return QString::fromUtf8(buffer.constData()); +} + + +bool Settings::hideUncheckedPlugins() const +{ + return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); +} + +bool Settings::forceEnableCoreFiles() const +{ + return m_Settings.value("Settings/force_enable_core_files", true).toBool(); +} + +bool Settings::automaticLoginEnabled() const +{ + return m_Settings.value("Settings/nexus_login", false).toBool(); +} + +QString Settings::getSteamAppID() const +{ + return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); +} + +QString Settings::getDownloadDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); +} + + +void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.beginGroup("Servers"); + + foreach (const QString &serverKey, m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + if (serverKey == serverName) { + data["downloadCount"] = data["downloadCount"].toInt() + 1; + data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); + m_Settings.setValue(serverKey, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +std::map Settings::getPreferredServers() +{ + std::map result; + m_Settings.beginGroup("Servers"); + + foreach (const QString &serverKey, m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + int preference = data["preferred"].toInt(); + if (preference > 0) { + result[serverKey] = preference; + } + } + m_Settings.endGroup(); + + return result; +} + +QString Settings::getCacheDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); +} + +QString Settings::getModDirectory() const +{ + return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); +} + +QString Settings::getNMMVersion() const +{ + static const QString MIN_NMM_VERSION = "0.47.0"; + QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); + if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { + result = MIN_NMM_VERSION; + } + return result; +} + +bool Settings::getNexusLogin(QString &username, QString &password) const +{ + if (m_Settings.value("Settings/nexus_login", false).toBool()) { + username = m_Settings.value("Settings/nexus_username", "").toString(); + password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); + return true; + } else { + return false; + } +} + +bool Settings::compactDownloads() const +{ + return m_Settings.value("Settings/compact_downloads", false).toBool(); +} + +bool Settings::metaDownloads() const +{ + return m_Settings.value("Settings/meta_downloads", false).toBool(); +} + +bool Settings::offlineMode() const +{ + return m_Settings.value("Settings/offline_mode", false).toBool(); +} + +int Settings::logLevel() const +{ + return m_Settings.value("Settings/log_level", 0).toInt(); +} + + +void Settings::setNexusLogin(QString username, QString password) +{ + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", username); + m_Settings.setValue("Settings/nexus_password", obfuscate(password)); +} + + +LoadMechanism::EMechanism Settings::getLoadMechanism() const +{ + switch (m_Settings.value("Settings/load_mechanism").toInt()) { + case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; + case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; + case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; + } + throw std::runtime_error("invalid load mechanism"); +} + + +void Settings::setupLoadMechanism() +{ + m_LoadMechanism.activate(getLoadMechanism()); +} + + +bool Settings::useProxy() +{ + return m_Settings.value("Settings/use_proxy", false).toBool(); +} + +bool Settings::displayForeign() +{ + return m_Settings.value("Settings/display_foreign", true).toBool(); +} + +void Settings::setMotDHash(uint hash) +{ + m_Settings.setValue("motd_hash", hash); +} + +uint Settings::getMotDHash() const +{ + return m_Settings.value("motd_hash", 0).toUInt(); +} + +QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); + } + + return *iterSetting; +} + +void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); +} + +QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); +} + +void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + if (!m_PluginSettings.contains(pluginName)) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + if (sync) { + m_Settings.sync(); + } +} + +QString Settings::language() +{ + QString result = m_Settings.value("Settings/language", "").toString(); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); + } else { + // fallback system locale + result = QLocale::system().name(); + } + } + return result; +} + +void Settings::updateServers(const QList &servers) +{ + m_Settings.beginGroup("Servers"); + QStringList oldServerKeys = m_Settings.childKeys(); + + foreach (const ServerInfo &server, servers) { + if (!oldServerKeys.contains(server.name)) { + // not yet known server + QVariantMap newVal; + newVal["premium"] = server.premium; + newVal["preferred"] = server.preferred ? 1 : 0; + newVal["lastSeen"] = server.lastSeen; + newVal["downloadCount"] = 0; + newVal["downloadSpeed"] = 0.0; + + m_Settings.setValue(server.name, newVal); + } else { + QVariantMap data = m_Settings.value(server.name).toMap(); + data["lastSeen"] = server.lastSeen; + data["premium"] = server.premium; + + m_Settings.setValue(server.name, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + +void Settings::addBlacklistPlugin(const QString &fileName) +{ + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); +} + +void Settings::writePluginBlacklist() +{ + m_Settings.beginWriteArray("pluginBlacklist"); + int idx = 0; + foreach (const QString &plugin, m_PluginBlacklist) { + m_Settings.setArrayIndex(idx++); + m_Settings.setValue("name", plugin); + } + + m_Settings.endArray(); +} + +void Settings::addLanguages(QComboBox *languageBox) +{ + languageBox->addItem("English", "en_US"); + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); + QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + QRegExp exp(pattern); + while (langIter.hasNext()) { + langIter.next(); + QString file = langIter.fileName(); + if (exp.exactMatch(file)) { + QString languageCode = exp.cap(1); + QLocale locale(languageCode); + QString languageString = QLocale::languageToString(locale.language()); + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (traditional)"; + } else { + languageString = "Chinese (simplified)"; + } + } + languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); + } + } +} + +void Settings::addStyles(QComboBox *styleBox) +{ + styleBox->addItem("None", ""); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + styleBox->addItem("Fusion", "Fusion"); +#else + styleBox->addItem("Plastique", "Plastique"); + styleBox->addItem("Cleanlooks", "Cleanlooks"); +#endif + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); + while (langIter.hasNext()) { + langIter.next(); + QString style = langIter.fileName(); + styleBox->addItem(style, style); + } +} + +void Settings::resetDialogs() +{ + m_Settings.beginGroup("DialogChoices"); + QStringList keys = m_Settings.childKeys(); + foreach (QString key, keys) { + m_Settings.remove(key); + } + + m_Settings.endGroup(); +} + + +void Settings::query(QWidget *parent) +{ + SettingsDialog dialog(parent); + + connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); + + // General Page + QComboBox *languageBox = dialog.findChild("languageBox"); + QComboBox *styleBox = dialog.findChild("styleBox"); + QComboBox *logLevelBox = dialog.findChild("logLevelBox"); + QCheckBox *compactBox = dialog.findChild("compactBox"); + QCheckBox *showMetaBox = dialog.findChild("showMetaBox"); + + QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); + QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); + QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); + + // nexus page + QCheckBox *loginCheckBox = dialog.findChild("loginCheckBox"); + QLineEdit *usernameEdit = dialog.findChild("usernameEdit"); + QLineEdit *passwordEdit = dialog.findChild("passwordEdit"); + QCheckBox *offlineBox = dialog.findChild("offlineBox"); + QCheckBox *proxyBox = dialog.findChild("proxyBox"); + + QListWidget *knownServersList = dialog.findChild("knownServersList"); + QListWidget *preferredServersList = dialog.findChild("preferredServersList"); + + // plugis page + QListWidget *pluginsList = dialog.findChild("pluginsList"); + QListWidget *pluginBlacklistList = dialog.findChild("pluginBlacklist"); + + // workarounds page + QCheckBox *forceEnableBox = dialog.findChild("forceEnableBox"); + QComboBox *mechanismBox = dialog.findChild("mechanismBox"); + QLineEdit *appIDEdit = dialog.findChild("appIDEdit"); + QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); + QCheckBox *hideUncheckedBox = dialog.findChild("hideUncheckedBox"); + QCheckBox *displayForeignBox = dialog.findChild("displayForeignBox"); + + + // + // set up current settings + // + LoadMechanism::EMechanism mechanismID = getLoadMechanism(); + int index = 0; + + if (m_LoadMechanism.isDirectLoadingSupported()) { + mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); + if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isScriptExtenderSupported()) { + mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); + if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { + index = mechanismBox->count() - 1; + } + } + + if (m_LoadMechanism.isProxyDLLSupported()) { + mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); + if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { + index = mechanismBox->count() - 1; + } + } + + mechanismBox->setCurrentIndex(index); + + { + addLanguages(languageBox); + QString languageCode = language(); + int currentID = languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both variants + if (currentID == -1) { + currentID = languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + languageBox->setCurrentIndex(currentID); + } + } + + { + addStyles(styleBox); + int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + styleBox->setCurrentIndex(currentID); + } + } + + compactBox->setChecked(compactDownloads()); + showMetaBox->setChecked(metaDownloads()); + + hideUncheckedBox->setChecked(hideUncheckedPlugins()); + displayForeignBox->setChecked(displayForeign()); + forceEnableBox->setChecked(forceEnableCoreFiles()); + + appIDEdit->setText(getSteamAppID()); + + if (automaticLoginEnabled()) { + loginCheckBox->setChecked(true); + usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); + passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); + } + + downloadDirEdit->setText(getDownloadDirectory()); + modDirEdit->setText(getModDirectory()); + cacheDirEdit->setText(getCacheDirectory()); + offlineBox->setChecked(offlineMode()); + proxyBox->setChecked(useProxy()); + nmmVersionEdit->setText(getNMMVersion()); + logLevelBox->setCurrentIndex(logLevel()); + + // display plugin settings + foreach (IPlugin *plugin, m_Plugins) { + QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); + listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); + listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); + listItem->setData(Qt::UserRole + 2, m_PluginDescriptions[plugin->name()]); + pluginsList->addItem(listItem); + } + + // display plugin blacklist + foreach (const QString &pluginName, m_PluginBlacklist) { + pluginBlacklistList->addItem(pluginName); + } + + // display server preferences + m_Settings.beginGroup("Servers"); + foreach (const QString &key, m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QString type = val["premium"].toBool() ? "(premium)" : "(free)"; + + QString descriptor = key + " " + type; + if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { + int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + descriptor += QString(" (%1 kbps)").arg(bps / 1024); + } + + QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); + + newItem->setData(Qt::UserRole, key); + newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); + if (val["preferred"].toInt() > 0) { + preferredServersList->addItem(newItem); + } else { + knownServersList->addItem(newItem); + } + preferredServersList->sortItems(Qt::DescendingOrder); + } + m_Settings.endGroup(); + + if (dialog.exec() == QDialog::Accepted) { + // + // transfer modified settings to configuration file + // + + m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); + m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); + m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); + m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); + + + { // advanced settings + if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && + (QMessageBox::question(NULL, tr("Confirm"), tr("Changing the mod directory affects all your profiles! " + "Mods not present (or named differently) in the new location will be disabled in all profiles. " + "There is no way to undo this unless you backed up your profiles manually. Proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + modDirEdit->setText(getModDirectory()); + } + + if (!QDir(downloadDirEdit->text()).exists()) { + QDir().mkpath(downloadDirEdit->text()); + } + if (!QDir(cacheDirEdit->text()).exists()) { + QDir().mkpath(cacheDirEdit->text()); + } + if (!QDir(modDirEdit->text()).exists()) { + QDir().mkpath(modDirEdit->text()); + } + + m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); + m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); + m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); + } + + + QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); + QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { + m_Settings.setValue("Settings/language", newLanguage); + emit languageChanged(newLanguage); + } + + QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { + m_Settings.setValue("Settings/style", newStyle); + emit styleChanged(newStyle); + } + + m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); + + if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { + m_Settings.setValue("Settings/app_id", appIDEdit->text()); + } else { + m_Settings.remove("Settings/app_id"); + } + if (loginCheckBox->isChecked()) { + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); + m_Settings.setValue("Settings/nexus_password", obfuscate(passwordEdit->text())); + } else { + m_Settings.setValue("Settings/nexus_login", false); + m_Settings.remove("Settings/nexus_username"); + m_Settings.remove("Settings/nexus_password"); + } + m_Settings.setValue("Settings/offline_mode", offlineBox->isChecked()); + m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked()); + m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked()); + + m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); + + // transfer plugin settings to in-memory structure + for (int i = 0; i < pluginsList->count(); ++i) { + QListWidgetItem *item = pluginsList->item(i); + m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + } + // store plugin settings on disc + for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { + m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + } + } + + // store plugin blacklist + m_PluginBlacklist.clear(); + foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + m_PluginBlacklist.insert(item->text()); + } + writePluginBlacklist(); + + // store server preference + m_Settings.beginGroup("Servers"); + for (int i = 0; i < knownServersList->count(); ++i) { + QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = 0; + m_Settings.setValue(key, val); + } + int count = preferredServersList->count(); + for (int i = 0; i < count; ++i) { + QString key = preferredServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = count - i; + m_Settings.setValue(key, val); + } + m_Settings.endGroup(); + } +} diff --git a/src/settings.h b/src/settings.h index 40fd2c5a..5d398f49 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,310 +1,311 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef WORKAROUNDS_H -#define WORKAROUNDS_H - -#include "loadmechanism.h" -#include "serverinfo.h" -#include - -#include -#include -#include - - -/** - * manages the settings for Mod Organizer. The settings are not cached - * inside the class but read/written directly from/to disc - **/ -class Settings : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - **/ - Settings(); - - virtual ~Settings(); - - static Settings &instance(); - - /** - * unregister all plugins from settings - */ - void clearPlugins(); - - /** - * @brief register plugin to be configurable - * @param plugin the plugin to register - * @return true if the plugin may be registered, false if it is blacklisted - */ - void registerPlugin(MOBase::IPlugin *plugin); - - /** - * displays a SettingsDialog that allows the user to change settings. If the - * user accepts the changes, the settings are immediately written - **/ - void query(QWidget *parent); - - /** - * set up the settings for the specified plugins - **/ - void addPluginSettings(const std::vector &plugins); - - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ - void setDownloadSpeed(const QString &serverName, int bytesPerSecond); - - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - - /** - * retrieve the directory where downloads are stored (with native separators) - **/ - QString getDownloadDirectory() const; - - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - - /** - * retrieve the directory where mods are stored (with native separators) - **/ - QString getModDirectory() const; - - /** - * returns the version of nmm to impersonate when connecting to nexus - **/ - QString getNMMVersion() const; - - /** - * retrieve the directory where the web cache is stored (with native separators) - **/ - QString getCacheDirectory() const; - - /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; - - /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusLogin(QString &username, QString &password) const; - - /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - - /** - * @return true if the user chose compact downloads - */ - bool compactDownloads() const; - - /** - * @return true if the user chose meta downloads - */ - bool metaDownloads() const; - - /** - * @return the configured log level - */ - int logLevel() const; - - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusLogin(QString username, QString password); - - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); - - /** - * @return true if the user configured the use of a network proxy - */ - bool useProxy(); - - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign(); - - /** - * @brief sets the new motd hash - **/ - void setMotDHash(uint hash); - - /** - * @return hash of the last displayed message of the day - **/ - uint getMotDHash() const; - - /** - * @brief allows direct access to the wrapped QSettings object - * @return the wrapped QSettings object - */ - QSettings &directInterface() { return m_Settings; } - - /** - * @brief retrieve a setting for one of the installed plugins - * @param pluginName name of the plugin - * @param key name of the setting to retrieve - * @return the requested value as a QVariant - * @note an invalid QVariant is returned if the the plugin/setting is not declared - */ - QVariant pluginSetting(const QString &pluginName, const QString &key) const; - - /** - * @brief set a setting for one of the installed mods - * @param pluginName name of the plugin - * @param key name of the setting to change - * @param value the new value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); - - /** - * @brief retrieve a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param def default value to return if the value is not set - * @return the requested value - */ - QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; - - /** - * @brief set a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param value value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); - - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - - /** - * @brief updates the list of known servers - * @param list of servers from a recent query - */ - void updateServers(const QList &servers); - - /** - * @brief add a plugin that is to be blacklisted - * @param fileName name of the plugin to blacklist - */ - void addBlacklistPlugin(const QString &fileName); - - /** - * @brief test if a plugin is blacklisted and shouldn't be loaded - * @param fileName name of the plugin - * @return true if the file is blacklisted - */ - bool pluginBlacklisted(const QString &fileName) const; - - /** - * @return all loaded MO plugins - */ - std::vector plugins() const { return m_Plugins; } - - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); -private: - - QString obfuscate(const QString &password) const; - QString deObfuscate(const QString &password) const; - - void addLanguages(QComboBox *languageBox); - void addStyles(QComboBox *styleBox); - void readPluginBlacklist(); - void writePluginBlacklist(); - -private slots: - - void resetDialogs(); - -signals: - - void languageChanged(const QString &newLanguage); - void styleChanged(const QString &newStyle); - -private: - - static Settings *s_Instance; - - QSettings m_Settings; - - LoadMechanism m_LoadMechanism; - - std::vector m_Plugins; - - QMap > m_PluginSettings; - - QSet m_PluginBlacklist; - -}; - -#endif // WORKAROUNDS_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef WORKAROUNDS_H +#define WORKAROUNDS_H + +#include "loadmechanism.h" +#include "serverinfo.h" +#include + +#include +#include +#include + + +/** + * manages the settings for Mod Organizer. The settings are not cached + * inside the class but read/written directly from/to disc + **/ +class Settings : public QObject +{ + + Q_OBJECT + +public: + + /** + * @brief constructor + **/ + Settings(); + + virtual ~Settings(); + + static Settings &instance(); + + /** + * unregister all plugins from settings + */ + void clearPlugins(); + + /** + * @brief register plugin to be configurable + * @param plugin the plugin to register + * @return true if the plugin may be registered, false if it is blacklisted + */ + void registerPlugin(MOBase::IPlugin *plugin); + + /** + * displays a SettingsDialog that allows the user to change settings. If the + * user accepts the changes, the settings are immediately written + **/ + void query(QWidget *parent); + + /** + * set up the settings for the specified plugins + **/ + void addPluginSettings(const std::vector &plugins); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual dat adirectory + **/ + bool hideUncheckedPlugins() const; + + /** + * @return true if files of the core game are forced-enabled so the user can't accidentally disable them + */ + bool forceEnableCoreFiles() const; + + /** + * @brief register download speed + * @param url complete download url + * @param bytesPerSecond download size in bytes per second + */ + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString getSteamAppID() const; + + /** + * retrieve the directory where downloads are stored (with native separators) + **/ + QString getDownloadDirectory() const; + + /** + * retrieve a sorted list of preferred servers + */ + std::map getPreferredServers(); + + /** + * retrieve the directory where mods are stored (with native separators) + **/ + QString getModDirectory() const; + + /** + * returns the version of nmm to impersonate when connecting to nexus + **/ + QString getNMMVersion() const; + + /** + * retrieve the directory where the web cache is stored (with native separators) + **/ + QString getCacheDirectory() const; + + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; + + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool getNexusLogin(QString &username, QString &password) const; + + /** + * @return true if the user disabled internet features + */ + bool offlineMode() const; + + /** + * @return true if the user chose compact downloads + */ + bool compactDownloads() const; + + /** + * @return true if the user chose meta downloads + */ + bool metaDownloads() const; + + /** + * @return the configured log level + */ + int logLevel() const; + + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + void setNexusLogin(QString username, QString password); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism getLoadMechanism() const; + + /** + * @brief activate the load mechanism selected by the user + **/ + void setupLoadMechanism(); + + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy(); + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign(); + + /** + * @brief sets the new motd hash + **/ + void setMotDHash(uint hash); + + /** + * @return hash of the last displayed message of the day + **/ + uint getMotDHash() const; + + /** + * @brief allows direct access to the wrapped QSettings object + * @return the wrapped QSettings object + */ + QSettings &directInterface() { return m_Settings; } + + /** + * @brief retrieve a setting for one of the installed plugins + * @param pluginName name of the plugin + * @param key name of the setting to retrieve + * @return the requested value as a QVariant + * @note an invalid QVariant is returned if the the plugin/setting is not declared + */ + QVariant pluginSetting(const QString &pluginName, const QString &key) const; + + /** + * @brief set a setting for one of the installed mods + * @param pluginName name of the plugin + * @param key name of the setting to change + * @param value the new value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + + /** + * @brief retrieve a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param def default value to return if the value is not set + * @return the requested value + */ + QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; + + /** + * @brief set a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param value value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + + /** + * @brief updates the list of known servers + * @param list of servers from a recent query + */ + void updateServers(const QList &servers); + + /** + * @brief add a plugin that is to be blacklisted + * @param fileName name of the plugin to blacklist + */ + void addBlacklistPlugin(const QString &fileName); + + /** + * @brief test if a plugin is blacklisted and shouldn't be loaded + * @param fileName name of the plugin + * @return true if the file is blacklisted + */ + bool pluginBlacklisted(const QString &fileName) const; + + /** + * @return all loaded MO plugins + */ + std::vector plugins() const { return m_Plugins; } + + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); +private: + + QString obfuscate(const QString &password) const; + QString deObfuscate(const QString &password) const; + + void addLanguages(QComboBox *languageBox); + void addStyles(QComboBox *styleBox); + void readPluginBlacklist(); + void writePluginBlacklist(); + +private slots: + + void resetDialogs(); + +signals: + + void languageChanged(const QString &newLanguage); + void styleChanged(const QString &newStyle); + +private: + + static Settings *s_Instance; + + QSettings m_Settings; + + LoadMechanism m_LoadMechanism; + + std::vector m_Plugins; + + QMap > m_PluginSettings; + QMap > m_PluginDescriptions; + + QSet m_PluginBlacklist; + +}; + +#endif // WORKAROUNDS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index a7d44d72..d1947b5a 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,173 +1,182 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "settingsdialog.h" -#include "ui_settingsdialog.h" -#include "categoriesdialog.h" -#include "helper.h" -#include "noeditdelegate.h" -#include -#include -#include -#include -#include -#define WIN32_LEAN_AND_MEAN -#include -#include "settings.h" - - -using namespace MOBase; -using namespace MOShared; - - -SettingsDialog::SettingsDialog(QWidget *parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) -{ - ui->setupUi(this); - - QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -void SettingsDialog::addPlugins(const std::vector &plugins) -{ - foreach (IPlugin *plugin, plugins) { - ui->pluginsList->addItem(plugin->name()); - } -} - -void SettingsDialog::accept() -{ - storeSettings(ui->pluginsList->currentItem()); - TutorableDialog::accept(); -} - - -void SettingsDialog::on_loginCheckBox_toggled(bool checked) -{ - QLineEdit *usernameEdit = findChild("usernameEdit"); - QLineEdit *passwordEdit = findChild("passwordEdit"); - if (checked) { - passwordEdit->setEnabled(true); - usernameEdit->setEnabled(true); - } else { - passwordEdit->setEnabled(false); - usernameEdit->setEnabled(false); - } -} - -void SettingsDialog::on_categoriesBtn_clicked() -{ - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - -void SettingsDialog::on_bsaDateBtn_clicked() -{ - Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); -} - -void SettingsDialog::on_browseDownloadDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->downloadDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseModDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->modDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseCacheDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); - if (!temp.isEmpty()) { - ui->cacheDirEdit->setText(temp); - } -} - -void SettingsDialog::on_resetDialogsButton_clicked() -{ - if (QMessageBox::question(this, tr("Confirm?"), - tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit resetDialogs(); - } -} - -void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) -{ - if (pluginItem != NULL) { - QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); - - for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { - const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); - settings[item->text(0)] = item->data(1, Qt::DisplayRole); - } - - pluginItem->setData(Qt::UserRole + 1, settings); - } -} - -void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - storeSettings(previous); - - ui->pluginSettingsList->clear(); - IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QMap settings = current->data(Qt::UserRole + 1).toMap(); - ui->pluginSettingsList->setEnabled(settings.count() != 0); - for (auto iter = settings.begin(); iter != settings.end(); ++iter) { - QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); - QVariant value = *iter; - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); - } -} - -void SettingsDialog::deleteBlacklistItem() -{ - ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); -} - -void SettingsDialog::on_associateButton_clicked() -{ - Settings::instance().registerAsNXMHandler(true); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "settingsdialog.h" +#include "ui_settingsdialog.h" +#include "categoriesdialog.h" +#include "helper.h" +#include "noeditdelegate.h" +#include +#include +#include +#include +#include +#define WIN32_LEAN_AND_MEAN +#include +#include "settings.h" + + +using namespace MOBase; +using namespace MOShared; + + +SettingsDialog::SettingsDialog(QWidget *parent) + : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); + + QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::addPlugins(const std::vector &plugins) +{ + foreach (IPlugin *plugin, plugins) { + ui->pluginsList->addItem(plugin->name()); + } +} + +void SettingsDialog::accept() +{ + storeSettings(ui->pluginsList->currentItem()); + TutorableDialog::accept(); +} + + +void SettingsDialog::on_loginCheckBox_toggled(bool checked) +{ + QLineEdit *usernameEdit = findChild("usernameEdit"); + QLineEdit *passwordEdit = findChild("passwordEdit"); + if (checked) { + passwordEdit->setEnabled(true); + usernameEdit->setEnabled(true); + } else { + passwordEdit->setEnabled(false); + usernameEdit->setEnabled(false); + } +} + +void SettingsDialog::on_categoriesBtn_clicked() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } +} + +void SettingsDialog::on_bsaDateBtn_clicked() +{ + Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); +} + +void SettingsDialog::on_browseDownloadDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->downloadDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseModDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); + if (!temp.isEmpty()) { + ui->modDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseCacheDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); + if (!temp.isEmpty()) { + ui->cacheDirEdit->setText(temp); + } +} + +void SettingsDialog::on_resetDialogsButton_clicked() +{ + if (QMessageBox::question(this, tr("Confirm?"), + tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit resetDialogs(); + } +} + +void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) +{ + if (pluginItem != NULL) { + QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); + + for (int i = 0; i < ui->pluginSettingsList->topLevelItemCount(); ++i) { + const QTreeWidgetItem *item = ui->pluginSettingsList->topLevelItem(i); + settings[item->text(0)] = item->data(1, Qt::DisplayRole); + } + + pluginItem->setData(Qt::UserRole + 1, settings); + } +} + +void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + storeSettings(previous); + + ui->pluginSettingsList->clear(); + IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QMap settings = current->data(Qt::UserRole + 1).toMap(); + QMap descriptions = current->data(Qt::UserRole + 2).toMap(); + ui->pluginSettingsList->setEnabled(settings.count() != 0); + for (auto iter = settings.begin(); iter != settings.end(); ++iter) { + QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(iter.key())); + QVariant value = *iter; + QString description; + { + auto descriptionIter = descriptions.find(iter.key()); + if (descriptionIter != descriptions.end()) { + description = descriptionIter->toString(); + } + } + + ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); + newItem->setData(1, Qt::DisplayRole, value); + newItem->setData(1, Qt::EditRole, value); + newItem->setToolTip(1, description); + + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui->pluginSettingsList->addTopLevelItem(newItem); + } +} + +void SettingsDialog::deleteBlacklistItem() +{ + ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +} + +void SettingsDialog::on_associateButton_clicked() +{ + Settings::instance().registerAsNXMHandler(true); +} -- cgit v1.3.1 From 5aa5c90d39f46cfcf8d169b4087f30fcccd89640 Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 10 Sep 2014 20:35:57 +0200 Subject: profiles are now created with an (empty) archives.txt --- src/profile.cpp | 1572 ++++++++++++++++++++++++++++--------------------------- src/profile.h | 661 +++++++++++------------ 2 files changed, 1120 insertions(+), 1113 deletions(-) diff --git a/src/profile.cpp b/src/profile.cpp index ca47d327..77e4f813 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -1,783 +1,789 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "profile.h" -#include "report.h" -#include "gameinfo.h" -#include "windows_error.h" -#include "dummybsa.h" -#include "modinfo.h" -#include "safewritefile.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -using namespace MOBase; -using namespace MOShared; - -Profile::Profile() - : m_SaveTimer(NULL) -{ - initTimer(); -} - -Profile::Profile(const QString &name, bool useDefaultSettings) - : m_SaveTimer(NULL) -{ - initTimer(); - QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); - QDir profileBase(profilesDir); - - QString fixedName = name; - if (!fixDirectoryName(fixedName)) { - throw MyException(tr("invalid profile name %1").arg(name)); - } - - if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { - throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); - } - QString fullPath = profilesDir + "/" + fixedName; - m_Directory = QDir(fullPath); - QFile modList(m_Directory.filePath("modlist.txt")); - if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(fixedName); - throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); - } - modList.close(); - - try { - GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); - } catch (...) { - // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); - throw; - } - refreshModStatus(); -} - - -Profile::Profile(const QDir& directory) - : m_Directory(directory), m_SaveTimer(NULL) -{ - initTimer(); - if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qPrintable(directory.path())); - } - - GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); - - if (!QFile::exists(getIniFileName())) { - reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); - } - refreshModStatus(); -} - - -Profile::Profile(const Profile& reference) - : m_Directory(reference.m_Directory), m_SaveTimer(NULL) -{ - initTimer(); - refreshModStatus(); -} - - -Profile::~Profile() -{ - writeModlistNow(); -} - - -void Profile::initTimer() -{ - m_SaveTimer = new QTimer(this); - m_SaveTimer->setSingleShot(true); - connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); -} - - -bool Profile::exists() const -{ - return m_Directory.exists(); -} - - -void Profile::writeModlist() const -{ - if (!m_SaveTimer->isActive()) { - m_SaveTimer->start(2000); - } -} - - -void Profile::cancelWriteModlist() const -{ - m_SaveTimer->stop(); -} - - -void Profile::writeModlistNow(bool onlyOnTimer) const -{ - if (onlyOnTimer && !m_SaveTimer->isActive()) return; - - m_SaveTimer->stop(); - if (!m_Directory.exists()) return; - - try { - QString fileName = getModlistFileName(); - SafeWriteFile file(fileName); - - file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - if (m_ModStatus.empty()) { - return; - } - - for (int i = m_ModStatus.size() - 1; i >= 0; --i) { - // the priority order was inverted on load so it has to be inverted again - unsigned int index = m_ModIndexByPriority[i]; - if (index != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - std::vector flags = modInfo->getFlags(); - if ((modInfo->getFixedPriority() == INT_MIN)) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - file->write("*"); - } else if (m_ModStatus[index].m_Enabled) { - file->write("+"); - } else { - file->write("-"); - } - file->write(modInfo->name().toUtf8()); - file->write("\r\n"); - } - } - } - - if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); - } - } catch (const std::exception &e) { - reportError(tr("failed to write mod list: %1").arg(e.what())); - return; - } -} - - -void Profile::createTweakedIniFile() -{ - QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); - - if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); - return; - } - - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - if (m_ModStatus[i].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - mergeTweaks(modInfo, tweakedIni); - } - } - - mergeTweak(getProfileTweaks(), tweakedIni); - - bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str())) { - error = true; - } - } - - if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); - } - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); -} - - -void Profile::refreshModStatus() -{ - QFile file(getModlistFileName()); - if (!file.exists()) { - throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); - } - - bool modStatusModified = false; - m_ModStatus.clear(); - m_ModStatus.resize(ModInfo::getNumMods()); - - std::set namesRead; - - // load mods from file and update enabled state and priority for them - file.open(QIODevice::ReadOnly); - int index = 0; - while (!file.atEnd()) { - QByteArray line = file.readLine(); - bool enabled = true; - QString modName; - if (line.length() == 0) { - // empty line - continue; - } else if (line.at(0) == '#') { - // comment line - continue; - } else if (line.at(0) == '-') { - enabled = false; - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else if ((line.at(0) == '+') - || (line.at(0) == '*')) { - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else { - modName = QString::fromUtf8(line.trimmed().constData()); - } - if (modName.size() > 0) { - QString lookupName = modName + (line.at(0) == '*' ? ModInfoForeign::INT_IDENTIFIER : ""); - if (namesRead.find(lookupName) != namesRead.end()) { - continue; - } else { - namesRead.insert(lookupName); - } - unsigned int modIndex = ModInfo::getIndex(lookupName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - if ((modIndex < m_ModStatus.size()) - && (info->getFixedPriority() == INT_MIN)) { - m_ModStatus[modIndex].m_Enabled = enabled || info->alwaysEnabled(); - if (m_ModStatus[modIndex].m_Priority == -1) { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - m_ModStatus[modIndex].m_Priority = index++; - } - } else { - qDebug("mod \"%s\" (profile \"%s\") not found", - modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; - } - } - } else { - // line was empty after trimming - } - } - - int numKnownMods = index; - - int topInsert = 0; - - // invert priority order to match that of the pluginlist. Also - // give priorities to mods not referenced in the profile - for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - if (m_ModStatus[i].m_Priority != -1) { - m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; - } else { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - m_ModStatus[i].m_Priority = --topInsert; - } else { - m_ModStatus[i].m_Priority = index++; - } - // also, mark the mod-list as changed - modStatusModified = true; - } - } - // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up - // to align priority with 0 - if (topInsert < 0) { - int offset = topInsert * -1; - for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - m_ModStatus[i].m_Priority += offset; - } - } - - file.close(); - updateIndices(); - if (modStatusModified) { - writeModlist(); - } -} - - -void Profile::dumpModStatus() const -{ - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); - } -} - - -void Profile::updateIndices() -{ - m_NumRegularMods = 0; - m_ModIndexByPriority.clear(); - m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - int priority = m_ModStatus[i].m_Priority; - if (priority < 0) { - // don't assign this to mapping at all, it's probably the overwrite mod - continue; - } else if (priority >= static_cast(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } - } -} - - -std::vector > Profile::getActiveMods() -{ - std::vector > result; - for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); - iter != m_ModIndexByPriority.end(); ++iter) { - if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); - result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); - } - } - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - if (overwriteIndex != UINT_MAX) { - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); - } else { - reportError(tr("Overwrite directory couldn't be parsed")); - } - return result; -} - - -unsigned int Profile::modIndexByPriority(unsigned int priority) const -{ - if (priority >= m_ModStatus.size()) { - throw MyException(tr("invalid priority %1").arg(priority)); - } - - return m_ModIndexByPriority[priority]; -} - - -void Profile::setModEnabled(unsigned int index, bool enabled) -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - // we could quit in the following case, this shouldn't be a change anyway, - // but at least this allows the situation to be fixed in case of an error - if (modInfo->alwaysEnabled()) { - enabled = true; - } - - if (enabled != m_ModStatus[index].m_Enabled) { - m_ModStatus[index].m_Enabled = enabled; - emit modStatusChanged(index); - } -} - - -bool Profile::modEnabled(unsigned int index) const -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - return m_ModStatus[index].m_Enabled; -} - - -int Profile::getModPriority(unsigned int index) const -{ - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - return m_ModStatus[index].m_Priority; -} - - -void Profile::setModPriority(unsigned int index, int &newPriority) -{ - if (m_ModStatus.at(index).m_Overwrite) { - // can't change priority of the overwrite - return; - } - - int newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); - - // don't try to place below overwrite - while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || - m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { - --newPriorityTemp; - } - - int oldPriority = m_ModStatus.at(index).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - } else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - ++newPriority; - } - - m_ModStatus.at(index).m_Priority = newPriorityTemp; - - updateIndices(); - writeModlist(); -} - - -Profile Profile::createFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return Profile(QDir(profileDirectory)); -} - - -Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory)); -} - - -void Profile::copyFilesTo(QString &target) const -{ - copyDir(m_Directory.absolutePath(), target, false); -} - - -std::vector Profile::splitDZString(const wchar_t *buffer) const -{ - std::vector result; - const wchar_t *pos = buffer; - size_t length = wcslen(pos); - while (length != 0U) { - result.push_back(pos); - pos += length + 1; - length = wcslen(pos); - } - return result; -} - - -void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const -{ - static const int bufferSize = 32768; - - std::wstring tweakNameW = ToWString(tweakName); - std::wstring tweakedIniW = ToWString(tweakedIni); - QScopedArrayPointer buffer(new wchar_t[bufferSize]); - - // retrieve a list of sections - DWORD size = ::GetPrivateProfileSectionNamesW( - buffer.data(), bufferSize, tweakNameW.c_str()); - - if (size == bufferSize - 2) { - // unfortunately there is no good way to find the required size - // of the buffer - throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); - } - - std::vector sections = splitDZString(buffer.data()); - - // now iterate over all sections and retrieve a list of keys in each - for (std::vector::iterator iter = sections.begin(); - iter != sections.end(); ++iter) { - // retrieve the names of all keys - size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(), - bufferSize, tweakNameW.c_str()); - if (size == bufferSize - 2) { - throw MyException(QString("Buffer too small. Please report this as a bug. " - "For now you might want to split up %1").arg(tweakName)); - } - - std::vector keys = splitDZString(buffer.data()); - - for (std::vector::iterator keyIter = keys.begin(); - keyIter != keys.end(); ++keyIter) { - //TODO this treats everything as strings but how could I differentiate the type? - ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), - NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); - ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), - buffer.data(), tweakedIniW.c_str()); - } - } -} - -void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const -{ - std::vector iniTweaks = modInfo->getIniTweaks(); - for (std::vector::iterator iter = iniTweaks.begin(); - iter != iniTweaks.end(); ++iter) { - mergeTweak(*iter, tweakedIni); - } -} - - -bool Profile::invalidationActive(bool *supported) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - *supported = true; - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value - // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno != 0x02) { - if (supported != NULL) { - *supported = false; - } - return false; - } else { - QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } - QStringList archives = ToQString(buffer).split(','); - - for (int i = 0; i < archives.count(); ++i) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - return true; - } - } - } else { - *supported = false; - } - return false; -} - - -void Profile::deactivateInvalidation() const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); - throw windows_error(errorMessage.toUtf8().constData()); - } else { - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - for (int i = 0; i < archives.count();) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - archives.removeAt(i); - } else { - ++i; - } - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } -} - - -void Profile::activateInvalidation(const QString& dataDirectory) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - throw windows_error("failed to parse ini file"); - } else { - // ignore. shouldn't have gotten here anyway - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); - - if (!archives.contains(invalidationBSA)) { - archives.insert(0, invalidationBSA); - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - - QString bsaFile = dataDirectory + "/" + invalidationBSA; - if (!QFile::exists(bsaFile)) { - DummyBSA bsa; - bsa.write(bsaFile); - } - } -} - - -bool Profile::localSavesEnabled() const -{ - return m_Directory.exists("saves"); -} - - -bool Profile::enableLocalSaves(bool enable) -{ - if (enable) { - if (m_Directory.exists("_saves")) { - m_Directory.rename("_saves", "saves"); - } else { - m_Directory.mkdir("saves"); - } - } else { - QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), - tr("Do you want to delete local savegames? (If you select \"No\", the save games " - "will show up again if you re-enable local savegames)"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, - QMessageBox::Cancel); - if (res == QMessageBox::Yes) { - shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); - } else if (res == QMessageBox::No) { - m_Directory.rename("saves", "_saves"); - } else { - return false; - } - } - - // default: assume success - return true; -} - - -QString Profile::getModlistFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); -} - -QString Profile::getPluginsFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); -} - -QString Profile::getLoadOrderFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); -} - -QString Profile::getLockedOrderFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); -} - -QString Profile::getArchivesFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); -} - -QString Profile::getDeleterFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); -} - -QString Profile::getIniFileName() const -{ - std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); - return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); -} - -QString Profile::getProfileTweaks() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); -} - -QString Profile::getPath() const -{ - return QDir::cleanPath(m_Directory.absolutePath()); -} - -void Profile::rename(const QString &newName) -{ - QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profileDir.rename(getName(), newName); - m_Directory = profileDir.absoluteFilePath(newName); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "profile.h" +#include "report.h" +#include "gameinfo.h" +#include "windows_error.h" +#include "dummybsa.h" +#include "modinfo.h" +#include "safewritefile.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +Profile::Profile() + : m_SaveTimer(NULL) +{ + initTimer(); +} + +void Profile::touchFile(QString fileName) +{ + QFile modList(m_Directory.filePath(fileName)); + if (!modList.open(QIODevice::ReadWrite)) { + throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath(fileName)).toUtf8().constData()); + } +} + +Profile::Profile(const QString &name, bool useDefaultSettings) + : m_SaveTimer(NULL) +{ + initTimer(); + QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); + QDir profileBase(profilesDir); + + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(name)); + } + + if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { + throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + fixedName; + m_Directory = QDir(fullPath); + + try { + // create files. Needs to happen after m_Directory was set! + touchFile("modlist.txt"); + touchFile("archives.txt"); + + GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); + } catch (...) { + // clean up in case of an error + shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); + throw; + } + refreshModStatus(); +} + + +Profile::Profile(const QDir& directory) + : m_Directory(directory), m_SaveTimer(NULL) +{ + initTimer(); + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { + qWarning("missing modlist.txt in %s", qPrintable(directory.path())); + } + + GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); + + if (!QFile::exists(getIniFileName())) { + reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); + } + refreshModStatus(); +} + + +Profile::Profile(const Profile& reference) + : m_Directory(reference.m_Directory), m_SaveTimer(NULL) +{ + initTimer(); + refreshModStatus(); +} + + +Profile::~Profile() +{ + writeModlistNow(); +} + + +void Profile::initTimer() +{ + m_SaveTimer = new QTimer(this); + m_SaveTimer->setSingleShot(true); + connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); +} + + +bool Profile::exists() const +{ + return m_Directory.exists(); +} + + +void Profile::writeModlist() const +{ + if (!m_SaveTimer->isActive()) { + m_SaveTimer->start(2000); + } +} + + +void Profile::cancelWriteModlist() const +{ + m_SaveTimer->stop(); +} + + +void Profile::writeModlistNow(bool onlyOnTimer) const +{ + if (onlyOnTimer && !m_SaveTimer->isActive()) return; + + m_SaveTimer->stop(); + if (!m_Directory.exists()) return; + + try { + QString fileName = getModlistFileName(); + SafeWriteFile file(fileName); + + file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + if (m_ModStatus.empty()) { + return; + } + + for (int i = m_ModStatus.size() - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + if (index != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + std::vector flags = modInfo->getFlags(); + if ((modInfo->getFixedPriority() == INT_MIN)) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { + file->write("*"); + } else if (m_ModStatus[index].m_Enabled) { + file->write("+"); + } else { + file->write("-"); + } + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); + } + } + } + + if (file.commitIfDifferent(m_LastModlistHash)) { + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); + } + } catch (const std::exception &e) { + reportError(tr("failed to write mod list: %1").arg(e.what())); + return; + } +} + + +void Profile::createTweakedIniFile() +{ + QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); + + if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { + reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + return; + } + + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + if (m_ModStatus[i].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + mergeTweaks(modInfo, tweakedIni); + } + } + + mergeTweak(getProfileTweaks(), tweakedIni); + + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (localSavesEnabled()) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str())) { + error = true; + } + } + + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); +} + + +void Profile::refreshModStatus() +{ + QFile file(getModlistFileName()); + if (!file.exists()) { + throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); + } + + bool modStatusModified = false; + m_ModStatus.clear(); + m_ModStatus.resize(ModInfo::getNumMods()); + + std::set namesRead; + + // load mods from file and update enabled state and priority for them + file.open(QIODevice::ReadOnly); + int index = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine(); + bool enabled = true; + QString modName; + if (line.length() == 0) { + // empty line + continue; + } else if (line.at(0) == '#') { + // comment line + continue; + } else if (line.at(0) == '-') { + enabled = false; + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else if ((line.at(0) == '+') + || (line.at(0) == '*')) { + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else { + modName = QString::fromUtf8(line.trimmed().constData()); + } + if (modName.size() > 0) { + QString lookupName = modName + (line.at(0) == '*' ? ModInfoForeign::INT_IDENTIFIER : ""); + if (namesRead.find(lookupName) != namesRead.end()) { + continue; + } else { + namesRead.insert(lookupName); + } + unsigned int modIndex = ModInfo::getIndex(lookupName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); + if ((modIndex < m_ModStatus.size()) + && (info->getFixedPriority() == INT_MIN)) { + m_ModStatus[modIndex].m_Enabled = enabled || info->alwaysEnabled(); + if (m_ModStatus[modIndex].m_Priority == -1) { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + m_ModStatus[modIndex].m_Priority = index++; + } + } else { + qDebug("mod \"%s\" (profile \"%s\") not found", + modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; + } + } + } else { + // line was empty after trimming + } + } + + int numKnownMods = index; + + int topInsert = 0; + + // invert priority order to match that of the pluginlist. Also + // give priorities to mods not referenced in the profile + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + if (m_ModStatus[i].m_Priority != -1) { + m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; + } else { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + m_ModStatus[i].m_Priority = --topInsert; + } else { + m_ModStatus[i].m_Priority = index++; + } + // also, mark the mod-list as changed + modStatusModified = true; + } + } + // to support insertion of new mods at the top we may now have mods with negative priority. shift them all up + // to align priority with 0 + if (topInsert < 0) { + int offset = topInsert * -1; + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + m_ModStatus[i].m_Priority += offset; + } + } + + file.close(); + updateIndices(); + if (modStatusModified) { + writeModlist(); + } +} + + +void Profile::dumpModStatus() const +{ + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + } +} + + +void Profile::updateIndices() +{ + m_NumRegularMods = 0; + m_ModIndexByPriority.clear(); + m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + int priority = m_ModStatus[i].m_Priority; + if (priority < 0) { + // don't assign this to mapping at all, it's probably the overwrite mod + continue; + } else if (priority >= static_cast(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; + } + } +} + + +std::vector > Profile::getActiveMods() +{ + std::vector > result; + for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); + iter != m_ModIndexByPriority.end(); ++iter) { + if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); + result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); + } + } + + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); + + if (overwriteIndex != UINT_MAX) { + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); + } else { + reportError(tr("Overwrite directory couldn't be parsed")); + } + return result; +} + + +unsigned int Profile::modIndexByPriority(unsigned int priority) const +{ + if (priority >= m_ModStatus.size()) { + throw MyException(tr("invalid priority %1").arg(priority)); + } + + return m_ModIndexByPriority[priority]; +} + + +void Profile::setModEnabled(unsigned int index, bool enabled) +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + // we could quit in the following case, this shouldn't be a change anyway, + // but at least this allows the situation to be fixed in case of an error + if (modInfo->alwaysEnabled()) { + enabled = true; + } + + if (enabled != m_ModStatus[index].m_Enabled) { + m_ModStatus[index].m_Enabled = enabled; + emit modStatusChanged(index); + } +} + + +bool Profile::modEnabled(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Enabled; +} + + +int Profile::getModPriority(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Priority; +} + + +void Profile::setModPriority(unsigned int index, int &newPriority) +{ + if (m_ModStatus.at(index).m_Overwrite) { + // can't change priority of the overwrite + return; + } + + int newPriorityTemp = (std::max)(0, (std::min)(m_ModStatus.size() - 1, newPriority)); + + // don't try to place below overwrite + while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || + m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { + --newPriorityTemp; + } + + int oldPriority = m_ModStatus.at(index).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + ++newPriority; + } + + m_ModStatus.at(index).m_Priority = newPriorityTemp; + + updateIndices(); + writeModlist(); +} + + +Profile Profile::createFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return Profile(QDir(profileDirectory)); +} + + +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory)); +} + + +void Profile::copyFilesTo(QString &target) const +{ + copyDir(m_Directory.absolutePath(), target, false); +} + + +std::vector Profile::splitDZString(const wchar_t *buffer) const +{ + std::vector result; + const wchar_t *pos = buffer; + size_t length = wcslen(pos); + while (length != 0U) { + result.push_back(pos); + pos += length + 1; + length = wcslen(pos); + } + return result; +} + + +void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const +{ + static const int bufferSize = 32768; + + std::wstring tweakNameW = ToWString(tweakName); + std::wstring tweakedIniW = ToWString(tweakedIni); + QScopedArrayPointer buffer(new wchar_t[bufferSize]); + + // retrieve a list of sections + DWORD size = ::GetPrivateProfileSectionNamesW( + buffer.data(), bufferSize, tweakNameW.c_str()); + + if (size == bufferSize - 2) { + // unfortunately there is no good way to find the required size + // of the buffer + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector sections = splitDZString(buffer.data()); + + // now iterate over all sections and retrieve a list of keys in each + for (std::vector::iterator iter = sections.begin(); + iter != sections.end(); ++iter) { + // retrieve the names of all keys + size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, buffer.data(), + bufferSize, tweakNameW.c_str()); + if (size == bufferSize - 2) { + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); + } + + std::vector keys = splitDZString(buffer.data()); + + for (std::vector::iterator keyIter = keys.begin(); + keyIter != keys.end(); ++keyIter) { + //TODO this treats everything as strings but how could I differentiate the type? + ::GetPrivateProfileStringW(iter->c_str(), keyIter->c_str(), + NULL, buffer.data(), bufferSize, ToWString(tweakName).c_str()); + ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), + buffer.data(), tweakedIniW.c_str()); + } + } +} + +void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const +{ + std::vector iniTweaks = modInfo->getIniTweaks(); + for (std::vector::iterator iter = iniTweaks.begin(); + iter != iniTweaks.end(); ++iter) { + mergeTweak(*iter, tweakedIni); + } +} + + +bool Profile::invalidationActive(bool *supported) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + *supported = true; + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value + // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno != 0x02) { + if (supported != NULL) { + *supported = false; + } + return false; + } else { + QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } + QStringList archives = ToQString(buffer).split(','); + + for (int i = 0; i < archives.count(); ++i) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + return true; + } + } + } else { + *supported = false; + } + return false; +} + + +void Profile::deactivateInvalidation() const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); + throw windows_error(errorMessage.toUtf8().constData()); + } else { + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + for (int i = 0; i < archives.count();) { + QString bsaName = archives.at(i).trimmed(); + if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { + archives.removeAt(i); + } else { + ++i; + } + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + } +} + + +void Profile::activateInvalidation(const QString& dataDirectory) const +{ + if (GameInfo::instance().requiresBSAInvalidation()) { + wchar_t buffer[1024]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); + errno = 0; + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 1024, iniFileName.c_str()) == 0) { + if (errno == 0x02) { + throw windows_error("failed to parse ini file"); + } else { + // ignore. shouldn't have gotten here anyway + return; + } + } + QStringList archives = ToQString(buffer).split(", "); + + QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); + + if (!archives.contains(invalidationBSA)) { + archives.insert(0, invalidationBSA); + } + + // just to be safe... + ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); + + if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || + !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { + QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); + throw windows_error(errorMessage.toUtf8().constData()); + } + + QString bsaFile = dataDirectory + "/" + invalidationBSA; + if (!QFile::exists(bsaFile)) { + DummyBSA bsa; + bsa.write(bsaFile); + } + } +} + + +bool Profile::localSavesEnabled() const +{ + return m_Directory.exists("saves"); +} + + +bool Profile::enableLocalSaves(bool enable) +{ + if (enable) { + if (m_Directory.exists("_saves")) { + m_Directory.rename("_saves", "saves"); + } else { + m_Directory.mkdir("saves"); + } + } else { + QMessageBox::StandardButton res = QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), + tr("Do you want to delete local savegames? (If you select \"No\", the save games " + "will show up again if you re-enable local savegames)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); + } else if (res == QMessageBox::No) { + m_Directory.rename("saves", "_saves"); + } else { + return false; + } + } + + // default: assume success + return true; +} + + +QString Profile::getModlistFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); +} + +QString Profile::getPluginsFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); +} + +QString Profile::getLoadOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); +} + +QString Profile::getLockedOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); +} + +QString Profile::getArchivesFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); +} + +QString Profile::getDeleterFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); +} + +QString Profile::getIniFileName() const +{ + std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); + return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); +} + +QString Profile::getProfileTweaks() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +} + +QString Profile::getPath() const +{ + return QDir::cleanPath(m_Directory.absolutePath()); +} + +void Profile::rename(const QString &newName) +{ + QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profileDir.rename(getName(), newName); + m_Directory = profileDir.absoluteFilePath(newName); +} diff --git a/src/profile.h b/src/profile.h index fa34ab9e..aa27d6e8 100644 --- a/src/profile.h +++ b/src/profile.h @@ -1,330 +1,331 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#ifndef PROFILE_H -#define PROFILE_H - - -#include "modinfo.h" - -#include -#include -#include -#include -#include -#include - - -/** - * @brief represents a profile - **/ -class Profile : public QObject -{ - - Q_OBJECT - -public: - - typedef boost::shared_ptr Ptr; - -public: - - /** - * @brief default constructor - * @todo This constructor initialised nothing, the resulting object is not usable - **/ - Profile(); - - /** - * @brief constructor - * - * This constructor is used to create a new profile so it is to be assumed a profile - * by this name does not yet exist - * @param name name of the new profile - * @param filter save game filter. Defaults to <no filter>. - **/ - Profile(const QString &name, bool useDefaultSettings); - /** - * @brief constructor - * - * This constructor is used to open an existing profile though it will also try to repair - * the profile if important files are missing (including the directory itself) so technically, - * invoking this should always produce a working profile - * @param directory directory to read the profile from - **/ - Profile(const QDir &directory); - - Profile(const Profile &reference); - - ~Profile(); - - /** - * @return true if this profile (still) exists on disc - */ - bool exists() const; - - /** - * @param name of the new profile - * @param reference profile to copy from - **/ - static Profile createFrom(const QString &name, const Profile &reference); - - /** - * @param name of the new profile - * @param reference profile to copy from - **/ - static Profile *createPtrFrom(const QString &name, const Profile &reference); - - /** - * @brief write out the modlist.txt - **/ - void writeModlist() const; - - /** - * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is - * saved while it's being modified - */ - void cancelWriteModlist() const; - - /** - * @brief test if this profile uses archive invalidation - * - * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile - * @return true if archive invalidation is active - * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist - **/ - bool invalidationActive(bool *supported) const; - - /** - * @brief deactivate archive invalidation if it was active - **/ - void deactivateInvalidation() const; - - /** - * @brief activate archive invalidation - * - * @param dataDirectory data directory of the game - * @todo passing the data directory as a parameter is useless, the function should - * be able to query it from GameInfo - **/ - void activateInvalidation(const QString &dataDirectory) const; - - /** - * @return true if this profile uses local save games - */ - bool localSavesEnabled() const; - - /** - * @brief enables or disables the use of local save games for this profile - * disabling this does not delete exising local saves but they will not be visible - * in the game - * @param enable if true, local saves are enabled, otherewise they are disabled - */ - bool enableLocalSaves(bool enable); - - /** - * @return name of the profile (this is identical to its directory name) - **/ - QString getName() const { return m_Directory.dirName(); } - - /** - * @return the path of the plugins file in this profile - * @todo is this required? can the functionality using this function be moved to the Profile-class? - **/ - QString getPluginsFileName() const; - - /** - * @return the path of the loadorder file in this profile - **/ - QString getLoadOrderFileName() const; - - /** - * @return the path of the file containing locked mod indices - */ - QString getLockedOrderFileName() const; - - /** - * @return the path of the modlist file in this profile - */ - QString getModlistFileName() const; - - /** - * @return path of the archives file in this profile - */ - QString getArchivesFileName() const; - - /** - * @return the path of the delete file in this profile - * @note the deleter file lists plugins that should be hidden from the game and other tools - **/ - QString getDeleterFileName() const; - - /** - * @return the path of the ini file in this profile - * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) - * the concept of this function is somewhat broken - **/ - QString getIniFileName() const; - - /** - * @return the path of the tweak ini in this profile - */ - QString getProfileTweaks() const; - - /** - * @return path to this profile - **/ - QString getPath() const; - - void rename(const QString &newName); - - /** - * @brief create the ini file to be used by the game - * - * the tweaked ini file constructed by this file is a merger - * of the game-ini of this profile with ini tweaks applied */ - void createTweakedIniFile(); - - /** - * @brief re-read the modlist.txt and update the mod status from it - **/ - void refreshModStatus(); - - /** - * @brief retrieve a list of mods that are enabled in this profile - * - * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path - **/ - std::vector > getActiveMods(); - - /** - * retrieve the number of mods for which this object has status information. - * This is usually the same as ModInfo::getNumMods() except between - * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() - * - * @return number of mods for which the profile has status information - **/ - unsigned int numMods() const { return m_ModStatus.size(); } - - /** - * @return the number of mods that can be enabled and where the priority can be modified - */ - unsigned int numRegularMods() const { return m_NumRegularMods; } - - /** - * @brief retrieve the mod index based on the priority - * - * @param priority priority to look up - * @return the index of the mod - * @throw std::exception an exception is thrown if there is no mod with the specified priority - **/ - unsigned int modIndexByPriority(unsigned int priority) const; - - /** - * @brief enable or disable a mod - * - * @param index index of the mod to enable/disable - * @param enabled true if the mod is to be enabled, false if it is to be disabled - **/ - void setModEnabled(unsigned int index, bool enabled); - - /** - * change the priority of a mod. Of course this also changes the priority of other mods. - * The priority of the mods in the range ]old, new priority] are shifted so that no gaps - * are possible. - * - * @param index index of the mod to change - * @param newPriority the new priority value - * - * @todo what happens if the new priority is outside the range? - **/ - void setModPriority(unsigned int index, int &newPriority); - - /** - * @brief determine if a mod is enabled - * - * @param index index of the mod to look up - * @return true if the mod is enabled, false otherwise - **/ - bool modEnabled(unsigned int index) const; - - /** - * @brief query the priority of a mod - * - * @param index index of the mod to look up - * @return priority of the specified mod - **/ - int getModPriority(unsigned int index) const; - - void dumpModStatus() const; - -signals: - - /** - * @brief emitted whenever the status (enabled/disabled) of a mod changed - * - * @param index index of the mod that changed - **/ - void modStatusChanged(unsigned int index); - -public slots: - - void writeModlistNow(bool onlyOnTimer = false) const; - -private: - - class ModStatus { - friend class Profile; - public: - ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} - private: - bool m_Overwrite; - bool m_Enabled; - int m_Priority; - }; - -private: - Profile& operator=(const Profile &reference); // not implemented - - void initTimer(); - - void updateIndices(); - - void copyFilesTo(QString &target) const; - - std::vector splitDZString(const wchar_t *buffer) const; - void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; - void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; - -private: - - QDir m_Directory; - - mutable QByteArray m_LastModlistHash; - std::vector m_ModStatus; - std::vector m_ModIndexByPriority; - unsigned int m_NumRegularMods; - - QTimer *m_SaveTimer; -}; - -Q_DECLARE_METATYPE(Profile) - - -#endif // PROFILE_H +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#ifndef PROFILE_H +#define PROFILE_H + + +#include "modinfo.h" + +#include +#include +#include +#include +#include +#include + + +/** + * @brief represents a profile + **/ +class Profile : public QObject +{ + + Q_OBJECT + +public: + + typedef boost::shared_ptr Ptr; + +public: + + /** + * @brief default constructor + * @todo This constructor initialised nothing, the resulting object is not usable + **/ + Profile(); + + /** + * @brief constructor + * + * This constructor is used to create a new profile so it is to be assumed a profile + * by this name does not yet exist + * @param name name of the new profile + * @param filter save game filter. Defaults to <no filter>. + **/ + Profile(const QString &name, bool useDefaultSettings); + /** + * @brief constructor + * + * This constructor is used to open an existing profile though it will also try to repair + * the profile if important files are missing (including the directory itself) so technically, + * invoking this should always produce a working profile + * @param directory directory to read the profile from + **/ + Profile(const QDir &directory); + + Profile(const Profile &reference); + + ~Profile(); + + /** + * @return true if this profile (still) exists on disc + */ + bool exists() const; + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile createFrom(const QString &name, const Profile &reference); + + /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile *createPtrFrom(const QString &name, const Profile &reference); + + /** + * @brief write out the modlist.txt + **/ + void writeModlist() const; + + /** + * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is + * saved while it's being modified + */ + void cancelWriteModlist() const; + + /** + * @brief test if this profile uses archive invalidation + * + * @param supported if this is not null, the parameter will be set to false if invalidation is not supported in this profile + * @return true if archive invalidation is active + * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist + **/ + bool invalidationActive(bool *supported) const; + + /** + * @brief deactivate archive invalidation if it was active + **/ + void deactivateInvalidation() const; + + /** + * @brief activate archive invalidation + * + * @param dataDirectory data directory of the game + * @todo passing the data directory as a parameter is useless, the function should + * be able to query it from GameInfo + **/ + void activateInvalidation(const QString &dataDirectory) const; + + /** + * @return true if this profile uses local save games + */ + bool localSavesEnabled() const; + + /** + * @brief enables or disables the use of local save games for this profile + * disabling this does not delete exising local saves but they will not be visible + * in the game + * @param enable if true, local saves are enabled, otherewise they are disabled + */ + bool enableLocalSaves(bool enable); + + /** + * @return name of the profile (this is identical to its directory name) + **/ + QString getName() const { return m_Directory.dirName(); } + + /** + * @return the path of the plugins file in this profile + * @todo is this required? can the functionality using this function be moved to the Profile-class? + **/ + QString getPluginsFileName() const; + + /** + * @return the path of the loadorder file in this profile + **/ + QString getLoadOrderFileName() const; + + /** + * @return the path of the file containing locked mod indices + */ + QString getLockedOrderFileName() const; + + /** + * @return the path of the modlist file in this profile + */ + QString getModlistFileName() const; + + /** + * @return path of the archives file in this profile + */ + QString getArchivesFileName() const; + + /** + * @return the path of the delete file in this profile + * @note the deleter file lists plugins that should be hidden from the game and other tools + **/ + QString getDeleterFileName() const; + + /** + * @return the path of the ini file in this profile + * @todo since the game can contain multiple ini files (i.e. skyrim.ini skyrimprefs.ini) + * the concept of this function is somewhat broken + **/ + QString getIniFileName() const; + + /** + * @return the path of the tweak ini in this profile + */ + QString getProfileTweaks() const; + + /** + * @return path to this profile + **/ + QString getPath() const; + + void rename(const QString &newName); + + /** + * @brief create the ini file to be used by the game + * + * the tweaked ini file constructed by this file is a merger + * of the game-ini of this profile with ini tweaks applied */ + void createTweakedIniFile(); + + /** + * @brief re-read the modlist.txt and update the mod status from it + **/ + void refreshModStatus(); + + /** + * @brief retrieve a list of mods that are enabled in this profile + * + * @return list of active mods sorted by priority (ascending). "first" is the mod name, "second" is its path + **/ + std::vector > getActiveMods(); + + /** + * retrieve the number of mods for which this object has status information. + * This is usually the same as ModInfo::getNumMods() except between + * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() + * + * @return number of mods for which the profile has status information + **/ + unsigned int numMods() const { return m_ModStatus.size(); } + + /** + * @return the number of mods that can be enabled and where the priority can be modified + */ + unsigned int numRegularMods() const { return m_NumRegularMods; } + + /** + * @brief retrieve the mod index based on the priority + * + * @param priority priority to look up + * @return the index of the mod + * @throw std::exception an exception is thrown if there is no mod with the specified priority + **/ + unsigned int modIndexByPriority(unsigned int priority) const; + + /** + * @brief enable or disable a mod + * + * @param index index of the mod to enable/disable + * @param enabled true if the mod is to be enabled, false if it is to be disabled + **/ + void setModEnabled(unsigned int index, bool enabled); + + /** + * change the priority of a mod. Of course this also changes the priority of other mods. + * The priority of the mods in the range ]old, new priority] are shifted so that no gaps + * are possible. + * + * @param index index of the mod to change + * @param newPriority the new priority value + * + * @todo what happens if the new priority is outside the range? + **/ + void setModPriority(unsigned int index, int &newPriority); + + /** + * @brief determine if a mod is enabled + * + * @param index index of the mod to look up + * @return true if the mod is enabled, false otherwise + **/ + bool modEnabled(unsigned int index) const; + + /** + * @brief query the priority of a mod + * + * @param index index of the mod to look up + * @return priority of the specified mod + **/ + int getModPriority(unsigned int index) const; + + void dumpModStatus() const; + +signals: + + /** + * @brief emitted whenever the status (enabled/disabled) of a mod changed + * + * @param index index of the mod that changed + **/ + void modStatusChanged(unsigned int index); + +public slots: + + void writeModlistNow(bool onlyOnTimer = false) const; + +private: + + class ModStatus { + friend class Profile; + public: + ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} + private: + bool m_Overwrite; + bool m_Enabled; + int m_Priority; + }; + +private: + Profile& operator=(const Profile &reference); // not implemented + + void initTimer(); + + void updateIndices(); + + void copyFilesTo(QString &target) const; + + std::vector splitDZString(const wchar_t *buffer) const; + void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; + void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; + void touchFile(QString fileName); + +private: + + QDir m_Directory; + + mutable QByteArray m_LastModlistHash; + std::vector m_ModStatus; + std::vector m_ModIndexByPriority; + unsigned int m_NumRegularMods; + + QTimer *m_SaveTimer; +}; + +Q_DECLARE_METATYPE(Profile) + + +#endif // PROFILE_H -- cgit v1.3.1 From 19cb2d4076d3515fa490fce7fbd339e92c7c3adf Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 10 Sep 2014 20:37:17 +0200 Subject: plugins can now query the list of masters for a plugin --- src/pluginlist.cpp | 14 ++++++++++++++ src/pluginlist.h | 1 + 2 files changed, 15 insertions(+) diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 43246b55..ff370fa4 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -667,6 +667,20 @@ bool PluginList::isMaster(const QString &name) const } } +QStringList PluginList::masters(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return QStringList(); + } else { + QStringList result; + foreach (const QString &master, m_ESPs[iter->second].m_Masters) { + result.append(master); + } + return result; + } +} + QString PluginList::origin(const QString &name) const { auto iter = m_ESPsByName.find(name.toLower()); diff --git a/src/pluginlist.h b/src/pluginlist.h index 8c06fefd..729c6f8b 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -224,6 +224,7 @@ public: virtual int priority(const QString &name) const; virtual int loadOrder(const QString &name) const; virtual bool isMaster(const QString &name) const; + virtual QStringList masters(const QString &name) const; virtual QString origin(const QString &name) const; virtual bool onRefreshed(const std::function &callback); virtual bool onPluginMoved(const std::function &func); -- cgit v1.3.1 From 7d271380868f96a7a2e1f36ee03b97ad9db7c52e Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 10 Sep 2014 20:43:33 +0200 Subject: when MO fails to overwrite its ini file it tries another method using qt functions --- src/mainwindow.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 984d8cfd..ae5c1e48 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2166,6 +2166,15 @@ void MainWindow::readSettings() } +bool renameFile(const QString &oldName, const QString &newName, bool overwrite = true) +{ + if (overwrite && QFile::exists(newName)) { + QFile::remove(newName); + } + return QFile::rename(oldName, newName); +} + + void MainWindow::storeSettings() { if (m_CurrentProfile == NULL) { @@ -2235,8 +2244,12 @@ void MainWindow::storeSettings() } if (result == QSettings::NoError) { if (!shellRename(iniFile + ".new", iniFile, true, this)) { - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError()))); + DWORD err = ::GetLastError(); + // make a second attempt using qt functions but if that fails print the error from the first attempt + if (!renameFile(iniFile + ".new", iniFile)) { + QMessageBox::critical(this, tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(err))); + } } } else { QString reason = result == QSettings::AccessError ? tr("File is write protected") -- cgit v1.3.1 From d3d7e302860fb18d7c59c213c9f3cb99c8ff4efb Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 10 Sep 2014 20:44:39 +0200 Subject: non-mo mods now have a constructed name that cannot conflict with a regular mod --- src/editexecutablesdialog.cpp | 578 ++++++------ src/modinfo.cpp | 2098 ++++++++++++++++++++--------------------- 2 files changed, 1338 insertions(+), 1338 deletions(-) diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 0e3aa55b..fe548a9a 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -1,289 +1,289 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "editexecutablesdialog.h" -#include "ui_editexecutablesdialog.h" -#include "filedialogmemory.h" -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - -EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) - : TutorableDialog("EditExecutables", parent), - ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList) -{ - ui->setupUi(this); - - refreshExecutablesWidget(); -} - -EditExecutablesDialog::~EditExecutablesDialog() -{ - delete ui; -} - -ExecutablesList EditExecutablesDialog::getExecutablesList() const -{ - ExecutablesList newList; - for (int i = 0; i < ui->executablesListBox->count(); ++i) { - newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value()); - } - return newList; -} - -void EditExecutablesDialog::refreshExecutablesWidget() -{ - QListWidget *executablesWidget = findChild("executablesListBox"); - - executablesWidget->clear(); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - - for(; current != end; ++current) { - QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); - QVariant temp; - temp.setValue(*current); - newItem->setData(Qt::UserRole, temp); - newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); - executablesWidget->addItem(newItem); - } - - ui->addButton->setEnabled(false); - ui->removeButton->setEnabled(false); -} - - -void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1) -{ - QFileInfo fileInfo(arg1); - ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); -} - -void EditExecutablesDialog::resetInput() -{ - ui->binaryEdit->setText(""); - ui->titleEdit->setText(""); - ui->workingDirEdit->clear(); - ui->argumentsEdit->setText(""); - ui->appIDOverwriteEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->closeCheckBox->setChecked(false); - m_CurrentItem = NULL; -} - - -void EditExecutablesDialog::saveExecutable() -{ - m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), - (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, - ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", - true, false); -} - - -void EditExecutablesDialog::delayedRefresh() -{ - int index = ui->executablesListBox->currentIndex().row(); - resetInput(); - refreshExecutablesWidget(); - ui->executablesListBox->setCurrentRow(index); -} - - -void EditExecutablesDialog::on_addButton_clicked() -{ - saveExecutable(); - - resetInput(); - refreshExecutablesWidget(); -} - -void EditExecutablesDialog::on_browseButton_clicked() -{ - QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, - tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); - - if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { - QString binaryPath; - { // try to find java automatically - std::wstring binaryNameW = ToWString(binaryName); - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(binaryNameW.c_str(), NULL, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty()) { - 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()) { - QMessageBox::information(this, tr("Java (32-bit) required"), - tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " - "from that installation as the binary.")); - } else { - ui->binaryEdit->setText(binaryPath); - } - - ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); - ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); - } else { - ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); - } -} - -void EditExecutablesDialog::on_browseDirButton_clicked() -{ - QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, - tr("Select a directory")); - - ui->workingDirEdit->setText(dirName); -} - -void EditExecutablesDialog::on_removeButton_clicked() -{ -// QLineEdit *binaryEdit = findChild("binaryEdit"); - - if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ExecutablesList.remove(ui->titleEdit->text()); - } - - resetInput(); - refreshExecutablesWidget(); -} - -void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) -{ - QPushButton *addButton = findChild("addButton"); - QPushButton *removeButton = findChild("removeButton"); - - QListWidget *executablesWidget = findChild("executablesListBox"); - - QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); - - addButton->setEnabled(arg1.length() != 0); - - if (existingItems.count() == 0) { - addButton->setText(tr("Add")); - removeButton->setEnabled(false); - } else { - // existing item. is it a custom one? - addButton->setText(tr("Modify")); - removeButton->setEnabled(true); - } -} - - -bool EditExecutablesDialog::executableChanged() -{ - if (m_CurrentItem != NULL) { - const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value(); - - return selectedExecutable.m_Arguments != ui->argumentsEdit->text() - || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() - || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) - || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); - } else { - return false; - } -} - - -void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - if (current == NULL) { - resetInput(); - return; - } - - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - // this invalidates the item passed as a a parameter - saveExecutable(); - - QTimer::singleShot(50, this, SLOT(delayedRefresh())); - return; - } - } - - m_CurrentItem = current; - - const Executable &selectedExecutable = current->data(Qt::UserRole).value(); - - ui->titleEdit->setText(selectedExecutable.m_Title); - ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); - ui->argumentsEdit->setText(selectedExecutable.m_Arguments); - ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); - ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); - if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { - ui->closeCheckBox->setEnabled(false); - ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); - } else { - ui->closeCheckBox->setEnabled(true); - ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); - } - ui->removeButton->setEnabled(selectedExecutable.m_Custom); - ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0); - if (selectedExecutable.m_SteamAppID != 0) { - ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); - } else { - ui->appIDOverwriteEdit->clear(); - } -} - - -void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) -{ - ui->appIDOverwriteEdit->setEnabled(checked); -} - -void EditExecutablesDialog::on_closeButton_clicked() -{ - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), - tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - saveExecutable(); - } - } - this->accept(); -} - +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "editexecutablesdialog.h" +#include "ui_editexecutablesdialog.h" +#include "filedialogmemory.h" +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + +EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) + : TutorableDialog("EditExecutables", parent), + ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList) +{ + ui->setupUi(this); + + refreshExecutablesWidget(); +} + +EditExecutablesDialog::~EditExecutablesDialog() +{ + delete ui; +} + +ExecutablesList EditExecutablesDialog::getExecutablesList() const +{ + ExecutablesList newList; + for (int i = 0; i < ui->executablesListBox->count(); ++i) { + newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value()); + } + return newList; +} + +void EditExecutablesDialog::refreshExecutablesWidget() +{ + QListWidget *executablesWidget = findChild("executablesListBox"); + + executablesWidget->clear(); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + + for(; current != end; ++current) { + QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); + QVariant temp; + temp.setValue(*current); + newItem->setData(Qt::UserRole, temp); + newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); + executablesWidget->addItem(newItem); + } + + ui->addButton->setEnabled(false); + ui->removeButton->setEnabled(false); +} + + +void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1) +{ + QFileInfo fileInfo(arg1); + ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); +} + +void EditExecutablesDialog::resetInput() +{ + ui->binaryEdit->setText(""); + ui->titleEdit->setText(""); + ui->workingDirEdit->clear(); + ui->argumentsEdit->setText(""); + ui->appIDOverwriteEdit->clear(); + ui->overwriteAppIDBox->setChecked(false); + ui->closeCheckBox->setChecked(false); + m_CurrentItem = NULL; +} + + +void EditExecutablesDialog::saveExecutable() +{ + m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), + ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), + (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, + ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", + true, false); +} + + +void EditExecutablesDialog::delayedRefresh() +{ + int index = ui->executablesListBox->currentIndex().row(); + resetInput(); + refreshExecutablesWidget(); + ui->executablesListBox->setCurrentRow(index); +} + + +void EditExecutablesDialog::on_addButton_clicked() +{ + saveExecutable(); + + resetInput(); + refreshExecutablesWidget(); +} + +void EditExecutablesDialog::on_browseButton_clicked() +{ + QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, + tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); + + if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + QString binaryPath; + { // try to find java automatically + std::wstring binaryNameW = ToWString(binaryName); + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(binaryNameW.c_str(), NULL, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty()) { + 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()) { + QMessageBox::information(this, tr("Java (32-bit) required"), + tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " + "from that installation as the binary.")); + } else { + ui->binaryEdit->setText(binaryPath); + } + + ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); + ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + } else { + ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); + } +} + +void EditExecutablesDialog::on_browseDirButton_clicked() +{ + QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, + tr("Select a directory")); + + ui->workingDirEdit->setText(dirName); +} + +void EditExecutablesDialog::on_removeButton_clicked() +{ +// QLineEdit *binaryEdit = findChild("binaryEdit"); + + if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ExecutablesList.remove(ui->titleEdit->text()); + } + + resetInput(); + refreshExecutablesWidget(); +} + +void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) +{ + QPushButton *addButton = findChild("addButton"); + QPushButton *removeButton = findChild("removeButton"); + + QListWidget *executablesWidget = findChild("executablesListBox"); + + QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); + + addButton->setEnabled(arg1.length() != 0); + + if (existingItems.count() == 0) { + addButton->setText(tr("Add")); + removeButton->setEnabled(false); + } else { + // existing item. is it a custom one? + addButton->setText(tr("Modify")); + removeButton->setEnabled(true); + } +} + + +bool EditExecutablesDialog::executableChanged() +{ + if (m_CurrentItem != NULL) { + const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value(); + + return selectedExecutable.m_Arguments != ui->argumentsEdit->text() + || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() + || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) + || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) + || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); + } else { + return false; + } +} + + +void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) +{ + if (current == NULL) { + resetInput(); + return; + } + + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return; + } else if (res == QMessageBox::Yes) { + // this invalidates the item passed as a a parameter + saveExecutable(); + + QTimer::singleShot(50, this, SLOT(delayedRefresh())); + return; + } + } + + m_CurrentItem = current; + + const Executable &selectedExecutable = current->data(Qt::UserRole).value(); + + ui->titleEdit->setText(selectedExecutable.m_Title); + ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); + ui->argumentsEdit->setText(selectedExecutable.m_Arguments); + ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); + ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); + if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { + ui->closeCheckBox->setEnabled(false); + ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); + } else { + ui->closeCheckBox->setEnabled(true); + ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); + } + ui->removeButton->setEnabled(selectedExecutable.m_Custom); + ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0); + if (selectedExecutable.m_SteamAppID != 0) { + ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); + } else { + ui->appIDOverwriteEdit->clear(); + } +} + + +void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) +{ + ui->appIDOverwriteEdit->setEnabled(checked); +} + +void EditExecutablesDialog::on_closeButton_clicked() +{ + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return; + } else if (res == QMessageBox::Yes) { + saveExecutable(); + } + } + this->accept(); +} + diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 4ab6d142..796dab71 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -1,1049 +1,1049 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer 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. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see . -*/ - -#include "modinfo.h" -#include "utility.h" -#include "installationtester.h" -#include "categories.h" -#include "report.h" -#include "modinfodialog.h" -#include "overwriteinfodialog.h" -#include "json.h" -#include "messagedialog.h" - -#include -#include - -#include - -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - - -std::vector ModInfo::s_Collection; -std::map ModInfo::s_ModsByName; -std::map > ModInfo::s_ModsByModID; -int ModInfo::s_NextID; -QMutex ModInfo::s_Mutex(QMutex::Recursive); - -QString ModInfo::s_HiddenExt(".mohidden"); - - -static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) -{ - return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; -} - - -ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure) -{ - QMutexLocker locker(&s_Mutex); -// int id = s_NextID++; - static QRegExp backupExp(".*backup[0-9]*"); - ModInfo::Ptr result; - if (backupExp.exactMatch(dir.dirName())) { - result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); - } else { - result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); - } - s_Collection.push_back(result); - return result; -} - -ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames - , DirectoryEntry ** directoryStructure) -{ - QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); - s_Collection.push_back(result); - return result; -} - -QString ModInfo::getContentTypeName(int contentType) -{ - switch (contentType) { - case CONTENT_PLUGIN: return tr("Plugins"); - case CONTENT_TEXTURE: return tr("Textures"); - case CONTENT_MESH: return tr("Meshes"); - case CONTENT_INTERFACE: return tr("UI Changes"); - case CONTENT_MUSIC: return tr("Music"); - case CONTENT_SOUND: return tr("Sound Effects"); - case CONTENT_SCRIPT: return tr("Scripts"); - case CONTENT_SKSE: return tr("SKSE Plugins"); - case CONTENT_SKYPROC: return tr("SkyProc Tools"); - case CONTENT_STRING: return tr("Strings"); - default: throw MyException(tr("invalid content type %1").arg(contentType)); - } -} - -void ModInfo::createFromOverwrite() -{ - QMutexLocker locker(&s_Mutex); - - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); -} - -unsigned int ModInfo::getNumMods() -{ - QMutexLocker locker(&s_Mutex); - return s_Collection.size(); -} - - -ModInfo::Ptr ModInfo::getByIndex(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - return s_Collection[index]; -} - - -std::vector ModInfo::getByModID(int modID) -{ - QMutexLocker locker(&s_Mutex); - - auto iter = s_ModsByModID.find(modID); - if (iter == s_ModsByModID.end()) { - return std::vector(); - } - - std::vector result; - for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { - result.push_back(getByIndex(*idxIter)); - } - - return result; -} - - -bool ModInfo::removeMod(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - - // update the indices first - ModInfo::Ptr modInfo = s_Collection[index]; - s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - - auto iter = s_ModsByModID.find(modInfo->getNexusID()); - if (iter != s_ModsByModID.end()) { - std::vector indices = iter->second; - std::remove(indices.begin(), indices.end(), index); - s_ModsByModID[modInfo->getNexusID()] = indices; - } - - // physically remove the mod directory - //TODO the return value is ignored because the indices were already removed here, so stopping - // would cause data inconsistencies. Instead we go through with the removal but the mod will show up - // again if the user refreshes - modInfo->remove(); - - // finally, remove the mod from the collection - s_Collection.erase(s_Collection.begin() + index); - - // and update the indices - updateIndices(); - return true; -} - - -unsigned int ModInfo::getIndex(const QString &name) -{ - QMutexLocker locker(&s_Mutex); - - std::map::iterator iter = s_ModsByName.find(name); - if (iter == s_ModsByName.end()) { - return UINT_MAX; - } - - return iter->second; -} - -unsigned int ModInfo::findMod(const boost::function &filter) -{ - for (unsigned int i = 0U; i < s_Collection.size(); ++i) { - if (filter(s_Collection[i])) { - return i; - } - } - return UINT_MAX; -} - - -void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign) -{ - QMutexLocker lock(&s_Mutex); - s_Collection.clear(); - s_NextID = 0; - - { // list all directories in the mod directory and make a mod out of each - QDir mods(QDir::fromNativeSeparators(modDirectory)); - mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); - QDirIterator modIter(mods); - while (modIter.hasNext()) { - createFrom(QDir(modIter.next()), directoryStructure); - } - } - - { // list plugins in the data directory and make a foreign-managed mod out of each - std::vector dlcPlugins = GameInfo::instance().getDLCPlugins(); - QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"); - foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) { - if ((file.baseName() != "Update") // hide update - && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp - && (displayForeign // show non-dlc bundles only if the user wants them - || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) { - QStringList archives; - foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) { - archives.append(dataDir.absoluteFilePath(archiveName)); - } - - createFromPlugin(file.fileName(), archives, directoryStructure); - } - } - } - - createFromOverwrite(); - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - - updateIndices(); -} - - -void ModInfo::updateIndices() -{ - s_ModsByName.clear(); - s_ModsByModID.clear(); - QRegExp backupRegEx(".*backup[0-9]*$"); - - for (unsigned int i = 0; i < s_Collection.size(); ++i) { - QString modName = s_Collection[i]->internalName(); - int modID = s_Collection[i]->getNexusID(); - s_ModsByName[modName] = i; - s_ModsByModID[modID].push_back(i); - } -} - - -ModInfo::ModInfo() - : m_Valid(false), m_PrimaryCategory(-1) -{ -} - - -void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver) -{ - if (modIDs.size() != 0) { - NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); - } -} - - -int ModInfo::checkAllForUpdate(QObject *receiver) -{ - int result = 0; - std::vector modIDs; - - modIDs.push_back(GameInfo::instance().getNexusModID()); - - for (std::vector::iterator iter = s_Collection.begin(); - iter != s_Collection.end(); ++iter) { - if ((*iter)->canBeUpdated()) { - modIDs.push_back((*iter)->getNexusID()); - if (modIDs.size() >= 255) { - checkChunkForUpdate(modIDs, receiver); - modIDs.clear(); - } - } - } - - checkChunkForUpdate(modIDs, receiver); - - return result; -} - -void ModInfo::setVersion(const VersionInfo &version) -{ - m_Version = version; -} - -bool ModInfo::hasFlag(ModInfo::EFlag flag) const -{ - std::vector flags = getFlags(); - return std::find(flags.begin(), flags.end(), flag) != flags.end(); -} - -bool ModInfo::hasContent(ModInfo::EContent content) const -{ - std::vector contents = getContents(); - return std::find(contents.begin(), contents.end(), content) != contents.end(); -} - -bool ModInfo::categorySet(int categoryID) const -{ - for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if ((*iter == categoryID) || - (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { - return true; - } - } - - return false; -} - - -void ModInfo::testValid() -{ - m_Valid = false; - QDirIterator dirIter(absolutePath()); - while (dirIter.hasNext()) { - dirIter.next(); - if (dirIter.fileInfo().isDir()) { - if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { - m_Valid = true; - break; - } - } else { - if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { - m_Valid = true; - break; - } - } - } - - // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the - // end - while (dirIter.hasNext()) { - dirIter.next(); - } -} - - -ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) - : m_DirectoryStructure(directoryStructure) {} - -void ModInfoWithConflictInfo::clearCaches() -{ - m_LastConflictCheck = QTime(); -} - -std::vector ModInfoWithConflictInfo::getFlags() const -{ - std::vector result; - switch (isConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); - } break; - default: { /* NOP */ } - } - return result; -} - - -void ModInfoWithConflictInfo::doConflictCheck() const -{ - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - bool regular = false; - - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } - - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - // for all files in this origin - for (auto iter = files.begin(); iter != files.end(); ++iter) { - const std::vector &alternatives = (*iter)->getAlternatives(); - if ((alternatives.size() == 0) - || (alternatives[0] == dataID)) { - // no alternatives -> no conflict - regular = true; - } else { - if ((*iter)->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } - // for all non-providing alternative origins - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - if ((*altIter != dataID) && (*altIter != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } - } - } - } - } - } - - m_LastConflictCheck = QTime::currentTime(); - - if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) { - if (!regular) { - m_CurrentConflictState = CONFLICT_REDUNDANT; - } else { - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } - } - else m_CurrentConflictState = CONFLICT_NONE; -} - -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const -{ - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - doConflictCheck(); - } - - return m_CurrentConflictState; -} - - -bool ModInfoWithConflictInfo::isRedundant() const -{ - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - bool ignore = false; - for (auto iter = files.begin(); iter != files.end(); ++iter) { - if ((*iter)->getOrigin(ignore) == origin.getID()) { - return false; - } - } - return true; - } else { - return false; - } -} - - - -ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_Name(path.dirName()) - , m_Path(path.absolutePath()) - , m_MetaInfoChanged(false) - , m_EndorsedState(ENDORSED_UNKNOWN) -{ - testValid(); - m_CreationTime = QFileInfo(path.absolutePath()).created(); - // read out the meta-file for information - readMeta(); - - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) - , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) - , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); - connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) - , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); -} - - -ModInfoRegular::~ModInfoRegular() -{ - try { - saveMeta(); - } catch (const std::exception &e) { - qCritical("failed to save meta information for \"%s\": %s", - m_Name.toUtf8().constData(), e.what()); - } -} - -bool ModInfoRegular::isEmpty() const -{ - QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - - -void ModInfoRegular::readMeta() -{ - QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); - - m_Notes = metaFile.value("notes", "").toString(); - m_NexusID = metaFile.value("modid", -1).toInt(); - m_Version.parse(metaFile.value("version", "").toString()); - m_NewestVersion = metaFile.value("newestVersion", "").toString(); - m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); - m_InstallationFile = metaFile.value("installationFile", "").toString(); - m_NexusDescription = metaFile.value("nexusDescription", "").toString(); - m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - if (metaFile.contains("endorsed")) { - if (metaFile.value("endorsed").canConvert()) { - switch (metaFile.value("endorsed").toInt()) { - case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; - case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; - case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; - default: m_EndorsedState = ENDORSED_UNKNOWN; break; - } - } else { - m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - } - - QString categoriesString = metaFile.value("category", "").toString(); - - QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); - for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - bool ok = false; - int categoryID = iter->toInt(&ok); - if (categoryID < 0) { - // ignore invalid id - continue; - } - if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { - m_Categories.insert(categoryID); - if (iter == categories.begin()) { - m_PrimaryCategory = categoryID; - } - } - } - - m_MetaInfoChanged = false; -} - -void ModInfoRegular::saveMeta() -{ - // only write meta data if the mod directory exists - if (m_MetaInfoChanged && QFile::exists(absolutePath())) { - QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); - if (metaFile.status() == QSettings::NoError) { - std::set temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - metaFile.setValue("modid", m_NexusID); - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - m_MetaInfoChanged = false; - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } -} - - -bool ModInfoRegular::updateAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); -} - - -bool ModInfoRegular::downgradeAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); -} - - -void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) -{ - QVariantMap result = resultData.toMap(); - m_NewestVersion.parse(result["version"].toString()); - m_NexusDescription = result["description"].toString(); - if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { - m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - m_LastNexusQuery = QDateTime::currentDateTime(); - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) -{ - m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) -{ - QString fullMessage = errorMessage; - if (userData.canConvert() && (userData.toInt() == 1)) { - fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; - } - if (QApplication::activeWindow() != NULL) { - MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); - } - emit modDetailsUpdated(false); -} - - -bool ModInfoRegular::updateNXMInfo() -{ - if (m_NexusID > 0) { - m_NexusBridge.requestDescription(m_NexusID, QVariant()); - return true; - } - return false; -} - - -void ModInfoRegular::setCategory(int categoryID, bool active) -{ - m_MetaInfoChanged = true; - - if (active) { - m_Categories.insert(categoryID); - if (m_PrimaryCategory == -1) { - m_PrimaryCategory = categoryID; - } - } else { - std::set::iterator iter = m_Categories.find(categoryID); - if (iter != m_Categories.end()) { - m_Categories.erase(iter); - } - if (categoryID == m_PrimaryCategory) { - if (m_Categories.size() == 0) { - m_PrimaryCategory = -1; - } else { - m_PrimaryCategory = *(m_Categories.begin()); - } - } - } -} - - -bool ModInfoRegular::setName(const QString &name) -{ - if (name.contains('/') || name.contains('\\')) { - return false; - } - - QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); - QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); - - if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { - QString tempName = name; - tempName.append("_temp"); - while (modDir.exists(tempName)) { - tempName.append("_"); - } - if (!modDir.rename(m_Name, tempName)) { - return false; - } - if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); - modDir.rename(tempName, m_Name); - return false; - } - } else { - if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", - qPrintable(name), ::GetLastError()); - return false; - } - } - - std::map::iterator nameIter = s_ModsByName.find(m_Name); - if (nameIter != s_ModsByName.end()) { - unsigned int index = nameIter->second; - s_ModsByName.erase(nameIter); - - m_Name = name; - m_Path = newPath; - - s_ModsByName[m_Name] = index; - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - updateIndices(); - } else { // otherwise mod isn't registered yet? - m_Name = name; - m_Path = newPath; - } - - return true; -} - -void ModInfoRegular::setNotes(const QString ¬es) -{ - m_Notes = notes; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNexusID(int modID) -{ - m_NexusID = modID; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setVersion(const VersionInfo &version) -{ - m_Version = version; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::setNewestVersion(const VersionInfo &version) { - m_NewestVersion = version; -} - -void ModInfoRegular::setNexusDescription(const QString &description) -{ - m_NexusDescription = description; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::addNexusCategory(int categoryID) -{ - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); -} - -void ModInfoRegular::setIsEndorsed(bool endorsed) -{ - if (m_EndorsedState != ENDORSED_NEVER) { - m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - } -} - - -void ModInfoRegular::setNeverEndorse() -{ - m_EndorsedState = ENDORSED_NEVER; - m_MetaInfoChanged = true; -} - - -bool ModInfoRegular::remove() -{ - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); -} - -void ModInfoRegular::endorse(bool doEndorse) -{ - if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); - } -} - - -QString ModInfoRegular::absolutePath() const -{ - return m_Path; -} - -void ModInfoRegular::ignoreUpdate(bool ignore) -{ - if (ignore) { - m_IgnoredVersion = m_NewestVersion; - } else { - m_IgnoredVersion.clear(); - } - m_MetaInfoChanged = true; -} - - -std::vector ModInfoRegular::getFlags() const -{ - std::vector result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) { - result.push_back(ModInfo::FLAG_NOTENDORSED); - } - if (!isValid()) { - result.push_back(ModInfo::FLAG_INVALID); - } - if (m_Notes.length() != 0) { - result.push_back(ModInfo::FLAG_NOTES); - } - return result; -} - - -std::vector ModInfoRegular::getContents() const -{ - std::vector result; - QDir dir(absolutePath()); - if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { - result.push_back(CONTENT_PLUGIN); - } - QString sePluginPath = ToQString(GameInfo::instance().getSEName()) + "/plugins"; - if (dir.exists("textures")) result.push_back(CONTENT_TEXTURE); - if (dir.exists("meshes")) result.push_back(CONTENT_MESH); - if (dir.exists("interface") - || dir.exists("menus")) result.push_back(CONTENT_INTERFACE); - if (dir.exists("music")) result.push_back(CONTENT_MUSIC); - if (dir.exists("sound")) result.push_back(CONTENT_SOUND); - if (dir.exists("scripts")) result.push_back(CONTENT_SCRIPT); - if (dir.exists(sePluginPath)) result.push_back(CONTENT_SKSE); - if (dir.exists("strings")) result.push_back(CONTENT_STRING); - if (dir.exists("SkyProc Patchers")) result.push_back(CONTENT_SKYPROC); - return result; -} - - -int ModInfoRegular::getHighlight() const -{ - return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; -} - - -QString ModInfoRegular::getDescription() const -{ - if (!isValid()) { - return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); - } else { - const std::set &categories = getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
          ")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; - } - - return ToQString(categoryString.str()); - } -} - -QString ModInfoRegular::notes() const -{ - return m_Notes; -} - -QDateTime ModInfoRegular::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoRegular::getNexusDescription() const -{ - return m_NexusDescription; -} - -ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const -{ - return m_EndorsedState; -} - -QDateTime ModInfoRegular::getLastNexusQuery() const -{ - return m_LastNexusQuery; -} - - -QStringList ModInfoRegular::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -std::vector ModInfoRegular::getIniTweaks() const -{ - QString metaFileName = absolutePath().append("/meta.ini"); - QSettings metaFile(metaFileName, QSettings::IniFormat); - - std::vector result; - - int numTweaks = metaFile.beginReadArray("INI Tweaks"); - - if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", - numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); - } - - for (int i = 0; i < numTweaks; ++i) { - metaFile.setArrayIndex(i); - QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); - result.push_back(filename); - } - metaFile.endArray(); - return result; -} - -std::vector ModInfoBackup::getFlags() const -{ - std::vector result = ModInfoRegular::getFlags(); - result.insert(result.begin(), ModInfo::FLAG_BACKUP); - return result; -} - - -QString ModInfoBackup::getDescription() const -{ - return tr("This is the backup of a mod"); -} - - -ModInfoBackup::ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoRegular(path, directoryStructure) -{ -} - - -ModInfoOverwrite::ModInfoOverwrite() - : m_StartupTime(QDateTime::currentDateTime()) -{ - testValid(); -} - - -bool ModInfoOverwrite::isEmpty() const -{ - QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; - return false; -} - -QString ModInfoOverwrite::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir())); -} - -std::vector ModInfoOverwrite::getFlags() const -{ - std::vector result; - result.push_back(FLAG_OVERWRITE); - return result; -} - -int ModInfoOverwrite::getHighlight() const -{ - return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; -} - -QString ModInfoOverwrite::getDescription() const -{ - return tr("This pseudo mod contains files from the virtual data tree that got " - "modified (i.e. by the construction kit)"); -} - -QStringList ModInfoOverwrite::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -const char ModInfoForeign::INT_IDENTIFIER[] = "__int__foreign"; - -QString ModInfoForeign::name() const -{ - return m_Name; -} - -QDateTime ModInfoForeign::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoForeign::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; -} - -std::vector ModInfoForeign::getFlags() const -{ - std::vector result = ModInfoWithConflictInfo::getFlags(); - result.push_back(FLAG_FOREIGN); - - return result; -} - -int ModInfoForeign::getHighlight() const -{ - return 0; -} - -QString ModInfoForeign::getDescription() const -{ - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); -} - -ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, - DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_ReferenceFile(referenceFile) - , m_Archives(archives) -{ - m_CreationTime = QFileInfo(referenceFile).created(); - m_Name = QFileInfo(m_ReferenceFile).baseName(); -} +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer 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. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "modinfo.h" +#include "utility.h" +#include "installationtester.h" +#include "categories.h" +#include "report.h" +#include "modinfodialog.h" +#include "overwriteinfodialog.h" +#include "json.h" +#include "messagedialog.h" + +#include +#include + +#include + +#include +#include +#include +#include + + +using namespace MOBase; +using namespace MOShared; + + +std::vector ModInfo::s_Collection; +std::map ModInfo::s_ModsByName; +std::map > ModInfo::s_ModsByModID; +int ModInfo::s_NextID; +QMutex ModInfo::s_Mutex(QMutex::Recursive); + +QString ModInfo::s_HiddenExt(".mohidden"); + + +static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) +{ + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; +} + + +ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure) +{ + QMutexLocker locker(&s_Mutex); +// int id = s_NextID++; + static QRegExp backupExp(".*backup[0-9]*"); + ModInfo::Ptr result; + if (backupExp.exactMatch(dir.dirName())) { + result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); + } else { + result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); + } + s_Collection.push_back(result); + return result; +} + +ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames + , DirectoryEntry ** directoryStructure) +{ + QMutexLocker locker(&s_Mutex); + ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); + s_Collection.push_back(result); + return result; +} + +QString ModInfo::getContentTypeName(int contentType) +{ + switch (contentType) { + case CONTENT_PLUGIN: return tr("Plugins"); + case CONTENT_TEXTURE: return tr("Textures"); + case CONTENT_MESH: return tr("Meshes"); + case CONTENT_INTERFACE: return tr("UI Changes"); + case CONTENT_MUSIC: return tr("Music"); + case CONTENT_SOUND: return tr("Sound Effects"); + case CONTENT_SCRIPT: return tr("Scripts"); + case CONTENT_SKSE: return tr("SKSE Plugins"); + case CONTENT_SKYPROC: return tr("SkyProc Tools"); + case CONTENT_STRING: return tr("Strings"); + default: throw MyException(tr("invalid content type %1").arg(contentType)); + } +} + +void ModInfo::createFromOverwrite() +{ + QMutexLocker locker(&s_Mutex); + + s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); +} + +unsigned int ModInfo::getNumMods() +{ + QMutexLocker locker(&s_Mutex); + return s_Collection.size(); +} + + +ModInfo::Ptr ModInfo::getByIndex(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); + + if (index >= s_Collection.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + return s_Collection[index]; +} + + +std::vector ModInfo::getByModID(int modID) +{ + QMutexLocker locker(&s_Mutex); + + auto iter = s_ModsByModID.find(modID); + if (iter == s_ModsByModID.end()) { + return std::vector(); + } + + std::vector result; + for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { + result.push_back(getByIndex(*idxIter)); + } + + return result; +} + + +bool ModInfo::removeMod(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); + + if (index >= s_Collection.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + // update the indices first + ModInfo::Ptr modInfo = s_Collection[index]; + s_ModsByName.erase(s_ModsByName.find(modInfo->name())); + + auto iter = s_ModsByModID.find(modInfo->getNexusID()); + if (iter != s_ModsByModID.end()) { + std::vector indices = iter->second; + std::remove(indices.begin(), indices.end(), index); + s_ModsByModID[modInfo->getNexusID()] = indices; + } + + // physically remove the mod directory + //TODO the return value is ignored because the indices were already removed here, so stopping + // would cause data inconsistencies. Instead we go through with the removal but the mod will show up + // again if the user refreshes + modInfo->remove(); + + // finally, remove the mod from the collection + s_Collection.erase(s_Collection.begin() + index); + + // and update the indices + updateIndices(); + return true; +} + + +unsigned int ModInfo::getIndex(const QString &name) +{ + QMutexLocker locker(&s_Mutex); + + std::map::iterator iter = s_ModsByName.find(name); + if (iter == s_ModsByName.end()) { + return UINT_MAX; + } + + return iter->second; +} + +unsigned int ModInfo::findMod(const boost::function &filter) +{ + for (unsigned int i = 0U; i < s_Collection.size(); ++i) { + if (filter(s_Collection[i])) { + return i; + } + } + return UINT_MAX; +} + + +void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign) +{ + QMutexLocker lock(&s_Mutex); + s_Collection.clear(); + s_NextID = 0; + + { // list all directories in the mod directory and make a mod out of each + QDir mods(QDir::fromNativeSeparators(modDirectory)); + mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + QDirIterator modIter(mods); + while (modIter.hasNext()) { + createFrom(QDir(modIter.next()), directoryStructure); + } + } + + { // list plugins in the data directory and make a foreign-managed mod out of each + std::vector dlcPlugins = GameInfo::instance().getDLCPlugins(); + QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"); + foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) { + if ((file.baseName() != "Update") // hide update + && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp + && (displayForeign // show non-dlc bundles only if the user wants them + || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) { + QStringList archives; + foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) { + archives.append(dataDir.absoluteFilePath(archiveName)); + } + + createFromPlugin(file.fileName(), archives, directoryStructure); + } + } + } + + createFromOverwrite(); + + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + + updateIndices(); +} + + +void ModInfo::updateIndices() +{ + s_ModsByName.clear(); + s_ModsByModID.clear(); + QRegExp backupRegEx(".*backup[0-9]*$"); + + for (unsigned int i = 0; i < s_Collection.size(); ++i) { + QString modName = s_Collection[i]->internalName(); + int modID = s_Collection[i]->getNexusID(); + s_ModsByName[modName] = i; + s_ModsByModID[modID].push_back(i); + } +} + + +ModInfo::ModInfo() + : m_Valid(false), m_PrimaryCategory(-1) +{ +} + + +void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver) +{ + if (modIDs.size() != 0) { + NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); + } +} + + +int ModInfo::checkAllForUpdate(QObject *receiver) +{ + int result = 0; + std::vector modIDs; + + modIDs.push_back(GameInfo::instance().getNexusModID()); + + for (std::vector::iterator iter = s_Collection.begin(); + iter != s_Collection.end(); ++iter) { + if ((*iter)->canBeUpdated()) { + modIDs.push_back((*iter)->getNexusID()); + if (modIDs.size() >= 255) { + checkChunkForUpdate(modIDs, receiver); + modIDs.clear(); + } + } + } + + checkChunkForUpdate(modIDs, receiver); + + return result; +} + +void ModInfo::setVersion(const VersionInfo &version) +{ + m_Version = version; +} + +bool ModInfo::hasFlag(ModInfo::EFlag flag) const +{ + std::vector flags = getFlags(); + return std::find(flags.begin(), flags.end(), flag) != flags.end(); +} + +bool ModInfo::hasContent(ModInfo::EContent content) const +{ + std::vector contents = getContents(); + return std::find(contents.begin(), contents.end(), content) != contents.end(); +} + +bool ModInfo::categorySet(int categoryID) const +{ + for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if ((*iter == categoryID) || + (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { + return true; + } + } + + return false; +} + + +void ModInfo::testValid() +{ + m_Valid = false; + QDirIterator dirIter(absolutePath()); + while (dirIter.hasNext()) { + dirIter.next(); + if (dirIter.fileInfo().isDir()) { + if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { + m_Valid = true; + break; + } + } else { + if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { + m_Valid = true; + break; + } + } + } + + // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the + // end + while (dirIter.hasNext()) { + dirIter.next(); + } +} + + +ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) + : m_DirectoryStructure(directoryStructure) {} + +void ModInfoWithConflictInfo::clearCaches() +{ + m_LastConflictCheck = QTime(); +} + +std::vector ModInfoWithConflictInfo::getFlags() const +{ + std::vector result; + switch (isConflicted()) { + case CONFLICT_MIXED: { + result.push_back(ModInfo::FLAG_CONFLICT_MIXED); + } break; + case CONFLICT_OVERWRITE: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); + } break; + case CONFLICT_OVERWRITTEN: { + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); + } break; + case CONFLICT_REDUNDANT: { + result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); + } break; + default: { /* NOP */ } + } + return result; +} + + +void ModInfoWithConflictInfo::doConflictCheck() const +{ + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + bool regular = false; + + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } + + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + // for all files in this origin + for (auto iter = files.begin(); iter != files.end(); ++iter) { + const std::vector &alternatives = (*iter)->getAlternatives(); + if ((alternatives.size() == 0) + || (alternatives[0] == dataID)) { + // no alternatives -> no conflict + regular = true; + } else { + if ((*iter)->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } + // for all non-providing alternative origins + for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { + if ((*altIter != dataID) && (*altIter != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); + } else { + m_OverwrittenList.insert(altIndex); + } + } + } + } + } + } + + m_LastConflictCheck = QTime::currentTime(); + + if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) { + if (!regular) { + m_CurrentConflictState = CONFLICT_REDUNDANT; + } else { + m_CurrentConflictState = CONFLICT_OVERWRITTEN; + } + } + else m_CurrentConflictState = CONFLICT_NONE; +} + +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); + } + + return m_CurrentConflictState; +} + + +bool ModInfoWithConflictInfo::isRedundant() const +{ + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + bool ignore = false; + for (auto iter = files.begin(); iter != files.end(); ++iter) { + if ((*iter)->getOrigin(ignore) == origin.getID()) { + return false; + } + } + return true; + } else { + return false; + } +} + + + +ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_Name(path.dirName()) + , m_Path(path.absolutePath()) + , m_MetaInfoChanged(false) + , m_EndorsedState(ENDORSED_UNKNOWN) +{ + testValid(); + m_CreationTime = QFileInfo(path.absolutePath()).created(); + // read out the meta-file for information + readMeta(); + + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) + , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) + , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) + , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); +} + + +ModInfoRegular::~ModInfoRegular() +{ + try { + saveMeta(); + } catch (const std::exception &e) { + qCritical("failed to save meta information for \"%s\": %s", + m_Name.toUtf8().constData(), e.what()); + } +} + +bool ModInfoRegular::isEmpty() const +{ + QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + + +void ModInfoRegular::readMeta() +{ + QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); + + m_Notes = metaFile.value("notes", "").toString(); + m_NexusID = metaFile.value("modid", -1).toInt(); + m_Version.parse(metaFile.value("version", "").toString()); + m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); + m_InstallationFile = metaFile.value("installationFile", "").toString(); + m_NexusDescription = metaFile.value("nexusDescription", "").toString(); + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + if (metaFile.contains("endorsed")) { + if (metaFile.value("endorsed").canConvert()) { + switch (metaFile.value("endorsed").toInt()) { + case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; + case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; + case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; + default: m_EndorsedState = ENDORSED_UNKNOWN; break; + } + } else { + m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + } + + QString categoriesString = metaFile.value("category", "").toString(); + + QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); + for (QStringList::iterator iter = categories.begin(); iter != categories.end(); ++iter) { + bool ok = false; + int categoryID = iter->toInt(&ok); + if (categoryID < 0) { + // ignore invalid id + continue; + } + if (ok && (categoryID != 0) && (CategoryFactory::instance().categoryExists(categoryID))) { + m_Categories.insert(categoryID); + if (iter == categories.begin()) { + m_PrimaryCategory = categoryID; + } + } + } + + m_MetaInfoChanged = false; +} + +void ModInfoRegular::saveMeta() +{ + // only write meta data if the mod directory exists + if (m_MetaInfoChanged && QFile::exists(absolutePath())) { + QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); + if (metaFile.status() == QSettings::NoError) { + std::set temp = m_Categories; + temp.erase(m_PrimaryCategory); + metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); + metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); + metaFile.setValue("version", m_Version.canonicalString()); + metaFile.setValue("modid", m_NexusID); + metaFile.setValue("notes", m_Notes); + metaFile.setValue("nexusDescription", m_NexusDescription); + metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + if (m_EndorsedState != ENDORSED_UNKNOWN) { + metaFile.setValue("endorsed", m_EndorsedState); + } + metaFile.sync(); // sync needs to be called to ensure the file is created + + if (metaFile.status() == QSettings::NoError) { + m_MetaInfoChanged = false; + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } +} + + +bool ModInfoRegular::updateAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); +} + + +bool ModInfoRegular::downgradeAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); +} + + +void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) +{ + QVariantMap result = resultData.toMap(); + m_NewestVersion.parse(result["version"].toString()); + m_NexusDescription = result["description"].toString(); + if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { + m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + m_LastNexusQuery = QDateTime::currentDateTime(); + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) +{ + m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); +} + + +void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) +{ + QString fullMessage = errorMessage; + if (userData.canConvert() && (userData.toInt() == 1)) { + fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; + } + if (QApplication::activeWindow() != NULL) { + MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); + } + emit modDetailsUpdated(false); +} + + +bool ModInfoRegular::updateNXMInfo() +{ + if (m_NexusID > 0) { + m_NexusBridge.requestDescription(m_NexusID, QVariant()); + return true; + } + return false; +} + + +void ModInfoRegular::setCategory(int categoryID, bool active) +{ + m_MetaInfoChanged = true; + + if (active) { + m_Categories.insert(categoryID); + if (m_PrimaryCategory == -1) { + m_PrimaryCategory = categoryID; + } + } else { + std::set::iterator iter = m_Categories.find(categoryID); + if (iter != m_Categories.end()) { + m_Categories.erase(iter); + } + if (categoryID == m_PrimaryCategory) { + if (m_Categories.size() == 0) { + m_PrimaryCategory = -1; + } else { + m_PrimaryCategory = *(m_Categories.begin()); + } + } + } +} + + +bool ModInfoRegular::setName(const QString &name) +{ + if (name.contains('/') || name.contains('\\')) { + return false; + } + + QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); + QDir modDir(m_Path.mid(0, m_Path.length() - m_Name.length())); + + if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { + QString tempName = name; + tempName.append("_temp"); + while (modDir.exists(tempName)) { + tempName.append("_"); + } + if (!modDir.rename(m_Name, tempName)) { + return false; + } + if (!modDir.rename(tempName, name)) { + qCritical("rename to final name failed after successful rename to intermediate name"); + modDir.rename(tempName, m_Name); + return false; + } + } else { + if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { + qCritical("failed to rename mod %s (errorcode %d)", + qPrintable(name), ::GetLastError()); + return false; + } + } + + std::map::iterator nameIter = s_ModsByName.find(m_Name); + if (nameIter != s_ModsByName.end()) { + unsigned int index = nameIter->second; + s_ModsByName.erase(nameIter); + + m_Name = name; + m_Path = newPath; + + s_ModsByName[m_Name] = index; + + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + updateIndices(); + } else { // otherwise mod isn't registered yet? + m_Name = name; + m_Path = newPath; + } + + return true; +} + +void ModInfoRegular::setNotes(const QString ¬es) +{ + m_Notes = notes; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNexusID(int modID) +{ + m_NexusID = modID; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setVersion(const VersionInfo &version) +{ + m_Version = version; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNewestVersion(const VersionInfo &version) { + m_NewestVersion = version; +} + +void ModInfoRegular::setNexusDescription(const QString &description) +{ + m_NexusDescription = description; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::addNexusCategory(int categoryID) +{ + m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); +} + +void ModInfoRegular::setIsEndorsed(bool endorsed) +{ + if (m_EndorsedState != ENDORSED_NEVER) { + m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + } +} + + +void ModInfoRegular::setNeverEndorse() +{ + m_EndorsedState = ENDORSED_NEVER; + m_MetaInfoChanged = true; +} + + +bool ModInfoRegular::remove() +{ + m_MetaInfoChanged = false; + return shellDelete(QStringList(absolutePath()), true); +} + +void ModInfoRegular::endorse(bool doEndorse) +{ + if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { + m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); + } +} + + +QString ModInfoRegular::absolutePath() const +{ + return m_Path; +} + +void ModInfoRegular::ignoreUpdate(bool ignore) +{ + if (ignore) { + m_IgnoredVersion = m_NewestVersion; + } else { + m_IgnoredVersion.clear(); + } + m_MetaInfoChanged = true; +} + + +std::vector ModInfoRegular::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) { + result.push_back(ModInfo::FLAG_NOTENDORSED); + } + if (!isValid()) { + result.push_back(ModInfo::FLAG_INVALID); + } + if (m_Notes.length() != 0) { + result.push_back(ModInfo::FLAG_NOTES); + } + return result; +} + + +std::vector ModInfoRegular::getContents() const +{ + std::vector result; + QDir dir(absolutePath()); + if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { + result.push_back(CONTENT_PLUGIN); + } + QString sePluginPath = ToQString(GameInfo::instance().getSEName()) + "/plugins"; + if (dir.exists("textures")) result.push_back(CONTENT_TEXTURE); + if (dir.exists("meshes")) result.push_back(CONTENT_MESH); + if (dir.exists("interface") + || dir.exists("menus")) result.push_back(CONTENT_INTERFACE); + if (dir.exists("music")) result.push_back(CONTENT_MUSIC); + if (dir.exists("sound")) result.push_back(CONTENT_SOUND); + if (dir.exists("scripts")) result.push_back(CONTENT_SCRIPT); + if (dir.exists(sePluginPath)) result.push_back(CONTENT_SKSE); + if (dir.exists("strings")) result.push_back(CONTENT_STRING); + if (dir.exists("SkyProc Patchers")) result.push_back(CONTENT_SKYPROC); + return result; +} + + +int ModInfoRegular::getHighlight() const +{ + return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; +} + + +QString ModInfoRegular::getDescription() const +{ + if (!isValid()) { + return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); + } else { + const std::set &categories = getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
          ")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; + } + categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; + } + + return ToQString(categoryString.str()); + } +} + +QString ModInfoRegular::notes() const +{ + return m_Notes; +} + +QDateTime ModInfoRegular::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoRegular::getNexusDescription() const +{ + return m_NexusDescription; +} + +ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const +{ + return m_EndorsedState; +} + +QDateTime ModInfoRegular::getLastNexusQuery() const +{ + return m_LastNexusQuery; +} + + +QStringList ModInfoRegular::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} + +std::vector ModInfoRegular::getIniTweaks() const +{ + QString metaFileName = absolutePath().append("/meta.ini"); + QSettings metaFile(metaFileName, QSettings::IniFormat); + + std::vector result; + + int numTweaks = metaFile.beginReadArray("INI Tweaks"); + + if (numTweaks != 0) { + qDebug("%d active ini tweaks in %s", + numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + } + + for (int i = 0; i < numTweaks; ++i) { + metaFile.setArrayIndex(i); + QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); + result.push_back(filename); + } + metaFile.endArray(); + return result; +} + +std::vector ModInfoBackup::getFlags() const +{ + std::vector result = ModInfoRegular::getFlags(); + result.insert(result.begin(), ModInfo::FLAG_BACKUP); + return result; +} + + +QString ModInfoBackup::getDescription() const +{ + return tr("This is the backup of a mod"); +} + + +ModInfoBackup::ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoRegular(path, directoryStructure) +{ +} + + +ModInfoOverwrite::ModInfoOverwrite() + : m_StartupTime(QDateTime::currentDateTime()) +{ + testValid(); +} + + +bool ModInfoOverwrite::isEmpty() const +{ + QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + +QString ModInfoOverwrite::absolutePath() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir())); +} + +std::vector ModInfoOverwrite::getFlags() const +{ + std::vector result; + result.push_back(FLAG_OVERWRITE); + return result; +} + +int ModInfoOverwrite::getHighlight() const +{ + return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; +} + +QString ModInfoOverwrite::getDescription() const +{ + return tr("This pseudo mod contains files from the virtual data tree that got " + "modified (i.e. by the construction kit)"); +} + +QStringList ModInfoOverwrite::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; +} + +const char ModInfoForeign::INT_IDENTIFIER[] = "__int__foreign"; + +QString ModInfoForeign::name() const +{ + return m_Name; +} + +QDateTime ModInfoForeign::creationTime() const +{ + return m_CreationTime; +} + +QString ModInfoForeign::absolutePath() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; +} + +std::vector ModInfoForeign::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + result.push_back(FLAG_FOREIGN); + + return result; +} + +int ModInfoForeign::getHighlight() const +{ + return 0; +} + +QString ModInfoForeign::getDescription() const +{ + return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); +} + +ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, + DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_ReferenceFile(referenceFile) + , m_Archives(archives) +{ + m_CreationTime = QFileInfo(referenceFile).created(); + m_Name = tr("Unmanaged") + ": " + QFileInfo(m_ReferenceFile).baseName(); +} -- cgit v1.3.1 From 637ce6bcc1b10198eb2a21570b6a97e122814f55 Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 19 Sep 2014 22:01:37 +0200 Subject: - removed a use of wostringstream which seems to have been involved in a bug. Still not sure how - rewrote widechar <-> multibyte conversion functions to handle arbitrary string lengths --- src/shared/util.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 61ca9cdc..4bc5a8a4 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include namespace MOShared { @@ -64,24 +65,28 @@ bool FileExists(const std::wstring &searchPath, const std::wstring &filename) std::string ToString(const std::wstring &source, bool utf8) { - char buffer[MAX_PATH]; - if (utf8) { - ::WideCharToMultiByte(CP_UTF8, 0, source.c_str(), -1, buffer, MAX_PATH, NULL, NULL); - } else { - ::WideCharToMultiByte(GetACP(), 0, source.c_str(), -1, buffer, MAX_PATH, NULL, NULL); + std::string result; + UINT codepage = utf8 ? CP_UTF8 : GetACP(); + int sizeRequired = ::WideCharToMultiByte(codepage, 0, &source[0], -1, NULL, 0, NULL, NULL); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to multibyte"); } - return std::string(buffer); + result.resize(sizeRequired, '\0'); + ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], sizeRequired, NULL, NULL); + return result; } std::wstring ToWString(const std::string &source, bool utf8) { - wchar_t buffer[MAX_PATH]; - if (utf8) { - ::MultiByteToWideChar(CP_UTF8, 0, source.c_str(), -1, buffer, MAX_PATH); - } else { - ::MultiByteToWideChar(GetACP(), 0, source.c_str(), -1, buffer, MAX_PATH); + std::wstring result; + UINT codepage = utf8 ? CP_UTF8 : GetACP(); + int sizeRequired = ::MultiByteToWideChar(codepage, 0, &source[0], (int)source.size(), NULL, 0); + if (sizeRequired == 0) { + throw windows_error("failed to convert string to wide character"); } - return std::wstring(buffer); + result.resize(sizeRequired, L'\0'); + ::MultiByteToWideChar(codepage, 0, &source[0], (int)source.size(), &result[0], sizeRequired); + return result; } std::string &ToLower(std::string &text) -- cgit v1.3.1 From 93bd29c13d3355b2544c2fd40dff1f4f985f9b57 Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 24 Sep 2014 19:51:51 +0200 Subject: - several style fixes suggested by static analysis - will now support up to 4 levels of version numbers (major.minor.subminor.subsubminor --- src/browserdialog.cpp | 2 +- src/categories.cpp | 8 +++--- src/installationmanager.h | 2 +- src/main.cpp | 11 +++----- src/mainwindow.cpp | 55 +++++++++++++++++++--------------------- src/modinfo.cpp | 2 +- src/modlist.cpp | 8 ++++-- src/modlist.h | 2 +- src/modlistsortproxy.cpp | 2 +- src/nexusinterface.cpp | 5 +++- src/pluginlist.cpp | 50 ++++++++++++++++++------------------- src/profile.cpp | 4 ++- src/selfupdater.h | 3 ++- src/settings.cpp | 2 +- src/shared/directoryentry.cpp | 6 ++--- src/shared/directoryentry.h | 2 +- src/shared/fallout3info.cpp | 21 ++++++++-------- src/shared/fallout3info.h | 6 ++--- src/shared/falloutnvinfo.cpp | 58 +++++++++++++++++++------------------------ src/shared/falloutnvinfo.h | 6 ++--- src/shared/gameinfo.cpp | 40 ++++++++--------------------- src/shared/leaktrace.cpp | 2 +- src/shared/oblivioninfo.cpp | 21 ++++++++-------- src/shared/oblivioninfo.h | 6 ++--- src/shared/skyriminfo.cpp | 53 ++++++++++++++++++--------------------- src/shared/skyriminfo.h | 8 +++--- 26 files changed, 176 insertions(+), 209 deletions(-) diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 521459d0..f93ffcae 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -22,10 +22,10 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "report.h" -#include "json.h" #include "persistentcookiejar.h" #include +#include "json.h" #include #include diff --git a/src/categories.cpp b/src/categories.cpp index 28b1f4a2..57e18a28 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -232,7 +232,7 @@ void CategoryFactory::loadDefaultCategories() int CategoryFactory::getParentID(unsigned int index) const { - if ((index < 0) || (index >= m_Categories.size())) { + if (index >= m_Categories.size()) { throw MyException(QObject::tr("invalid index %1").arg(index)); } @@ -267,7 +267,7 @@ bool CategoryFactory::isDecendantOf(int id, int parentID) const bool CategoryFactory::hasChildren(unsigned int index) const { - if ((index < 0) || (index >= m_Categories.size())) { + if (index >= m_Categories.size()) { throw MyException(QObject::tr("invalid index %1").arg(index)); } @@ -277,7 +277,7 @@ bool CategoryFactory::hasChildren(unsigned int index) const QString CategoryFactory::getCategoryName(unsigned int index) const { - if ((index < 0) || (index >= m_Categories.size())) { + if (index >= m_Categories.size()) { throw MyException(QObject::tr("invalid index %1").arg(index)); } @@ -287,7 +287,7 @@ QString CategoryFactory::getCategoryName(unsigned int index) const int CategoryFactory::getCategoryID(unsigned int index) const { - if ((index < 0) || (index >= m_Categories.size())) { + if (index >= m_Categories.size()) { throw MyException(QObject::tr("invalid index %1").arg(index)); } diff --git a/src/installationmanager.h b/src/installationmanager.h index d430c065..336c1ce3 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -55,7 +55,7 @@ public: **/ explicit InstallationManager(QWidget *parent); - ~InstallationManager(); + virtual ~InstallationManager(); /** * @brief update the directory where mods are to be installed diff --git a/src/main.cpp b/src/main.cpp index 3198208a..642bfa1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -455,14 +455,9 @@ int main(int argc, char *argv[]) settings.setValue("gamePath", gamePath.toUtf8().constData()); } - int edition = 0; - if (settings.contains("game_edition")) { - edition = settings.value("game_edition").toInt(); - } else { + if (!settings.contains("game_edition")) { std::vector editions = GameInfo::instance().getSteamVariants(); - if (editions.size() < 2) { - edition = 0; - } else { + if (editions.size() > 1) { SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL); int index = 0; for (auto iter = editions.begin(); iter != editions.end(); ++iter) { @@ -475,7 +470,7 @@ int main(int argc, char *argv[]) } } } - +#pragma message("edition isn't used?") qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); ExecutablesList executablesList; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ae5c1e48..19be758e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1137,7 +1137,6 @@ void MainWindow::registerPluginTool(IPluginTool *tool) void MainWindow::registerModPage(IPluginModPage *modPage) { - QToolButton *browserBtn = NULL; // turn the browser action into a drop-down menu if necessary if (ui->actionNexus->menu() == NULL) { QAction *nexusAction = ui->actionNexus; @@ -1147,10 +1146,8 @@ void MainWindow::registerModPage(IPluginModPage *modPage) ui->toolBar->removeAction(nexusAction); actionToToolButton(ui->actionNexus); - browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); browserBtn->menu()->addAction(nexusAction); - } else { - browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); } QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); @@ -1434,30 +1431,32 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; - bool isJobHandle = true; + { + bool isJobHandle = true; - DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { - if (isJobHandle) { - if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - break; - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; + DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + break; + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } } } - } - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); - res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + } } ::CloseHandle(processHandle); @@ -2191,11 +2190,7 @@ void MainWindow::storeSettings() QSettings::Status result = QSettings::NoError; { QSettings settings(iniFile + ".new", QSettings::IniFormat); - if (m_CurrentProfile != NULL) { - settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); - } else { - settings.remove("selected_profile"); - } + settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); settings.setValue("mod_list_state", ui->modList->header()->saveState()); settings.setValue("plugin_list_state", ui->espList->header()->saveState()); @@ -5560,11 +5555,11 @@ void MainWindow::on_bossButton_clicked() DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; - bool isJobHandle = true; - ULONG lastProcessID; HANDLE processHandle = loot; if (loot != INVALID_HANDLE_VALUE) { + bool isJobHandle = true; + ULONG lastProcessID; DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { if (isJobHandle) { diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 796dab71..189e67b2 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -156,7 +156,7 @@ bool ModInfo::removeMod(unsigned int index) auto iter = s_ModsByModID.find(modInfo->getNexusID()); if (iter != s_ModsByModID.end()) { std::vector indices = iter->second; - std::remove(indices.begin(), indices.end(), index); + indices.erase(std::remove(indices.begin(), indices.end(), index), indices.end()); s_ModsByModID[modInfo->getNexusID()] = indices; } diff --git a/src/modlist.cpp b/src/modlist.cpp index eedf1ec6..fb8df15e 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -49,8 +49,12 @@ using namespace MOBase; ModList::ModList(QObject *parent) - : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), - m_FontMetrics(QFont()), m_DropOnItems(false) + : QAbstractItemModel(parent) + , m_Profile(NULL) + , m_NexusInterface(NULL) + , m_Modified(false) + , m_FontMetrics(QFont()) + , m_DropOnItems(false) { m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(QIcon(":/MO/gui/content/plugin"), ":/MO/gui/content/plugin", tr("Game plugins (esp/esm)")); m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(QIcon(":/MO/gui/content/interface"), ":/MO/gui/content/interface", tr("Interface")); diff --git a/src/modlist.h b/src/modlist.h index 632689c6..cf52b2ec 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -267,7 +267,7 @@ private: struct TModInfo { TModInfo(unsigned int index, ModInfo::Ptr modInfo) - : modInfo(modInfo), nameOrder(index) {} + : modInfo(modInfo), nameOrder(index), priorityOrder(0), modIDOrder(0), categoryOrder(0) {} ModInfo::Ptr modInfo; unsigned int nameOrder; unsigned int priorityOrder; diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index da5d99d5..8907e712 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -298,7 +298,7 @@ bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const } break; case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { ModInfo::EEndorsedState state = info->endorsedState(); - if ((state == ModInfo::ENDORSED_FALSE) && (state != ModInfo::ENDORSED_NEVER)) return true; + if ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)) return true; } break; case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 30221f4b..b4006097 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -20,7 +20,7 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "utility.h" -#include +#include "json.h" #include "selectiondialog.h" #include #include @@ -580,6 +580,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_URL(url) , m_SubModule(subModule) , m_NexusGameID(nexusGameId) + , m_Endorse(false) {} NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList @@ -600,6 +601,7 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList , m_URL(url) , m_SubModule(subModule) , m_NexusGameID(nexusGameId) + , m_Endorse(false) {} NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID @@ -620,4 +622,5 @@ NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID , m_URL(url) , m_SubModule(subModule) , m_NexusGameID(nexusGameId) + , m_Endorse(false) {} diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index ff370fa4..973e3cfc 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -685,7 +685,7 @@ QString PluginList::origin(const QString &name) const { auto iter = m_ESPsByName.find(name.toLower()); if (iter == m_ESPsByName.end()) { - return false; + return QString(); } else { return m_ESPs[iter->second].m_OriginName; } @@ -836,7 +836,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), std::inserter(enabledMasters, enabledMasters.end())); - if (enabledMasters.size() > 0) { + if (!enabledMasters.empty()) { text += "
          " + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); } if (m_ESPs[index].m_HasIni) { @@ -1102,34 +1102,34 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event) ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { QItemSelectionModel *selectionModel = itemView->selectionModel(); const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - // remove elements that aren't supposed to be movable - QMutableListIterator iter(rows); - while (iter.hasNext()) { - if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { - iter.remove(); + if (proxyModel != NULL) { + int diff = -1; + if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { + diff = 1; } - } - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); + QModelIndexList rows = selectionModel->selectedRows(); + // remove elements that aren't supposed to be movable + QMutableListIterator iter(rows); + while (iter.hasNext()) { + if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { + iter.remove(); + } } - } - foreach (QModelIndex idx, rows) { - if (proxyModel != NULL) { - idx = proxyModel->mapToSource(idx); + if (keyEvent->key() == Qt::Key_Down) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } } - int newPriority = m_ESPs[idx.row()].m_Priority + diff; - if ((newPriority >= 0) && (newPriority < rowCount())) { - setPluginPriority(idx.row(), newPriority); + foreach (QModelIndex idx, rows) { + idx = proxyModel->mapToSource(idx); + int newPriority = m_ESPs[idx.row()].m_Priority + diff; + if ((newPriority >= 0) && (newPriority < rowCount())) { + setPluginPriority(idx.row(), newPriority); + } } + refreshLoadOrder(); } - refreshLoadOrder(); return true; } else if (keyEvent->key() == Qt::Key_Space) { QItemSelectionModel *selectionModel = itemView->selectionModel(); diff --git a/src/profile.cpp b/src/profile.cpp index 77e4f813..958084d7 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -585,7 +585,9 @@ void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const bool Profile::invalidationActive(bool *supported) const { if (GameInfo::instance().requiresBSAInvalidation()) { - *supported = true; + if (supported != NULL) { + *supported = true; + } wchar_t buffer[1024]; std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value diff --git a/src/selfupdater.h b/src/selfupdater.h index 9648f15a..75b0ff45 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -65,7 +65,8 @@ public: * @todo passing the nexus interface is unneccessary **/ SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); - ~SelfUpdater(); + + virtual ~SelfUpdater(); /** * @brief start the update process diff --git a/src/settings.cpp b/src/settings.cpp index 04a5f279..0ca811a3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,7 +25,7 @@ along with Mod Organizer. If not, see . #include #include #include -#include +#include "json.h" #include #include diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 5d785822..24868a93 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -325,13 +325,13 @@ static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS) FileEntry::FileEntry() - : m_Index(UINT_MAX), m_Name(), m_Parent(NULL), m_LastAccessed(time(NULL)) + : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(NULL), m_LastAccessed(time(NULL)) { LEAK_TRACE; } FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) - : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L""), m_LastAccessed(time(NULL)) + : m_Index(index), m_Name(name), m_Origin(-1), m_Parent(parent), m_Archive(L""), m_LastAccessed(time(NULL)) { LEAK_TRACE; } @@ -636,7 +636,7 @@ void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origi void DirectoryEntry::removeFile(FileEntry::Index index) { - if (m_Files.size() != 0) { + if (!m_Files.empty()) { auto iter = std::find_if(m_Files.begin(), m_Files.end(), [&index](const std::pair &iter) -> bool { return iter.second == index; } ); diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 096f373e..d588ab02 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -219,7 +219,7 @@ public: void clear(); bool isPopulated() const { return m_Populated; } - bool isEmpty() const { return (m_Files.size() == 0) && (m_SubDirectories.size() == 0); } + bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } const DirectoryEntry *getParent() const { return m_Parent; } diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index 9487d2de..22db91ac 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -54,15 +54,17 @@ std::wstring Fallout3Info::getRegPathStatic() 0, KEY_QUERY_VALUE, &key); if (errorcode != ERROR_SUCCESS) { - return L""; + return std::wstring(); } WCHAR temp[MAX_PATH]; DWORD bufferSize = MAX_PATH; - errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); - - return std::wstring(temp); + if (::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) { + return std::wstring(temp); + } else { + return std::wstring(); + } } std::wstring Fallout3Info::getInvalidationBSA() @@ -233,15 +235,12 @@ void Fallout3Info::createProfile(const std::wstring &directory, bool useDefaults } } { // copy falloutprefs.ini-file - std::wstring target = directory.substr().append(L"\\falloutprefs.ini"); + std::wstring target = directory + L"\\falloutprefs.ini"; if (!FileExists(target)) { - std::wostringstream source; - source << getMyGamesDirectory() << L"\\Fallout3\\falloutprefs.ini"; - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + std::wstring source = getMyGamesDirectory() + L"\\Fallout3\\falloutprefs.ini"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { if (::GetLastError() != ERROR_FILE_EXISTS) { - std::ostringstream stream; - stream << "failed to copy ini file: " << ToString(source.str(), false); - throw windows_error(stream.str()); + throw windows_error(std::string("failed to copy ini file: ") + ToString(source, false)); } } } diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index 8e4c260d..d1356de1 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -38,7 +38,7 @@ public: virtual unsigned long getBSAVersion(); static std::wstring getRegPathStatic(); - virtual std::wstring getRegPath() { return Fallout3Info::getRegPathStatic(); } + virtual std::wstring getRegPath() { return getRegPathStatic(); } virtual std::wstring getBinaryName() { return L"Fallout3.exe"; } virtual GameInfo::Type getType() { return TYPE_FALLOUT3; } @@ -75,9 +75,9 @@ public: virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); - virtual std::wstring getNexusInfoUrl() { return Fallout3Info::getNexusInfoUrlStatic(); } + virtual std::wstring getNexusInfoUrl() { return getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); - virtual int getNexusModID() { return Fallout3Info::getNexusModIDStatic(); } + virtual int getNexusModID() { return getNexusModIDStatic(); } virtual int getNexusGameID() { return 120; } virtual void createProfile(const std::wstring &directory, bool useDefaults); diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 9bba7fe4..0dde4db1 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -55,15 +55,17 @@ std::wstring FalloutNVInfo::getRegPathStatic() 0, KEY_QUERY_VALUE, &key); if (errorcode != ERROR_SUCCESS) { - return L""; + return std::wstring(); } WCHAR temp[MAX_PATH]; DWORD bufferSize = MAX_PATH; - errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); - - return std::wstring(temp); + if (::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) { + return std::wstring(temp); + } else { + return std::wstring(); + } } std::wstring FalloutNVInfo::getInvalidationBSA() @@ -162,56 +164,46 @@ std::wstring FalloutNVInfo::getSteamAPPId(int) const void FalloutNVInfo::createProfile(const std::wstring &directory, bool useDefaults) { - std::wostringstream target; + std::wstring target = directory + L"\\plugins.txt"; // copy plugins.txt - target << directory << "\\plugins.txt"; - - if (!FileExists(target.str())) { - std::wostringstream source; - source << getLocalAppFolder() << "\\FalloutNV\\plugins.txt"; - if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { - HANDLE file = ::CreateFileW(target.str().c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (!FileExists(target)) { + std::wstring source = getLocalAppFolder() + L"\\FalloutNV\\plugins.txt"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { + HANDLE file = ::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); ::CloseHandle(file); } } // copy ini-file - target.str(L""); target.clear(); - target << directory << L"\\fallout.ini"; + target = directory + L"\\fallout.ini"; - if (!FileExists(target.str())) { - std::wostringstream source; + if (!FileExists(target)) { + std::wstring source; if (useDefaults) { - source << getGameDirectory() << L"\\fallout_default.ini"; + source = getGameDirectory() + L"\\fallout_default.ini"; } else { - source << getMyGamesDirectory() << L"\\FalloutNV"; - if (FileExists(source.str(), L"fallout.ini")) { - source << L"\\fallout.ini"; + source = getMyGamesDirectory() + L"\\FalloutNV"; + if (FileExists(source, L"fallout.ini")) { + source += L"\\fallout.ini"; } else { - source.str(L""); - source << getGameDirectory() << L"\\fallout_default.ini"; + source = getGameDirectory() + L"\\fallout_default.ini"; } } - if (!::CopyFileW(source.str().c_str(), target.str().c_str(), true)) { + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { if (::GetLastError() != ERROR_FILE_EXISTS) { - std::ostringstream stream; - stream << "failed to copy ini file: " << ToString(source.str(), false); - throw windows_error(stream.str()); + throw windows_error("failed to copy ini file: " + ToString(source, false)); } } } { // copy falloutprefs.ini-file - std::wstring target = directory.substr().append(L"\\falloutprefs.ini"); + std::wstring target = directory + L"\\falloutprefs.ini"; if (!FileExists(target)) { - std::wostringstream source; - source << getMyGamesDirectory() << L"\\FalloutNV\\falloutprefs.ini"; - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + std::wstring source = getMyGamesDirectory() + L"\\FalloutNV\\falloutprefs.ini"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { if (::GetLastError() != ERROR_FILE_EXISTS) { - std::ostringstream stream; - stream << "failed to copy ini file: " << ToString(source.str(), false); - throw windows_error(stream.str()); + throw windows_error("failed to copy ini file: " + ToString(source, false)); } } } diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index cfd373c7..50a0d00d 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -38,7 +38,7 @@ public: virtual unsigned long getBSAVersion(); static std::wstring getRegPathStatic(); - virtual std::wstring getRegPath() { return FalloutNVInfo::getRegPathStatic(); } + virtual std::wstring getRegPath() { return getRegPathStatic(); } virtual std::wstring getBinaryName() { return L"FalloutNV.exe"; } virtual GameInfo::Type getType() { return TYPE_FALLOUTNV; } @@ -76,9 +76,9 @@ public: virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); - virtual std::wstring getNexusInfoUrl() { return FalloutNVInfo::getNexusInfoUrlStatic(); } + virtual std::wstring getNexusInfoUrl() { return getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); - virtual int getNexusModID() { return FalloutNVInfo::getNexusModIDStatic(); } + virtual int getNexusModID() { return getNexusModIDStatic(); } virtual int getNexusGameID() { return 130; } virtual void createProfile(const std::wstring &directory, bool useDefaults); diff --git a/src/shared/gameinfo.cpp b/src/shared/gameinfo.cpp index 21e9a586..5439efff 100644 --- a/src/shared/gameinfo.cpp +++ b/src/shared/gameinfo.cpp @@ -57,7 +57,7 @@ void GameInfo::identifyMyGamesDirectory(const std::wstring &file) { // this function attempts 3 (three!) ways to determine the correct "My Games" folder. wchar_t myDocuments[MAX_PATH]; - memset(myDocuments, '\0', MAX_PATH); + memset(myDocuments, '\0', MAX_PATH * sizeof(wchar_t)); m_MyGamesDirectory.clear(); @@ -137,71 +137,53 @@ std::wstring GameInfo::getGameDirectory() const std::wstring GameInfo::getModsDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << L"\\mods"; - return temp.str(); + return m_OrganizerDirectory + L"\\mods"; } std::wstring GameInfo::getProfilesDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << L"\\profiles"; - return temp.str(); + return m_OrganizerDirectory + L"\\profiles"; } std::wstring GameInfo::getIniFilename() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << L"\\ModOrganizer.ini"; - return temp.str(); + return m_OrganizerDirectory + L"\\ModOrganizer.ini"; } std::wstring GameInfo::getDownloadDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << L"\\downloads"; - return temp.str(); + return m_OrganizerDirectory + L"\\downloads"; } std::wstring GameInfo::getCacheDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << L"\\webcache"; - return temp.str(); + return m_OrganizerDirectory + L"\\webcache"; } std::wstring GameInfo::getOverwriteDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << "\\overwrite"; - return temp.str(); + return m_OrganizerDirectory + L"\\overwrite"; } std::wstring GameInfo::getLogDir() const { - std::wostringstream temp; - temp << m_OrganizerDataDirectory << "\\logs"; - return temp.str(); + return m_OrganizerDirectory + L"\\logs"; } std::wstring GameInfo::getLootDir() const { - std::wostringstream temp; - temp << m_OrganizerDirectory << "\\loot"; - return temp.str(); + return m_OrganizerDirectory + L"\\loot"; } std::wstring GameInfo::getTutorialDir() const { - std::wostringstream temp; - temp << m_OrganizerDirectory << "\\tutorials"; - return temp.str(); + return m_OrganizerDirectory + L"\\tutorials"; } @@ -219,7 +201,7 @@ std::vector GameInfo::getSteamVariants() const std::wstring GameInfo::getLocalAppFolder() const { wchar_t localAppFolder[MAX_PATH]; - memset(localAppFolder, '\0', MAX_PATH); + memset(localAppFolder, '\0', MAX_PATH * sizeof(wchar_t)); if (::SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, localAppFolder) == S_OK) { return localAppFolder; diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp index 68e57609..729eb42e 100644 --- a/src/shared/leaktrace.cpp +++ b/src/shared/leaktrace.cpp @@ -36,7 +36,7 @@ class StackData { public: StackData() - : m_FunctionName("Dummy"), m_CodeLine(0) + : m_Count(0), m_Hash(0UL), m_FunctionName("Dummy"), m_CodeLine(0) {} StackData(const char *functionName, int line) { m_Count = ::CaptureStackBackTrace(FRAMES_TO_SKIP, FRAMES_TO_CAPTURE, m_Stack, &m_Hash); diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index f317812f..790fcdb0 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -55,15 +55,17 @@ std::wstring OblivionInfo::getRegPathStatic() 0, KEY_QUERY_VALUE, &key); if (errorcode != ERROR_SUCCESS) { - return L""; + return std::wstring(); } WCHAR temp[MAX_PATH]; DWORD bufferSize = MAX_PATH; - errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); - - return std::wstring(temp); + if (::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) { + return std::wstring(temp); + } else { + return std::wstring(); + } } std::wstring OblivionInfo::getInvalidationBSA() @@ -188,16 +190,13 @@ void OblivionInfo::createProfile(const std::wstring &directory, bool useDefaults } { // copy oblivionprefs.ini-file - std::wstring target = directory.substr().append(L"\\oblivionprefs.ini"); + std::wstring target = directory + L"\\oblivionprefs.ini"; if (!FileExists(target)) { - std::wostringstream source; - source << getMyGamesDirectory() << L"\\Oblivion\\oblivionprefs.ini"; - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + std::wstring source = getMyGamesDirectory() + L"\\Oblivion\\oblivionprefs.ini"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { if ((::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL) == INVALID_HANDLE_VALUE) && (::GetLastError() != ERROR_FILE_EXISTS)) { - std::ostringstream stream; - stream << "failed to create ini file: " << ToString(target.c_str(), false); - throw windows_error(stream.str()); + throw windows_error(std::string("failed to create ini file: ") + ToString(target, false)); } } } diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index 02fd90b4..e64ae37b 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -36,7 +36,7 @@ public: virtual unsigned long getBSAVersion(); static std::wstring getRegPathStatic(); - virtual std::wstring getRegPath() { return OblivionInfo::getRegPathStatic(); } + virtual std::wstring getRegPath() { return getRegPathStatic(); } virtual std::wstring getBinaryName() { return L"Oblivion.exe"; } virtual GameInfo::Type getType() { return TYPE_OBLIVION; } @@ -72,9 +72,9 @@ public: virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); - virtual std::wstring getNexusInfoUrl() { return OblivionInfo::getNexusInfoUrlStatic(); } + virtual std::wstring getNexusInfoUrl() { return getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); - virtual int getNexusModID() { return OblivionInfo::getNexusModIDStatic(); } + virtual int getNexusModID() { return getNexusModIDStatic(); } virtual int getNexusGameID() { return 101; } virtual void createProfile(const std::wstring &directory, bool useDefaults); diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index c985fe9f..319e58d5 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -63,15 +63,17 @@ std::wstring SkyrimInfo::getRegPathStatic() 0, KEY_QUERY_VALUE, &key); if (errorcode != ERROR_SUCCESS) { - return L""; + return std::wstring(); } WCHAR temp[MAX_PATH]; DWORD bufferSize = MAX_PATH; - errorcode = ::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize); - - return std::wstring(temp); + if (::RegQueryValueExW(key, L"Installed Path", NULL, NULL, (LPBYTE)temp, &bufferSize) == ERROR_SUCCESS) { + return std::wstring(temp); + } else { + return std::wstring(); + } } @@ -222,7 +224,7 @@ int SkyrimInfo::getNexusModIDStatic() void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) { { // copy plugins.txt - std::wstring target = directory.substr().append(L"\\plugins.txt"); + std::wstring target = directory + L"\\plugins.txt"; if (!FileExists(target)) { std::wostringstream source; source << getLocalAppFolder() << "\\Skyrim\\plugins.txt"; @@ -231,11 +233,10 @@ void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) ::CloseHandle(file); } } - target = directory.substr().append(L"\\loadorder.txt"); + target = directory + L"\\loadorder.txt"; if (!FileExists(target)) { - std::wostringstream source; - source << getLocalAppFolder() << "\\Skyrim\\loadorder.txt"; - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + std::wstring source = getLocalAppFolder() + L"\\Skyrim\\loadorder.txt"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { HANDLE file = ::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); ::CloseHandle(file); } @@ -243,42 +244,36 @@ void SkyrimInfo::createProfile(const std::wstring &directory, bool useDefaults) } { // copy skyrim.ini-file - std::wstring target = directory.substr().append(L"\\skyrim.ini"); + std::wstring target = directory + L"\\skyrim.ini"; if (!FileExists(target)) { - std::wostringstream source; + std::wstring source; if (useDefaults) { - source << getGameDirectory() << L"\\skyrim_default.ini"; + source = getGameDirectory() + L"\\skyrim_default.ini"; } else { - source << getMyGamesDirectory() << L"\\Skyrim"; - if (FileExists(source.str(), L"skyrim.ini")) { - source << L"\\skyrim.ini"; + source = getMyGamesDirectory() + L"\\Skyrim"; + if (FileExists(source, L"skyrim.ini")) { + source += L"\\skyrim.ini"; } else { - source.str(L""); - source << getGameDirectory() << L"\\skyrim_default.ini"; + source = getGameDirectory() + L"\\skyrim_default.ini"; } } - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { if (::GetLastError() != ERROR_FILE_EXISTS) { - std::ostringstream stream; - stream << "failed to copy ini file: " << ToString(source.str(), false); - throw windows_error(stream.str()); + throw windows_error(std::string("failed to copy ini file: ") + ToString(source, false)); } } } } { // copy skyrimprefs.ini-file - std::wstring target = directory.substr().append(L"\\skyrimprefs.ini"); + std::wstring target = directory + L"\\skyrimprefs.ini"; if (!FileExists(target)) { - std::wostringstream source; - source << getMyGamesDirectory() << L"\\Skyrim\\skyrimprefs.ini"; - if (!::CopyFileW(source.str().c_str(), target.c_str(), true)) { - log("failed to copy ini file %ls", source.str().c_str()); + std::wstring source = getMyGamesDirectory() + L"\\Skyrim\\skyrimprefs.ini"; + if (!::CopyFileW(source.c_str(), target.c_str(), true)) { + log("failed to copy ini file %ls", source.c_str()); // create empty if (::CreateFileW(target.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) == INVALID_HANDLE_VALUE) { - std::ostringstream stream; - stream << "failed to copy ini file: " << ToString(source.str(), false); - throw windows_error(stream.str()); + throw windows_error(std::string("failed to copy ini file: ") + ToString(source, false)); } } } diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index 132f2aee..a7aff8dc 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -38,7 +38,7 @@ public: virtual unsigned long getBSAVersion(); static std::wstring getRegPathStatic(); - virtual std::wstring getRegPath() { return SkyrimInfo::getRegPathStatic(); } + virtual std::wstring getRegPath() { return getRegPathStatic(); } virtual std::wstring getBinaryName() { return L"TESV.exe"; } virtual GameInfo::Type getType() { return TYPE_SKYRIM; } @@ -81,11 +81,11 @@ public: virtual std::wstring getNexusPage(bool nmmScheme = true); static std::wstring getNexusInfoUrlStatic(); - virtual std::wstring getNexusInfoUrl() { return SkyrimInfo::getNexusInfoUrlStatic(); } + virtual std::wstring getNexusInfoUrl() { return getNexusInfoUrlStatic(); } static int getNexusModIDStatic(); - virtual int getNexusModID() { return SkyrimInfo::getNexusModIDStatic(); } + virtual int getNexusModID() { return getNexusModIDStatic(); } static int getNexusGameIDStatic() { return 110; } - virtual int getNexusGameID() { return SkyrimInfo::getNexusGameIDStatic(); } + virtual int getNexusGameID() { return getNexusGameIDStatic(); } virtual void createProfile(const std::wstring &directory, bool useDefaults); virtual void repairProfile(const std::wstring &directory); -- cgit v1.3.1 From 2ce135e0bf7ce2c801b1a8d57fe10b099b9007aa Mon Sep 17 00:00:00 2001 From: Tannin Date: Sat, 27 Sep 2014 19:18:33 +0200 Subject: updated minimum nmm compatible version number --- src/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.cpp b/src/settings.cpp index 0ca811a3..3b28c788 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -231,7 +231,7 @@ QString Settings::getModDirectory() const QString Settings::getNMMVersion() const { - static const QString MIN_NMM_VERSION = "0.47.0"; + static const QString MIN_NMM_VERSION = "0.52.2"; QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { result = MIN_NMM_VERSION; -- cgit v1.3.1 From db0e278817cf5a36e15f1945c52e73726598e8d9 Mon Sep 17 00:00:00 2001 From: Tannin Date: Mon, 29 Sep 2014 20:35:35 +0200 Subject: - moved the hook-recursion-protection to tls - some code cleanup and consolidation - hook.dll will now report all of its own exceptions - some more logging during startup - changed the way urls are encoded for download requests - now displaying (one of the) process name(s) while waiting for a program to end - bugfix: spawned processes were forced to leave the job --- src/downloadmanager.cpp | 50 ++++++++++++++++--------------------------- src/mainwindow.cpp | 15 +++++++++++++ src/profile.cpp | 7 +++--- src/shared/directoryentry.cpp | 10 ++++----- src/spawn.cpp | 2 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index bc31adf4..b3b18a38 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -331,7 +331,8 @@ bool DownloadManager::addDownload(const QStringList &URLs, fileName = "unknown"; } - QNetworkRequest request(URLs.first()); + QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); + QNetworkRequest request(preferredUrl); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); } @@ -1198,47 +1199,34 @@ void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userD m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); } - -// sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +int evaluateFileInfoMap(const QVariantMap &map, const std::map &preferredServers) { - int LHSVal = 0; - int RHSVal = 0; + int result = 0; - QVariantMap LHSMap = LHS.toMap(); - QVariantMap RHSMap = RHS.toMap(); - - int LHSUsers = LHSMap["ConnectedUsers"].toInt(); - int RHSUsers = RHSMap["ConnectedUsers"].toInt(); + int users = map["ConnectedUsers"].toInt(); // 0 users is probably a sign that the server is offline. Since there is currently no // mechanism to try a different server, we avoid those without users - if (LHSUsers == 0) { - LHSVal -= 500; - } else { - LHSVal -= LHSUsers; - } - if (RHSUsers == 0) { - RHSVal -= 500; + if (users == 0) { + result -= 500; } else { - RHSVal -= RHSUsers; + result -= users; } - // user preference. This is a bit silly because the more servers on the preferred list the higher the boost - auto LHSPreference = preferredServers.find(LHSMap["Name"].toString()); - auto RHSPreference = preferredServers.find(RHSMap["Name"].toString()); + auto preference = preferredServers.find(map["Name"].toString()); - if (LHSPreference != preferredServers.end()) { - LHSVal += 100 + LHSPreference->second * 20; - } - if (RHSPreference != preferredServers.end()) { - RHSVal += 100 + RHSPreference->second * 20; + if (preference != preferredServers.end()) { + result += 100 + preference->second * 20; } - // premium isn't valued high because premium servers already get a massive boost for having few users online - if (LHSMap["IsPremium"].toBool()) LHSVal += 5; - if (RHSMap["IsPremium"].toBool()) RHSVal += 5; + if (map["IsPremium"].toBool()) result += 5; + + return result; +} - return RHSVal < LHSVal; +// sort function to sort by best download server +bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +{ + return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers); } int DownloadManager::startDownloadURLs(const QStringList &urls) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19be758e..17743312 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1410,6 +1410,15 @@ HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arg } } +std::wstring getProcessName(DWORD processId) +{ + HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId); + + DWORD value = MAX_PATH; + wchar_t buffer[MAX_PATH]; + ::QueryFullProcessImageNameW(process, 0, buffer, &value); + return buffer; +} void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) { @@ -1432,6 +1441,7 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, JOBOBJECT_BASIC_PROCESS_ID_LIST info; { + DWORD currentProcess = 0UL; bool isJobHandle = true; DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); @@ -1439,6 +1449,11 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, if (isJobHandle) { if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { + } else { + if (info.ProcessIdList[0] != currentProcess) { + currentProcess = info.ProcessIdList[0]; + dialog->setProcessName(ToQString(getProcessName(currentProcess))); + } break; } } else { diff --git a/src/profile.cpp b/src/profile.cpp index 958084d7..6e9d8f0f 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -217,7 +217,7 @@ void Profile::createTweakedIniFile() } if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"0", ToWString(tweakedIni).c_str())) { error = true; } @@ -238,7 +238,7 @@ void Profile::createTweakedIniFile() void Profile::refreshModStatus() { QFile file(getModlistFileName()); - if (!file.exists()) { + if (!file.open(QIODevice::ReadOnly)) { throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); } @@ -249,10 +249,9 @@ void Profile::refreshModStatus() std::set namesRead; // load mods from file and update enabled state and priority for them - file.open(QIODevice::ReadOnly); int index = 0; while (!file.atEnd()) { - QByteArray line = file.readLine(); + QByteArray line = file.readLine().trimmed(); bool enabled = true; QString modName; if (line.length() == 0) { diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 24868a93..0adf0812 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -72,8 +72,7 @@ public: } bool exists(const std::wstring &name) { - std::map::iterator iter = m_OriginsNameMap.find(name); - return iter != m_OriginsNameMap.end(); + return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); } FilesOrigin &getByID(Index ID) { @@ -369,16 +368,14 @@ std::wstring FileEntry::getFullPath() const bool ignore = false; result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin recurseParents(result, m_Parent); // all intermediate directories - result.append(L"\\").append(m_Name); // the actual filename - return result; + return result + L"\\" + m_Name; } std::wstring FileEntry::getRelativePath() const { std::wstring result; recurseParents(result, m_Parent); // all intermediate directories - result.append(L"\\").append(m_Name); // the actual filename - return result; + return result + L"\\" + m_Name; } @@ -446,6 +443,7 @@ void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::ws boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str()); + buffer.get()[offset] = L'\0'; addFiles(origin, buffer.get(), offset); } m_Populated = true; diff --git a/src/spawn.cpp b/src/spawn.cpp index ddcf573e..cd7e202e 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -120,7 +120,7 @@ HANDLE startBinary(const QFileInfo &binary, JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo; ::QueryInformationJobObject(NULL, JobObjectExtendedLimitInformation, &jobInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), NULL); - jobInfo.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK | JOB_OBJECT_LIMIT_BREAKAWAY_OK; + jobInfo.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK; HANDLE jobObject = ::CreateJobObject(NULL, NULL); -- cgit v1.3.1 From df0bd3331a4b2174f99117c5a6f21ff6bddca1ba Mon Sep 17 00:00:00 2001 From: Tannin Date: Wed, 5 Nov 2014 23:48:06 +0100 Subject: - archive library can now query for password during extraction (seems to be necessary for rars) - process blacklist is now taken from a file if there is one, not hardcoded - removed workaround for the papyrus compiler - updated loot client to work with the actual api - loot client now links with loot32.dll at runtime - loot client now produces its output in a (json-)file which includes all plugin messages and dirty flags - fomod installer now tries to parse the xml with several encodings - fomod installer will now display a diagnostics warning if the jpg imageformat isn't supported - base preview plugin now tries to be a bit smarter about resizing images to fit the screen - bugfix: fomod installer no longer tries to open an image even after detecting its invalid - bugfix: potential null-pointer dereferentiation in getprivateprofile... hooks - bugfix: potential null-pointer dereferentiation in download manager - bugfix: internal origin name showed up in one more place - bugfix: ToString function produced strings that were one (zero-termination-)character too long --- src/aboutdialog.cpp | 1 - src/dlls.manifest.debug.qt5 | 3 +++ src/downloadmanager.cpp | 2 +- src/main.cpp | 2 +- src/mainwindow.cpp | 48 ++++++++++++++++++++++++++++++++------------- src/mainwindow.h | 2 +- src/organizer.pro | 2 +- src/pluginlist.cpp | 12 +++++++++++- src/settings.cpp | 2 +- src/shared/util.cpp | 9 ++++++--- 10 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index b5bfeb04..90dcd073 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -41,7 +41,6 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("Boost Library", LICENSE_BOOST); addLicense("7-zip", LICENSE_LGPL3); addLicense("ZLib", LICENSE_ZLIB); - addLicense("NIF File Format Library", LICENSE_BSD3); 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); diff --git a/src/dlls.manifest.debug.qt5 b/src/dlls.manifest.debug.qt5 index 6cc0a83d..1bbdf691 100644 --- a/src/dlls.manifest.debug.qt5 +++ b/src/dlls.manifest.debug.qt5 @@ -8,7 +8,10 @@ + + + diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index bc31adf4..82701a05 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -606,7 +606,7 @@ void DownloadManager::pauseDownload(int index) DownloadInfo *info = m_ActiveDownloads.at(index); if (info->m_State == STATE_DOWNLOADING) { - if (info->m_Reply->isRunning()) { + if ((info->m_Reply != NULL) && (info->m_Reply->isRunning())) { setState(info, STATE_PAUSING); } else { setState(info, STATE_PAUSED); diff --git a/src/main.cpp b/src/main.cpp index 642bfa1a..b4139f09 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -332,7 +332,7 @@ int main(int argc, char *argv[]) } } - application.addLibraryPath(application.applicationDirPath() + "/dlls"); + application.setLibraryPaths(QStringList() << (application.applicationDirPath() + "/dlls")); SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 19be758e..d9db84ae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -103,6 +103,10 @@ along with Mod Organizer. If not, see . #include #include #include +#include +#include +#include +#include #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) #include #else @@ -1268,14 +1272,14 @@ void MainWindow::unloadPlugins() ui->actionTool->menu()->clear(); } - foreach (QPluginLoader *loader, m_PluginLoaders) { - qDebug("unloading %s", qPrintable(loader->fileName())); + while (!m_PluginLoaders.empty()) { + QPluginLoader *loader = m_PluginLoaders.back(); + m_PluginLoaders.pop_back(); if (!loader->unload()) { qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); } delete loader; } - m_PluginLoaders.clear(); } void MainWindow::loadPlugins() @@ -1322,7 +1326,7 @@ void MainWindow::loadPlugins() if (QLibrary::isLibrary(pluginName)) { QPluginLoader *pluginLoader = new QPluginLoader(pluginName, this); if (pluginLoader->instance() == NULL) { - m_UnloadedPlugins.push_back(pluginName); + m_FailedPlugins.push_back(pluginName); qCritical("failed to load plugin %s: %s", qPrintable(pluginName), qPrintable(pluginLoader->errorString())); } else { @@ -1330,7 +1334,7 @@ void MainWindow::loadPlugins() qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); m_PluginLoaders.push_back(pluginLoader); } else { - m_UnloadedPlugins.push_back(pluginName); + m_FailedPlugins.push_back(pluginName); qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); } } @@ -2285,7 +2289,7 @@ void MainWindow::on_tabWidget_currentChanged(int index) std::vector MainWindow::activeProblems() const { std::vector problems; - if (m_UnloadedPlugins.size() != 0) { + if (m_FailedPlugins.size() != 0) { problems.push_back(PROBLEM_PLUGINSNOTLOADED); } if (m_PluginList.enabledCount() > 255) { @@ -2314,7 +2318,7 @@ QString MainWindow::fullDescription(unsigned int key) const switch (key) { case PROBLEM_PLUGINSNOTLOADED: { QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
            "; - foreach (const QString &plugin, m_UnloadedPlugins) { + foreach (const QString &plugin, m_FailedPlugins) { result += "
          • " + plugin + "
          • "; } result += "
              "; @@ -5375,13 +5379,10 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU foreach (const std::string &line, lines) { if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t reportidx = line.find("[Report]"); - size_t erroridx = line.find("[error]"); + size_t progidx = line.find("[progress]"); + size_t erroridx = line.find("[error]"); if (progidx != std::string::npos) { dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (reportidx != std::string::npos) { - reportURL = line.substr(reportidx + 9); } else if (erroridx != std::string::npos) { qWarning("%s", line.c_str()); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); @@ -5525,12 +5526,15 @@ void MainWindow::on_bossButton_clicked() dialog.setMaximum(0); dialog.show(); + QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); + QStringList parameters; parameters << "--unattended" << "--stdout" << "--noreport" << "--game" << ToQString(GameInfo::instance().getGameShortName()) - << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); + << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())) + << "--out" << outPath; if (m_DidUpdateMasterList) { parameters << "--skipUpdateMasterlist"; @@ -5540,7 +5544,7 @@ void MainWindow::on_bossButton_clicked() HANDLE stdOutWrite = INVALID_HANDLE_VALUE; HANDLE stdOutRead = INVALID_HANDLE_VALUE; createStdoutPipe(&stdOutRead, &stdOutWrite); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/LOOT.exe"), + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), m_CurrentProfile->getName(), m_Settings.logLevel(), @@ -5615,6 +5619,22 @@ void MainWindow::on_bossButton_clicked() return; } else { success = true; + QFile outFile(outPath); + outFile.open(QIODevice::ReadOnly); + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + m_PluginList.addInformation(pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + } + if (pluginObj["dirty"].toString() == "yes") + m_PluginList.addInformation(pluginObj["name"].toString(), "dirty"); + } + } } else { reportError(tr("failed to start loot")); diff --git a/src/mainwindow.h b/src/mainwindow.h index 8bd663ac..ea79fc37 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -381,7 +381,7 @@ private: std::vector m_DiagnosisPlugins; std::vector m_DiagnosisConnections; std::vector m_ModPages; - std::vector m_UnloadedPlugins; + std::vector m_FailedPlugins; std::vector m_PluginLoaders; QFile m_PluginsCheck; diff --git a/src/organizer.pro b/src/organizer.pro index b24586e6..fd1e6aad 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -341,7 +341,7 @@ SRCDIR ~= s,/,$$QMAKE_DIR_SEP,g DSTDIR ~= s,/,$$QMAKE_DIR_SEP,g QMAKE_POST_LINK += xcopy /y /I $$quote($$SRCDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n) -QMAKE_POST_LINK += xcopy /y /I $$quote($$SRCDIR\\ModOrganizer*.pdb) $$quote($$DSTDIR) $$escape_expand(\\n) +QMAKE_POST_LINK += xcopy /y /I $$quote($$SRCDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n) QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\stylesheets) $$quote($$DSTDIR)\\stylesheets $$escape_expand(\\n) QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\tutorials) $$quote($$DSTDIR)\\tutorials $$escape_expand(\\n) QMAKE_POST_LINK += xcopy /y /s /I $$quote($$BASEDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n) diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 973e3cfc..b2d02cd2 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see . #include "settings.h" #include "safewritefile.h" #include "scopeguard.h" +#include "modinfo.h" #include #include #include @@ -158,7 +159,14 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD QString iniPath = QFileInfo(filename).baseName() + ".ini"; bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != NULL; - m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni)); + QString originName = ToQString(origin.getName()); + unsigned int modIndex = ModInfo::getIndex(originName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + originName = modInfo->name(); + } + + m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), originName, ToQString(current->getFullPath()), hasIni)); } catch (const std::exception &e) { reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); } @@ -290,6 +298,8 @@ void PluginList::addInformation(const QString &name, const QString &message) if (iter != m_ESPsByName.end()) { m_AdditionalInfo[name.toLower()].m_Messages.append(message); + } else { + qWarning("failed to associate message for \"%s\"", qPrintable(name)); } } diff --git a/src/settings.cpp b/src/settings.cpp index 0ca811a3..274e5979 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -231,7 +231,7 @@ QString Settings::getModDirectory() const QString Settings::getNMMVersion() const { - static const QString MIN_NMM_VERSION = "0.47.0"; + static const QString MIN_NMM_VERSION = "0.52.3"; QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { result = MIN_NMM_VERSION; diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 4bc5a8a4..d4a77929 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -71,7 +71,9 @@ std::string ToString(const std::wstring &source, bool utf8) if (sizeRequired == 0) { throw windows_error("failed to convert string to multibyte"); } - result.resize(sizeRequired, '\0'); + // the size returned by WideCharToMultiByte contains zero termination IF -1 is specified for the length. + // we don't want that \0 in the string because then the length field would be wrong. Because madness + result.resize(sizeRequired - 1, '\0'); ::WideCharToMultiByte(codepage, 0, &source[0], (int)source.size(), &result[0], sizeRequired, NULL, NULL); return result; } @@ -117,14 +119,15 @@ std::wstring ToLower(const std::wstring &text) VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName) { - DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), NULL); + DWORD handle; + DWORD size = ::GetFileVersionInfoSizeW(fileName.c_str(), &handle); if (size == 0) { throw windows_error("failed to determine file version info size"); } void *buffer = new char[size]; try { - if (!::GetFileVersionInfoW(fileName.c_str(), 0UL, size, buffer)) { + if (!::GetFileVersionInfoW(fileName.c_str(), handle, size, buffer)) { throw windows_error("failed to determine file version info"); } -- cgit v1.3.1 From 53d1f3e00e8f2cfc8f2d715b4bbbf0309dcb8947 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 6 Nov 2014 00:03:44 +0100 Subject: - workaround for GetModuleFileName-calls that supply wrong buffer size - removed obsoleted calls to GetVersionEx - bugfix: download urls were potentially not encoded correctly - bugfixes to tls-based recursion protection --- src/downloadmanager.cpp | 3 ++- src/installationmanager.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index b3b18a38..28605409 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -332,6 +332,7 @@ bool DownloadManager::addDownload(const QStringList &URLs, } QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); + qDebug("selected download url: %s", qPrintable(preferredUrl.toString())); QNetworkRequest request(preferredUrl); return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); } @@ -645,7 +646,7 @@ void DownloadManager::resumeDownloadInt(int index) info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); } qDebug("request resume from url %s", qPrintable(info->currentURL())); - QNetworkRequest request(info->currentURL()); + QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); info->m_ResumePos = info->m_Output.size(); qDebug("resume at %lld bytes", info->m_ResumePos); QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 29914b65..d6435e57 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -257,7 +257,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool new MethodCallback(this, &InstallationManager::dummyProgressFile), new MethodCallback(this, &InstallationManager::report7ZipError))) { m_InstallationProgress.hide(); - throw std::runtime_error("extracting failed"); + throw MyException(QString("extracting failed (%1)").arg(m_CurrentArchive->getLastError())); } m_InstallationProgress.hide(); @@ -542,7 +542,7 @@ bool InstallationManager::doInstall(GuessedValue &modName, int modID, if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { return false; } else { - throw std::runtime_error("extracting failed"); + throw MyException(QString("extracting failed (%1)").arg(m_CurrentArchive->getLastError())); } } -- cgit v1.3.1 From 9ab7bada1c81eb82d97df23da64a56a0eba0bf42 Mon Sep 17 00:00:00 2001 From: Tannin Date: Sun, 9 Nov 2014 14:01:48 +0100 Subject: - bsa parser will now cancel in case of a read error. Before, when attempting to parse a broken bsa it could take forever and continuously allocate memory - better error message when bsa parsing fails - slightly better support for font colors in bbcode converter - configurator now also uses pyqt5 - bugfix: bsa hashing function converted backslashes to slashes instead of the other way around. hash calculation is still often wrong on folder names... --- src/aboutdialog.cpp | 2 +- src/bbcode.cpp | 15 +++++++++------ src/directoryrefresher.cpp | 10 +++++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 90dcd073..d7749187 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -36,7 +36,7 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt"; m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; - addLicense("Qt 4.8.5", LICENSE_LGPL3); + addLicense("Qt 5.3", LICENSE_LGPL3); addLicense("Qt Json", LICENSE_GPL3); addLicense("Boost Library", LICENSE_BOOST); addLicense("7-zip", LICENSE_LGPL3); diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 92eb427f..2e22859d 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -80,11 +80,15 @@ public: if (tagName == "color") { QString color = tagIter->second.first.cap(1); QString content = tagIter->second.first.cap(2); - auto colIter = m_ColorMap.find(color.toLower()); - if (colIter != m_ColorMap.end()) { - color = colIter->second; + if (color.at(0) == "#") { + return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); + } else { + auto colIter = m_ColorMap.find(color.toLower()); + if (colIter != m_ColorMap.end()) { + color = colIter->second; + } + return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); } - return temp.replace(tagIter->second.first, QString("%2").arg(color, content)); } else { qWarning("don't know how to deal with tag %s", qPrintable(tagName)); } @@ -131,7 +135,7 @@ private: m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), ""); m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "\\2"); + "\\2"); m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), "
              \\1
              "); m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), @@ -231,7 +235,6 @@ QString convertToHTML(const QString &inputParam) QString input = inputParam.mid(0).replace("\r\n", "
              "); input.replace("\\\"", "\"").replace("\\'", "'"); - QString result; int lastBlock = 0; int pos = 0; diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index 2186f9a5..24fda501 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -83,8 +83,12 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure foreach (const QString &archive, archives) { QFileInfo fileInfo(archive); if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { - directoryStructure->addFromBSA(ToWString(modName), directoryW, - ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); + try { + directoryStructure->addFromBSA(ToWString(modName), directoryW, + ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); + } catch (const std::exception &e) { + throw MyException(tr("failed to parse bsa %1: %2").arg(archive, e.what())); + } } } } @@ -143,7 +147,7 @@ void DirectoryRefresher::refresh() try { addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives); } catch (const std::exception &e) { - emit error(tr("failed to read bsa: %1").arg(e.what())); + emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what())); } emit progress((i * 100) / m_Mods.size() + 1); } -- cgit v1.3.1 From 7c1273b246117740dd17fb70ad712346421224d7 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 11 Nov 2014 21:41:22 +0100 Subject: some work on installer scripts --- src/mainwindow.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 17743312..90ddcf81 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -5435,11 +5435,22 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList QString steamAppID; if (executable.contains('\\') || executable.contains('/')) { // file path + binary = QFileInfo(executable); if (binary.isRelative()) { // relative path, should be relative to game directory binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); } + + std::vector::iterator current, end; + m_ExecutablesList.getExecutables(current, end); + for (; current != end; ++current) { + if (current->m_BinaryInfo == binary) { + steamAppID = current->m_SteamAppID; + currentDirectory = current->m_WorkingDirectory; + } + } + if (cwd.length() == 0) { currentDirectory = binary.absolutePath(); } -- cgit v1.3.1 From 01265e9b0300cb4fe2dbed53050570ddee653da4 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 11 Nov 2014 21:46:16 +0100 Subject: - re-enabled use of img-tags in bbcode converter - addded a workaround for cases where, after a MO update, the stored modlist layout has no size for new columns - using a webview again for the nexus view of the modinfo dialog --- src/bbcode.cpp | 4 ++-- src/mainwindow.cpp | 11 +++++++++-- src/modinfodialog.cpp | 3 ++- src/modinfodialog.ui | 35 ++++++++++++++--------------------- src/modlistsortproxy.cpp | 30 ------------------------------ src/modlistsortproxy.h | 1 - 6 files changed, 27 insertions(+), 57 deletions(-) diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 2e22859d..0f9170d4 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -177,9 +177,9 @@ private: m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), - "\\1"); + ""); m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), - "\\2"); + "\"\\1\""); m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), "\\2"); m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ede14448..eb1e1b09 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -226,8 +226,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) + 1); - ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) - 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize - 1); } else { // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); @@ -399,6 +399,13 @@ void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) #endif } + // ensure the columns aren't so small you can't see them any more + for (int i = 0; i < ui->modList->header()->count(); ++i) { + if (ui->modList->header()->sectionSize(i) < 10) { + ui->modList->header()->resizeSection(i, 10); + } + } + if (!pluginListCustom) { // resize plugin list to fit content #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 1cea9e1e..9133b166 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -84,7 +84,8 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); - connect(ui->descriptionView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + connect(ui->descriptionView, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + ui->descriptionView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); if (directory->originExists(ToWString(modInfo->name()))) { m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index d3862b58..a03edef7 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -232,8 +232,8 @@ 0 0 - 676 - 126 + 98 + 28 @@ -678,25 +678,11 @@ p, li { white-space: pre-wrap; } - - - - 0 - 200 - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - - - Qt::TextBrowserInteraction - - - false + + + + about:blank + @@ -819,6 +805,13 @@ p, li { white-space: pre-wrap; } + + + QWebView + QWidget +
              QtWebKitWidgets/QWebView
              +
              +
              diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 8907e712..e132b71e 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -79,36 +79,6 @@ Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const return flags; } -void ModListSortProxy::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - for (int i = 0; i <= ModList::COL_LASTCOLUMN; ++i) { - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(ModList::getColumnName(i)); - checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - int i = 0; - - emit layoutAboutToBeChanged(); - m_EnabledColumns.reset(); - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast(widgetAction->defaultWidget()); - if (checkBox != NULL) { - m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); - } - } - ++i; - } - emit layoutChanged(); -} - void ModListSortProxy::enableAllVisible() { if (m_Profile == NULL) return; diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 71da79eb..05392c0b 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -95,7 +95,6 @@ public: public slots: - void displayColumnSelection(const QPoint &pos); void updateFilter(const QString &filter); signals: -- cgit v1.3.1 From 53896e66f113519253892903404264b4e49ab8a6 Mon Sep 17 00:00:00 2001 From: Tannin Date: Tue, 11 Nov 2014 23:48:22 +0100 Subject: - updated staging script to use qt5 and to fetch translations from transifex - removed call to function unavailable in Windows XP --- src/ModOrganizer.pro | 1 + src/mainwindow.cpp | 20 +- src/organizer_es.ts | 1247 ++++++++++++++------------- src/organizer_fr.ts | 2232 +++++++++++++++++++++++++----------------------- src/organizer_ru.ts | 1132 ++++++++++++------------ src/organizer_zh_CN.ts | 1692 ++++++++++++++++++------------------ src/organizer_zh_TW.ts | 2138 ++++++++++++++++++++++++---------------------- 7 files changed, 4336 insertions(+), 4126 deletions(-) diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 9b2d998b..a8f7f53e 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -15,6 +15,7 @@ SUBDIRS = bsatk \ loot_cli \ esptk +pythonRunner.depends = uibase plugins.depends = pythonRunner uibase hookdll.depends = shared organizer.depends = shared uibase plugins diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d6d4ebe3..024e2510 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -226,8 +226,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1); - ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize - 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) + 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, ui->modList->header()->sectionSize(ModList::COL_CONTENT) - 1); } else { // hide these columns by default ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); @@ -1425,10 +1425,18 @@ std::wstring getProcessName(DWORD processId) { HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId); - DWORD value = MAX_PATH; wchar_t buffer[MAX_PATH]; - ::QueryFullProcessImageNameW(process, 0, buffer, &value); - return buffer; + if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { + wchar_t *fileName = wcsrchr(buffer, L'\\'); + if (fileName == nullptr) { + fileName = buffer; + } else { + fileName += 1; + } + return fileName; + } else { + return std::wstring(L"unknown"); + } } void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) @@ -1460,12 +1468,12 @@ void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, if (isJobHandle) { if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { if (info.NumberOfProcessIdsInList == 0) { + break; } else { if (info.ProcessIdList[0] != currentProcess) { currentProcess = info.ProcessIdList[0]; dialog->setProcessName(ToQString(getProcessName(currentProcess))); } - break; } } else { // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there diff --git a/src/organizer_es.ts b/src/organizer_es.ts index cf6f18a6..1803ca7f 100644 --- a/src/organizer_es.ts +++ b/src/organizer_es.ts @@ -1,6 +1,4 @@ - - - + AboutDialog @@ -62,7 +60,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> @@ -159,22 +157,22 @@ Si existe un componente que es requerido llamado "00 Core" . Las opcio Some Page - + Alguna Página Search - + Búsqueda new - + nuevo failed to start download - + Error al iniciar descarga @@ -225,20 +223,16 @@ Si existe un componente que es requerido llamado "00 Core" . Las opcio <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Puede coincidir con una o varias categorías de una ID interna de Nexus. Cada vez que se descarga un mod de la página de Nexus, Mod Organizador tratará de resolver la categoría definida en Nexus a una disponible en MO</span></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { espacio blanco: pre envoltura; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Se puede sincronizar una o varias categorías de nexo a un ID interno. Cada vez que se descarga un mod de la Página Nexus, Mod Organizer tratará de resolver la categoría definida en el Nexus a uno disponible en MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Para saber la categoria de una ID usada por nexo, visitar la lista de categorías de la página nexo y discernir sobre los enlaces de allí.</span></p></body></html> Parent ID - Parent ID + ID Paternal @@ -461,7 +455,7 @@ p, li { white-space: pre-wrap; } Un-Hide - Un-Hide + Hacer Visible @@ -577,7 +571,7 @@ p, li { white-space: pre-wrap; } Un-Hide - Un-Hide + Hacer Visible @@ -622,7 +616,7 @@ p, li { white-space: pre-wrap; } Remove All... - Quitar todos... + Eliminando todos... @@ -635,7 +629,7 @@ p, li { white-space: pre-wrap; } Memory allocation error (in refreshing directory). - + Error de asignación de memoria (en directorio refrescante). @@ -705,7 +699,7 @@ p, li { white-space: pre-wrap; } No known download urls. Sorry, this download can't be resumed. - + No se conocen descarga urls. Lo sentimos, esta descarga no se puede reanudar. @@ -750,12 +744,12 @@ p, li { white-space: pre-wrap; } Memory allocation error (in processing progress event). - + Error de asignación de memoria (en el procesamiento de eventos de progreso). Memory allocation error (in processing downloaded data). - + Error de asignación de memoria (en el procesamiento de datos descargados). @@ -830,13 +824,13 @@ p, li { white-space: pre-wrap; } Binary - Fichero + Binario Binary to run - Fiechero para ser ejecutado + Binario para funcionar @@ -846,18 +840,18 @@ p, li { white-space: pre-wrap; } Browse filesystem for the executable to run. - Examinar en busca del ejecutable. + Explorar sistema de archivos para hacer funcionar el ejecutable. ... - Examinar + ... Start in - Comienzo en + Comenzar en @@ -956,7 +950,7 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p Java (32-bit) required - Java (32-bit) requeredo + Java (32-bit) requerido @@ -1006,12 +1000,12 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p Find - Buscar + Encontrar: Find what: - Buscar: + Encuentra en: @@ -1028,7 +1022,7 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p &Find Next - &Encontrar + &Buscar siguiente @@ -1133,7 +1127,7 @@ En este momento el único caso que conozco donde esto debe ser sobreescrito es p <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1177,87 +1171,87 @@ p, li { white-space: pre-wrap; } - + Extracting files Extrayendo ficheros - + failed to create backup fallo al crear copia de seguridad - + Mod Name Nombre del Mod - + Name Nombre - + Invalid name Nombre inválido - + The name you entered is invalid, please enter a different one. El nombre introducido no es válido, por favor introduzca uno diferente. - + File format "%1" not supported Formato de archivo no soportado para "%1" - + None of the available installer plugins were able to handle that archive Ninguno de los plugins del instalador disponible son capaces de manejar este archivo - + no error sin error - + 7z.dll not found 7z.dll no se encuentra - + 7z.dll isn't valid 7z.dll no es valido - + archive not found archivo no encontrado - + failed to open archive Error abriendo el fichero - + unsupported archive type formato de fichero no soportado - + internal library error error interno de libreria - + archive invalid archivo invalido - + unknown archive error Error de fichero desconocido @@ -1277,7 +1271,7 @@ p, li { white-space: pre-wrap; } MO is locked while the executable is running. - MO esta bloqueado mientras se ejecute el programa. + MO esta bloqueado mientras se ejecuta el programa. @@ -1317,27 +1311,27 @@ p, li { white-space: pre-wrap; } Click blank area to deselect - + Púlsar en el área en blanco para anular la selección If checked, only mods that match all selected categories are displayed. - + Si se selecciona, sólo mods que responden a todas las categorías seleccionadas son mostrados. And - + Y If checked, all mods that match at least one of the selected categories are displayed. - + Si se selecciona, se muestran todos los mods que coincidan con al menos una de las categorías seleccionadas. Or - + O @@ -1354,7 +1348,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1364,6 +1358,11 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Crear perfiles aquí. Cada perfil contiene su propia lista de mods activos y esps. De esta manera puedes cambiar rápidamente entre configuraciones para diferentes juegos.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ten en cuenta que en estos momentos tu carga de esp no se mantiene separado para diferentes perfiles.</span></p></body></html> + + + Open list options... + Abrir Opciones de la lista... + Refresh list. This is usually not necessary unless you modified data outside the program. @@ -1373,13 +1372,13 @@ p, li { white-space: pre-wrap; } Restore Backup... - Restaurar copia de seguridad + Restaurar copia de seguridad... Create Backup - + Crear Copia de Seguridad @@ -1423,7 +1422,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1443,7 +1442,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1466,7 +1465,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1484,6 +1483,11 @@ p, li { white-space: pre-wrap; } Plugins Plugins + + + Sort + Ordenar + List of available esp/esm files @@ -1494,7 +1498,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1502,30 +1506,20 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta lista contiene los ESP y ESMS contenidos en los mods activos. Requieren su propio orden de carga. Utilice arrastrar y soltar para modificar este orden de carga. Tenga en cuenta que MO sólo salvará el orden de carga de los mods que están activos/comprobados.<br />Hay una gran herramienta llamada &quot;BOSS&quot; para ordenar automáticamente los archivos.</span></p></body></html> - - - Sort - Ordenar - - - - Open list options... - - Archives - + Archivos <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>BSAs son paquetes de activos de juego (texturas, scripts, ...). Por defecto, el motor carga estos paquetes en una etapa distinta de archivos sueltos. MO puede gestionar esos archivos para alinear su orden de carga con la de archivos sueltos:</p><p>Si los archivos son <span style=" font-weight:600;">gestionados</span>, se especifica el orden de carga por la prioridad del mod correspondiente (panel izquierdo), lo mismo que los archivos sueltos. Puede activar manualmente cualquier BSA que no tiene un plugin correspondiente activo<br/></p><p>Si los archivos no son <span style=" font-weight:600;">gestionados</span> su orden de carga es especificado por la prioridad del plugin correspondiente (panel de la derecha, pestaña plugins). No podras, activar manualmente BSAs donde el plugin no está activo.</p><p>En cualquiera de los casos no se puede deshabilitar archivos si hay un plugin coincidente, el juego los cargará cueste lo que cueste.</p></body></html> <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> - + <html><head/><body><p>Dejar MO manejar archivos (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">leer más</span></a>)</p></body></html> @@ -1566,8 +1560,8 @@ BSA marcado aquí se cargan de tal manera que su orden de instalación se cumple - - + + Refresh Recargar @@ -1602,8 +1596,8 @@ BSA marcado aquí se cargan de tal manera que su orden de instalación se cumple <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1747,7 +1741,7 @@ p, li { white-space: pre-wrap; } - + Update Actualizacion @@ -1791,19 +1785,19 @@ Ahora esto tiene una funcionalidad muy limitada - + Endorse Mod Organizer Avalar Mod Organizer Copy Log to Clipboard - Copiar al Portapapeles + Copiar Log al Portapapeles Ctrl+C - Ctrl+M + Ctrl+C @@ -1920,6 +1914,25 @@ Ahora esto tiene una funcionalidad muy limitada Plugin "%1" failed Plugin "%1" fallido + + + Download? + Descarga? + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + Una descarga se ha iniciado, pero no hay página de plugin instalado reconocido. +Si lo descarga de todos modos ninguna información (ej. la versión) se asociará con la descarga. +Continuar? + + + + Browse Mod Page + Explorar Página de Mod + failed to init plugin %1: %2 @@ -1952,6 +1965,11 @@ Ahora esto tiene una funcionalidad muy limitada Please press OK once you're logged into steam. Por favor, pulsa OK una vez que hayas iniciado sesión en steam. + + + Executable "%1" not found + Ejecutable "%1" no encontrado + Start Steam? @@ -1963,924 +1981,889 @@ Ahora esto tiene una funcionalidad muy limitada Steam es requerido para iniciar correctamente el juego. ¿Debería MO tratar de iniciar ahora steam? - + Also in: <br> También en: <br> - + No conflict Sin conflictos - + <Edit...> <Editar...> - + + Failed to refresh list of esps: %1 + Fallo al actualizar la lista de esps: %1 + + + This bsa is enabled in the ini file so it may be required! Esta bsa está habilitada en el archivo ini, por lo que puede ser necesario - + Activating Network Proxy Activación de proxy de red - - - Installation successful - Instalacion completada - - - - - Configure Mod - Configurar Mod + + + Failed to write settings + Error al escribir la configuración - - - This mod contains ini tweaks. Do you want to configure them now? - Este mod contiene ajustes del ini. ¿Quieres configurarlos ahora? + + + An error occured trying to write back MO settings: %1 + Ha ocurrido un error tratando de escribir de nuevo la configuración de MO: %1 - - - mod "%1" not found - mod "%1" no encontrado + + File is write protected + El archivo está protegido contra escritura - - - Installation cancelled - Instalación cancelada + + Invalid file format (probably a bug) + Formato de archivo no válido (probablemente sea un bug) - - - The mod was not installed completely. - El mod no fue instalado completamente. + + Unknown error %1 + Error desconocido %1 - + Some plugins could not be loaded Algún plugins no se pudo cargar - + Too many esps and esms enabled Demasiados esps y esms habilitado - - + + Description missing Falta la descripción - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Los siguientes plugins no se pudieron cargar. La razón puede ser dependencias faltantes (es decir python) o una versión obsoleta: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> El juego no permite cargar más de 255 plugins activos (incluidos los oficiales). Tienes que desactivar algunos plugins no utilizados o fusionar algunos plugins en uno solo. Aquí podras encontrar una guía: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Seleccione Mod - + Mod Archive Archivo Mod - + + + Installation successful + Instalacion completada + + + + + Configure Mod + Configurar Mod + + + + + This mod contains ini tweaks. Do you want to configure them now? + Este mod contiene ajustes del ini. ¿Quieres configurarlos ahora? + + + + + mod "%1" not found + mod "%1" no encontrado + + + + + Installation cancelled + Instalación cancelada + + + + + The mod was not installed completely. + El mod no fue instalado completamente. + + + Start Tutorial? Iniciar tutorial? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Estás a punto de iniciar un tutorial. Por razones técnicas, no es posible terminar el tutorial antes de tiempo. ¿Desea continuar? - - + + Download started Descarga iniciada - + failed to update mod list: %1 Fallo al actualizar la lista de Mods: %1 - + failed to spawn notepad.exe: %1 Fallo al cargar el Bloc de notas: %1 - + failed to open %1 Fallo al abrir %1 - + failed to change origin name: %1 fallo al cambiar el nombre original del fichero %1 - - Executable "%1" not found - Ejecutable "%1" no encontrado - - - - Failed to refresh list of esps: %1 - Fallo al actualizar la lista de esps: %1 + + failed to move "%1" from mod "%2" to "%3": %4 + Error al mover "%1" desde mod "%2" to "%3": %4 - - failed to move "%1" from mod "%2" to "%3": %4 - + + <Contains %1> + <Contiene %1> - + <Checked> <Marcado> - + <Unchecked> <Desmarcado> - + <Update> <Actualizacion> - + + <Managed by MO> + <Gestionado por MO> + + + + <Managed outside MO> + <Gestionado fuera MO> + + + <No category> <No categoría> - + <Conflicted> <En conflicto> - + <Not Endorsed> <No Avalado> - + failed to rename mod: %1 fallo al renombrar el mod: %1 - + Overwrite? - ¿Sobrescribir? + Sobrescribir? - + This will replace the existing mod "%1". Continue? Esto reemplazará el vigente mod "%1". ¿Desea continuar? - + failed to remove mod "%1" Fallo eliminando mod "%1" - - - + + + failed to rename "%1" to "%2" Fallo al renombrar "%1" a "%2" - + Multiple esps activated, please check that they don't conflict. Múltiples esps activados, por favor verifique que no entren en conflicto. - - - - + + + + Confirm Confirmar - + Remove the following mods?<br><ul>%1</ul> ¿Quitar el siguiente mods?<br><ul>%1</ul> - + failed to remove mod: %1 fallo al eliminar mod: %1 - - + + Failed Fallo - + Installation file no longer exists El archivo de instalación ya no existe - + Mods installed with old versions of MO can't be reinstalled in this way. Mods instalados con las viejas versiones de MO no pueden ser instalados de nuevo de este modo. - - - You need to be logged in with Nexus to endorse - Necesita estar conectado con Nexus para avalar - - - - Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. - - - - - - - - Delete %n save(s) - - - - - - - - Extract BSA - Extraer BSA + + You need to be logged in with Nexus to resume a download + Tienes que estar conectado con Nexus para reanudar una descarga - - - failed to read %1: %2 - fallo al leer %1: %2 + + + You need to be logged in with Nexus to endorse + Necesita estar conectado con Nexus para avalar - - This archive contains invalid hashes. Some files may be broken. - Este archivo contiene hashes no válidos. Algunos archivos pueden estar rotos. + + Failed to display overwrite dialog: %1 + No se pudo mostrar diálogo de sobreescritura: %1 - + Nexus ID for this Mod is unknown Se desconoce la ID en Nexus para este Mod - - Download? - Descargas - - - - A download has been started but no installed page plugin recognizes it. -If you download anyway no information (i.e. version) will be associated with the download. -Continue? - - - - - Browse Mod Page - - - - - - Failed to write settings - - - - - - An error occured trying to write back MO settings: %1 - - - - - File is write protected - - - - - Invalid file format (probably a bug) - - - - - Unknown error %1 - - - - - <Managed by MO> - - - - - <Managed outside MO> - - - - - You need to be logged in with Nexus to resume a download - Necesita estar conectado con Nexus para avalar - - - - Failed to display overwrite dialog: %1 - - - - - + + Create Mod... Crear Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Esto moverá todos los archivos de sobrescritura en un nuevo mod, regular. Por favor, introduzca un nombre: - + A mod with this name already exists Ya existe un mod con este nombre - + Continue? ¿Continuar? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. El esquema de versiones decide qué versión es considerada más nueva una que otra. Esta función adivinará el esquema de versiones bajo el supuesto de que la versión instalada es obsoleta. - - + + Sorry Lo siento - + I don't know a versioning scheme where %1 is newer than %2. Se desconoce un esquema de versiones donde %1 es más reciente que %2. - + Really enable all visible mods? ¿Permitir realmente todos los mods visibles? - + Really disable all visible mods? ¿Realmente desactivar todos los mods visibles? - + Choose what to export Elija un archivo a exportar - + Everything Todo - + All installed mods are included in the list Todos los mods instalados están incluidos en la lista - + Active Mods Mods Activos - + Only active (checked) mods from your current profile are included Mods sólo activos (Marcados) es incluido de su perfil actual - + Visible Visible - + All mods visible in the mod list are included Todo mods visible en la lista de mod son incluidos - + export failed: %1 Falló al exportar: %1 - + Install Mod... Instalar Mod... - + Enable all visible Activar todos los visibles - + Disable all visible Desactivar todo lo visible - + Check all for update Comprobar todo para actualizar - + Export to csv... Exportar a CSV... - + All Mods - + Todo los Mods - + Sync to Mods... Sincronizar con Mods... - + Restore Backup Restaurar copia de seguridad - + Remove Backup... Eliminar copia de seguridad... - + Add/Remove Categories Añadir/Quitar Categorías - + Replace Categories Remplazar Categorías - + Primary Category Categoría Primaria - + Change versioning scheme Cambiar esquema de versiones - + Un-ignore update No ignorar actualización - + Ignore update No Ignorar actualización - + Rename Mod... Renombrar Mod... - + Remove Mod... Quitar Mod... - + Reinstall Mod Reinstalar Mod - + Un-Endorse No Avalado - - + + Endorse Avalado - + Won't endorse - No avalar + No avalado - + Endorsement state unknown Estado de avalado desconocido - + Ignore missing data Ignorar data desaparecido - + Visit on Nexus Visite Nexus - + Open in explorer Abrir en explorador - + Information... Informacion... - - + + Exception: Excepción: - - + + Unknown exception Excepción desconocida - + <All> <Todo> - + <Multiple> <Multiple> - - - Please wait while LOOT is running - + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + Seguro que desea eliminar el siguiente %n salvado(s)?<br><ul>%1</ul><br>Los salvados Eliminados serán enviados a la papelera de reciclaje.Seguro que desea eliminar el siguiente %n salvado(s)?<br><ul>%1</ul><br>Los salvados Eliminados serán enviados a la papelera de reciclaje. - Really delete "%1"? - Realmente desea borrar "%1"? + + Please wait while LOOT is running + Por favor espera mientras se está ejecutando LOOT - + Fix Mods... - Fix Mods... + Arreglar Mods... - - Delete - Eliminar + + + Delete %n save(s) + Eliminar %n guardado(s)Eliminar %n guardo(s) - + failed to remove %1 Fallo eliminando %1 - - + + failed to create %1 Fallo al crear %1 - + Can't change download directory while downloads are in progress! No se puede cambiar el directorio de descarga, mientras que las descargas están en curso - + Download failed Descarga fallida - + failed to write to file %1 Fallo de escritura en el fichero %1 - + %1 written %1 escrito - + Select binary Selecciona el binario - + Binary Binario - + Enter Name Introducir Nombre - + Please enter a name for the executable Por favor, introduce un nombre para el ejecutable - + Not an executable No es un ejecutable - + This is not a recognized executable. Esto no es un ejecutable reconocido. - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación del archivo falló - - + + Failed to remove "%1". Maybe you lack the required file permissions? Fallo al eliminar "%1". ¿Tal vez no tengas los permisos necesarios? - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + file not found: %1 archivo no encontrado: %1 - + failed to generate preview for %1 fallo al generar vista anticipada para %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. Lo sentimos, no se puede obtener una vista previa de nada. Esta función no admite la extracción de bsas. - + Update available Actualización disponible - + Open/Execute Abrir/Ejecutar - + Add as Executable Añadir un ejecutable - + Preview Previsualizar - + Un-Hide Desocultar - + Hide Ocultar - + Write To File... Escribir al fichero... - + Do you want to endorse Mod Organizer on %1 now? ¿Quieres avalar Mod Organizer en %1 ahora? - + Thank you! - + Gracias! - + Thank you for your endorsement! - + Gracias por su respaldo! - + Request to Nexus failed: %1 Solicitud de Nexus ha fallado: %1 - - + + login successful login correcto - + login failed: %1. Trying to download anyway login fallado: %1. Intentando descarga de todos modos - + login failed: %1 Falló el inicio de sesión: %1 - + login failed: %1. You need to log-in with Nexus to update MO. login fallido: %1. Necesitas hacer login con Nexus para actualizar MO. - + + + failed to read %1: %2 + fallo al leer %1: %2 + + + Error Error - + failed to extract %1 (errorcode %2) fallo al extraer %1 (Código de error %2) - + + Extract BSA + Extraer BSA + + + + This archive contains invalid hashes. Some files may be broken. + Este archivo contiene hashes no válidos. Algunos archivos pueden estar rotos. + + + Extract... Extraer... - + Edit Categories... Editar Categorías... - + Deselect filter - + Deselecciona filtro - + Remove Eliminar - + Enable all Activar todo - + Disable all Desactivar todos - + Unlock load order Desbloquear el orden de carga - + Lock load order Orden de carga bloqueado - + depends on missing "%1" - + depende de que falta "%1" - + incompatible with "%1" - + incompatible con "%1" - + No profile set - + No conjunto de perfil - LOOT working - BOSS trabajando - - - + loot failed. Exit code was: %1 - + loot falló. Código de salida fue: %1 - + failed to start loot - + Error al iniciar loot - + failed to run loot: %1 - fallo al ejecutar boss: %1 + Error al ejecutar loot: %1 - + Errors occured - + Se han producido errores - + Backup of load order created - + Copia de seguridad de orden de carga creado - + Choose backup to restore - + Elije copia de seguridad para restaurar - + No Backups - + No hay copias de seguridad - + There are no backups to restore - + No hay copias de seguridad para restaurar - - + + Restore failed - + Error en la restauración - - + + Failed to restore the backup. Errorcode: %1 - + Error al restaurar la copia de seguridad. Código de error: %1 - + Backup of modlist created - + Copia de seguridad de la lista de mod creado @@ -2895,8 +2878,63 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers ModInfo - - + + Plugins + Plugins + + + + Textures + Texturas + + + + Meshes + Meshes + + + + UI Changes + UI Cambios + + + + Music + Music + + + + Sound Effects + Efectos de Sonido + + + + Scripts + Scripts + + + + SKSE Plugins + SKSE Plugins + + + + SkyProc Tools + SkyProc Tools + + + + Strings + Strings + + + + invalid content type %1 + tipo de contenido inválido %1 + + + + invalid index %1 indice invalido %1 @@ -2904,7 +2942,7 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers ModInfoBackup - + This is the backup of a mod Esta es la copia de seguridad de un mod @@ -2945,7 +2983,7 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers Ini Files - + Archivos Ini @@ -2960,17 +2998,17 @@ Esta función adivinará el esquema de versiones bajo el supuesto de que la vers Ini Tweaks - + Ini Tweaks This is a list of ini tweaks (ini modifications that can be toggled). - + Esta es una lista de ajustes ini (modificaciones INI que pueden activarse). This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. - + Esta es una lista de ajustes ini. los ajustes Ini son (generalmente pequeños) fragmentos de archivos ini que se aplican sobre los valores existentes en el skyrim.ini/skyrimprefs.ini. Cada truco se puede activar de forma individual. Debes comprobar la descripción del mod los ajustes son opcionales. @@ -3120,7 +3158,7 @@ La mayoría de los mods no tienen esps opcionales, por lo que es muy probable qu <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3133,7 +3171,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3149,12 +3187,12 @@ p, li { white-space: pre-wrap; } Refresh - Recargar + Refrescar Refresh all information from Nexus. - Recargar toda la información de Nexus. + Refrescar toda la información de Nexus. @@ -3166,7 +3204,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3187,7 +3225,7 @@ p, li { white-space: pre-wrap; } Filetree - Contenido + Árbol de archivo @@ -3199,14 +3237,14 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } +p, li { espacio blanco: pre envoltura; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Esta es una vista de directorio modificable del directorio mod. Puedes moverte por archivos mediante arrastrar y soltar; y cambiar su nombre con (doble clic).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Los cambios pasan inmediatamente sobre el disco, también hacen</span><span style=" font-size:8pt; font-weight:600;"> ser cuidadoso</span><span style=" font-size:8pt;">.</span></p></body></html> @@ -3225,227 +3263,227 @@ p, li { white-space: pre-wrap; } Cerrar - + &Delete - &Delete + &Borrar - + &Rename - &Rename + &Renombrar - + &Hide &Ocultar - + &Unhide &Mostrar - + &Open &Abrir - + &New Folder &Nueva Carpeta - - + + Save changes? - ¿Guardar cambios? + Guardar cambios? - - + + Save changes to "%1"? - ¿Guardar cambios a %1? + Guardar cambios a %1? - + File Exists - Existe el fichero + Archivo Existe - + A file with that name exists, please enter a new one Un fichero con ese nombre ya existe, por favor selecciona otro nombre - + failed to move file Error al mover el fichero - + failed to create directory "optional" Error al crear el directorio "optional" - - + + Info requested, please wait Informacion solicitada, por favor espere - + Main Principal - + Update Actualizacion - + Optional Opcional - + Old Antiguo - + Misc Misc - + Unknown Desconocido - + Current Version: %1 Version actual: %1 - + No update available Sin actualizacion - + (description incomplete, please visit nexus) (descripción incompleta, por favor visite nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Visite en Nexus</a> - + Failed to delete %1 Error borrando %1 - - + + Confirm Confirma - + Are sure you want to delete "%1"? Estas seguro de querer borrar "%1"? - + Are sure you want to delete the selected files? Etas seguro de querer borrar los ficheros seleccionados? - - + + New Folder Nueva Carpeta - + Failed to create "%1" Fallo al crear "%1" - - + + Replace file? ¿Reemplazar archivo? - + There already is a hidden version of this file. Replace it? Ya existe una versión oculta de este archivo. Reemplazarlo? - - + + File operation failed La operación de archivo falló. - - + + Failed to remove "%1". Maybe you lack the required file permissions? Fallo al eliminar "%1". Tal vez no tienes los permisos necesarios? - - + + failed to rename %1 to %2 Fallo al renombrar %1 a %2 - + There already is a visible version of this file. Replace it? Ya existe una versión visible de este archivo. ¿Reemplazarlo? - + Un-Hide Desocultar - + Hide Ocultar - + Name Nombre - + Please enter a name Por favor, introduzca un nombre - - + + Error Error - + Invalid name. Must be a valid file name Nombre no válido. Debe ser un nombre de archivo válido - + A tweak by that name exists Existe un ajuste con ese nombre - + Create Tweak Crear Ajuste Fino @@ -3453,15 +3491,15 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. - + Esta seudo mod representa contenido administrado fuera MO. No se modifica por MO. ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Esta pseudo-mod contiene archivos en el árbol de datos virtual que fue modificado (es decir, mediante el kit de construcción) @@ -3469,18 +3507,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - fallo al escribir %1/meta.ini: %2 + error al escribir %1/meta.ini: error %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 no contiene ningún esp/esm y ningún directorio activo (textures, meshes, interface, ...) - + Categories: <br> Categorias: <br> @@ -3490,52 +3528,52 @@ p, li { white-space: pre-wrap; } Game plugins (esp/esm) - + Plugins de Juego (esp/esm) Interface - + Interfaz Meshes - + Meshes Music - + Music Scripts (Papyrus) - + Scripts (Papyrus) Script Extender Plugin - + Script Extender Plugin SkyProc Patcher - + SkyProc Patcher Sound - + Sound Strings - + Strings Textures - + Texturas @@ -3580,7 +3618,7 @@ p, li { white-space: pre-wrap; } Non-MO - + Non-MO @@ -3590,7 +3628,6 @@ p, li { white-space: pre-wrap; } installed version: "%1", newest version: "%2" - installed version: %1, newest version: %2 version instalada: "%1", nueva version: "%2" @@ -3631,7 +3668,7 @@ p, li { white-space: pre-wrap; } Content - Contenido + Contenido @@ -3702,7 +3739,7 @@ p, li { white-space: pre-wrap; } Depicts the content of the mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> - + Representa el contenido del mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> @@ -3739,22 +3776,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus Inicio de sesión en Nexus - + timeout Tiempo de espera - + Unknown error - + Error desconocido - + Please check your password Por favor introduzca su contraseña @@ -3812,7 +3849,7 @@ p, li { white-space: pre-wrap; } %1 not found - %1 no encontrado + %1 no encontrado @@ -3850,114 +3887,114 @@ p, li { white-space: pre-wrap; } PluginList - + Name Nombre - + Priority Prioridad - + Mod Index Índice de Mod - + Flags Banderas - - + + unknown Desconocido - + Name of your mods Nombre de tus mods - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. Prioridad de carga de tu mod. Cuanto mayor sea, más "importante" es y por lo tanto sobrescribe los datos del plugins con menor prioridad. - + The modindex determins the formids of objects originating from this mods. El índice de mod determins la forma ids de objetos que provienen de este mods. - + failed to update esp info for file %1 (source id: %2), error: %3 fallo al actualizar información del esp del archivo %1 (fuente id: %2), error: %3 - + esp not found: %1 ESP no encontrado: %1 - - + + Confirm Confirmar - + Really enable all plugins? ¿Realmente habilitar todos los plugins? - + Really disable all plugins? ¿Realmente deshabilitar todos los plugins? - + The file containing locked plugin indices is broken El fichero que contiene los índices del plugin están bloqueados o rotos - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Algunos de los plugins tienen nombres no válidos! Estos plugins no pueden ser cargados por el juego. Por favor, consulte mo_interface.log para ver una lista de plugins afectados y cambiarles el nombre. - <b>Origin</b>: %1 - + This plugin can't be disabled (enforced by the game) + Este plugin no se puede desactivar (impuesto por el juego) + <b>Origin</b>: %1 + <b>Origen</b>: %1 + + + Author Autor - + Description Descripcion - - This plugin can't be disabled (enforced by the game) - Este plugin no se puede desactivar (impuesto por el juego) - - - + Missing Masters Maestros Desaparecidos - + Enabled Masters Activar Maestros - + failed to restore load order for %1 fallo al restaurar el orden de carga %1 @@ -3987,7 +4024,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -4042,7 +4079,7 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - + "%1" no se encuentra o inaccesible @@ -4135,7 +4172,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> @@ -4168,7 +4205,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> @@ -4469,62 +4506,62 @@ p, li { white-space: pre-wrap; } Fallo al configurar la carga por proxy-dll - + Permissions required Se requieren permisos - + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. La cuenta de usuario actual no tiene los permisos de acceso requeridos para ejecutar Mod Organizer. Los cambios necesarios se pueden hacer de forma automática (el directorio MO hará escritura para la cuenta de usuario actual). Se le pedirá ejecutar "helper.exe" con derechos administrativos. - - + + Woops Woops - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened ¡ModOrganizer se ha estrellado! ¿Se debe crear un archivo de diagnóstico? Si me envía el fichero (%1) a sherb@gmx.net, el error es mucho más probable que se arregle. Por favor, incluya una breve descripción de lo que estaba haciendo cuando ocurrió el accidente - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 ¡ModOrganizer se ha estrellado! Lamentablemente no fue capaz de escribir un archivo de diagnóstico: %1 - + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Ya se está ejecutando una instancia de Mod Organizer - + No game identified in "%1". The directory is required to contain the game binary and its launcher. Juego no identificado en "%1". Se requiere que el directorio contenga el binario del juego y su lanzador. - - + + Please select the game to manage Por favor seleccione el juego - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) Por favor selecciona la edición del juego que tienes (MO no puede iniciar el juego correctamente si esto está mal ajustado!) - + failed to start application: %1 - + Error al iniciar la aplicación: %1 @@ -4532,28 +4569,28 @@ p, li { white-space: pre-wrap; } Por favor utilice "Ayuda" en la barra superior para obtener informacion sobre todos los elementos - - + + <Manage...> - <Definir...> + <Gestionar...> - + failed to parse profile %1: %2 no se pudo analizar el perfil %1: %2 - + failed to find "%1" fallo al encontrar %1 - + failed to access %1 Fallo al acceder %1 - + failed to set file time %1 Fallo al definir la hora al fihcero %1 @@ -4565,8 +4602,7 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - "%1" is missing - + "%1" no se encuentra o inaccesible @@ -4579,6 +4615,11 @@ p, li { white-space: pre-wrap; } Error Error + + + failed to open temporary file + Fallo al abrir el archivo temporal + @@ -4602,17 +4643,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" Fallo al crear "%1" - + Elevation required Elevación requerida - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4627,30 +4668,25 @@ puede ser instalado para trabajar sin elevación. ¿Comenzar elevación de todos modos? (se le preguntará si desea permitir a Mod Organizer.exe realizar cambios en el sistema) - + failed to spawn "%1": %2 Fallo al crear "%1": %2 - + "%1" doesn't exist "%1" no existe - + failed to inject dll into "%1": %2 Fallo al injectar la dll en "%1": %2 - + failed to run "%1" Fallo al abrir %1 - - - failed to open temporary file - Fallo al abrir el archivo temporal - QueryOverwriteDialog @@ -4868,12 +4904,12 @@ puede ser instalado para trabajar sin elevación. Failed - Fallo + Error Sorry, failed to start the helper application - + Lo sentimos, no se pudo iniciar la aplicación auxiliar @@ -4919,7 +4955,7 @@ puede ser instalado para trabajar sin elevación. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5013,27 +5049,27 @@ p, li { white-space: pre-wrap; } User interface - + Interfaz de usuario If checked, the download interface will be more compact. - + Si se marca, la interfaz de descarga será más compacto. Compact Download Interface - + Interfaz de Descarga Compacto If checked, the download list will display meta information instead of file names. - + Si se selecciona, la lista de descargas mostrará la información de metadatos en lugar de nombres de archivo. Download Meta Information - + Descarga Información Meta @@ -5077,7 +5113,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5205,7 +5241,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> @@ -5243,7 +5279,7 @@ p, li { white-space: pre-wrap; } There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. Mod Organizer necesita una dll que se inyecta en el juego para que todos los mods sean visibles a ella. @@ -5267,8 +5303,8 @@ Si utilizas la versión Steam de Oblivion por defecto NO funcionará. En este ca Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer utiliza una API proporcionada por Nexus para proporcionar características como la comprobación de actualizaciones y descarga de archivos. Por desgracia, esta API no ha sido puesta a disposición oficialmente a terceros, como MO por lo que tenemos que pasar por el Nexus Mod Manager para ser permitido. @@ -5284,7 +5320,7 @@ tl;dr-version: Si Nexus-features no funciona, introduzca el número de la versi - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Parece que los juegos ocasionalmente cargan archivos ESP o ESM, incluso si ellos todavía no se ha activado como plugins. No, pero sabe cuáles son las circunstancias, pero informes de usuario implican en algunos casos no deseados. Si esto se comprueba, ESPs ESMs no marcadas en la lista son invisibles para el juego y no se pueden cargar. @@ -5314,20 +5350,23 @@ Desactiva esta opción si deseas utilizar Mod Organizer con conversiones totales Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + Desactiva esto para más no mostrar mods instalado fuera de MO en la lista de mod (panel izquierdo). El activo de esos mods entonces serán tratados como si tuvieran prioridad más baja junto con el contenido original del juego. By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. - + Por defecto Mod Organizer mostrará esp+bsa instalados con herramientas extranjeras como mods (panel izquierdo). Esto le permite controlar su prioridad en relación con otros mods. Esto es particularmente útil si también usas Steam Workshop para instalar mods. +Sin embargo, si has instalado mods de archivos sueltos fuera de MO que están en conflicto con BSA también instalara fuera de MO esos conflictos no se pueden resolver correctamente. + +i se desactiva esta función, MO sólo mostrará DLCs oficiales de esta manera. Tenga en cuenta que los plugins (ESP y ESM) que aparecen en el panel derecho seran completamente afectado por esta característica. Display mods installed outside MO - + Mostrar mods instalados fuera MO @@ -5422,7 +5461,7 @@ Para el resto de los juegos no es un sustituto suficiente para AI failed to communicate with running instance: %1 - Error al conectarse a la instancia en ejecución: %1 + Error al comunicarse con instancia en ejecución: %1 @@ -5585,4 +5624,4 @@ C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves Mover todas las partidas guardadas "%1" para la localización global? Tenga en cuenta que esto se hace un lío con el número consecutivo de juegos salvados. - + \ No newline at end of file diff --git a/src/organizer_fr.ts b/src/organizer_fr.ts index 9e5af1af..004499e8 100644 --- a/src/organizer_fr.ts +++ b/src/organizer_fr.ts @@ -1,48 +1,46 @@ - - - + AboutDialog About - + A propros Revision: - + Révision : Used Software - + Logiciel utilisé Credits - + Crédits Translators - + Traducteurs Others - + Autres Close - Fermer + Fermer No license - + Pas de license @@ -62,7 +60,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> @@ -159,22 +157,22 @@ Si un composant est nommée "00 Core"; il est habituellement nécessai Some Page - + Quelques pages Search - + Rechercher new - + nouveau failed to start download - + échec du lancement du téléchargement @@ -225,7 +223,7 @@ Si un composant est nommée "00 Core"; il est habituellement nécessai <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> @@ -296,7 +294,7 @@ p, li { white-space: pre-wrap; } failed to read bsa: %1 - Échec de lecture bsa: %1 + échec de lecture du bsa: %1 @@ -314,7 +312,7 @@ p, li { white-space: pre-wrap; } Done - Terminé + Terminé @@ -324,7 +322,7 @@ p, li { white-space: pre-wrap; } pending download - + téléchargement en attente @@ -333,7 +331,7 @@ p, li { white-space: pre-wrap; } Placeholder - substitut + *Substitut* @@ -367,7 +365,7 @@ p, li { white-space: pre-wrap; } Placeholder - *substitut* + *Substitut* @@ -380,17 +378,17 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + < mod %1 file %2 > Pending - + En attente Paused - En pause + En pause @@ -458,17 +456,17 @@ p, li { white-space: pre-wrap; } Delete - Supprimer + Supprimer Un-Hide - + Montrer Remove from View - Enlever de la liste + Cacher de la liste @@ -493,7 +491,7 @@ p, li { white-space: pre-wrap; } Delete Installed... - Supprimer ceux installés... + Supprimer ceux installés... @@ -516,12 +514,12 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + < mod %1 file %2 > Pending - + En attente @@ -539,7 +537,7 @@ p, li { white-space: pre-wrap; } Are you sure? - Êtes-vous certain? + Êtes-vous certain ? @@ -574,17 +572,17 @@ p, li { white-space: pre-wrap; } Delete - Supprimer + Supprimer Un-Hide - + Montrer Remove from View - Enlever de la liste + Cacher de la liste @@ -619,12 +617,12 @@ p, li { white-space: pre-wrap; } Remove Installed... - Enlever de la liste ceux installés... + Enlever ceux installés de la liste ... Remove All... - Enlever tout de la liste... + Tour enlever de la liste... @@ -632,12 +630,12 @@ p, li { white-space: pre-wrap; } failed to rename "%1" to "%2" - Impossible de renommer "%1" en "%2" + impossible de renommer "%1" en "%2" Memory allocation error (in refreshing directory). - + Erreur d'allocation de la mémoire (dans le répertoire d'actualisation). @@ -652,17 +650,17 @@ p, li { white-space: pre-wrap; } failed to download %1: could not open output file: %2 - impossible de télécharger %1: impossible d'écrire le fichier: %2 + impossible de télécharger %1: l'écriture sur le fichier à échouée : %2 Wrong Game - + Jeu incorrect The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + Le lien de téléchargement est pour un mod pour "%1", mais cette instance de MO à été installée pour "%2". @@ -707,7 +705,7 @@ p, li { white-space: pre-wrap; } No known download urls. Sorry, this download can't be resumed. - + Aucune adresse de téléchargement connue. Désolé, ce téléchargement ne peut être repris. @@ -722,42 +720,42 @@ p, li { white-space: pre-wrap; } Main - Principal + Principal Update - + Mise à jour Optional - Optionnel + Optionnel Old - Ancien + Obsolète Misc - Divers + Divers Unknown - Inconnu + Inconnu Memory allocation error (in processing progress event). - + Erreur d'allocation de la mémoire (lors du traitement de la progression événementielle). Memory allocation error (in processing downloaded data). - + Erreur d'allocation de la mémoire (lors du traitement des données de téléchargement). @@ -783,12 +781,12 @@ p, li { white-space: pre-wrap; } Failed to request file info from nexus: %1 - Impossible de demander l'info du fichier sur Nexus: %1 + Impossible de demander l'information du fichier sur Nexus: %1 Download failed. Server reported: %1 - + Téléchargement échoué : rapport du serveur : %1 @@ -943,7 +941,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Close - Fermer + Fermer @@ -953,7 +951,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Executable (%1) - Executable (%1) + Exécutable (%1) @@ -989,13 +987,13 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Save Changes? - + Enregistrer les changements ? You made changes to the current executable, do you want to save them? - + Vous avez appliqué des modifications aux exécutables courants, voulez vous les enregistrer ? @@ -1013,19 +1011,19 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat Find what: - Rechercher: + Rechercher : Search term - Expression à rechercher + Terme à rechercher Find next occurence from current file position. - Rechercher le suivant à partir de l'emplacement présent. + Rechercher l’occurrence suivante à partir de l'emplacement présent. @@ -1135,7 +1133,7 @@ Actuellement le seul cas à ma connaissance ou ceci est nécessaire est le Creat <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1146,7 +1144,7 @@ p, li { white-space: pre-wrap; } Placeholder - *substitut* + *Substitut* @@ -1179,87 +1177,87 @@ p, li { white-space: pre-wrap; } - + Extracting files Extraction des fichiers - + failed to create backup - Impossible de créer une sauvegarde + Impossible de créer une sauvegarde de backup - + Mod Name Nom du mod - + Name Nom - + Invalid name Nom incorrect - + The name you entered is invalid, please enter a different one. Le nom que vous avez entré est invalide, essayez-en un autre SVP. - + File format "%1" not supported Format de fichier "%1" non supporté - + None of the available installer plugins were able to handle that archive Aucun des plugins installés n'arrive à traiter cette archive - + no error aucune erreur - + 7z.dll not found 7z.dll introuvable - + 7z.dll isn't valid 7z.dll invalide - + archive not found archive introuvable - + failed to open archive impossible d'ouvrir l'archive - + unsupported archive type type d'archive non supporté - + internal library error erreur de bibliothèque interne - + archive invalid archive invalide - + unknown archive error erreur d'archive inconnue @@ -1319,27 +1317,27 @@ p, li { white-space: pre-wrap; } Click blank area to deselect - + Cliquer sur une zone blanche pour désélectionner If checked, only mods that match all selected categories are displayed. - + Si coché, seul les mods correspondants à toutes les catégories sélectionnées sont affichés. And - + Et If checked, all mods that match at least one of the selected categories are displayed. - + Si coché, tout les mods correspondants à au moins une des catégories sélectionnées sont affichés. Or - + Ou @@ -1356,7 +1354,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1366,6 +1364,11 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Créez les profils ici. Chaque profil contient sa propre liste de mods et d'ESPs activés. Vous pouvez ainsi basculer rapidement entre les configurations pour différentes parties.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Veuillez noter que pour l'instant, l'ordre de chargement des ESPs est commun à tous les profils.</span></p></body></html> + + + Open list options... + Ouvre les options de liste... + Refresh list. This is usually not necessary unless you modified data outside the program. @@ -1375,13 +1378,13 @@ p, li { white-space: pre-wrap; } Restore Backup... - + Restaurer Sauvegarde Create Backup - + Créer Sauvegarde @@ -1425,7 +1428,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1445,7 +1448,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1468,7 +1471,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1484,7 +1487,12 @@ p, li { white-space: pre-wrap; } Plugins - + Plugins + + + + Sort + Ordonner @@ -1496,7 +1504,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1504,35 +1512,25 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Cette liste contient tous les ESPs et ESMs contenus dans les mods actifs. Ceux-ci requièrent leur propre ordre de chargement. Utilisez le glisser-déposer pour modifier cet ordre. Veuillez noter que MO enregistre l'ordre de chargement seulement pour les mods actif/cochés.<br />Il y a un excellent outil nommé &quot;BOSS&quot; qui classe automatiquement ces fichiers.</span></p></body></html> - - - Sort - - - - - Open list options... - - Archives - + Archives <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - + Liste des Besthesda Softworks Archives. Les archives non cochées ici ne sont pas gérées par MO et ignorent l’ordre d'installation. @@ -1540,226 +1538,223 @@ p, li { white-space: pre-wrap; } By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - + Les fichiers BSA sont des archives (comparables aux fichiers .zip) qui contiennent les données de ressource (meshes, textures,...) qui sont utilisées par le jeu. Ainsi, ils sont en "compétition" avec les fichiers "lâches" -non compressés- dans votre répertoire DATA. +Par défaut, les BSAs qui partagent leur nom de base avec un ESP actif (ex : plugin.esp et plugin.bsa) sont automatiquement chargés et ont la précédence sur tout les fichiers lâches; l'ordre d'installation que vous établissez sur la partie gauche est alors ignoré ! + +Les BSAs cochés ici sont chargés d'une telle manière que votre ordre d'installation est correctement respecté. File - Fichier + Fichier Data - DATA + Data refresh data-directory overview - Actualiser la vue d'ensemble du dossier DATA + actualiser la vue d’ensemble du répertoire data Refresh the overview. This may take a moment. - Actualiser la vue d'ensemble. Ceci peut demander un moment. + Actualiser la vue d'ensemble. Ceci peut prendre un moment. - - + + Refresh - Actualiser + Actualiser This is an overview of your data directory as visible to the game (and tools). - Ceci est une vue d'ensemble du dossier DATA tel qu'il apparaît pour le jeu (et les outils). + Ceci est une vue d’ensemble du répertoire Data, tel qu'il apparaît à votre jeu (et outils) lancés dans MO. Mod - Mod + Mod Filter the above list so that only conflicts are displayed. - Filtrer la liste ci-dessus pour afficher seulement les conflits. + Filtrer la liste ci-dessus afin que seuls les conflits soient affichés. Show only conflicts - Afficher seulement les conflits + Uniquement afficher les conflits Saves - Sauvegardes + Sauvegardes <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Ceci est une liste de toutes les parties sauvegardées pour ce jeu. Survolez une entrée de la liste pour obtenir des informations détaillées sur la sauvegarde, incluant une liste de tous les ESPs/EMSs utilisés lors de la sauvegarde mais actuellement désactivés ou absents.</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Si vous cliquez &quot;Réparer Mods...&quot; dans le menu contextuel, MO tentera d'activer tous les mods, ESPs et ESMs nécessaires pour résoudre le problème. Rien ne sera désactivé!</span></p></body></html> + Downloads - Téléchargements + Téléchargements This is a list of mods you downloaded from Nexus. Double click one to install it. - Ceci est une liste de mods que vous avez téléchargé de Nexus. Double-cliquez en un pour l'installer. + Ceci est une liste de mods que vous avez téléchargé depuis de Nexus. Double-clic sur un mod pour l'installer. Show Hidden - + Montrer les éléments cachés Tool Bar - Barre d'outils + Barre d'outil Install Mod - Installer mod + Installer le Mod Install &Mod - Installer &mod + Installer &Mod Install a new mod from an archive - Installer un nouveau mod à partir d'une archive + Installe un nouveau mod depuis une archive Ctrl+M - Ctrl+M + Ctrl+M Profiles - Profils + Profils &Profiles - &Profils + &Profils Configure Profiles - Configurer les profils + Configurer les Profils Ctrl+P - Ctrl+P + Ctrl+P Executables - Programmes + Exécutables &Executables - Programm&es + &Exécutables Configure the executables that can be started through Mod Organizer - Configure les programmes pouvant être lancés via Mod Organizer + Configure les exécutables qui peuvent êtres lancés à travers Mod Organizer Ctrl+E - Ctrl+E + Ctrl+E Tools - + Outils &Tools - + &Outils Ctrl+I - Ctrl+H + Ctrl+I Settings - Réglages + Paramètres &Settings - Réglage&s + &Paramètres Configure settings and workarounds - Configurer les réglages et solutions de rechange + Configure les paramètres et solutions alternatives Ctrl+S - Ctrl+S + Ctrl+S Nexus - Nexus + Nexus Search nexus network for more mods - Effectuer une recherche sur Nexus pour plus de mods + Recherche sur le réseau nexus pour plus de mods Ctrl+N - Ctrl+N + Ctrl+N - + Update Mise-à-jour Mod Organizer is up-to-date - Mod Organizer est à jour + Mod Organizer est à jour. No Problems - + Aucun problèmes @@ -1767,1101 +1762,1107 @@ p, li { white-space: pre-wrap; } !Work in progress! Right now this has very limited functionality - + Ce bouton apparaît en surbrillance si MO découvre des problèmes potentiels avec votre configuration et offre une aide pour les résoudre. +!Work in progress! +Actuellement une fonctionnalité encore très limitée. Help - Aide + Aide Ctrl+H - Ctrl+H + Ctrl+H Endorse MO - + Recommander MO - + Endorse Mod Organizer - + Recommander Mod Organizer Copy Log to Clipboard - + Copier les Logs dans le Presse-papier Ctrl+C - + Ctrl+C Toolbar - Barre d'outils + Barre d'outil Desktop - + Bureau Start Menu - + Menu démarrer Problems - + Problèmes There are potential problems with your setup - + Il y a des problèmes potentiels dans votre configuration Everything seems to be in order - + Tout semble en ordre Help on UI - + Aide sur l'interface Documentation Wiki - + Wiki de Documentation Report Issue - + Reporter un problème Tutorials - + Tutoriels About - + A propos About Qt - + A propos de Qt failed to save load order: %1 - impossible d'enregistrer l'ordre de chargement: %1 + Échec de l'enregistrement de l’ordre de chargement : %1 Name - Nom + Nom Please enter a name for the new profile - Veuillez inscrire un nom pour le nouveau profil + Veuillez entrer un nom pour le nouveau profil failed to create profile: %1 - impossible de créer le profil: %1 + création de profil échouée : %1 Show tutorial? - + Afficher le tutoriel ? You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + Vous lancez Mod Organizer pour la première fois. Voulez-vous afficher un tutoriel concernant ses fonctionnalité élémentaires ? Si vous choisissez non, vous pouvez toujours démarrer le tutoriel depuis le menu "Aide". Downloads in progress - Téléchargements en cours + Téléchargement en cours There are still downloads in progress, do you really want to quit? - Il encore des téléchargements en cours, voulez-vous vraiment quitter? + Il y a toujours des téléchargements en cours, voulez vous vraiment quitter ? failed to read savegame: %1 - impossible de lire la sauvegarde: %1 + échec de lecture de la sauvegarde : %1 Plugin "%1" failed: %2 - + Le Plugin "%1" à échoué: %2 Plugin "%1" failed - + Le Plugin "%1" à échoué + + + + Download? + Télécharger ? + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + Consulter la page-web du Mod failed to init plugin %1: %2 - + impossible d'initialiser le plugin %1: %2 Plugin error - + Erreur de plugin It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Il semble que le plugin "%1" n'a pas pu être chargé lors du démarrage précédent, provoquant un crash de MO. Voulez vous le désactiver ? +(Note : si il s'agit de la première fois que vous voyez ce message pour ce plugin, vous devriez tenter à nouveau l'opération. Le plugin pourrait cette fois être en mesure de corriger le problème). Failed to start "%1" - impossible de lancer "%1" + Échec du lancement de "%1" Waiting - Attente + Attente Please press OK once you're logged into steam. - Veuillez cliquer OK une fois connecté à steam. + Veuillez appuyer sur OK une fois connecté à Steam. + + + + Executable "%1" not found + Exécutable "%1" introuvable Start Steam? - + Démarrer Steam ? Steam is required to be running already to correctly start the game. Should MO try to start steam now? - + Steam doit être déjà démarré afin de correctement lancer le jeu. MO devrait-il essayer de lancer Steam maintenant ? - + Also in: <br> - + Aussi dans : <br> - + No conflict - Aucun conflit + Aucun conflit - + <Edit...> - <Modifier...> + <Editer...> - - This bsa is enabled in the ini file so it may be required! - + + Failed to refresh list of esps: %1 + Impossible d'actualiser la liste des esps : %1 - - Activating Network Proxy - + + This bsa is enabled in the ini file so it may be required! + Ce bsa étant activé dans le fichier ini, cela devrait être requis ! - - - Installation successful - Installation réussie + + Activating Network Proxy + Activation du Proxy de Réseau. - - - Configure Mod - Configurer mod + + + Failed to write settings + Échec de l'écriture des paramètres - - - This mod contains ini tweaks. Do you want to configure them now? - Ce mod contient des ajustement pour les fichiers ini. Désirez-vous les configurer maintenant? + + + An error occured trying to write back MO settings: %1 + Une erreur est survenue lors de la récupération des paramètres de MO : %1 - - - mod "%1" not found - "%1" introuvable + + File is write protected + Le fichier est protégé en écriture - - - Installation cancelled - + + Invalid file format (probably a bug) + Format de fichier invalide (probablement un bug) - - - The mod was not installed completely. - + + Unknown error %1 + Erreur inconnue %1 - + Some plugins could not be loaded - + Certains plugins n'ont pas pu être chargés - + Too many esps and esms enabled - + Trop d'esps et d'esms activés - - + + Description missing - + Description manquante - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + Les plugins suivants n'ont pas pu être chargés. Cela peut être dû à des dépendances manquantes (ex : python) ou une version obsolète : - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Le jeu n'autorise pas le plus de 255 plugins actifs (plugins officiels inclus) à être chargés. Vous devriez désactiver les plugins inusités ou fusionner certains plugins dans un seul. Vous pouvez trouver un guide ici : <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod - Choisir mod + Choisissez un Mod - + Mod Archive - Archive de mod + Archive de Mod + + + + + Installation successful + Installation réussie - + + + Configure Mod + Configurer le Mod + + + + + This mod contains ini tweaks. Do you want to configure them now? + Ce mod contient des ini tweaks. Désirez-vous les configurer maintenant? + + + + + mod "%1" not found + mod "%1" introuvable + + + + + Installation cancelled + Installation annulée + + + + + The mod was not installed completely. + Le mod n'a pas été installé complètement + + + Start Tutorial? - + Démarrer le Tutoriel ? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + Vous allez commencer un tutoriel. Pour des raisons techniques, il n'est pas possible de quitter le tutoriel en cours de route. Continuer ? - - + + Download started - Téléchargement commencé + Le téléchargement vient de démarrer - + failed to update mod list: %1 - impossible de mettre à jour la liste de mods: %1 + échec lors de l'actualisation de la liste de Mod: %1 - + failed to spawn notepad.exe: %1 - impossible de lancer notepad.exe: %1 + impossible d'invoquer notepad.exe : %1 - + failed to open %1 - impossible d'ouvrir %1 + impossible d'ouvrir %1 - + failed to change origin name: %1 - impossible de changer le nom d'origine: %1 + impossible de changer le nom d'origine : %1 - - Executable "%1" not found - - - - - Failed to refresh list of esps: %1 - + + failed to move "%1" from mod "%2" to "%3": %4 + impossible de déplacer "%1" depuis le mod "%2" vers "%3" : %4 - - failed to move "%1" from mod "%2" to "%3": %4 - + + <Contains %1> + <Contient %1> - + <Checked> - <Cochés> + <Coché> - + <Unchecked> - <Décochés> + <Décoché> - + <Update> - <Rafraichir> + <Mise à jour disponible> - + + <Managed by MO> + <Géré par MO> + + + + <Managed outside MO> + <Géré à l’extérieur de MO> + + + <No category> - + <Sans catégorie> - + <Conflicted> - + <Conflits> - + <Not Endorsed> - + <Non Recommandé> - + failed to rename mod: %1 - impossible de renommer le mod: %1 + impossible de renommer le Mod: %1 - + Overwrite? - + Écraser ? - + This will replace the existing mod "%1". Continue? - + Cela va remplacer le Mod existant "%1". Continuer ? - + failed to remove mod "%1" - Impossible de supprimer %1 + Impossible de supprimer le Mod "%1" - - - + + + failed to rename "%1" to "%2" - Impossible de renommer %1 en %2 + Impossible de renommer "%1" en "%2" - + Multiple esps activated, please check that they don't conflict. - + Plusieurs esps activés, veuillez vérifier qu'ils n'entrent pas en conflit. - - - - + + + + Confirm - Confirmer + Confirmer - + Remove the following mods?<br><ul>%1</ul> - + Supprimer les Mods suivants?<br><ul>%1</ul> - + failed to remove mod: %1 - impossible de renommer le mod: %1 + Impossible de supprimer le Mod: %1 - - + + Failed - + Échec - + Installation file no longer exists - + Les fichiers d'installations n'existent plus - + Mods installed with old versions of MO can't be reinstalled in this way. - - - - - - You need to be logged in with Nexus to endorse - - - - - Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. - - - - - - - - Delete %n save(s) - - - - + Les Mods installés avec une anciennes versions de MO ne peuvent être réinstaller de cette manière. - - Extract BSA - + + You need to be logged in with Nexus to resume a download + Vous devez être connecté à Nexus pour continuer le téléchargement - - - failed to read %1: %2 - Échec de lecture %1: %2 + + + You need to be logged in with Nexus to endorse + Vous devez être connecté à Nexus pour recommander un mod. - - This archive contains invalid hashes. Some files may be broken. - + + Failed to display overwrite dialog: %1 + Impossible d'afficher la boite de dialogue overwrite : %1 - + Nexus ID for this Mod is unknown - - - - - Download? - - - - - A download has been started but no installed page plugin recognizes it. -If you download anyway no information (i.e. version) will be associated with the download. -Continue? - - - - - Browse Mod Page - - - - - - Failed to write settings - - - - - - An error occured trying to write back MO settings: %1 - - - - - File is write protected - - - - - Invalid file format (probably a bug) - - - - - Unknown error %1 - - - - - <Managed by MO> - - - - - <Managed outside MO> - - - - - You need to be logged in with Nexus to resume a download - - - - - Failed to display overwrite dialog: %1 - + L'ID Nexus de ce Mod est inconnu - - + + Create Mod... - + Crée le Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + Ceci va déplacer tout les fichiers présent dans Overwrite dans un nouveau mod ordinaire. +Veuillez nommer ce mod : - + A mod with this name already exists - + Un Mod avec ce nom existe déjà - + Continue? - + Continuer ? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + Le système de syntaxe de version décide quelle version est considérée plus récente qu'une autre. +Cette fonction va deviner quel système de version utiliser dans l'hypothèse que la version installée est obsolète. - - + + Sorry - + Désolé - + I don't know a versioning scheme where %1 is newer than %2. - + Je ne connait pas un système de version dans lequel %1 est plus récent que %2. - + Really enable all visible mods? - + Voulez vous vraiment activer tout les mods visibles ? - + Really disable all visible mods? - + Voulez vous vraiment désactiver tout les mods visibles ? - + Choose what to export - + Choisissez ce que vous voulez exporter - + Everything - + Tout - + All installed mods are included in the list - + Tous les Mods installés sont inclus dans cette liste - + Active Mods - Activer Mods + Mods actifs - + Only active (checked) mods from your current profile are included - + Seuls les mods actifs (cochés) de votre profil actif seront inclus - + Visible - + Visible - + All mods visible in the mod list are included - + Tout les mods visible dans la liste de mod sont inclus - + export failed: %1 - + échec de l'exportation : %1 - + Install Mod... - Installer mod... + Installer le Mod... - + Enable all visible - Activer tous les mods visibles + Activer tout mod visible - + Disable all visible - Désactiver tous les mods visibles + Désactiver tout mod visible - + Check all for update - Vérifier toutes les mises à jour + Vérifier toutes les mises à jour - + Export to csv... - + Exporter en csv... - + All Mods - + Tout les Mods - + Sync to Mods... - + Synchroniser vers les Mods... - + Restore Backup - + Restaurer la sauvegarde - + Remove Backup... - + Supprimer la sauvegarde... - + Add/Remove Categories - + Ajouter/Enlever des catégories - + Replace Categories - + Remplacer les catégories - + Primary Category - + Catégorie primaire - + Change versioning scheme - + Changer le système de version - + Un-ignore update - + Ne plus ignorer les mises à jour - + Ignore update - + Ignorer la MàJ - + Rename Mod... - Renommer mod... + Renommer le Mod... - + Remove Mod... - Supprimer mod... + Supprimer le Mod... - + Reinstall Mod - Installer mod + Réinstaller le Mod - + Un-Endorse - + Ne plus recommander - - + + Endorse - + Recommander - + Won't endorse - + Ne vais pas recommander - + Endorsement state unknown - + Statut de recommandation inconnu - + Ignore missing data - + Ignorer les données manquantes - + Visit on Nexus - + Aller à la page Nexus - + Open in explorer - + Ouvrir dans l'exploreur - + Information... - Information... + Informations... - - + + Exception: - + Exception : - - + + Unknown exception - + Exception Inconnue - + <All> - <Tous> + <Tous> - + <Multiple> - + <Multiple> + + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + Voulez vraiment supprimer les/la sauvegarde(s) suivante %n ?<br> +<ul>%1</ul> <br>Les sauvegardes supprimées seront envoyée dans la corbeille.Voulez vraiment supprimer les/la sauvegarde(s) suivante %n ?<br> +<ul>%1</ul> <br>Les sauvegardes supprimées seront envoyée dans la corbeille. - + Please wait while LOOT is running - + Veuillez patienter pendant que LOOT s'exécute - + Fix Mods... - Réparer mods... + Corriger les Mods... + + + + Delete %n save(s) + Supprimer %n sauvegarde(s)Supprimer %n sauvegarde(s) - + failed to remove %1 - Impossible de supprimer %1 + Impossible d'enlever %1 - - + + failed to create %1 - impossible de créer %1 + Impossible de créer %1 - + Can't change download directory while downloads are in progress! - + Vous ne pouvez pas changer le répertoire de téléchargement pendant qu'un téléchargement est en cours ! - + Download failed - Téléchargement commencé + Téléchargement échoué - + failed to write to file %1 - impossible d'écrire dans le fichier %1 + Échec de l'écriture sur le fichier %1 - + %1 written - %1 écrit + %1 écrit - + Select binary - Choisir un programme + Sélectionner un exécutable binaire - + Binary - Programme + Exécutable binaire - + Enter Name - + Entrez un nom - + Please enter a name for the executable - Veuillez inscrire un nom pour le nouveau profil + Veuillez entrer un nom pour l'exécutable - + Not an executable - Ajouter un programme + Pas un exécutable - + This is not a recognized executable. - + Ceci n'est pas un exécutable reconnu. - - + + Replace file? - + Remplacer le fichier? - + There already is a hidden version of this file. Replace it? - + Ceci est déjà une version cachée de ce fichier. La remplacer ? - - + + File operation failed - + Échec de l'opération sur les fichiers - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + Impossible de retirer "%1". Peut-être n'avez vous pas les permissions nécessaires sur le fichier. - + There already is a visible version of this file. Replace it? - + Il existe déjà une version visible de ce fichier. La remplacer ? - + file not found: %1 - + fichier introuvable : %1 - + failed to generate preview for %1 - + impossible de générer l'aperçu de %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + Désolé, impossible de générer tout aperçu. Cette fonction ne supporte actuellement pas l'extraction d'un bsa. - + Update available - Mise à jour disponible + Mise à jour disponible - + Open/Execute - + Ouvrir/Exécuter - + Add as Executable - Ajouter un programme + Ajouter en tant qu'Exécutable - + Preview - + Aperçu - + Un-Hide - + Montrer - + Hide - + Cacher - + Write To File... - Écriture du fichier... + Écriture du fichier... - + Do you want to endorse Mod Organizer on %1 now? - + Désirez vous recommander Mod Organizer sur %1 maintenant ? - + Thank you! - + Merci ! - + Thank you for your endorsement! - + Merci de votre recommandation ! - + Request to Nexus failed: %1 - + La requête au Nexus à échouée : %1 - - + + login successful - + connexion réussie - + login failed: %1. Trying to download anyway - + échec de login : %1. Tentative de téléchargement malgré tout - + login failed: %1 - + échec de login : %1 - + login failed: %1. You need to log-in with Nexus to update MO. - + échec de login : %1. Vous devez vous connecter au Nexus pour mettre à jour MO. - + + + failed to read %1: %2 + impossible de lire %1 : %2 + + + Error - Erreur + Erreur - + failed to extract %1 (errorcode %2) - + impossible d'extraire %1 (code d'erreur %2) - + + Extract BSA + Extraire le BSA + + + + This archive contains invalid hashes. Some files may be broken. + Cette archive contient des données de hachages invalides. Certains fichiers peuvent être corrompus. + + + Extract... - + Extraire... - + Edit Categories... - + Modfier les catégories - + Deselect filter - + Filtre de déselection - + Remove Supprimer - + Enable all - + Activer tout - + Disable all - + Désactiver tout - + Unlock load order - + Déverrouiller l'ordre de chargement - + Lock load order - + Verrouiller l’ordre de chargement - + depends on missing "%1" - + dépend de "%1" manquant - + incompatible with "%1" - + incompatible avec "%1" - + No profile set - + Aucun profil - + loot failed. Exit code was: %1 - + échec de LOOT. Le code de fermeture était : %1 - + failed to start loot - + impossible de démarrer LOOT - + failed to run loot: %1 - + impossible d'exécuter LOOT : %1 - + Errors occured - + Erreurs survenues - + Backup of load order created - + Backup de l'ordre de chargement créé - + Choose backup to restore - + Choisissez le backup à restaurer - + No Backups - + Aucun Backups - + There are no backups to restore - + Il n'y a aucun backup à restaurer - - + + Restore failed - + Restauration échouée - - + + Failed to restore the backup. Errorcode: %1 - + Échec de la restauration du backup. Code d'erreur : %1 - + Backup of modlist created - + Backup de la liste de mods créé @@ -2870,14 +2871,69 @@ This function will guess the versioning scheme under the assumption that the ins Placeholder - Signet + *Substitut* ModInfo - - + + Plugins + Plugins + + + + Textures + Textures + + + + Meshes + Meshes + + + + UI Changes + Modifications de l'UI + + + + Music + Musiques + + + + Sound Effects + Sons + + + + Scripts + Scripts + + + + SKSE Plugins + Plugins SKSE + + + + SkyProc Tools + Outils SkyProc + + + + Strings + Strings + + + + invalid content type %1 + type de contenu invalide %1 + + + + invalid index %1 index invalide %1 @@ -2885,9 +2941,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + Ceci est le backup d'un mod @@ -2926,7 +2982,7 @@ This function will guess the versioning scheme under the assumption that the ins Ini Files - + Fichiers INI @@ -2941,17 +2997,17 @@ This function will guess the versioning scheme under the assumption that the ins Ini Tweaks - + Tweaks INI This is a list of ini tweaks (ini modifications that can be toggled). - + Ceci est une liste des tweaks ini (modifications d'ini qui peuvent être activées). This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. - + Ceci est une liste des tweaks ini. Les tweaks ini sont des fragments de fichiers INI (usuellement petits) qui sont appliqués par dessus les paramètres existants dans skyrim.ini/skyrimprefs.ini. Chaque tweak peut être activé individuellement. Vous devriez vérifier la description du mod quand à savoir si les tweaks sont réellement optionnels. @@ -2976,7 +3032,7 @@ This function will guess the versioning scheme under the assumption that the ins This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + Ceci liste toutes les images (.jpg et .png) présentes dans le répertoire du mod, comme les screenshots et autres. Cliquez sur l'objet pour obtenir une vue plus large. @@ -2995,7 +3051,7 @@ This function will guess the versioning scheme under the assumption that the ins They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + @@ -3035,48 +3091,48 @@ Most mods do not have optional esps, so chances are good you are looking at an e Conflicts - + Conflits The following conflicted files are provided by this mod - + Les fichiers conflictuels suivants sont originaire de ce mod File - Fichier + Fichier Overwritten Mods - + Mods écrasés The following conflicted files are provided by other mods - + Les fichiers conflictuels suivants sont originaires d'autres mods Providing Mod - + Mod source Non-Conflicted files - + Fichiers non conflictuels Categories - Catégories + Catégories Primary Category - + Catégorie primaire @@ -3098,20 +3154,16 @@ Most mods do not have optional esps, so chances are good you are looking at an e <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">ID de ce mod sur Nexus. Rempli automatiquement si vous téléchargez et installez un mod à partir de MO. Sinon, vous pouvez l'inscrire manuellement. Pour connaître le bon ID, trouvez le mod sur Nexus. L'URL ressemblera à ceci: <a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" text-decoration: underline; color:#0000ff;">http://www.skyrimnexus.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" color:#000000;">. Dans cet example, 1334 est l'ID que vous cherchez. En passant: Ce lien est celui de Mod Organizer sur le Nexus. Pourquoi ne pas le visiter et lui donner votre aval?</span></a></p></body></html> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3127,12 +3179,12 @@ p, li { white-space: pre-wrap; } Refresh - Actualiser + Actualiser Refresh all information from Nexus. - + Actualiser toutes les informations provenant du Nexus @@ -3144,19 +3196,23 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> Endorse - + Recommander Notes - + Notes @@ -3173,7 +3229,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -3186,7 +3242,7 @@ p, li { white-space: pre-wrap; } Previous - + Précédant @@ -3199,264 +3255,264 @@ p, li { white-space: pre-wrap; } Fermer - + &Delete Supprimer - + &Rename &Renommer - + &Hide - + &Cacher - + &Unhide - + &Ne plus cacher - + &Open &Ouvrir - + &New Folder &Nouveau dossier - - + + Save changes? - Enregistrer les changements? + Enregistrer les changements? - - + + Save changes to "%1"? - + Enregistrer les changements sur "%1" ? - + File Exists Un fichier du même nom existe - + A file with that name exists, please enter a new one Un fichier ainsi nommé existe déjà, veuillez entrer un nouveau nom - + failed to move file impossible de déplacer le fchier - + failed to create directory "optional" - Impossible de créer le dossier "optional" + impossible de créer le dossier "optional" - - + + Info requested, please wait Info demandée, veuillez patienter - + Main Principal - + Update Mise-à-jour - + Optional Optionnel - + Old Ancien - + Misc Divers - + Unknown Inconnu - + Current Version: %1 Version courante: %1 - + No update available Aucune mise-à-jour disponible - + (description incomplete, please visit nexus) - + (description incomplète, veuillez aller sur le Nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Visiter sur Nexus</a> - + Failed to delete %1 - impossible d'effacer %1 + Impossible d'effacer %1 - - + + Confirm Confirmer - + Are sure you want to delete "%1"? - Voulez-vous vraiment supprimer "%1"? + Voulez-vous vraiment supprimer "%1" ? - + Are sure you want to delete the selected files? - Voulez-vous vraiment supprimer les fichiers sélectionnés? + Voulez-vous vraiment supprimer les fichiers sélectionnés ? - - + + New Folder Nouveau dossier - + Failed to create "%1" Impossible de créer "%1" - - + + Replace file? - + Remplacer le fichier ? - + There already is a hidden version of this file. Replace it? - + Ceci est déjà une version cachée de ce fichier. La remplacer ? - - + + File operation failed - + Opération de fichier échouée - - + + Failed to remove "%1". Maybe you lack the required file permissions? - + Impossible de supprimer "%1". Peut-être n'avez vous pas les permissions nécessaires sur le fichier. - - + + failed to rename %1 to %2 - Impossible de renommer %1 en %2 + impossible de renommer %1 en %2 - + There already is a visible version of this file. Replace it? - + Il existe déjà une version visible de ce fichier. La remplacer ? - + Un-Hide - + Ne plus cacher - + Hide - + Cacher - + Name Nom - + Please enter a name - + Veuillez entrer un nom - - + + Error - Erreur + Erreur - + Invalid name. Must be a valid file name - + Nom invalide. Veuillez entrer un nom valide - + A tweak by that name exists - + Un tweak de ce nom existe déjà - + Create Tweak - + Créer un Tweak ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. - + Ce pseudo mod représente un contenu géré à l’extérieur de MO. Il n'est pas modifié par MO. ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - + Ce pseudo mod contient des fichiers provenant du répertoire Data virtualisé qui ont été modifiés (ex : par le construction kit) ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - + impossible d'écrire %1/meta.ini : erreur %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - %1 ne contient ni esp/esm, ni dossier d'éléments de jeu (textures, meshes, interface, ...) + %1 ne contient ni esp/esm, ni dossier d'éléments de jeu (textures, meshes, interface, ...) - + Categories: <br> - + Catégories : <br> @@ -3464,118 +3520,117 @@ p, li { white-space: pre-wrap; } Game plugins (esp/esm) - + Elder Scrolls Plugins (esp/esm) Interface - + Interface Meshes - + Meshes Music - + Music Scripts (Papyrus) - + Scripts (Papyrus) Script Extender Plugin - + Script Extender Plugin SkyProc Patcher - + SkyProc Patcher Sound - + Sound Strings - + Strings Textures - + Textures This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit) - + Cette entrée contient des fichiers qui ont été créés à l'intérieur du répertoire Data virtualisé (ex : par le construction kit) Backup - + Backup No valid game data - + Aucune donnée de jeu valide Not endorsed yet - + Pas encore recommandé Overwrites files - + Fichiers d'Overwrite Overwritten files - + Fichiers écrasés Overwrites & Overwritten - + Overwrites & Overwritten Redundant - + Redondant Non-MO - + Non-MO invalid - + invalide installed version: "%1", newest version: "%2" - installed version: %1, newest version: %2 - + version installée : "%1", nouvelle version : "%2" The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + La version la plus récente disponible sur le Nexus semble être plus ancienne que celle que vous avez installée. Ceci peut signifier que la version que vous utiliser à été retirée (ex : pour cause de bug) ou que l'auteur utilise une syntaxe de version non standardisée et que la dernière version est en fait plus récente. D'une manière ou d'une autre vous pourriez vouloir "mettre à jour". Categories: <br> - + Catégories : <br> @@ -3585,7 +3640,7 @@ p, li { white-space: pre-wrap; } drag&drop failed: %1 - + le glissé&déposé à échoué : %1 @@ -3600,12 +3655,12 @@ p, li { white-space: pre-wrap; } Flags - + Flags Content - Contenu + Contenu @@ -3625,28 +3680,28 @@ p, li { white-space: pre-wrap; } Category - + Catégorie Nexus ID - IDs Nexus + ID Nexus Installation - + Installation unknown - Inconnu + inconnu Name of your mods - + Nom de vos mods @@ -3661,27 +3716,27 @@ p, li { white-space: pre-wrap; } Category of the mod. - + Catégorie du mod. Id of the mod as used on Nexus. - + ID du mod sur le Nexus Emblemes to highlight things that might require attention. - + Emblèmes pour mettre en évidence les choses qui pourraient devoir retenir votre attention. Depicts the content of the mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> - + Représente le contenu du mod ::<br><img src=":/MO/gui/content/plugin" width=32/>Plugins de jeu (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> Time this mod was installed - + Date à laquelle le mod a été installé @@ -3689,12 +3744,12 @@ p, li { white-space: pre-wrap; } Message of the Day - + Message du Jour OK - OK + OK @@ -3702,35 +3757,35 @@ p, li { white-space: pre-wrap; } Overwrites - + Overwrites not implemented - + non implémenté NXMAccessManager - + Logging into Nexus - + Connexion au Nexus - + timeout - + session expirée - + Unknown error - + Erreur inconnue - + Please check your password - + Veuillez vérifier votre mot de passe @@ -3738,17 +3793,17 @@ p, li { white-space: pre-wrap; } Failed to guess mod id for "%1", please pick the correct one - + Impossible de deviner l'ID de mod pour "%1", veuillez déterminer la bonne empty response - + réponse nulle invalid response - + réponse invalide @@ -3756,184 +3811,184 @@ p, li { white-space: pre-wrap; } Overwrite - + Overwrite You can use drag&drop to move files and directories to regular mods. - + Vous pouvez glisser&déposer pour déplacer les fichiers et les répertoires dans les mods usuels. &Delete - Supprimer + &Supprimer &Rename - &Renommer + &Renommer &Open - &Ouvrir + &Ouvrir &New Folder - &Nouveau dossier + &Nouveau dossier %1 not found - %1 introuvable + %1 introuvable Failed to delete "%1" - impossible d'effacer %1 + impossible d'effacer "%1" Confirm - Confirmer + Confirmer Are sure you want to delete "%1"? - Voulez-vous vraiment supprimer "%1"? + Voulez-vous vraiment supprimer "%1" ? Are sure you want to delete the selected files? - Voulez-vous vraiment supprimer les fichiers sélectionnés? + Voulez-vous vraiment supprimer les fichiers sélectionnés ? New Folder - Nouveau dossier + Nouveau dossier Failed to create "%1" - Impossible de créer "%1" + Impossible de créer "%1" PluginList - + Name Nom - + Priority Priorité - + Mod Index - + Index du mod - + Flags - + Flags - - + + unknown - Inconnu + inconnu - + Name of your mods - + Nom de vos mods - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + Priorité de chargement de votre mod. Plus elle est élevée, plus le mod est "important" et écrasera donc les fichiers des mods de priorité inférieure. - + The modindex determins the formids of objects originating from this mods. - + L'index des mods détermine les "formids" des objets provenant de ces mods. - + failed to update esp info for file %1 (source id: %2), error: %3 - + impossible de mettre à jour les informations d'esp pour le fichier %1 (source ID : %2), erreur : %3 - + esp not found: %1 ESP introuvable: %1 - - + + Confirm Confirmer - + Really enable all plugins? - + Voulez vous vraiment activer tout les plugins ? - + Really disable all plugins? - + Voulez vous vraiment désactiver tout les plugins ? - + The file containing locked plugin indices is broken - + Le fichier contenant l'indice des plugins verrouillés est corrompu - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. - + Certains de vos plugins ont un nom invalide ! Ces plugions ne peuvent pas être chargés par le jeu. Veuillez vous référer à mo_interface.log pour la liste de tout les plugins affectés et les renommer. - <b>Origin</b>: %1 - + This plugin can't be disabled (enforced by the game) + Ce plugin ne peut être désactivé (forcé par le jeu) - Author - Auteur + <b>Origin</b>: %1 + <b>Origine</b> : %1 - - Description - Description + + Author + Auteur - - This plugin can't be disabled (enforced by the game) - + + Description + Description - + Missing Masters - + Masters manquants - + Enabled Masters - + Masters activés - + failed to restore load order for %1 - + La restauration de l’ordre de chargement à échouée pour %1 @@ -3941,12 +3996,12 @@ p, li { white-space: pre-wrap; } Preview - + Aperçu Close - Fermer + Fermer @@ -3954,16 +4009,20 @@ p, li { white-space: pre-wrap; } Problems - + Problèmes <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> @@ -3974,12 +4033,12 @@ p, li { white-space: pre-wrap; } Fix - + Corriger No guided fix - + Aucune guide de correction @@ -3987,32 +4046,32 @@ p, li { white-space: pre-wrap; } invalid profile name %1 - + nom du profil invalide %1 failed to create %1 - impossible de créer %1 + impossible de créer %1 failed to write mod list: %1 - impossible de mettre à jour la liste de mods: %1 + impossible d'écrire la liste de mod : %1 failed to update tweaked ini file, wrong settings may be used: %1 - + impossible de mettre à jour le fichier de tweak ini : des paramètres incorrects pourraient être utilisés : %1 failed to create tweaked ini: %1 - + impossible de créer le fichier de tweak ini : %1 "%1" is missing or inaccessible - + "%1" est manquant ou inaccessible @@ -4026,7 +4085,7 @@ p, li { white-space: pre-wrap; } Overwrite directory couldn't be parsed - + le répertoire Overwrite n'a pas pu être traité @@ -4041,23 +4100,23 @@ p, li { white-space: pre-wrap; } failed to parse ini file (%1): %2 - impossible d'analyser le profil %1: %2 + impossible de traiter le fichier ini (%1) : %2 failed to modify "%1" - impossible de trouver "%1" + impossible de modifier "%1" Delete savegames? - + Supprimer les sauvegardes ? Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + @@ -4065,27 +4124,27 @@ p, li { white-space: pre-wrap; } Dialog - + Dialogue Please enter a name for the new profile - Veuillez inscrire un nom pour le nouveau profil + Veuillez entrer un nom pour le nouveau profil If checked, the new profile will use the default game settings. - + Si coché, le nouveau profil utilisera les paramètres de jeu par défaut. If checked, the new profile will use the default game settings instead of the "global" settings. Global settings are the settings you configure when running the game launcher directly, without MO. - + Default Game Settings - + Paramètres de jeu par défaut @@ -4105,7 +4164,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> @@ -4121,12 +4180,12 @@ p, li { white-space: pre-wrap; } If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + Sauvegardes locales @@ -4138,7 +4197,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> @@ -4147,7 +4206,7 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les jeux Oblivion, Fallout 3 et Fallout NV contiennent un bug empêchant le fonctionnement des textures et modèles remplaceant ceux déjà existants dans le jeu.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Les jeux Oblivion, Fallout 3 et Fallout NV contiennent un bug empêchant le fonctionnement des textures et modèles remplaçant ceux déjà existants dans le jeu.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod Organizer utilise une méthode nommée &quot;BSA redirection&quot; (google est votre ami) pour circonvenir le problème une fois pour toute. Activez simplement et n'y pensez plus.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Avec Skyrim, le problème semble partiellement résolu, mais l'activation d'un mod dans le jeu dépends toujours des dates des fichiers. Il est donc encore préférable de l'activer..</span></p></body></html> @@ -4197,18 +4256,18 @@ p, li { white-space: pre-wrap; } Rename - &Renommer + Renommer Transfer save games to the selected profile. - + Transférer les sauvegardes de jeu vers le profil sélectionné. Transfer Saves - + Transférer les Sauvegardes @@ -4239,7 +4298,7 @@ p, li { white-space: pre-wrap; } failed to copy profile: %1 - impossible de copier le profil: %1 + impossible de copier le profil : %1 @@ -4249,7 +4308,7 @@ p, li { white-space: pre-wrap; } Invalid profile name - + Nom de profil invalide @@ -4259,27 +4318,27 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Voulez-vous vraiment supprimer ce profil (y compris ses sauvegardes locales s'il y en a) ? Profile broken - + Profil corrompu This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + Rename Profile - + Renommer le Profil New Name - + Nouveau Nom @@ -4310,52 +4369,52 @@ p, li { white-space: pre-wrap; } invalid category id %1 - + ID de catégorie invalide %1 invalid field name "%1" - + nom de champ invalide "%1" invalid type for "%1" (should be integer) - + type invalide pour "%1" (devrait être entier) invalid type for "%1" (should be string) - + type invalide pour "%1" (devrait être un "string") invalid type for "%1" (should be float) - + type invalide pour "%1" (devrait être "flottant") no fields set up yet! - + encore aucun champs installé ! field not set "%1" - + champ non déterminé "%1" invalid character in field "%1" - + caractère invalide dans le champ "%1" empty field name - + nom du champ vide invalid game type %1 - + type de jeu invalide %1 @@ -4439,62 +4498,62 @@ p, li { white-space: pre-wrap; } Impossible de mettre en place le chargement via DLL par procuration - + Permissions required Permissions requises - + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + - - + + Woops - + Oups - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 - + - + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Une copie du Mod Organizer tourne déjà - + No game identified in "%1". The directory is required to contain the game binary and its launcher. - + - - + + Please select the game to manage Veuillez choisir le jeu à gérer - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + - + failed to start application: %1 - + impossible de démarrer l'application : %1 @@ -4502,28 +4561,28 @@ p, li { white-space: pre-wrap; } Veuillez utiliser l'aide dans la barre d'outil pour obtenir des instructions à propos de tous les éléments - - + + <Manage...> <Gérer...> - + failed to parse profile %1: %2 impossible d'analyser le profil %1: %2 - + failed to find "%1" impossible de trouver "%1" - + failed to access %1 impossible d'accéder à %1 - + failed to set file time %1 impossible de changer la date du fichier %1 @@ -4535,13 +4594,12 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - "%1" is missing - + "%1" est manquant ou innacessible Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile! - + Avant de pouvoir utiliser ModOrganizer, vous devez créer au moins un profile. ATTENTION : Lancer le jeu une fois avant de créer un profile! @@ -4549,6 +4607,11 @@ p, li { white-space: pre-wrap; } Error Erreur + + + failed to open temporary file + impossible d'ouvrir le fichier temporaire + @@ -4564,7 +4627,7 @@ p, li { white-space: pre-wrap; } Script Extender - Extenseur de script + Script Extender @@ -4572,87 +4635,82 @@ p, li { white-space: pre-wrap; } DLL par procuration - + failed to spawn "%1" impossible de lancer "%1" - + Elevation required - + Permissions requises - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - + failed to spawn "%1": %2 impossible de lancer "%1": %2 - + "%1" doesn't exist "%1" inexistant - + failed to inject dll into "%1": %2 impossible d'injecter le DLL dans "%1": %2 - + failed to run "%1" impossible de lancer "%1" - - - failed to open temporary file - - QueryOverwriteDialog Mod Exists - + Le Mod existe déjà This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Ce Mod semble déjà installé. Voulez-vous ajouter les fichiers provenants de cette archive (écrase les fichiers existants) ou voulez-vous remplacer complètement les fichiers existants (les vieux fichiers seront effacés)? Alternativement vous pouvez installer ce Mod sous un nom différend. Keep Backup - + Garder une sauvegarde Merge - + Fusionner Replace - + Remplacer Rename - &Renommer + Renommer Cancel - Annuler + Annuler @@ -4660,27 +4718,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Enregistrer # Character - + Personnage Level - + Niveau Location - + Location Date - DATA + Date @@ -4688,7 +4746,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Missing ESPs - ESP manquant + ESPs manquants @@ -4696,37 +4754,37 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - + Dialogue Copy To Clipboard - + Copier dans le presse papier Save As... - + Sauvegarder en tant que... Close - Fermer + Fermer Save CSV - + Sauvegarder le CSV Text Files - Fichiers texte + Fichiers Textes failed to open "%1" for writing - + impossible d'ouvrir "%1" pour écriture @@ -4734,17 +4792,17 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Select - + Sélectionner Placeholder - Signet + *Substitut* Cancel - Annuler + Annuler @@ -4790,7 +4848,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe failed to move outdated files: %1. Please update manually. - + impossible de déplacer les fichiers obsolètes : %1. Veuillez les mettre à jour manuellement. @@ -4805,22 +4863,22 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Failed to parse response. Please report this as a bug and include the file mo_interface.log. - + Échec du traitement de la réponse. Veuillez s'il vous plait reporter ceci comme un bug, en incluant le fichier mo_interface.log. No incremental update available for this version, the complete package needs to be downloaded (%1 kB) - + no file for update found. Please update manually. - + aucun fichier de mise à jour trouvé. Veuillez mettre à jour manuellement. Failed to retrieve update information: %1 - + Impossible de récupérer les informations de mise à jour : %1 @@ -4833,28 +4891,28 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Failed - + Échec Sorry, failed to start the helper application - + Désolé, impossible de démarrer l'application d'aide attempt to store setting for unknown plugin "%1" - + Confirm - Confirmer + Confimer Changing the mod directory affects all your profiles! Mods not present (or named differently) in the new location will be disabled in all profiles. There is no way to undo this unless you backed up your profiles manually. Proceed? - + @@ -4862,7 +4920,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Settings - Réglages + Paramètres @@ -4884,7 +4942,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -4895,124 +4953,124 @@ p, li { white-space: pre-wrap; } Style - + Style graphical style - + Style graphique graphical style of the MO user interface - + Style graphique de l'interface utilise de MO Log Level - + Niveau de log Decides the amount of data printed to "ModOrganizer.log" - + Décide de la quantité de donnée écrite dans "ModOrganizer.log" Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Debug - + Débogage Info - + Information Error - Erreur + Erreur Advanced - + Avancé Directory where downloads are stored. - + Répertoire où les Mods sont stockés Mod Directory - + Répertoire de Mod Directory where mods are stored. - + Répertoire où les Mods sont stockés. Directory where mods are stored. Please note that changing this will break all associations of profiles with mods that don't exist in the new location (with the same name). - + Download Directory - + Répertoire de téléchargement Cache Directory - + Répertoire en cache User interface - + Interface utiliser If checked, the download interface will be more compact. - + Si coché, l'interface des téléchargements sera plus compacte. Compact Download Interface - + Interface de téléchargent compacte If checked, the download list will display meta information instead of file names. - + Download Meta Information - + Télécharger les Informations Meta Reset stored information from dialogs. - + This will make all dialogs show up again where you checked the "Remember selection"-box. - + Reset Dialogs - + Réinitialiser les dialogues @@ -5041,7 +5099,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5052,7 +5110,7 @@ p, li { white-space: pre-wrap; } If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + @@ -5072,82 +5130,82 @@ p, li { white-space: pre-wrap; } Disable automatic internet features - + Désactive les fonctionnalités automatiques nécessitant une connexion internet Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Mode hors-connection Use a proxy for network connections. - + Utiliser un proxy pour les connections sur le réseau. Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + Utiliser un Proxy HTTP (Utilise les propriétés du système) Associate with "Download with manager" links - + Associer avec les liens "Download with manager" Known Servers (updated on download) - + Serveurs connus (MàJ tout les téléchargements) Preferred Servers (Drag & Drop) - + Serveurs préférés (Glisser & Déposer) Plugins - + Plugins Author: - Auteur + Auteur: Version: - Version + Version: Description: - Description + Description: Key - + Clé Value - + Valeur Blacklisted Plugins (use <del> to remove): - + Plugin black-listé (utilisez <del> pour le supprimer) : @@ -5169,7 +5227,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> @@ -5207,29 +5265,29 @@ p, li { white-space: pre-wrap; } There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - + NMM Version - + Version de NMM The Version of Nexus Mod Manager to impersonate. - + La Version de Nexus Mod Manager à impersonnaliser. Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - + @@ -5238,7 +5296,7 @@ tl;dr-version: If Nexus-features don't work, insert the current version num - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Il semble que les jeux chargent parfois des fichiers ESP ou ESM même s'ils ne sont pas activés. Je n'en connais pas encore les circonstances, mais les rapports des usagers impliquent que dans certains cas, ce comportement est indésirable. Si vous cochez ceci, les ESPs et ESMs qui ne sotn pas cochés seront invisible pour le jeu et ne pourront pas être chargés. @@ -5251,36 +5309,36 @@ Je n'en connais pas encore les circonstances, mais les rapports des usagers If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Forcer l'activation des fichiers de jeu. Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. - + Display mods installed outside MO - + Affichers les Mods qui n'ont pas été installer par MO @@ -5298,32 +5356,32 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des ar These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + Il s'agit de solutions alternatives pour des problèmes internes à Mod Organizer. Veuillez d'abord vous assurer de lire le texte d'aide avant de modifier quoi que ce soit ici. Select download directory - Sélectionnez un répertoire + Sélectionner le répertoire de téléchargement Select mod directory - Sélectionnez un répertoire + Sélectionner le répertoire de Mod Select cache directory - Sélectionnez un répertoire + Sélectionner le répertoire en cache Confirm? - Confirmer + Confirmer ? This will make all dialogs show up again where you checked the "Remember selection"-box. Continue? - + @@ -5375,7 +5433,7 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des ar failed to communicate with running instance: %1 - + Communication échouée avec l'instance active: %1 @@ -5388,32 +5446,32 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des ar Sync Overwrite - + Synchroniser Overwrite Name - Nom + Nom Sync To - + Synchroniser Vers <don't sync> - + <ne pas synchroniser> failed to remove %1 - Impossible de supprimer %1 + Impossible de supprimer %1 failed to move %1 to %2 - impossible de copier %1 vers %2 + Impossible de déplacer de %1 à %1 @@ -5421,17 +5479,17 @@ Pour les autres jeux, ceci ne suffit pas à remplacer l'invalidation des ar Transfer Savegames - + Transférer les sauvegardes de jeux Global Characters - + Personnages Globaux This is a list of characters in the global location. - + Ceci est une liste des personnages globaux. @@ -5443,7 +5501,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -5456,47 +5514,47 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Déplacer -> Copy -> - + Copier -> <- Move - + <- Déplacer <- Copy - + <- Copier Done - Terminé + Terminé Profile Characters - + Profil des personnages Overwrite - + Écraser Overwrite the file "%1" - + Écraser le fichier "%1" @@ -5504,23 +5562,23 @@ On Windows XP: Confirm - Confirmer + Confirmer Copy all save games of character "%1" to the profile? - + Copier toutes les sauvegardes du personnage "%1" vers le profil ? Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + - + \ No newline at end of file diff --git a/src/organizer_ru.ts b/src/organizer_ru.ts index 47978e40..7fc47167 100644 --- a/src/organizer_ru.ts +++ b/src/organizer_ru.ts @@ -1,6 +1,4 @@ - - - + AboutDialog @@ -62,7 +60,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> @@ -225,7 +223,7 @@ If there is a component called "00 Core" it is usually required. Optio <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> @@ -989,7 +987,7 @@ Right now the only case I know of where this needs to be overwritten is for the Save Changes? - Сохранить изменения? + Сохранить изменения? @@ -1135,7 +1133,7 @@ Right now the only case I know of where this needs to be overwritten is for the <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1156,7 +1154,7 @@ p, li { white-space: pre-wrap; } Cancel - Отмена + Отмена @@ -1179,87 +1177,87 @@ p, li { white-space: pre-wrap; } - + Extracting files Извлечение файлов - + failed to create backup не удалось создать резервную копию - + Mod Name Имя мода - + Name - Имя + Имя - + Invalid name Недопустимое имя - + The name you entered is invalid, please enter a different one. Введенное вами имя недопустимо, пожалуйста введите другое. - + File format "%1" not supported Формат файла "%1" не поддерживается - + None of the available installer plugins were able to handle that archive Не один из доступных плагинов-установщиков не смог просмотреть этот архив - + no error ошибки отсутствуют - + 7z.dll not found 7z.dll не найден - + 7z.dll isn't valid 7z.dll поврежден - + archive not found архив не найден - + failed to open archive не удалось открыть архив - + unsupported archive type не поддерживаемый тип архива - + internal library error внутренняя ошибка библиотеки - + archive invalid архив поврежден - + unknown archive error неизвестная ошибка архива @@ -1314,7 +1312,7 @@ p, li { white-space: pre-wrap; } Categories - Категории + Категории @@ -1356,7 +1354,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1366,6 +1364,11 @@ p, li { white-space: pre-wrap; } <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Создать профили здесь. Каждый профиль включает свой собственный список активных модов и esp. Таким образом, вы можете быстро переключаться между установками для различных прохождений игры.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Обратите внимание, что порядок загрузки esp одинаков для всех профилей.</span></p></body></html> + + + Open list options... + Открыть список вариантов... + Refresh list. This is usually not necessary unless you modified data outside the program. @@ -1425,7 +1428,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1445,7 +1448,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1468,7 +1471,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1486,6 +1489,11 @@ p, li { white-space: pre-wrap; } Plugins Плагины + + + Sort + Сортировать + List of available esp/esm files @@ -1496,7 +1504,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1504,16 +1512,6 @@ p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Этот список содержит esp и esm файлы активных модов. Для них требуется определенный порядок загрузки. Используйте перетаскивание для изменения порядка загрузки. Обратите внимание, что MO сохранит порядок загрузки только для активными/проверенных модов.<br />Существует замечательная утилита, называющаяся &quot;BOSS&quot; , которая автоматически сортирует эти файлы.</span></p></body></html> - - - Sort - Сортировать - - - - Open list options... - Открыть список вариантов... - Archives @@ -1549,7 +1547,7 @@ BSA, отмеченные здесь, загружаются так, чтобы File - Файл + Файл @@ -1568,8 +1566,8 @@ BSA, отмеченные здесь, загружаются так, чтобы - - + + Refresh Обновить @@ -1581,7 +1579,7 @@ BSA, отмеченные здесь, загружаются так, чтобы Mod - Мод + Мод @@ -1604,8 +1602,8 @@ BSA, отмеченные здесь, загружаются так, чтобы <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -1749,7 +1747,7 @@ p, li { white-space: pre-wrap; } - + Update Обновление @@ -1793,7 +1791,7 @@ Right now this has very limited functionality - + Endorse Mod Organizer Одобрить Mod Organizer @@ -1875,7 +1873,7 @@ Right now this has very limited functionality Name - Имя + Имя @@ -1922,6 +1920,25 @@ Right now this has very limited functionality Plugin "%1" failed Плагин "%1" не удалось + + + Download? + Загрузить? + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + Загрузка началась, но не была установлена страница плагина. +Если вы все равно загрузите, то никакой информации (т.е. версия) не будет ассоциировано с загрузкой. +Продолжить? + + + + Browse Mod Page + Смотреть страницу мода + failed to init plugin %1: %2 @@ -1954,6 +1971,11 @@ Right now this has very limited functionality Please press OK once you're logged into steam. Нажмите OK как только вы войдете в Steam. + + + Executable "%1" not found + Исполняемый файл "%1" не найден + Start Steam? @@ -1965,926 +1987,887 @@ Right now this has very limited functionality Требуется запущенный Steam, для корректного запуска игры. Должен ли MO попытаться запустить Steam сейчас? - + Also in: <br> Также в: <br> - + No conflict Конфликтов нет - + <Edit...> <Правка...> - + + Failed to refresh list of esps: %1 + Не удалось обновить список esp: %1 + + + This bsa is enabled in the ini file so it may be required! Этот bsa подключен через ini, так что он может быть необходим! - + Activating Network Proxy Подключение сетевого прокси - - - Installation successful - Установка завершена - - - - - Configure Mod - Настройка мода + + + Failed to write settings + Не удалось записать настройки - - - This mod contains ini tweaks. Do you want to configure them now? - Этот мод включает настройки ini. Вы хотите настроить их сейчас? + + + An error occured trying to write back MO settings: %1 + Ошибка при попытке записать обратно настройки MO: %1 - - - mod "%1" not found - мод "%1" не найден + + File is write protected + Файл защищён от записи - - - Installation cancelled - Установка отменена + + Invalid file format (probably a bug) + Неверный формат файла (возможно баг) - - - The mod was not installed completely. - Мод не был установлен полностью. + + Unknown error %1 + Неизвестная ошибка %1 - + Some plugins could not be loaded Некоторые плагины не могут быть загружены - + Too many esps and esms enabled Подключено слишком много esp и esm - - + + Description missing Описание отсутствует - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: Следующие плагины не могут быть загружены. Причина возможно в отсутствующих зависимостях (таких как python) или в устаревшей версии: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> Игра не позволяет загрузить больше 255 активных плагинов (включая официальные). Вам нужно отключить некоторые ненужные плагины или объединить несколько небольших плагинов в один. Инструкция может быть найдена здесь: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod Выберете мод - + Mod Archive Архив мода - + + + Installation successful + Установка завершена + + + + + Configure Mod + Настройка мода + + + + + This mod contains ini tweaks. Do you want to configure them now? + Этот мод включает настройки ini. Вы хотите настроить их сейчас? + + + + + mod "%1" not found + мод "%1" не найден + + + + + Installation cancelled + Установка отменена + + + + + The mod was not installed completely. + Мод не был установлен полностью. + + + Start Tutorial? Начать урок? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? Вы собираетесь открыть урок. По техническим причинам будет невозможно закончить его досрочно. Продолжить? - - + + Download started Загрузка начата - + failed to update mod list: %1 не удалось обновить список модов: %1 - + failed to spawn notepad.exe: %1 не удалось вызвать notepad.exe: %1 - + failed to open %1 не удалось открыть %1 - + failed to change origin name: %1 не удалось изменить оригинальное имя: %1 - - Executable "%1" not found - Исполняемый файл "%1" не найден - - - - Failed to refresh list of esps: %1 - Не удалось обновить список esp: %1 - - - + failed to move "%1" from mod "%2" to "%3": %4 не удалось переместить "%1" из мода "%2" в "%3": %4 - + + <Contains %1> + <Содержит %1> + + + <Checked> <Подключен> - + <Unchecked> <Отключен> - + <Update> <Обновлен> - + + <Managed by MO> + <Управляется в MO> + + + + <Managed outside MO> + <Управляется вне MO> + + + <No category> <Без категории> - + <Conflicted> <Конфликтует> - + <Not Endorsed> <Не одобрено> - + failed to rename mod: %1 не удалось переименовать мод: %1 - + Overwrite? Перезаписать? - + This will replace the existing mod "%1". Continue? Это заменит существующий мод "%1". Продолжить? - + failed to remove mod "%1" не удалось удалить мод "%1" - - - + + + failed to rename "%1" to "%2" не удалось переименовать "%1" в "%2" - + Multiple esps activated, please check that they don't conflict. Подключено несколько esp, выберете из них не конфликтующие. - - - - + + + + Confirm Подтверждение - + Remove the following mods?<br><ul>%1</ul> Удалить следующие моды?<br><ul>%1</ul> - + failed to remove mod: %1 не удалось удалить мод: %1 - - + + Failed Неудача - + Installation file no longer exists Установочный файл больше не существует - + Mods installed with old versions of MO can't be reinstalled in this way. Моды, установленные с использованием старых версий MO не могут быть переустановленны таким образом. - - - You need to be logged in with Nexus to endorse - Вы должны быть авторизированы на Nexus, чтобы одобрять. - - - - Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. - - - - - - - - - Delete %n save(s) - - - - - - - - - Extract BSA - Распаковать BSA + + You need to be logged in with Nexus to resume a download + Вы должны быть авторизированы на Nexus, чтобы продолжить загрузку - - - failed to read %1: %2 - не удалось прочесть %1: %2 + + + You need to be logged in with Nexus to endorse + Вы должны быть авторизированы на Nexus, чтобы одобрять. - - This archive contains invalid hashes. Some files may be broken. - Архив содержит неверные хеш-суммы. Некоторые файлы могут быть испорчены. + + Failed to display overwrite dialog: %1 + Ошибка при отображении диалогового окна перезаписи: %1 - + Nexus ID for this Mod is unknown Nexus ID для этого мода неизвестен - - Download? - Загрузить? - - - - A download has been started but no installed page plugin recognizes it. -If you download anyway no information (i.e. version) will be associated with the download. -Continue? - Загрузка началась, но не была установлена страница плагина. -Если вы все равно загрузите, то никакой информации (т.е. версия) не будет ассоциировано с загрузкой. -Продолжить? - - - - Browse Mod Page - Смотреть страницу мода - - - - - Failed to write settings - Не удалось записать настройки - - - - - An error occured trying to write back MO settings: %1 - Ошибка при попытке записать обратно настройки MO: %1 - - - - File is write protected - Файл защищён от записи - - - - Invalid file format (probably a bug) - Неверный формат файла (возможно баг) - - - - Unknown error %1 - Неизвестная ошибка %1 - - - - <Managed by MO> - <Управляется в MO> - - - - <Managed outside MO> - <Управляется вне MO> - - - - You need to be logged in with Nexus to resume a download - Вы должны быть авторизированы на Nexus, чтобы продолжить загрузку - - - - Failed to display overwrite dialog: %1 - - - - - + + Create Mod... Создать мод... - + This will move all files from overwrite into a new, regular mod. Please enter a name: Это переместит все файлы из перезаписи в новый, стандартный мод. Пожалуйста введите имя: - + A mod with this name already exists Мод с таким именем уже существует - + Continue? Продолжить? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. Схема управления версиями принимает решение, какая версия считается новее другой. Функция может попробовать угадать схему управления версиями, при условии, что установленная версия является устаревшей. - - + + Sorry Извините - + I don't know a versioning scheme where %1 is newer than %2. Мне неизвестна схема управления версиями, где %1 новее %2. - + Really enable all visible mods? Действительно подключить все видимые моды? - + Really disable all visible mods? Действительно отключить все видимые моды? - + Choose what to export Выберете, что экспортировать - + Everything Всё - + All installed mods are included in the list Все установленные моды, включенные в список - + Active Mods Активные моды - + Only active (checked) mods from your current profile are included Включены все активные (подключенные) моды вашего текущего профиля - + Visible Видимые - + All mods visible in the mod list are included Включены все моды, видимые в списке модов - + export failed: %1 экспорт не удался: %1 - + Install Mod... Установить мод... - + Enable all visible Включить все видимые - + Disable all visible Отключить все видимые - + Check all for update Проверить все на обновления - + Export to csv... Экспорт в csv... - + All Mods Все моды - + Sync to Mods... Синхронизировать с модами... - + Restore Backup Восстановить из резервной копии - + Remove Backup... Удалить резервную копию... - + Add/Remove Categories Добавить/Удалить категории - + Replace Categories Заменить категории - + Primary Category Основная категория - + Change versioning scheme Изменить схему управления версиями - + Un-ignore update Снять игнорирование обновления - + Ignore update Игнорировать обновление - + Rename Mod... Переименовать мод... - + Remove Mod... Удалить мод... - + Reinstall Mod Переустановить мод - + Un-Endorse Отменить одобрение - - + + Endorse Одобрить - + Won't endorse Не одобрять - + Endorsement state unknown Статус одобрения неизвестен - + Ignore missing data Игнорировать отсутствующие данные - + Visit on Nexus Перейти на Nexus - + Open in explorer Открыть в проводнике - + Information... Информация... - - + + Exception: Исключение: - - + + Unknown exception Неизвестное исключение - + <All> <Все> - + <Multiple> <Несколько> - - - Please wait while LOOT is running - + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + Вы уверены, что хотите удалить следующие %n сохранения?<br><ul>%1</ul><br>Удаленные сохранения будут помещены в Корзину.Вы уверены, что хотите удалить следующие %n сохранения?<br><ul>%1</ul><br>Удаленные сохранения будут помещены в Корзину.Вы уверены, что хотите удалить следующие %n сохранения?<br><ul>%1</ul><br>Удаленные сохранения будут помещены в Корзину. - Really delete "%1"? - Действительно удалить "%1"? + + Please wait while LOOT is running + Пожалуйста, подождите, пока LOOT работает - + Fix Mods... Исправить моды... - - Delete - Удалить + + + Delete %n save(s) + Удалить сохранение(я)Удалить сохранение(я)Удалить сохранение(я) - + failed to remove %1 не удалось удалить %1 - - + + failed to create %1 не удалось создать %1 - + Can't change download directory while downloads are in progress! Нельзя изменить каталог для загрузок, когда загрузки ещё не завершены! - + Download failed Загрузка не удалась - + failed to write to file %1 ошибка записи в файл %1 - + %1 written %1 записан - + Select binary Выберете исполняемый файл - + Binary Исполняемый файл - + Enter Name Введите имя - + Please enter a name for the executable Введите название для программы - + Not an executable Не является исполняемым - + This is not a recognized executable. Это неверный исполняемый файл. - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Уже существует скрытая версия этого файла. Заменить? - - + + File operation failed Операция с файлом не удалась - - + + Failed to remove "%1". Maybe you lack the required file permissions? Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + file not found: %1 файл не найден: %1 - + failed to generate preview for %1 не удалось получить предосмотр для %1 - + Sorry, can't preview anything. This function currently does not support extracting from bsas. Невозможно получить предосмотр чего-либо. Функция на данный момент не поддерживает извлечение из bsa. - + Update available Доступно обновление - + Open/Execute Открыть/Выполнить - + Add as Executable Добавить как исполняемый - + Preview Предосмотр - + Un-Hide Показать - + Hide Скрыть - + Write To File... Записать в файл... - + Do you want to endorse Mod Organizer on %1 now? Вы хотите одобрить Mod Organizer на %1 сейчас? - + Thank you! Спасибо Вам! - + Thank you for your endorsement! Спасибо Вам за одобрение! - + Request to Nexus failed: %1 Запрос на Nexus не удался: %1 - - + + login successful успешный вход - + login failed: %1. Trying to download anyway вход не удался: %1. Пытаюсь загрузить всё равно - + login failed: %1 войти не удалось: %1 - + login failed: %1. You need to log-in with Nexus to update MO. войти не удалось: %1. Вам нужно войти на Nexus, чтобы обновить MO. - + + + failed to read %1: %2 + не удалось прочесть %1: %2 + + + Error Ошибка - + failed to extract %1 (errorcode %2) не удалось распаковать %1 (код ошибки %2) - + + Extract BSA + Распаковать BSA + + + + This archive contains invalid hashes. Some files may be broken. + Архив содержит неверные хеш-суммы. Некоторые файлы могут быть испорчены. + + + Extract... Распаковать... - + Edit Categories... Изменить категории... - + Deselect filter Снять выбор с фильтра - + Remove Удалить - + Enable all Включить все - + Disable all Отключить все - + Unlock load order Снять фиксацию порядка загрузки - + Lock load order Зафиксировать порядок загрузки - + depends on missing "%1" зависит от отсутствующего "%1" - + incompatible with "%1" - + несовместимый с "%1" - + No profile set Нет установленного профиля - LOOT working - LOOT работает - - - + loot failed. Exit code was: %1 Запуск LOOT не удался. Код завершения: %1 - + failed to start loot - + Не удалось запустить LOOT - + failed to run loot: %1 не удалось запустить LOOT: %1 - + Errors occured Возникли ошибки - + Backup of load order created Резервная копия порядка загрузки создана - + Choose backup to restore Выберите резервную копию для восстановления - + No Backups Резервных копий нет - + There are no backups to restore Резервные копии для восстановления отсутствуют - - + + Restore failed Восстановление не удалось - - + + Failed to restore the backup. Errorcode: %1 Не удалось восстановить резервную копию. Код ошибки: %1 - + Backup of modlist created Резервная копия списка модов создана @@ -2901,8 +2884,63 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + Plugins + Плагины + + + + Textures + Текстуры + + + + Meshes + Полигональные сетки + + + + UI Changes + Изменения интерфейса + + + + Music + Музыка + + + + Sound Effects + Звуковые эффекты + + + + Scripts + Скрипты + + + + SKSE Plugins + Плагины SKSE + + + + SkyProc Tools + Инструменты SkyProc + + + + Strings + Строки + + + + invalid content type %1 + Некорректный тип содержания %1 + + + + invalid index %1 неверный индекс %1 @@ -2910,7 +2948,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod Это резервная копия мода @@ -3126,7 +3164,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3139,7 +3177,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3172,7 +3210,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3205,7 +3243,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -3228,230 +3266,230 @@ p, li { white-space: pre-wrap; } Close - Закрыть + Закрыть - + &Delete &Удалить - + &Rename &Переименовать - + &Hide &Скрыть - + &Unhide &Показать - + &Open - &Открыть + &Открыть - + &New Folder &Новая папка - - + + Save changes? Сохранить изменения? - - + + Save changes to "%1"? Сохранить изменения в "%1"? - + File Exists Файл уже существует - + A file with that name exists, please enter a new one Файл с таким именем уже существует, укажите другое - + failed to move file не удалось переместить файл - + failed to create directory "optional" не удалось создать папку "optional" - - + + Info requested, please wait Информация запрошена, пожалуйста, подождите - + Main Главное - + Update Обновление - + Optional Опционально - + Old Старые - + Misc Разное - + Unknown Неизвестно - + Current Version: %1 Текущая версия: %1 - + No update available Нет доступных обновлений - + (description incomplete, please visit nexus) (описание не завершено, смотрите на nexus) - + <a href="%1">Visit on Nexus</a> <a href="%1">Перейти на Nexus</a> - + Failed to delete %1 - Не удалось удалить %1 + Не удалось удалить %1 - - + + Confirm Подтверждение - + Are sure you want to delete "%1"? Вы уверены, что хотите удалить "%1"? - + Are sure you want to delete the selected files? Вы уверены, что хотите удалить выбранные файлы? - - + + New Folder Новая папка - + Failed to create "%1" Не удалось создать "%1" - - + + Replace file? Заменить файл? - + There already is a hidden version of this file. Replace it? Скрытая версия этого файла уже существует. Заменить? - - + + File operation failed Не удалась операция с файлом - - + + Failed to remove "%1". Maybe you lack the required file permissions? Не удалось удалить "%1". Может быть, вам не хватает необходимых прав доступа к файлу? - - + + failed to rename %1 to %2 не удалось переименовать %1 в %2 - + There already is a visible version of this file. Replace it? Видимая версия этого файла уже существует. Заменить? - + Un-Hide Показать - + Hide Скрыть - + Name Имя - + Please enter a name Пожалуйста, введите имя - - + + Error Ошибка - + Invalid name. Must be a valid file name Неверное имя. Необходимо допустимое имя файла. - + A tweak by that name exists Настройка с таким именем существует - + Create Tweak Создать настройку @@ -3459,7 +3497,7 @@ p, li { white-space: pre-wrap; } ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. Этот псевдо-мод отображает содержимое, управляемое из вне MO. Оно не модифицировано в MO. @@ -3467,7 +3505,7 @@ p, li { white-space: pre-wrap; } ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) Этот псевдо-мод содержит файлы из виртуального древа данных, которые были изменены (в Construction Kit и других программах) @@ -3475,18 +3513,18 @@ p, li { white-space: pre-wrap; } ModInfoRegular - - + + failed to write %1/meta.ini: error %2 не удалось записать %1/meta.ini: ошибка %2 - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory %1 не содержит ни esp/esm, ни папок ресурсов (textures, meshes, interface, ...) - + Categories: <br> Категории: <br> @@ -3496,52 +3534,52 @@ p, li { white-space: pre-wrap; } Game plugins (esp/esm) - + Игровые плагины (esp/esm) Interface - + Интерфейс Meshes - + Полигональные сетки Music - + Музыка Scripts (Papyrus) - + Скрипты (Papyrus) Script Extender Plugin - + Script Extender плагин SkyProc Patcher - + Патч SkyProc Sound - + Звук Strings - + Строки Textures - + Текстуры @@ -3596,7 +3634,6 @@ p, li { white-space: pre-wrap; } installed version: "%1", newest version: "%2" - installed version: %1, newest version: %2 установлена версия: %1, новейшая версия: %2 @@ -3637,7 +3674,7 @@ p, li { white-space: pre-wrap; } Content - Содержание + Содержание @@ -3647,7 +3684,7 @@ p, li { white-space: pre-wrap; } Version - Версия + Версия @@ -3708,7 +3745,7 @@ p, li { white-space: pre-wrap; } Depicts the content of the mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> - + Показывает содержимое мода:<br><img src=":/MO/gui/content/plugin" width=32/>Плагины (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>интерфейс<br><img src=":/MO/gui/content/mesh" width=32/>Полигональные сетки<br><img src=":/MO/gui/content/texture" width=32/>Текстуры<br><img src=":/MO/gui/content/sound" width=32/>Звуки<br><img src=":/MO/gui/content/music" width=32/>Музыка<br><img src=":/MO/gui/content/string" width=32/>Строки<br><img src=":/MO/gui/content/script" width=32/>Скрипты (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Плагины Script Extender<br><img src=":/MO/gui/content/skyproc" width=32/>Патч SkyProc<br> @@ -3745,22 +3782,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus Авторизация на Nexus - + timeout задержка - + Unknown error Неизвестная ошибка - + Please check your password Проверьте ваш пароль @@ -3808,7 +3845,7 @@ p, li { white-space: pre-wrap; } &Open - &Открыть + &Открыть @@ -3818,7 +3855,7 @@ p, li { white-space: pre-wrap; } %1 not found - %1 не найден + %1 не найден @@ -3856,114 +3893,114 @@ p, li { white-space: pre-wrap; } PluginList - + Name - Имя + Имя - + Priority Приоритет - + Mod Index Индекс - + Flags Флаги - - + + unknown неизвестно - + Name of your mods Имена ваших модов - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. Приоритет загрузки ваших модов. Моды с большим приоритетом перезапишут данные модов с меньшим приоритетом. - + The modindex determins the formids of objects originating from this mods. Индекс модов, определяющий formid объектов, происходящих из этих модов. - + failed to update esp info for file %1 (source id: %2), error: %3 не удалось обновить информацию о esp для файла %1 (id источника: %2), ошибка: %3 - + esp not found: %1 esp не найден: %1 - - + + Confirm Подтвердить - + Really enable all plugins? Действительно подключить все плагины? - + Really disable all plugins? Действительно отключить все плагины? - + The file containing locked plugin indices is broken Файл, содержащий индексы заблокированного плагина, не работает. - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. Некоторые из ваших плагинов имеют неверные имена. Эти плагины не могут быть загружены игрой. Смотрите mo_interface.log для получения списка таких плагинов и переименуйте их. + This plugin can't be disabled (enforced by the game) + Этот плагин не может быть отключен (грузится игрой принудительно) + + + <b>Origin</b>: %1 <b>Источник</b>: %1 - + Author Автор - + Description Описание - - This plugin can't be disabled (enforced by the game) - Этот плагин не может быть отключен (грузится игрой принудительно) - - - + Missing Masters Отсутствующие мастерфайлы - + Enabled Masters Подключенные мастерфайлы - + failed to restore load order for %1 не удалось восстановить порядок загрузки для %1 @@ -3993,7 +4030,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -4141,7 +4178,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> @@ -4174,7 +4211,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> @@ -4249,7 +4286,7 @@ p, li { white-space: pre-wrap; } Close - Закрыть + Закрыть @@ -4265,7 +4302,7 @@ p, li { white-space: pre-wrap; } Name - Имя + Имя @@ -4424,7 +4461,7 @@ p, li { white-space: pre-wrap; } Failed to delete %1 - Не удалось удалить %1 + Не удалось удалить %1 @@ -4475,60 +4512,60 @@ p, li { white-space: pre-wrap; } Не удалось установить загрузку proxy-dll - + Permissions required Требуются права доступа - + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. Текущий аккаунт пользователя не имеет требуемых прав доступа для запуска Mod Organizer. Необходимые изменения могут быть сделаны автоматически (папка MO будет сделана записываемой для текущего аккаунта пользователя). Вы получите запрос о запуске "helper.exe" с правами администратора. - - + + Woops Упс - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened Mod Organizer вышел из строя! Нужно ли создать диагностический файл? Если вы вышлите файл (%1) по адресу sherb@gmx.net, ошибка с намного большей вероятностью будет исправлена. Пожалуйста, добавьте краткое описание своих действий, перед тем, как произошла ошибка - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 ModOrganizer вышел из строя! К сожалению не удалось записать диагностический файл: %1 - + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Другой экземпляр Mod Organizer уже запущен - + No game identified in "%1". The directory is required to contain the game binary and its launcher. Игра не обнаружена в "%1". Требуется, чтобы папка содержала исполняемые файлы игры. - - + + Please select the game to manage Выберете игру для управления - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) Пожалуйста, выберете редакцию игры, которую вы имеете (MO не сможет правильно запустить игру, если это будет установлено неверно!) - + failed to start application: %1 не удалось запустить приложение: %1 @@ -4538,28 +4575,28 @@ p, li { white-space: pre-wrap; } Используйте пункт "Справка" на панели инструментов, чтобы получить инструкции по использованию всех элементов. - - + + <Manage...> <Управлять...> - + failed to parse profile %1: %2 не удалось обработать профиль %1: %2 - + failed to find "%1" не удалось найти "%1" - + failed to access %1 не удалось получить доступ к %1 - + failed to set file time %1 не удалось изменить дату модификации для %1 @@ -4571,7 +4608,6 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - "%1" is missing "%1" отсутствует или недоступен @@ -4585,6 +4621,11 @@ p, li { white-space: pre-wrap; } Error Ошибка + + + failed to open temporary file + не удалось открыть временный файл + @@ -4608,17 +4649,17 @@ p, li { white-space: pre-wrap; } Proxy DLL - + failed to spawn "%1" не удалось вызвать "%1" - + Elevation required Требуется повышение прав - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" @@ -4633,30 +4674,25 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Запустить с повышенными правами в любом случае? (будет выведен запрос о разрешении ModOrganizer.exe сделать изменения в системе) - + failed to spawn "%1": %2 не удалось вызвать "%1": %2 - + "%1" doesn't exist "%1" не существует - + failed to inject dll into "%1": %2 не удалось подключить dll к "%1": %2 - + failed to run "%1" не удалось запустить "%1" - - - failed to open temporary file - не удалось открыть временный файл - QueryOverwriteDialog @@ -4683,7 +4719,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Replace - Заменить + Заменить @@ -4693,7 +4729,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Cancel - Отмена + Отмена @@ -4752,7 +4788,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Close - Закрыть + Закрыть @@ -4762,7 +4798,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Text Files - Текстовые файлы + Текстовые файлы @@ -4780,12 +4816,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Placeholder - Заполнитель + Метка-заполнитель Cancel - Отмена + Отмена @@ -4874,12 +4910,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Failed - Неудача + Неудача Sorry, failed to start the helper application - + Не удалось запустить программу-помощник @@ -4925,7 +4961,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5083,7 +5119,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5104,12 +5140,12 @@ p, li { white-space: pre-wrap; } Username - Имя пользователя + Имя пользователя Password - Пароль + Пароль @@ -5211,7 +5247,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> @@ -5249,7 +5285,7 @@ p, li { white-space: pre-wrap; } There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. Mod Organizer необходимо подключить dll к игре, чтобы все моды были видны в ней. @@ -5273,8 +5309,8 @@ If you use the Steam version of Oblivion the default will NOT work. In this case Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer использует API Nexus , для использования таких возможностей, как проверка обновлений и загрузка файлов. К сожалению этот API не был сделан официально доступным прочим утилитам, вроде MO, так что нужно представляться как Nexus Mod Manager, чтобы получить доступ. @@ -5290,7 +5326,7 @@ tl;dr-версия: Если возможности Nexus не работают, - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. Кажется, что иногда игры загружают ESP и ESM файлы, даже если они не были не подключены как плагины Обстоятельства этого пока не известны, но отчеты пользователей подразумевают, что это в ряде случаев нежелательно. Если этот флажок отмечен, не отмеченные в списке ESP и ESM не будут видимы в списке и не будут загружены. @@ -5325,7 +5361,7 @@ Uncheck this if you want to use Mod Organizer with total conversions (like Nehri By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. По умолчанию Mod Organizer отобразит пакеты esp+bsa, установленные из других инструментов, как моды (левая панель). Это позволяет вам контроллировать их приоритет по отношению к другим модам. Это особенно полезно, если вы используете Steam Workshop, для установки модов. @@ -5392,7 +5428,7 @@ For the other games this is not a sufficient replacement for AI! Name - Имя + Имя @@ -5413,7 +5449,7 @@ For the other games this is not a sufficient replacement for AI! Cancel - Отмена + Отмена @@ -5431,7 +5467,7 @@ For the other games this is not a sufficient replacement for AI! failed to communicate with running instance: %1 - не удалось подключиться к запущенному экземпляру: %1 + Не удалось подключиться к запущенному экземпляру: %1 @@ -5449,7 +5485,7 @@ For the other games this is not a sufficient replacement for AI! Name - Имя + Имя @@ -5477,7 +5513,7 @@ For the other games this is not a sufficient replacement for AI! Transfer Savegames - Передать сохранения + Перенести сохранения @@ -5552,7 +5588,7 @@ On Windows XP: Done - Готово + Готово @@ -5594,4 +5630,4 @@ On Windows XP: Скопировать все сохранения с персонажем "%1" в общее месторасположение? Имейте ввиду, что это запутает текущую нумерацию сохранений. - + \ No newline at end of file diff --git a/src/organizer_zh_CN.ts b/src/organizer_zh_CN.ts index 9b8754bb..ae964233 100644 --- a/src/organizer_zh_CN.ts +++ b/src/organizer_zh_CN.ts @@ -1,38 +1,36 @@ - - - + AboutDialog About - + 关于 Revision: - + 版本 Used Software - + 用到的软件 Credits - + 制作组 Translators - + 翻译机 Others - + 其它 @@ -42,7 +40,7 @@ No license - + 无授权 @@ -62,7 +60,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> @@ -160,22 +158,22 @@ If there is a component called "00 Core" it is usually required. Optio Some Page - + 某些页面 Search - + 搜索 new - + 新建 failed to start download - + 启动下载失败 @@ -226,7 +224,7 @@ If there is a component called "00 Core" it is usually required. Optio <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> @@ -289,7 +287,7 @@ p, li { white-space: pre-wrap; } Never ask again - 无法读取bsa + 不再过问 @@ -325,7 +323,7 @@ p, li { white-space: pre-wrap; } pending download - + 挂起下载 @@ -381,12 +379,12 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + < mod %1 file %2 > Pending - + 挂起 @@ -401,12 +399,12 @@ p, li { white-space: pre-wrap; } Fetching Info 2 - 抓取信息 2 + 抓取信息 2 Installed - 抓取信息 2 + 已安装 @@ -459,7 +457,7 @@ p, li { white-space: pre-wrap; } Delete - &删除 + 删除 @@ -517,22 +515,22 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + < mod %1 file %2 > Pending - + 挂起 Fetching Info 1 - 抓取信息 1 + 抓取信息 1 Fetching Info 2 - 抓取信息 2 + 抓取信息 2 @@ -638,7 +636,7 @@ p, li { white-space: pre-wrap; } Memory allocation error (in refreshing directory). - + 地址分配错误(刷新目录时). @@ -658,12 +656,12 @@ p, li { white-space: pre-wrap; } Wrong Game - + 错误的游戏 The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + @@ -708,7 +706,7 @@ p, li { white-space: pre-wrap; } No known download urls. Sorry, this download can't be resumed. - + 未知的下载地址。很抱歉,下载无法继续。 @@ -728,7 +726,7 @@ p, li { white-space: pre-wrap; } Update - 更新 + 更新 @@ -753,12 +751,12 @@ p, li { white-space: pre-wrap; } Memory allocation error (in processing progress event). - + 地址分配错误(处理程序事件时). Memory allocation error (in processing downloaded data). - + 地址分配错误(处理下载数据时). @@ -789,7 +787,7 @@ p, li { white-space: pre-wrap; } Download failed. Server reported: %1 - + 下载失败。服务器报告: %1 @@ -964,7 +962,7 @@ Right now the only case I know of where this needs to be overwritten is for the MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + MO需要32位java来运行该应用程序。如果你已经安装,在安装目录下选择javaw.exe。 @@ -990,13 +988,13 @@ Right now the only case I know of where this needs to be overwritten is for the Save Changes? - 保存更改吗? + 保存修改? You made changes to the current executable, do you want to save them? - + 当前可执行文件被修改,是否需要保存? @@ -1136,7 +1134,7 @@ Right now the only case I know of where this needs to be overwritten is for the <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1180,87 +1178,87 @@ p, li { white-space: pre-wrap; } - + Extracting files 正在解压文件 - + failed to create backup 创建备份失败。 - + Mod Name 模组名称 - + Name 名称 - + Invalid name 无效名称 - + The name you entered is invalid, please enter a different one. 你输入的名称无效,请重新输入。 - + File format "%1" not supported 暂不支持文件格式: "%1" - + None of the available installer plugins were able to handle that archive 没有可用的安装插件能够处理此压缩包 - + no error 没有错误 - + 7z.dll not found 未找到 7z.dll - + 7z.dll isn't valid 无效的 7z.dll - + archive not found 未找到压缩包 - + failed to open archive 无法打开压缩包 - + unsupported archive type 不支持的压缩包类型 - + internal library error 内部库错误 - + archive invalid 无效的压缩包 - + unknown archive error 未知压缩包错误 @@ -1301,7 +1299,7 @@ p, li { white-space: pre-wrap; } an error occured: %1 - 发生错误: %1 + 发生错误: %1 @@ -1320,27 +1318,27 @@ p, li { white-space: pre-wrap; } Click blank area to deselect - + 点击空白处取消选择 If checked, only mods that match all selected categories are displayed. - + 如果选中,仅匹配所有选定类别的 Mod 会被显示。 And - + If checked, all mods that match at least one of the selected categories are displayed. - + 如果选中,匹配任意一个选定类别的 Mod 会被显示。 Or - + @@ -1350,22 +1348,22 @@ p, li { white-space: pre-wrap; } Pick a module collection - 选择一个配置文件 + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在这里创建配置文件,每个配置文件都包含了它们自己的 Mod 和 esp 的激活方案。这样您就可以通过快速切换设置来体验不同的游戏历程了。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">请注意: 当前您的配置文件的 esp 加载顺序并不是分开保存的。</span></p></body></html> + + + + + Open list options... + 打开选项列表... @@ -1376,13 +1374,13 @@ p, li { white-space: pre-wrap; } Restore Backup... - 还原备份 + 还原备份 Create Backup - + 创建备份 @@ -1397,7 +1395,7 @@ p, li { white-space: pre-wrap; } Filter - 过滤器 + 过滤器 @@ -1426,15 +1424,10 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">选择要运行的程序。一旦您开始使用 Mod Organizer,您应该始终从这里或通过在这里创建的快捷方式来运行您的游戏和工具,否则任何经由 MO 安装的 Mod 都会变得不可见。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保证一些我没有测试过的工具能够正常工作。</span></p></body></html> + @@ -1446,13 +1439,9 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 启用的状态下运行指定的程序。</span></p></body></html> + @@ -1469,13 +1458,9 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">创建一个开始菜单快捷方式,使您可以直接在 MO 激活状态下运行指定的程序。</span></p></body></html> + @@ -1487,6 +1472,11 @@ p, li { white-space: pre-wrap; } Plugins 插件 + + + Sort + 排序 + List of available esp/esm files @@ -1497,43 +1487,29 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这个列表中包含了位于已激活 Mod 里的 esp 和 esm 文件。这些文件都需要它们自己的加载顺序,您可以使用拖放来修改加载顺序。请注意: MO 将只保存已激活或已勾选状态的 Mod 的加载顺序。<br />有个非常棒的工具叫作 &quot;BOSS&quot;,它可以自动对这些文件进行排序。</span></p></body></html> - - - - Sort - - - - - Open list options... - + Archives - + 压缩包 <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> - + <html><head/><body><p>使用 MO 管理压缩包 (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">查看更多</span></a>)</p></body></html> List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - 可用 BSA 文件的列表。未勾选的项目不会被 MO 管理并且会忽略安装顺序。 + 可用 BSA 压缩包的列表。未勾选的项目不会被 MO 管理并且会忽略安装顺序。 @@ -1541,10 +1517,7 @@ p, li { white-space: pre-wrap; } By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - BSA 文件是 Bethesda 专用的压缩包文件 (区别于 .zip 文件),里面包含了游戏所用的 Data 内的文件 (meshes, textures 等)。这与 Data 目录里分散的文件是不同的。 -默认情况下,BSA 文件的名称取决于 ESP 插件的名称 (例: plugins.esp 对应 plugins.bsa)。游戏运行时,ESP 对应的 BSA 将会自动加载,并且比所有分散的文件优先级都高,左边您设置的安装顺序最终会被忽略掉。 - -这里勾选的 BSA 将会依从您的安装顺序,并且会自行调整加载顺序。 + @@ -1555,7 +1528,7 @@ BSAs checked here are loaded in such a way that your installation order is obeye Data - Data + Data @@ -1569,8 +1542,8 @@ BSAs checked here are loaded in such a way that your installation order is obeye - - + + Refresh 刷新 @@ -1605,17 +1578,11 @@ BSAs checked here are loaded in such a way that your installation order is obeye <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">这是此游戏所有存档的列表,将鼠标悬停在项目上来获取该存档的详细信息,里面包含了现在没有被激活但是当存档被创建时所使用的 esp 或 esm 的清单。</span></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右键菜单中点击“修复 Mod”,那么 MO 便会尝试激活所有 Mod 和 esp 来修复那些缺失的 esp,它并不会禁用任何东西!</span></p></body></html> + @@ -1630,7 +1597,7 @@ p, li { white-space: pre-wrap; } Show Hidden - + 显示隐藏 @@ -1645,7 +1612,7 @@ p, li { white-space: pre-wrap; } Install &Mod - 安装 &Mod + 安装 &Mod @@ -1665,12 +1632,12 @@ p, li { white-space: pre-wrap; } &Profiles - &配置文件 + &配置文件 Configure Profiles - 设置配置文件 + 设置配置文件 @@ -1685,7 +1652,7 @@ p, li { white-space: pre-wrap; } &Executables - &可执行程序 + &可执行程序 @@ -1706,7 +1673,7 @@ p, li { white-space: pre-wrap; } &Tools - 工具(&T) + &工具 @@ -1721,12 +1688,12 @@ p, li { white-space: pre-wrap; } &Settings - &设置 + &设置 Configure settings and workarounds - 配置设定和解决方案 + 配置设定和解决方案 @@ -1750,9 +1717,9 @@ p, li { white-space: pre-wrap; } - + Update - 更新 + 更新 @@ -1763,7 +1730,7 @@ p, li { white-space: pre-wrap; } No Problems - 没有问题 + 没有问题 @@ -1794,19 +1761,19 @@ Right now this has very limited functionality - + Endorse Mod Organizer 称赞 Mod Organizer Copy Log to Clipboard - 复制到剪贴板 + 复制到剪贴板 Ctrl+C - Ctrl+M + Ctrl+C @@ -1826,7 +1793,7 @@ Right now this has very limited functionality Problems - 问题 + 问题 @@ -1836,17 +1803,17 @@ Right now this has very limited functionality Everything seems to be in order - 一切井然有序 + Help on UI - 界面帮助 + 界面帮助 Documentation Wiki - 说明文档 (维基) + 说明文档 (维基) @@ -1861,17 +1828,17 @@ Right now this has very limited functionality About - + About Qt - + 关于 Qt failed to save load order: %1 - 无法保存加载顺序: %1 + 无法保存加载顺序: %1 @@ -1886,7 +1853,7 @@ Right now this has very limited functionality failed to create profile: %1 - 无法创建配置文件: %1 + 无法创建配置文件: %1 @@ -1911,49 +1878,71 @@ Right now this has very limited functionality failed to read savegame: %1 - 无法读取存档: %1 + 无法读取存档: %1 Plugin "%1" failed: %2 - 插件 "%1" 失败: %2 + 插件 "%1" 失败: %2 Plugin "%1" failed - 插件 "%1" 失败 + 插件 "%1" 失败 + + + + Download? + 下载? + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + failed to init plugin %1: %2 - 插件初始化失败 %1: %2 + 插件初始化失败 %1: %2 Plugin error - + 插件错误 It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + Failed to start "%1" - 无法启动 "%1" + 无法启动 "%1" Waiting - 稍等 + 稍等 Please press OK once you're logged into steam. 当您登录 Steam 时请点击确定。 + + + Executable "%1" not found + 可执行程序 "%1" 未找到 + Start Steam? @@ -1965,912 +1954,888 @@ Right now this has very limited functionality 想要正确地启动游戏,Steam 必须处于运行状态。需要MO尝试启动 Steam 吗? - + Also in: <br> - 也在: <br> + 也在: <br> - + No conflict 没有冲突 - + <Edit...> - <编辑...> + <编辑...> - + + Failed to refresh list of esps: %1 + 无法刷新 esp 列表 : %1 + + + This bsa is enabled in the ini file so it may be required! 该 BSA 已在 ini 文件中启用,因此它可能是必需的。 - + Activating Network Proxy 激活网络代理 - - - Installation successful - 安装成功 - - - - - Configure Mod - 配置 Mod + + + Failed to write settings + 无法写入设置 - - - This mod contains ini tweaks. Do you want to configure them now? - 此 Mod 中包含 ini 设定文件,您想现在就对它们进行配置吗? + + + An error occured trying to write back MO settings: %1 + - - - mod "%1" not found - Mod "%1" 未找到 + + File is write protected + - - - Installation cancelled - 安装已取消 + + Invalid file format (probably a bug) + - - - The mod was not installed completely. - 该模组没有完全安装。 + + Unknown error %1 + 未知错误 %1 - + Some plugins could not be loaded 一些插件无法载入 - + Too many esps and esms enabled - + esp 和 esm 开启太多 - - + + Description missing - + 描述丢失 - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + - + Choose Mod 选择模组 - + Mod Archive - Mod 压缩包 + + + + + + Installation successful + 安装成功 + + + + + Configure Mod + 配置 Mod + + + + + This mod contains ini tweaks. Do you want to configure them now? + 此 Mod 中包含 ini 设定文件,您想现在就对它们进行配置吗? + + + + + mod "%1" not found + Mod "%1" 未找到 - + + + Installation cancelled + 安装已取消 + + + + + The mod was not installed completely. + 该模组没有完全安装。 + + + Start Tutorial? 开始教程? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? 即将开始帮助教程。因为技术原因可能无法随时中断。是否继续? - - + + Download started 开始下载 - + failed to update mod list: %1 - 无法更新 Mod 列表: %1 + 无法更新 Mod 列表: %1 - + failed to spawn notepad.exe: %1 - 无法生成 notepad.exe: %1 + 无法生成 notepad.exe: %1 - + failed to open %1 - 无法打开 %1 + - + failed to change origin name: %1 - 无法更改原始文件名: %1 - - - - Executable "%1" not found - + 无法更改原始文件名: %1 - - Failed to refresh list of esps: %1 - + + failed to move "%1" from mod "%2" to "%3": %4 + 无法移动 "%1" 从 mod "%2" 到 "%3": %4 - - failed to move "%1" from mod "%2" to "%3": %4 - + + <Contains %1> + <Contains %1> - + <Checked> - <已勾选> + <Checked> - + <Unchecked> - <未勾选> + - + <Update> - <有更新> + - + + <Managed by MO> + + + + + <Managed outside MO> + + + + <No category> - <无类别> + - + <Conflicted> - <有冲突> + - + <Not Endorsed> - + - + failed to rename mod: %1 - 无法重命名 Mod: %1 + 无法重命名 Mod: %1 - + Overwrite? 覆盖 - + This will replace the existing mod "%1". Continue? - 这将会覆盖已存在的mod "%1"。是否继续? + 这将会覆盖已存在的mod "%1"。是否继续? - + failed to remove mod "%1" - 无法移动 Mod: %1 + 无法移动 mod %1 - - - + + + failed to rename "%1" to "%2" 重命名 "%1 "为 "%2" 时出错 - + Multiple esps activated, please check that they don't conflict. 多个esp已激活,请检查以确保不冲突。 - - - - + + + + Confirm 确认 - + Remove the following mods?<br><ul>%1</ul> - 是否删除下列mod?<br><ul>%1</ul> + 是否删除下列mod?<br><ul>%1</ul> - + failed to remove mod: %1 - 无法移动 Mod: %1 + 无法移动 Mod: %1 - - + + Failed - 失败 + 失败 - + Installation file no longer exists - 安装文件不复存在 + 安装文件不复存在 - + Mods installed with old versions of MO can't be reinstalled in this way. - 旧版 MO 安装的 Mod 无法使用此方法重新安装。 + 旧版 MO 安装的 Mod 无法使用此方法重新安装。 - - - You need to be logged in with Nexus to endorse + + You need to be logged in with Nexus to resume a download 你必须登录 Nexus 才能点“称赞” - - - Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. - - - - - - - Delete %n save(s) - - - - - - - Extract BSA - 解压 BSA - - - - failed to read %1: %2 - 无法读取 %1: %2 + + + You need to be logged in with Nexus to endorse + 你必须登录 Nexus 才能点“称赞” - - This archive contains invalid hashes. Some files may be broken. - 压缩包 Hash 值错误。部分文件可能已经损坏。 + + Failed to display overwrite dialog: %1 + 无法显示 overwrite 对话: %1 - + Nexus ID for this Mod is unknown 此模组的Nexus ID未知 - - Download? - 下载 - - - - A download has been started but no installed page plugin recognizes it. -If you download anyway no information (i.e. version) will be associated with the download. -Continue? - - - - - Browse Mod Page - - - - - - Failed to write settings - - - - - - An error occured trying to write back MO settings: %1 - - - - - File is write protected - - - - - Invalid file format (probably a bug) - - - - - Unknown error %1 - - - - - <Managed by MO> - - - - - <Managed outside MO> - - - - - You need to be logged in with Nexus to resume a download - 你必须登录 Nexus 才能点“称赞” - - - - Failed to display overwrite dialog: %1 - - - - - + + Create Mod... 创建Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + 这会移动所有的 overwirite 文件成为一个新建的 mod。 +请输入名称: - + A mod with this name already exists 同名模组已存在。 - + Continue? - + 继续? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - - + + Sorry - + 抱歉 - + I don't know a versioning scheme where %1 is newer than %2. - + - + Really enable all visible mods? 确定要启用全部可见的模组吗? - + Really disable all visible mods? 确定要禁用全部可见的模组吗? - + Choose what to export 选择要导出的内容 - + Everything - 全部 + 全部 - + All installed mods are included in the list - 所有包含在列表的已安装mod + 所有包含在列表的已安装mod - + Active Mods 激活模组 - + Only active (checked) mods from your current profile are included 仅包含当前配置文件中已激活(打勾)的mod - + Visible - 可见的 + 可见的 - + All mods visible in the mod list are included - 包含列表中所有可见的mod + 包含列表中所有可见的mod - + export failed: %1 - 导出失败: %1 + 导出失败: %1 - + Install Mod... 安装模组... - + Enable all visible 启用所有可见项目 - + Disable all visible 禁用所有可见项目 - + Check all for update 检查所有更新 - + Export to csv... 导出为 CSV... - + All Mods - + 所有模组 - + Sync to Mods... - 同步到 Mod... + 同步到模组... - + Restore Backup 还原备份 - + Remove Backup... - 还原备份... + 移除备份 - + Add/Remove Categories - + 添加/移除 类别 - + Replace Categories - + 更换类别 - + Primary Category 主分类 - + Change versioning scheme - + - + Un-ignore update - + - + Ignore update - + - + Rename Mod... 重命名模组... - + Remove Mod... 移除模组... - + Reinstall Mod 重新安装模组 - + Un-Endorse 取消称赞 - - + + Endorse 称赞 - + Won't endorse 不想称赞 - + Endorsement state unknown 称赞状态不明 - + Ignore missing data 忽略丢失的数据 - + Visit on Nexus 在Nexus上浏览 - + Open in explorer 在资源管理器中打开 - + Information... - 信息... + - - + + Exception: - 例外: + - - + + Unknown exception - 未知的例外 + - + <All> - <全部> + - + <Multiple> - XX + + + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + - + Please wait while LOOT is running - + - + Fix Mods... 修复模组... - - Delete - &删除 + + + Delete %n save(s) + - + failed to remove %1 - 无法删除 %1 + - - + + failed to create %1 - 无法创建 %1 + - + Can't change download directory while downloads are in progress! 下载文件时不能修改下载目录! - + Download failed 下载失败 - + failed to write to file %1 - 无法写入文件 %1 + - + %1 written - 已写入 %1 + - + Select binary - 选择可执行文件 + - + Binary 程序 - + Enter Name 输入名称 - + Please enter a name for the executable 请为该可执行程序输入一个名称 - + Not an executable - 不是可执行程序 + - + This is not a recognized executable. - 无法识别的可执行文件 + - - + + Replace file? - 替换文件? + - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 无法移除 "%1"。也许您需要足够的文件权限? - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + file not found: %1 - esp未找到:%1 + - + failed to generate preview for %1 - + - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + - + Update available 更新可用 - + Open/Execute 打开/执行 - + Add as Executable 添加为可执行文件 - + Preview - + - + Un-Hide 取消隐藏 - + Hide 隐藏 - + Write To File... - 写入文件... + - + Do you want to endorse Mod Organizer on %1 now? - 是否现在就在 %1 点赞支持 Mod Organizer? + - + Thank you! - + - + Thank you for your endorsement! - + - + Request to Nexus failed: %1 - 发往 Nexus 的请求失败: %1 + - - + + login successful 登录成功 - + login failed: %1. Trying to download anyway - 登录失败: %1,请尝试使用别的方法下载 + - + login failed: %1 无法登录: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - 登录失败: %1。您需要登录到N网才能更新 MO + - + + + failed to read %1: %2 + + + + Error 错误 - + failed to extract %1 (errorcode %2) - 无法解压 %1 (错误代码 %2) + + + + + Extract BSA + 解压 BSA - + + This archive contains invalid hashes. Some files may be broken. + + + + Extract... 解压... - + Edit Categories... 编辑类别... - + Deselect filter - + - + Remove 移除 - + Enable all 全部启用 - + Disable all 全部禁用 - + Unlock load order 解锁加载顺序 - + Lock load order 锁定加载顺序 - + depends on missing "%1" - + - + incompatible with "%1" - + - + No profile set - + - + loot failed. Exit code was: %1 - + - + failed to start loot - + - + failed to run loot: %1 - + - + Errors occured - + - + Backup of load order created - + - + Choose backup to restore - + - + No Backups - + - + There are no backups to restore - + - - + + Restore failed - + - - + + Failed to restore the backup. Errorcode: %1 - + - + Backup of modlist created - + @@ -2885,8 +2850,63 @@ This function will guess the versioning scheme under the assumption that the ins ModInfo - - + + Plugins + + + + + Textures + + + + + Meshes + + + + + UI Changes + + + + + Music + + + + + Sound Effects + + + + + Scripts + + + + + SKSE Plugins + + + + + SkyProc Tools + + + + + Strings + + + + + invalid content type %1 + + + + + invalid index %1 无效的索引 %1 @@ -2894,7 +2914,7 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod 这是模组的备份 @@ -2935,7 +2955,7 @@ This function will guess the versioning scheme under the assumption that the ins Ini Files - + @@ -2950,17 +2970,17 @@ This function will guess the versioning scheme under the assumption that the ins Ini Tweaks - + This is a list of ini tweaks (ini modifications that can be toggled). - + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. - + @@ -2985,7 +3005,7 @@ This function will guess the versioning scheme under the assumption that the ins This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + @@ -3004,7 +3024,7 @@ This function will guess the versioning scheme under the assumption that the ins They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + @@ -3107,7 +3127,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3120,7 +3140,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3153,19 +3173,19 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - + Endorse - 称赞 + Notes - 笔记 + @@ -3182,7 +3202,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -3208,264 +3228,264 @@ p, li { white-space: pre-wrap; } 关闭 - + &Delete &删除 - + &Rename &重命名 - + &Hide &隐藏 - + &Unhide &取消隐藏 - + &Open &打开 - + &New Folder &新建文件夹 - - + + Save changes? 保存更改吗? - - + + Save changes to "%1"? - 将更改保存到“%1”吗? + - + File Exists 文件已存在 - + A file with that name exists, please enter a new one 文件名已存在,请输入其它名称 - + failed to move file 无法移动文件 - + failed to create directory "optional" 无法创建 "optional" 目录 - - + + Info requested, please wait 请求信息已发出,请稍后 - + Main 主要文件 - + Update 更新 - + Optional 可选文件 - + Old 旧档 - + Misc 杂项 - + Unknown 未知 - + Current Version: %1 当前版本: %1 - + No update available 没有可用的更新 - + (description incomplete, please visit nexus) (描述信息不完整,请访问N网) - + <a href="%1">Visit on Nexus</a> <a href="%1">访问N网</a> - + Failed to delete %1 无法删除 %1 - - + + Confirm 确认 - + Are sure you want to delete "%1"? 确定要删除 "%1" 吗? - + Are sure you want to delete the selected files? 确定要删除所选的文件吗? - - + + New Folder 新建文件夹 - + Failed to create "%1" 无法创建 "%1" - - + + Replace file? 替换文件? - + There already is a hidden version of this file. Replace it? 已存在同名文件,但该文件被隐藏了。确定要覆盖吗? - - + + File operation failed 文件操作错误 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 无法移除 "%1"。也许您需要足够的文件权限? - - + + failed to rename %1 to %2 无法重命名 %1 为 %2 - + There already is a visible version of this file. Replace it? 已存在同名文件。确定要覆盖吗? - + Un-Hide 取消隐藏 - + Hide 隐藏 - + Name 名称 - + Please enter a name - + - - + + Error 错误 - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. - + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - 此虚拟安装包内包含来自虚拟 Data 树的文件,但文件发生了变化 (例: 被CK修改了) + ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - 无法写入 %1/meta.ini: %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - %1 中未包含 esp 或 esm 和有效的目录 (textures, meshes, interface, ...) + - + Categories: <br> - 种类: <br> + @@ -3473,52 +3493,52 @@ p, li { white-space: pre-wrap; } Game plugins (esp/esm) - + Interface - + Meshes - + Music - + Scripts (Papyrus) - + Script Extender Plugin - + SkyProc Patcher - + Sound - + Strings - + Textures - + @@ -3543,12 +3563,12 @@ p, li { white-space: pre-wrap; } Overwrites files - Overwrites文件 + Overwritten files - 覆盖的 Mod + @@ -3558,12 +3578,12 @@ p, li { white-space: pre-wrap; } Redundant - 冗余 + Non-MO - + @@ -3573,13 +3593,12 @@ p, li { white-space: pre-wrap; } installed version: "%1", newest version: "%2" - installed version: %1, newest version: %2 - 当前版本: %1,最新版本: %2 + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + @@ -3594,7 +3613,7 @@ p, li { white-space: pre-wrap; } drag&drop failed: %1 - 拖拽失败: %1 + @@ -3609,12 +3628,12 @@ p, li { white-space: pre-wrap; } Flags - 标志 + Content - 内容 + @@ -3644,7 +3663,7 @@ p, li { white-space: pre-wrap; } Installation - 安装 + @@ -3680,17 +3699,17 @@ p, li { white-space: pre-wrap; } Emblemes to highlight things that might require attention. - 需要注意被标记为高亮的 + Depicts the content of the mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> - + Time this mod was installed - + @@ -3722,22 +3741,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus - + - + timeout 超时 - + Unknown error - + - + Please check your password 请检查您的密码 @@ -3747,7 +3766,7 @@ p, li { white-space: pre-wrap; } Failed to guess mod id for "%1", please pick the correct one - 提取mod "%1"的ID编号失败,请自行选择正确项。 + @@ -3795,7 +3814,7 @@ p, li { white-space: pre-wrap; } %1 not found - 找不到 %1 + @@ -3833,116 +3852,116 @@ p, li { white-space: pre-wrap; } PluginList - + Name 名称 - + Priority 优先级 - + Mod Index Mod 索引 - + Flags - 标志 + - - + + unknown - 未知 + - + Name of your mods 你的mod名称 - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 - esp未找到:%1 + - - + + Confirm 确认 - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. 您的一些插件名称无效!这些插件无法被游戏载入。请查看 mo_interface.log 来确认那些受影响的插件并重命名它们。 - <b>Origin</b>: %1 - + This plugin can't be disabled (enforced by the game) + 这个插件不能被禁用 (由游戏执行) + <b>Origin</b>: %1 + + + + Author 作者 - + Description 描述 - - This plugin can't be disabled (enforced by the game) - 这个插件不能被禁用 (由游戏执行) - - - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - 恢复 %1 加载顺序失败 + @@ -3950,7 +3969,7 @@ p, li { white-space: pre-wrap; } Preview - + @@ -3963,16 +3982,16 @@ p, li { white-space: pre-wrap; } Problems - 问题 + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + @@ -3983,12 +4002,12 @@ p, li { white-space: pre-wrap; } Fix - 修复 + No guided fix - + @@ -3996,32 +4015,32 @@ p, li { white-space: pre-wrap; } invalid profile name %1 - + failed to create %1 - 无法创建 %1 + failed to write mod list: %1 - 无法更新 Mod 列表: %1 + failed to update tweaked ini file, wrong settings may be used: %1 - 更新tweaked ini文件失败,可能会应用错误的设置: %1 + failed to create tweaked ini: %1 - 创建 tweaked ini: %1 失败 + "%1" is missing or inaccessible - + @@ -4045,7 +4064,7 @@ p, li { white-space: pre-wrap; } failed to parse ini file (%1) - 无法解析 Ini 文件 (%1) + @@ -4056,7 +4075,7 @@ p, li { white-space: pre-wrap; } failed to modify "%1" - 未能找到 "%1" + @@ -4114,7 +4133,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> @@ -4147,7 +4166,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> @@ -4206,7 +4225,7 @@ p, li { white-space: pre-wrap; } Rename - &重命名 + @@ -4258,7 +4277,7 @@ p, li { white-space: pre-wrap; } Invalid profile name - + @@ -4268,17 +4287,17 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + @@ -4324,47 +4343,47 @@ p, li { white-space: pre-wrap; } invalid field name "%1" - 无效的名称 "%1" + invalid type for "%1" (should be integer) - 无效的类型 "%1" (应该是整数) + invalid type for "%1" (should be string) - 无效的类型 "%1" (应该是字符串) + invalid type for "%1" (should be float) - 无效的类型 "%1" (应该是浮点数) + no fields set up yet! - + field not set "%1" - + invalid character in field "%1" - + empty field name - + invalid game type %1 - + @@ -4448,62 +4467,62 @@ p, li { white-space: pre-wrap; } 无法设置代理DLL加载 - + Permissions required 需要权限 - + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + - - + + Woops 糟糕 - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - ModOrganizer已经崩溃。诊断文件是否已经产生?如果你将文件(%1)发送至 sherb@gmx.net ,这个bug有可能会被修复。最好加入崩溃发生时情况的简短说明(用英文吧)。 + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 Mod Organizer 崩溃了!遗憾的是,我无法生成诊断文件: %1 - + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Mod Organizer 的一个实例正在运行 - + No game identified in "%1". The directory is required to contain the game binary and its launcher. "%1" 中未检测到游戏。请确保该路径中包含游戏执行程序以及对应的 Launcher 文件。 - - + + Please select the game to manage 请选择想要管理的游戏 - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + - + failed to start application: %1 - + @@ -4511,28 +4530,28 @@ p, li { white-space: pre-wrap; } 请使用工具栏上的“帮助”来获得所有元素的使用说明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 无法解析配置文件 %1: %2 - + failed to find "%1" 未能找到 "%1" - + failed to access %1 无法访问 %1 - + failed to set file time %1 无法设置文件时间 %1 @@ -4544,8 +4563,7 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - "%1" is missing - + @@ -4558,6 +4576,11 @@ p, li { white-space: pre-wrap; } Error 错误 + + + failed to open temporary file + + @@ -4581,62 +4604,57 @@ p, li { white-space: pre-wrap; } 代理DLL - + failed to spawn "%1" 无法生成 "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - + failed to spawn "%1": %2 无法生成 "%1": %2 - + "%1" doesn't exist "%1" 不存在 - + failed to inject dll into "%1": %2 无法注入 dll 到 "%1": %2 - + failed to run "%1" 无法运行 "%1" - - - failed to open temporary file - - QueryOverwriteDialog Mod Exists - Mod 已存在 + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - 这个mod看上去已经存在。你是希望将本次压缩包文件添加(覆盖已存在文件)或完全覆盖(旧的文件全删除)?另外你还可以起别的名称安装本mod。 + @@ -4651,17 +4669,17 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Replace - 替换 + Rename - &重命名 + Cancel - 取消 + @@ -4669,27 +4687,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - 等级 + Location - 位置 + Date - 时间 + @@ -4697,7 +4715,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Missing ESPs - 缺失的 ESP + @@ -4705,7 +4723,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - 对话框 + @@ -4730,12 +4748,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Text Files - 文本文件 + failed to open "%1" for writing - + @@ -4761,7 +4779,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe archive.dll not loaded: "%1" - archive.dll 没有载入: "%1" + @@ -4799,7 +4817,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe failed to move outdated files: %1. Please update manually. - 移除过时文件失败: %1。请手动更新 + @@ -4824,7 +4842,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe no file for update found. Please update manually. - 没有发现可更新。请手动更新。 + @@ -4842,18 +4860,18 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Failed - 失败 + Sorry, failed to start the helper application - + attempt to store setting for unknown plugin "%1" - + @@ -4893,7 +4911,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -4904,12 +4922,12 @@ p, li { white-space: pre-wrap; } Style - + graphical style - 界面样式 + @@ -4919,23 +4937,23 @@ p, li { white-space: pre-wrap; } Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Debug - + @@ -4986,37 +5004,37 @@ p, li { white-space: pre-wrap; } User interface - + If checked, the download interface will be more compact. - + Compact Download Interface - + If checked, the download list will display meta information instead of file names. - + Download Meta Information - + Reset stored information from dialogs. - 重设对话框信息。 + This will make all dialogs show up again where you checked the "Remember selection"-box. - 全部对话框将全部重新显示,包括你已勾取过“记住选择”的对话框。 + @@ -5050,7 +5068,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5061,7 +5079,7 @@ p, li { white-space: pre-wrap; } If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - 勾取并在下面输入正确账户,将自动登录Nexus (浏览和下载)。 + @@ -5086,7 +5104,7 @@ p, li { white-space: pre-wrap; } Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - 取消自动联网功能。这并不影响用户调用功能(如检查mod更新,点赞支持mod,打开网页浏览)。 + @@ -5101,7 +5119,7 @@ p, li { white-space: pre-wrap; } Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + @@ -5111,12 +5129,12 @@ p, li { white-space: pre-wrap; } Associate with "Download with manager" links - + Known Servers (updated on download) - + @@ -5146,17 +5164,17 @@ p, li { white-space: pre-wrap; } Key - 关键 + Value - + Blacklisted Plugins (use <del> to remove): - + @@ -5178,7 +5196,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> @@ -5216,10 +5234,10 @@ p, li { white-space: pre-wrap; } There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - + @@ -5234,8 +5252,8 @@ If you use the Steam version of Oblivion the default will NOT work. In this case Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. Mod Organizer 使用了一个N网所提供的 API 来进行类似于检查更新和下载文件这样的操作。遗憾的是这个 API 并没有给第三方工具 (比如 MO) 正式的授权,所以我们需要模拟 NMM 来进行这些操作。 @@ -5251,7 +5269,7 @@ tl;dr-version: If Nexus-features don't work, insert the current version num - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. 看来,游戏偶尔会加载一些没有被激活成插件的 ESP 或 ESM 文件。 我还尚不知道它在什么情况下会这样,但是有用户报告说它在某些情况下是很不必要的。如果这个选项被选中,那么在列表中没有被勾选的 ESP 和 ESM 将不会在游戏中出现,并且也不会被载入。 @@ -5264,36 +5282,36 @@ I don't yet know what the circumstances are, but user reports imply it is i If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. - + Display mods installed outside MO - + @@ -5311,7 +5329,7 @@ For the other games this is not a sufficient replacement for AI! These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + @@ -5388,7 +5406,7 @@ For the other games this is not a sufficient replacement for AI! failed to communicate with running instance: %1 - 无法连接到正在运行的实例: %1 + @@ -5434,17 +5452,17 @@ For the other games this is not a sufficient replacement for AI! Transfer Savegames - 转移存档 + Global Characters - + This is a list of characters in the global location. - 这里是全局位置存档角色列表。 + @@ -5456,7 +5474,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -5469,7 +5487,7 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -5499,17 +5517,17 @@ On Windows XP: Profile Characters - 配置文件中角色 + Overwrite - 覆盖 + Overwrite the file "%1" - 覆盖文件 "%1" + @@ -5523,17 +5541,17 @@ On Windows XP: Copy all save games of character "%1" to the profile? - 是否复制角色 "%1" 的所有游戏存档到这个配置中? + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - 是否移动角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - 是否拷贝角色 "%1" 的所有游戏存档到全局路径?请注意这将使游戏存档的运行编号变得混乱。 + - + \ No newline at end of file diff --git a/src/organizer_zh_TW.ts b/src/organizer_zh_TW.ts index 00cb02ae..08c9ed2e 100644 --- a/src/organizer_zh_TW.ts +++ b/src/organizer_zh_TW.ts @@ -1,38 +1,36 @@ - - - + AboutDialog About - + 關於 Revision: - + 版本: Used Software - + 使用的軟體 Credits - + 歸功於 Translators - + 翻譯者 Others - + 其他 @@ -42,7 +40,7 @@ No license - + @@ -50,19 +48,19 @@ Activate Mods - 激活 Mod + 啟動 Mod This is a list of esps and esms that were active when the save game was created. - 這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被激活。 + 這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被啟動。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of esps and esms that were active when the save game was created.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">For each esp, the right column contains the mod (or mods) that can be enabled to make the missing esps/esms available.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> @@ -71,11 +69,11 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被激活。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是 esp 和 esm 檔案的列表,當您的存檔被建立時將會被啟動。</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">對於每個 esp,右列中包含了可以通過啟用來使缺失的 esp 或 esm 變得可用的 Mod。</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您點擊確定,那麼所有在右列中已選的並且可用的 Mod 和缺失的 esp 都將會被激活。</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您點擊確定,那麼所有在右列中已選的並且可用的 Mod 和缺失的 esp 都將會被啟動。</span></p></body></html> @@ -160,22 +158,22 @@ If there is a component called "00 Core" it is usually required. Optio Some Page - + Search - + 搜尋 new - + failed to start download - + 下載失敗 @@ -226,7 +224,7 @@ If there is a component called "00 Core" it is usually required. Optio <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can match one or multiple nexus categories to a internal ID. Whenever you download a mod from a Nexus Page, Mod Organizer will try to resolve the category defined on the Nexus to one available in MO.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">To find out a category id used by the nexus, visit the categories list of the nexus page and hover over the links there.</span></p></body></html> @@ -315,7 +313,7 @@ p, li { white-space: pre-wrap; } Done - 完成 + 完成 @@ -325,7 +323,7 @@ p, li { white-space: pre-wrap; } pending download - + 等待中的下載 @@ -334,7 +332,7 @@ p, li { white-space: pre-wrap; } Placeholder - 占位符 + 預留位置 @@ -347,7 +345,7 @@ p, li { white-space: pre-wrap; } Paused - Double Click to resume - + 暫停 - 雙擊以回復 @@ -359,7 +357,7 @@ p, li { white-space: pre-wrap; } Uninstalled - Double Click to re-install - 已安裝 - 雙擊重新安裝 + 解除安裝 - 雙擊重新安裝 @@ -368,7 +366,7 @@ p, li { white-space: pre-wrap; } Placeholder - 占位符 + 預留位置 @@ -381,27 +379,27 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + Pending - + 等待 Paused - 暫停 + 暫停 Fetching Info 1 - + Fetching Info 2 - + @@ -411,7 +409,7 @@ p, li { white-space: pre-wrap; } Uninstalled - + 已解除安裝 @@ -439,12 +437,12 @@ p, li { white-space: pre-wrap; } This will permanently remove all finished downloads from this list (but NOT from disk). - + 這將會從列表中永久移除所有已完成的下載(但不會從硬碟中移除)。 This will permanently remove all installed downloads from this list (but NOT from disk). - + 這將會從列表中永久移除所有已安裝的下載(但不會從硬碟中移除)。 @@ -459,17 +457,17 @@ p, li { white-space: pre-wrap; } Delete - &刪除 + 刪除 Un-Hide - 取消隱藏 + 取消隱藏 Remove from View - + @@ -494,12 +492,12 @@ p, li { white-space: pre-wrap; } Delete Installed... - 移除已安裝的項目... + 刪除已安裝的項目... Delete All... - + 刪除所有... @@ -517,22 +515,22 @@ p, li { white-space: pre-wrap; } < mod %1 file %2 > - + Pending - + 等待 Fetching Info 1 - + Fetching Info 2 - + @@ -555,12 +553,12 @@ p, li { white-space: pre-wrap; } This will remove all finished downloads from this list (but NOT from disk). - 這將會從列表和磁碟中移除所有已完成的下載。 + 這將會從列表中移除所有已完成的下載(但不會從硬碟中移除)。 This will remove all installed downloads from this list (but NOT from disk). - 這將會從列表和磁碟中移除所有已安裝的下載項目。 + 這將會從列表中移除所有已安裝的下載(但不會從硬碟中移除)。 @@ -575,17 +573,17 @@ p, li { white-space: pre-wrap; } Delete - &刪除 + 刪除 Un-Hide - 取消隱藏 + 取消隱藏 Remove from View - + @@ -610,12 +608,12 @@ p, li { white-space: pre-wrap; } Delete Installed... - 移除已安裝的項目... + 刪除已安裝的項目... Delete All... - + 刪除所有... @@ -638,7 +636,7 @@ p, li { white-space: pre-wrap; } Memory allocation error (in refreshing directory). - + 記憶體分配錯誤(於重新整理目錄時)。 @@ -658,12 +656,12 @@ p, li { white-space: pre-wrap; } Wrong Game - + The download link is for a mod for "%1" but this instance of MO has been set up for "%2". - + @@ -708,7 +706,7 @@ p, li { white-space: pre-wrap; } No known download urls. Sorry, this download can't be resumed. - + 沒有已知的下載url。抱歉,無法回復這個下載。 @@ -728,7 +726,7 @@ p, li { white-space: pre-wrap; } Update - 更新 + 更新 @@ -753,12 +751,12 @@ p, li { white-space: pre-wrap; } Memory allocation error (in processing progress event). - + 記憶體分配錯誤(於處理程序時)。 Memory allocation error (in processing downloaded data). - + 記憶體分配錯誤(於處理已下載資料時)。 @@ -789,7 +787,7 @@ p, li { white-space: pre-wrap; } Download failed. Server reported: %1 - + 下載失敗。伺服器回報: %1 @@ -959,12 +957,12 @@ Right now the only case I know of where this needs to be overwritten is for the Java (32-bit) required - + 需要Java (32位元) MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary. - + MO需要32位元java來執行這個程式。如果你已經安裝了,選擇那個安裝下的binary中的javaw.exe。 @@ -990,13 +988,13 @@ Right now the only case I know of where this needs to be overwritten is for the Save Changes? - 儲存更改嗎? + 儲存修改? You made changes to the current executable, do you want to save them? - + 你對現在的可執行檔案做出了改變,你想要保存它們嗎? @@ -1104,7 +1102,7 @@ Right now the only case I know of where this needs to be overwritten is for the New Mod - 新增 + 新增 Mod @@ -1136,7 +1134,7 @@ Right now the only case I know of where this needs to be overwritten is for the <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This displays the content of the archive. &lt;data&gt; represents the base directory which will map to the game's data directory. You can change the base directory via the right-click context menu and you can move around files via drag&amp;drop</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -1147,17 +1145,17 @@ p, li { white-space: pre-wrap; } Placeholder - 占位符 + 預留位置 OK - 確定 + 確定 Cancel - 取消 + 取消 @@ -1165,7 +1163,7 @@ p, li { white-space: pre-wrap; } archive.dll not loaded: "%1" - + archive.dll 並未載入: "%1" @@ -1180,87 +1178,87 @@ p, li { white-space: pre-wrap; } - + Extracting files 正在解壓檔案 - + failed to create backup - + 產生備份失敗 - + Mod Name - + Mod 名稱 - + Name - 名稱 + 名稱 - + Invalid name - + 無效的名稱 - + The name you entered is invalid, please enter a different one. - + 你輸入的名稱是無效的,請輸入不同的名稱。 - + File format "%1" not supported 暫不支持檔案格式: "%1" - + None of the available installer plugins were able to handle that archive - + 沒有任何可用的安裝插件能夠處理那個壓縮檔 - + no error 沒有錯誤 - + 7z.dll not found 未找到 7z.dll - + 7z.dll isn't valid 無效的 7z.dll - + archive not found 未找到壓縮包 - + failed to open archive 無法開啟壓縮包 - + unsupported archive type 不支持的壓縮包類型 - + internal library error 內部庫錯誤 - + archive invalid 無效的壓縮包 - + unknown archive error 未知壓縮包錯誤 @@ -1301,12 +1299,12 @@ p, li { white-space: pre-wrap; } an error occured: %1 - 發生錯誤: %1 + 發生錯誤: %1 an error occured - 發生錯誤 + 發生錯誤 @@ -1320,89 +1318,94 @@ p, li { white-space: pre-wrap; } Click blank area to deselect - + 點擊空白區域以取消選擇 If checked, only mods that match all selected categories are displayed. - + 如果打勾,符合所有選擇的類別的mod會被顯示。 And - + 以及 If checked, all mods that match at least one of the selected categories are displayed. - + 如果打勾,符合至少一項選擇的類別的mod會被顯示。 Or - + 或者 Profile - 配置檔案 + 配置檔案 Pick a module collection - 選擇一個配置檔案 + 選擇一個配置檔案 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Create profiles here. Each profile contains its own list of active mods and esps. This way you can quickly switch between setups for different play throughs.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Please note that right now your esp load order is not kept seperate for different profiles.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在這裡建立配置檔案,每個配置檔案都包含了它們自己的 Mod 和 esp 的激活方案。這樣您就可以通過快速切換設定來體驗不同的遊戲歷程了。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">請注意: 當前您的配置檔案的 esp 加載順序並不是分開儲存的。</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在這裡建立配置檔案,每個配置檔案都包含了它們自己的 Mod 和 esp 的啟動方案。這樣您就可以通過快速切換設定來體驗不同的遊戲歷程了。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">請注意: 當前您的配置檔案的 esp 載入順序並不是分開儲存的。</span></p></body></html> + + + + Open list options... + 打開名單選項... Refresh list. This is usually not necessary unless you modified data outside the program. - 重新整理列表,這通常不是必須的,除非您在程式之外修改了檔案的數據。 + 重新整理列表,除非您在程式之外修改了檔案的資料,否則這通常是不必要的。 Restore Backup... - + 回復備份... Create Backup - + 產生備份 List of available mods. - + 可用 Mod 名單 This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders. - + 這是已安裝的 Mod 名單。使用複選框來啟動/關閉 Mod 並拖曳 Mod 來改變它們的"安裝"順序。 Filter - 過濾器 + 篩選 No groups - + 無群組 @@ -1414,126 +1417,121 @@ p, li { white-space: pre-wrap; } Namefilter - + Pick a program to run. - 選擇要運行的程式。 + 選擇要執行的程式。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Choose the program to run. Once you start using ModOrganizer, you should always run your game and tools from here or through shortcuts created here, otherwise mods installed through MO will not be visible.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">You can add new Tools to this list, but I can't promise tools I haven't tested will work.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">選擇要運行的程式。一旦您開始使用 Mod Organizer,您應該始終從這裡或通過在這裡建立的捷徑來運行您的遊戲和工具,否則任何經由 MO 安裝的 Mod 都會變得不可見。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保證一些我沒有測試過的工具能够正常工作。</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">選擇要執行的程式。一旦您開始使用 Mod Organizer,您應該始終從這裡或通過在這裡建立的捷徑來運行您的遊戲和工具,否則任何經由 MO 安裝的 Mod 都會變得不可見。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">您可以添加新的工具到此列表中,但我不能保證一些我沒有測試過的工具能夠正常工作。</span></p></body></html> Run program - 運行程式 + 執行程式 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Run the selected program with ModOrganizer enabled.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 啟用的狀態下運行指定的程式。</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">在 Mod Organizer 啟用的狀態下執行指定的程式。</span></p></body></html> Run - 運行 + 執行 Create a shortcut in your start menu or on the desktop to the specified program - + 為特定的程式在開始功能列或桌面上產生一個捷徑 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This creates a start menu shortcut that directly starts the selected program with the MO active.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">建立一個開始菜單捷徑,使您可以直接在 MO 激活狀態下運行指定的程式。</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">建立一個開始功能列捷徑,使您可以直接在 MO 啟動狀態下執行指定的程式。</span></p></body></html> Shortcut - + 捷徑 Plugins - + 插件 + + + + Sort + 排序 List of available esp/esm files - 可用 esp 或 esm 檔案的列表 + 可用 esp/esm 檔案名單 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This list contains the esps and esms contained in the active mods. These require their own load order. Use drag&amp;drop to modify this load order. Please note that MO will only save the load order for mods that are active/checked.<br />There is a great tool named &quot;BOSS&quot; to automatically sort these files.</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這個列表中包含了位于已激活 Mod 裡的 esp 和 esm 檔案。這些檔案都需要它們自己的加載順序,您可以使用拖放來修改加載順序。請注意: MO 將只儲存已激活或已勾選狀態的 Mod 的加載順序。<br />有個非常棒的工具叫作 &quot;BOSS&quot;,它可以自動對這些檔案進行排序。</span></p></body></html> - - - - Sort - - - - - Open list options... - +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這個列表中包含了位於已啟動 Mod 裡的 esp 和 esm 檔案。這些檔案都需要它們自己的載入順序,您可以拖曳來修改載入順序。請注意: MO 將只儲存已啟動或已勾選狀態的 Mod 的載入順序。<br />有個非常棒的工具叫作 "BOSS",它可以自動對這些檔案進行排序。</span></p></body></html> Archives - + 壓縮包 <html><head/><body><p>BSAs are bundles of game assets (textures, scripts, ...). By default, the engine loads these bundles in a separate step from loose files. MO can manage those archives to align their load order with that of loose files:</p><p>If archives are <span style=" font-weight:600;">managed</span>, their load order is specified by the priority of the corresponding mod (left pane), the same as the loose files. You can manually enable any BSA that has no corresponding plugin active.<br/></p><p>If archives are <span style=" font-weight:600;">not managed</span> their load order is specified by the priority of the corresponding plugin (right pane, plugins tab). You can then not manually enable BSAs where the plugin isn't active.</p><p>In either case you can not disable archives if there is a matching plugin, the game will load them no matter what.</p></body></html> - + <html><head/><body><p>BSA 檔案是遊戲資源的包裹(材質、腳本......)。預設情況下,遊戲引擎自分散的檔案中分別載入這些包裹。MO 可以透過分散的檔案來管理這些壓縮包以調整它們的載入順序:</p><p>如果壓縮包<span style=" font-weight:600;">有被管理</span>,它們的載入順序是被對應的 Mod(左側的面板)決定其優先程度,分散的檔案也是如此。您可以手動啟用任何沒有對應插件於啟動狀態的 BSA 檔案。<br/></p><p>如果壓縮包 <span style=" font-weight:600;">沒有被管理</span>,它們的載入順序是被對應的插件(右側的面板,插件選項)決定其優先程度。您就不可以手動啟用沒有插件於啟動狀態的 BSA 檔案。</p><p>在兩種狀況下您都不可以關閉有對應插件的壓縮包,遊戲仍然會載入它們。</p></body></html> <html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html> - + List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order. - 可用 BSA 檔案的列表。未勾選的項目不會被 MO 管理並且會忽略安裝順序。 + 可用 BSA 檔案的列表。未勾選的項目不會被 MO 管理並且會忽略安裝順序。 @@ -1541,229 +1539,229 @@ p, li { white-space: pre-wrap; } By default, BSAs that share their base name with an enabled ESP (i.e. plugin.esp and plugin.bsa) are automatically loaded and will have precedence over all loose files, the installation order you set up to the left is then ignored! BSAs checked here are loaded in such a way that your installation order is obeyed properly. - BSA 檔案是 Bethesda 專用的壓縮包檔案 (區別於 .zip 檔案),裡面包含了遊戲所用的 Data 內的檔案 (meshes, textures 等)。這與 Data 目錄裡分散的檔案是不同的。 + BSA 檔案是 Bethesda 專用的壓縮包檔案 (區別於 .zip 檔案),裡面包含了遊戲所用的 Data 內的檔案 (meshes, textures 等)。這與 Data 目錄裡分散的檔案是不同的。 默認情況下,BSA 檔案的名稱取決於 ESP 插件的名稱 (例: plugins.esp 對應 plugins.bsa)。遊戲運行時,ESP 對應的 BSA 將會自動加載,並且比所有分散的檔案優先級都高,左邊您設定的安裝順序最終會被忽略掉。 -這裡勾選的 BSA 將會依從您的安裝順序,並且會自行調整加載順序。 +這裡勾選的 BSA 將會依從您的安裝順序,並且會自行調整載入順序。 File - 檔案 + 檔案 Data - Data + 資料 refresh data-directory overview - 重新整理 Data 目錄總覽 + 重新整理 Data 目錄總覽 Refresh the overview. This may take a moment. - 重新整理總覽,這可能需要一些時間。 + 重新整理總覽,這可能需要一些時間。 - - + + Refresh - 重新整理 + 重新整理 This is an overview of your data directory as visible to the game (and tools). - 這是在遊戲中可見的 Data 目錄 (和工具) 的總覽。 + 這是在遊戲中可見的 Data 目錄 (和工具) 的總覽。 Mod - Mod + Mod Filter the above list so that only conflicts are displayed. - 過濾上面的列表,使您只能看到有衝突的檔案。 + 篩選上面的列表,使您只能看到有衝突的檔案。 Show only conflicts - 只顯示衝突 + 只顯示衝突 Saves - 存檔 + 存檔 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a list of all savegames for this game. Hover over a list entry to get detailed information about the save including a list of esps/esms that were used at the time this save was created but aren't active now.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you click &quot;Fix Mods...&quot; in the context menu, MO will try to activate all mods and esps to fix those missing esps. It will not disable anything!</span></p></body></html> - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是此遊戲所有存檔的列表,將滑鼠懸停在項目上來獲取該存檔的詳細信息,裡面包含了現在沒有被激活但是當存檔被建立時所使用的 esp 或 esm 的清單。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">這是此遊戲所有存檔的列表,將滑鼠懸停在項目上來獲取該存檔的詳細信息,裡面包含了現在沒有被啟動但是當存檔被建立時所使用的 esp 或 esm 的清單。</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右鍵菜單中點擊“修復 Mod”,那麼 MO 便會嘗試激活所有 Mod 和 esp 來修復那些缺失的 esp,它並不會禁用任何東西!</span></p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您在右鍵選項中點擊“修復 Mod”,那麼 MO 便會嘗試啟動所有 Mod 和 esp 來修復那些缺失的 esp,它並不會禁用任何東西!</span></p></body></html> Downloads - 下載 + 下載 This is a list of mods you downloaded from Nexus. Double click one to install it. - 這是當前已下載的 Mod 的列表,雙擊進行安裝。 + 這是自N網下載的 Mod 的列表,雙擊進行安裝。 Show Hidden - + 顯示隱藏 Tool Bar - 工具欄 + 工具欄 Install Mod - 安裝 Mod + 安裝 Mod Install &Mod - 安裝 &Mod + 安裝 &Mod Install a new mod from an archive - 通過壓縮包來安裝一個新 Mod + 通過壓縮包來安裝一個新 Mod Ctrl+M - Ctrl+M + Ctrl+M Profiles - 配置檔案 + 配置檔案 &Profiles - &配置檔案 + &配置檔案 Configure Profiles - 設定配置檔案 + 設定配置檔案 Ctrl+P - Ctrl+P + Ctrl+P Executables - 可執行程式 + 可執行程式 &Executables - &可執行程式 + &可執行程式 Configure the executables that can be started through Mod Organizer - 配置可通過 MO 來啟動的程式 + 配置可透過 Mod Organizer 來啟動的程式 Ctrl+E - Ctrl+E + Ctrl+E Tools - + 工具 &Tools - + &工具 Ctrl+I - Ctrl+I + Ctrl+I Settings - 設定 + 設定 &Settings - &設定 + &設定 Configure settings and workarounds - 配置設定和解決方案 + 配置設定和解決方案 Ctrl+S - Ctrl+S + Ctrl+S Nexus - N網 + N網 Search nexus network for more mods - 搜尋N網以獲取更多 Mod + 搜尋N網以獲取更多 Mod Ctrl+N - Ctrl+N + Ctrl+N - + Update - 更新 + 更新 Mod Organizer is up-to-date - Mod Organizer 現在是最新版本 + Mod Organizer 現在是最新版本 No Problems - 沒有問題 + 沒有問題 @@ -1771,7 +1769,7 @@ p, li { white-space: pre-wrap; } !Work in progress! Right now this has very limited functionality - 如果 MO 檢測到您的安裝中存在潛在的問題,那麼此按鈕將會高亮顯示,同時 MO 也會給您相應的修復提示。 + 如果 MO 檢測到您的安裝中存在潛在的問題,那麼此按鈕將會高亮顯示,同時 MO 也會給您相應的修復提示。 !此功能尚未完善! 當前此功能所能提供的項目非常有限 @@ -1780,103 +1778,103 @@ Right now this has very limited functionality Help - 幫助 + 幫助 Ctrl+H - Ctrl+M + Ctrl+M Endorse MO - + 贊同 MO - + Endorse Mod Organizer - + 贊同 Mod Organizer Copy Log to Clipboard - + 複製紀錄到剪貼簿上 Ctrl+C - Ctrl+M + Ctrl+C Toolbar - 工具欄 + 工具欄 Desktop - + 桌面 Start Menu - + 開始功能列 Problems - 問題 + 問題 There are potential problems with your setup - 您的安裝中存在潛在的問題 + 您的安裝中存在潛在的問題 Everything seems to be in order - 一切井然有序 + 一切看來井然有序 Help on UI - 介面幫助 + 使用者介面幫助 Documentation Wiki - 說明文檔 (維基) + 說明文檔 (Wiki) Report Issue - 報告問題 + 回報問題 Tutorials - + 教學 About - + 關於 About Qt - + 關於 Qt failed to save load order: %1 - 無法儲存加載順序: %1 + 儲存加載順序失敗: %1 Name - 名稱 + 名稱 @@ -1886,991 +1884,990 @@ Right now this has very limited functionality failed to create profile: %1 - 無法建立配置檔案: %1 + 建立配置檔案失敗: %1 Show tutorial? - + 顯示教學? You are starting Mod Organizer for the first time. Do you want to show a tutorial of its basic features? If you choose no you can always start the tutorial from the "Help"-menu. - + 您正第一次啟動 Mod Organizer。您想要顯示其基本功能的教學嗎?如果你選擇不要,您仍然可以自"幫助"選單開啟教學。 Downloads in progress - 正在下載 + 正在下載 There are still downloads in progress, do you really want to quit? - 仍有正在進行中的下載,您確定要退出嗎? + 仍有正在進行中的下載,您確定要退出嗎? failed to read savegame: %1 - 無法讀取存檔: %1 + 讀取存檔失敗: %1 Plugin "%1" failed: %2 - + 插件 "%1" 無效: %2 Plugin "%1" failed - + 插件 "%1" 無效 + + + + Download? + 下載? + + + + A download has been started but no installed page plugin recognizes it. +If you download anyway no information (i.e. version) will be associated with the download. +Continue? + + + + + Browse Mod Page + 瀏覽 Mod 頁面 failed to init plugin %1: %2 - + 初始化插件 %1 失敗: %2 Plugin error - + 插件錯誤 It appears the plugin "%1" failed to load last startup and caused MO to crash. Do you want to disable it? (Please note: If this is the first time you see this message for this plugin you may want to give it another try. The plugin may be able to recover from the problem) - + 插件 "%1" 在開始時載入失敗並導致 MO 崩潰。您想要關閉它嗎? +(請注意:如果這是您第一次看到有關這個插件的訊息,您最好再讓它試一次。這個插件可能有辦法自問題中修復。) Failed to start "%1" - 無法啟動 "%1" + 啟動 "%1" 失敗 Waiting - 稍等 + 稍等 Please press OK once you're logged into steam. - 當您登入 Steam 時請點擊確定。 + 當您登入 Steam 後請點擊確定。 + + + + Executable "%1" not found + 找不到可執行程式 "%1" Start Steam? - 啟動 Steam? + 啟動 Steam? Steam is required to be running already to correctly start the game. Should MO try to start steam now? - 想要正確地啟動遊戲,Steam 必須處於運行狀態,MO 要立即啟動 Steam 嗎? + Steam 必須處於運行狀態以正確地啟動遊戲,MO 要立即啟動 Steam 嗎? - + Also in: <br> - 也在: <br> + 也在: <br> - + No conflict - 沒有衝突 + 沒有衝突 - + <Edit...> - <編輯...> + <編輯...> - - This bsa is enabled in the ini file so it may be required! - 該 BSA 已在 Ini 檔案中啟用,因此它可能是必需的。 + + Failed to refresh list of esps: %1 + 重新整理 esp 列表失敗: %1 - - Activating Network Proxy - + + This bsa is enabled in the ini file so it may be required! + 該 BSA 已在 Ini 檔案中啟用,因此它可能是必需的。 - - - Installation successful - 安裝成功 + + Activating Network Proxy + - - - Configure Mod - 配置 Mod + + + Failed to write settings + 寫入設定失敗 - - - This mod contains ini tweaks. Do you want to configure them now? - 此 Mod 中包含 Ini 設定檔案,您想現在就對它們進行配置嗎? + + + An error occured trying to write back MO settings: %1 + - - - mod "%1" not found - Mod "%1" 未找到 + + File is write protected + 檔案是唯獨的狀態 - - - Installation cancelled - 安裝已取消 + + Invalid file format (probably a bug) + 無效的檔案格式 (可能是 bug) - - - The mod was not installed completely. - Mod 沒有完全安裝。 + + Unknown error %1 + 未知錯誤 %1 - + Some plugins could not be loaded - + 有些插件無法被載入 - + Too many esps and esms enabled - + 過多的 esp 和 esm 啟動 - - + + Description missing - + 描述遺失 - + The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version: - + 以下的插件無法被載入。原因可能是因為遺失了它的依存檔案(例如 python)或是過期的版本: - + The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or merge some plugins into one. You can find a guide here: <a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + 遊戲不允許超過 255 個啟動的插件(包括正式的)被載入。您必須關閉一些未使用的插件或是合併一些插件。您可以在此找到導覽:<a href="http://wiki.step-project.com/Guide:Merging_Plugins">http://wiki.step-project.com/Guide:Merging_Plugins</a> - + Choose Mod - 選擇 Mod + 選擇 Mod - + Mod Archive - Mod 壓縮包 + Mod 壓縮包 + + + + + Installation successful + 安裝成功 + + + + + Configure Mod + 配置 Mod + + + + + This mod contains ini tweaks. Do you want to configure them now? + 此 Mod 中包含 ini 設定檔案,您想現在就對它們進行配置嗎? + + + + + mod "%1" not found + Mod "%1" 未找到 + + + + + Installation cancelled + 安裝已取消 + + + + + The mod was not installed completely. + Mod 沒有完全安裝。 - + Start Tutorial? - + 開始教學? - + You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue? - + 您即將開始教學。因為技術上的原因所已無法提前結束教學。繼續? - - + + Download started - 開始下載 + 開始下載 - + failed to update mod list: %1 - 無法更新 Mod 列表: %1 + 更新 Mod 列表失敗: %1 - + failed to spawn notepad.exe: %1 - 無法生成 notepad.exe: %1 + 生成 notepad.exe 失敗: %1 - + failed to open %1 - 無法開啟 %1 + 開啟 %1 失敗 - + failed to change origin name: %1 - 無法更改原始檔案名: %1 + 更改原始檔名失敗: %1 - - Executable "%1" not found - - - - - Failed to refresh list of esps: %1 - + + failed to move "%1" from mod "%2" to "%3": %4 + 自 Mod "%2" 移動 "%1" 到 Mod "%3" 失敗: %4 - - failed to move "%1" from mod "%2" to "%3": %4 - + + <Contains %1> + - + <Checked> - <已勾選> + <已勾選> - + <Unchecked> - <未勾選> + <未勾選> - + <Update> - <有更新> + <有更新> + + + + <Managed by MO> + <由 MO 管理> - + + <Managed outside MO> + <非由 MO 管理> + + + <No category> - <無類別> + <無類別> - + <Conflicted> - + <有衝突> - + <Not Endorsed> - + <尚未贊同> - + failed to rename mod: %1 - 無法重新命名 Mod: %1 + 重新命名 Mod 失敗: %1 - + Overwrite? - 覆蓋 + 覆蓋? - + This will replace the existing mod "%1". Continue? - + 即將取代已存在的 Mod "%1"。繼續? - + failed to remove mod "%1" - 無法移動 Mod: %1 + 移除 Mod 失敗 "%1" - - - + + + failed to rename "%1" to "%2" 重新命名 "%1 "為 "%2" 時出錯 - + Multiple esps activated, please check that they don't conflict. - + - - - - + + + + Confirm - 確認 + 確認 - + Remove the following mods?<br><ul>%1</ul> - + - + failed to remove mod: %1 - 無法移動 Mod: %1 + 移除 Mod 失敗: %1 - - + + Failed - 失敗 + 失敗 - + Installation file no longer exists - 安裝檔案不複存在 + 安裝檔案不復存在 - + Mods installed with old versions of MO can't be reinstalled in this way. - 舊版 MO 安裝的 Mod 無法使用此方法重新安裝。 - - - - - You need to be logged in with Nexus to endorse - - - - - Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. - - - - - - - Delete %n save(s) - - - + 舊版 MO 安裝的 Mod 無法使用此方法重新安裝。 - - Extract BSA - 解壓 BSA + + You need to be logged in with Nexus to resume a download + 您必須登入N網以回復下載。 - - - failed to read %1: %2 - 無法讀取 %1: %2 + + + You need to be logged in with Nexus to endorse + 您必須登入N網以贊同。 - - This archive contains invalid hashes. Some files may be broken. - 壓縮包 Hash 值錯誤。部分檔案可能已經損壞。 + + Failed to display overwrite dialog: %1 + 顯示覆蓋的對話失敗: %1 - + Nexus ID for this Mod is unknown - 此 Mod 的N網 ID 未知 - - - - Download? - 下載 - - - - A download has been started but no installed page plugin recognizes it. -If you download anyway no information (i.e. version) will be associated with the download. -Continue? - - - - - Browse Mod Page - - - - - - Failed to write settings - - - - - - An error occured trying to write back MO settings: %1 - - - - - File is write protected - - - - - Invalid file format (probably a bug) - - - - - Unknown error %1 - - - - - <Managed by MO> - - - - - <Managed outside MO> - - - - - You need to be logged in with Nexus to resume a download - - - - - Failed to display overwrite dialog: %1 - + 此 Mod 的N網 ID 未知 - - + + Create Mod... - + 創造 Mod... - + This will move all files from overwrite into a new, regular mod. Please enter a name: - + 即將移動所有在 overwrite 的檔案到一個新的、正常的 Mod。 +請輸入一個名稱: - + A mod with this name already exists - + - + Continue? - + 繼續? - + The versioning scheme decides which version is considered newer than another. This function will guess the versioning scheme under the assumption that the installed version is outdated. - + - - + + Sorry - + 抱歉 - + I don't know a versioning scheme where %1 is newer than %2. - + - + Really enable all visible mods? - 確定要啟用全部可見的 Mod 嗎? + 確定要啟用所有可見的 Mod 嗎? - + Really disable all visible mods? - 確定要禁用全部可見的 Mod 嗎? + 確定要禁用所有可見的 Mod 嗎? - + Choose what to export - + - + Everything - + - + All installed mods are included in the list - + - + Active Mods - 激活 Mod + 啟動 Mod - + Only active (checked) mods from your current profile are included - + - + Visible - + 可見的 - + All mods visible in the mod list are included - + - + export failed: %1 - + - + Install Mod... - 安裝 Mod... + 安裝 Mod... - + Enable all visible - 啟用所有可見項目 + 啟用所有可見項目 - + Disable all visible - 禁用所有可見項目 + 禁用所有可見項目 - + Check all for update - 檢查更新 + 檢查所有的更新 - + Export to csv... - + - + All Mods - + 所有 Mod - + Sync to Mods... - 同步到 Mod... + 同步到 Mod... - + Restore Backup - + 回復備份 - + Remove Backup... - + 移除備份 - + Add/Remove Categories - + 增加/移除類別 - + Replace Categories - + 取代類別 - + Primary Category - + 主要類別 - + Change versioning scheme - + - + Un-ignore update - + 取消忽略更新 - + Ignore update - + 忽略更新 - + Rename Mod... - 重新命名... + 重新命名 Mod... - + Remove Mod... - 移除 Mod... + 移除 Mod... - + Reinstall Mod - 重新安裝 Mod + 重新安裝 Mod - + Un-Endorse - + 取消贊同 - - + + Endorse - + 贊同 - + Won't endorse - + 拒絕贊同 - + Endorsement state unknown - + 贊同狀態不明 - + Ignore missing data - + 忽略遺失的檔案 - + Visit on Nexus - 在N網上流覽 + 在N網上訪問 - + Open in explorer - 在檔案總管中開啟 + 在檔案總管中開啟 - + Information... - 訊息... + 訊息... - - + + Exception: - 例外: + 例外: - - + + Unknown exception - 未知的例外 + 未知的例外 - + <All> - <全部> + <全部> - + <Multiple> - + + + + + Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin. + - + Please wait while LOOT is running - + 在 LOOT 執行時請等待 - + Fix Mods... - 修復 Mod... + 修復 Mod... - - Delete - &刪除 + + + Delete %n save(s) + 刪除存檔 %n - + failed to remove %1 - 無法刪除 %1 + 刪除 %1 失敗 - - + + failed to create %1 - 無法建立 %1 + 建立 %1 失敗 - + Can't change download directory while downloads are in progress! - 下載檔案時不能修改下載目錄! + 下載檔案時不能修改下載路徑! - + Download failed - 下載失敗 + 下載失敗 - + failed to write to file %1 - 無法寫入檔案 %1 + 寫入檔案 %1 失敗 - + %1 written - 已寫入 %1 + 已寫入 %1 - + Select binary - 選擇可執行檔案 + 選擇可執行檔案 - + Binary 程式 - + Enter Name - 輸入名稱 + 輸入名稱 - + Please enter a name for the executable - 請為程式輸入一個名稱 + 請為程式輸入一個名稱 - + Not an executable - 不是可執行程式 + 不是可執行程式 - + This is not a recognized executable. - 無法識別的可執行檔案 + 無法識別的可執行檔案 - - + + Replace file? - 取代檔案? + 取代檔案? - + There already is a hidden version of this file. Replace it? - 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? + 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed - 檔案操作錯誤 + 檔案操作失敗 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 無法移除 "%1"。也許您需要足夠的檔案權限? - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + file not found: %1 - + 找不到檔案: %1 - + failed to generate preview for %1 - + - + Sorry, can't preview anything. This function currently does not support extracting from bsas. - + - + Update available - 更新可用 + 有可用的更新 - + Open/Execute - 開啟/執行 + 開啟/執行 - + Add as Executable - 添加為可執行檔案 + 添加為可執行檔案 - + Preview - + 預覽 - + Un-Hide - 取消隱藏 + 取消隱藏 - + Hide - 隱藏 + 隱藏 - + Write To File... - 寫入檔案... + 寫入檔案... - + Do you want to endorse Mod Organizer on %1 now? - + - + Thank you! - + 謝謝您! - + Thank you for your endorsement! - + 感謝您的贊同! - + Request to Nexus failed: %1 - + - - + + login successful - 登入成功 + 登入成功 - + login failed: %1. Trying to download anyway - 登入失敗: %1,請嘗試使用別的方法下載 + 登入失敗: %1,仍然嘗試下載 - + login failed: %1 無法登入: %1 - + login failed: %1. You need to log-in with Nexus to update MO. - 登入失敗: %1。您需要登入到N網才能更新 MO + 登入失敗: %1。您需要登入到N網才能更新 MO - + + + failed to read %1: %2 + 讀取 %1 失敗: %2 + + + Error - 錯誤 + 錯誤 - + failed to extract %1 (errorcode %2) - 無法解壓 %1 (錯誤代碼 %2) + 解壓 %1 失敗(錯誤代碼 %2) + + + + Extract BSA + 解壓 BSA + + + + This archive contains invalid hashes. Some files may be broken. + 壓縮包的 Hash 值錯誤。部分檔案可能已經損壞。 - + Extract... - 解壓... + 解壓... - + Edit Categories... - 編輯類別... + 編輯類別... - + Deselect filter - + - + Remove 移除 - + Enable all - 全部啟用 + 全部啟用 - + Disable all - 全部禁用 + 全部禁用 - + Unlock load order - + 解鎖載入順序 - + Lock load order - + 鎖定載入順序 - + depends on missing "%1" - + - + incompatible with "%1" - + 與 "%1" 不相容 - + No profile set - + - + loot failed. Exit code was: %1 - + - + failed to start loot - + - + failed to run loot: %1 - + - + Errors occured - + - + Backup of load order created - + 產生的載入順序備份 - + Choose backup to restore - + - + No Backups - + - + There are no backups to restore - + - - + + Restore failed - + - - + + Failed to restore the backup. Errorcode: %1 - + - + Backup of modlist created - + @@ -2879,14 +2876,69 @@ This function will guess the versioning scheme under the assumption that the ins Placeholder - 占位符 + 預留位置 ModInfo - - + + Plugins + + + + + Textures + + + + + Meshes + + + + + UI Changes + + + + + Music + + + + + Sound Effects + + + + + Scripts + + + + + SKSE Plugins + + + + + SkyProc Tools + + + + + Strings + + + + + invalid content type %1 + + + + + invalid index %1 無效的索引 %1 @@ -2894,9 +2946,9 @@ This function will guess the versioning scheme under the assumption that the ins ModInfoBackup - + This is the backup of a mod - + @@ -2935,7 +2987,7 @@ This function will guess the versioning scheme under the assumption that the ins Ini Files - + @@ -2950,17 +3002,17 @@ This function will guess the versioning scheme under the assumption that the ins Ini Tweaks - + This is a list of ini tweaks (ini modifications that can be toggled). - + This is a list of ini tweaks. Ini Tweaks are (usually small) fragments of ini files that are applied over existing settings in skyrim.ini/skyrimprefs.ini. Each tweak can be toggled individually. You should check the description of the mod wether the tweaks are really optional. - + @@ -2985,7 +3037,7 @@ This function will guess the versioning scheme under the assumption that the ins This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view. - + @@ -3004,7 +3056,7 @@ This function will guess the versioning scheme under the assumption that the ins They usually contain optional functionality, see the readme. Most mods do not have optional esps, so chances are good you are looking at an empty list. - + @@ -3024,7 +3076,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e This moves a esp to the esp directory so it can be enabled in the main window. Please note that the ESP merely becomes "available", it will not necessarily be loaded! That is configured in the main window of omo. - 移動一個 esp 檔案到 esp 目錄,這樣它就可以在主窗口中啟用了。請注意: ESP 只是變得“可用”,它并不一定會被載入!想要载入请在 MO 的主窗口中勾選。 + 移動一個 esp 檔案到 esp 目錄,這樣它就可以在主視窗中啟用了。請注意: ESP 只是變得“可用”,它並不一定會被載入!想要載入請在 MO 的主視窗中調整。 @@ -3085,7 +3137,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e Primary Category - + @@ -3107,7 +3159,7 @@ Most mods do not have optional esps, so chances are good you are looking at an e <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Mod ID for this mod on Nexus. This is filled in automatically if you downloaded and installed the mod from inside MO. Otherwise you can enter it manually. To find the correct id, find the mod on nexus. The URL will look like this: </span><a href=" http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; text-decoration: underline; color:#0000ff;">http://skyrim.nexusmods.com/downloads/file.php?id=1334</span></a><a href="http://www.skyrimnexus.com/downloads/file.php?id=1334"><span style=" font-size:8pt; color:#000000;">. In this example, 1334 is the id you're looking for. Besides: The above is the link to Mod Organizer on the Nexus. Why not go there now and endorse?</span></a></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3120,7 +3172,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Installed Version of the Mod. The tooltip will contain the current version available on nexus. The installed version is only set if you installed the mod through MO.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -3136,12 +3188,12 @@ p, li { white-space: pre-wrap; } Refresh - 重新整理 + Refresh all information from Nexus. - + @@ -3153,19 +3205,19 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - + Endorse - + Notes - + @@ -3182,7 +3234,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">This is a modifiable directory view of the mod directory. You can move around files using drag &amp; drop and rename them (double click).</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Changes happen immediately on disc, so do</span><span style=" font-size:8pt; font-weight:600;"> be careful</span><span style=" font-size:8pt;">.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> @@ -3195,7 +3247,7 @@ p, li { white-space: pre-wrap; } Previous - + @@ -3208,264 +3260,264 @@ p, li { white-space: pre-wrap; } 關閉 - + &Delete &刪除 - + &Rename &重新命名 - + &Hide &隱藏 - + &Unhide &取消隱藏 - + &Open &開啟 - + &New Folder &新增資料夾 - - + + Save changes? 儲存更改嗎? - - + + Save changes to "%1"? - + - + File Exists 檔案已存在 - + A file with that name exists, please enter a new one 檔案名已存在,請輸入其它名稱 - + failed to move file 無法移動檔案 - + failed to create directory "optional" 無法建立 "optional" 目錄 - - + + Info requested, please wait 請求訊息已發出,請稍後 - + Main 主要檔案 - + Update 更新 - + Optional 可選檔案 - + Old 舊檔 - + Misc 雜項 - + Unknown 未知 - + Current Version: %1 當前版本: %1 - + No update available 沒有可用的更新 - + (description incomplete, please visit nexus) (描述訊息不完整,請訪問N網) - + <a href="%1">Visit on Nexus</a> <a href="%1">訪問N網</a> - + Failed to delete %1 無法刪除 %1 - - + + Confirm 確認 - + Are sure you want to delete "%1"? 確定要刪除 "%1" 嗎? - + Are sure you want to delete the selected files? 確定要刪除所選的檔案嗎? - - + + New Folder 新增資料夾 - + Failed to create "%1" 無法建立 "%1" - - + + Replace file? 取代檔案? - + There already is a hidden version of this file. Replace it? 已存在同名檔案,但該檔案被隱藏了。確定要覆蓋嗎? - - + + File operation failed 檔案操作錯誤 - - + + Failed to remove "%1". Maybe you lack the required file permissions? 無法移除 "%1"。也許您需要足夠的檔案權限? - - + + failed to rename %1 to %2 無法重新命名 %1 為 %2 - + There already is a visible version of this file. Replace it? 已存在同名檔案。確定要覆蓋嗎? - + Un-Hide 取消隱藏 - + Hide 隱藏 - + Name 名稱 - + Please enter a name - + - - + + Error - 錯誤 + - + Invalid name. Must be a valid file name - + - + A tweak by that name exists - + - + Create Tweak - + ModInfoForeign - + This pseudo mod represents content managed outside MO. It isn't modified by MO. - + ModInfoOverwrite - + This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit) - 此虛擬安裝包內包含來自虛擬 Data 樹的檔案,但檔案發生了變化 (例: 被CK修改了) + ModInfoRegular - - + + failed to write %1/meta.ini: error %2 - 無法寫入 %1/meta.ini: %2 + - + %1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory - %1 中未包含 esp 或 esm 和有效的目錄 (textures, meshes, interface, ...) + - + Categories: <br> - 種類: <br> + @@ -3473,52 +3525,52 @@ p, li { white-space: pre-wrap; } Game plugins (esp/esm) - + Interface - + Meshes - + Music - + Scripts (Papyrus) - + Script Extender Plugin - + SkyProc Patcher - + Sound - + Strings - + Textures - + @@ -3528,58 +3580,57 @@ p, li { white-space: pre-wrap; } Backup - + No valid game data - + 無有效的遊戲資料 Not endorsed yet - + Overwrites files - 覆蓋的 Mod + 覆蓋檔案 Overwritten files - 覆蓋的 Mod + Overwrites & Overwritten - + 覆蓋與被覆蓋 Redundant - + Non-MO - + invalid - + installed version: "%1", newest version: "%2" - installed version: %1, newest version: %2 - 當前版本: %1,最新版本: %2 + The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn (i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. Either way you may want to "upgrade". - + @@ -3589,12 +3640,12 @@ p, li { white-space: pre-wrap; } Invalid name - + drag&drop failed: %1 - + @@ -3609,17 +3660,17 @@ p, li { white-space: pre-wrap; } Flags - + Content - 內容 + Mod Name - + @@ -3634,28 +3685,28 @@ p, li { white-space: pre-wrap; } Category - + Nexus ID - N網 ID + Installation - + unknown - 未知 + Name of your mods - + @@ -3670,27 +3721,27 @@ p, li { white-space: pre-wrap; } Category of the mod. - + Id of the mod as used on Nexus. - + Emblemes to highlight things that might require attention. - + Depicts the content of the mod:<br><img src=":/MO/gui/content/plugin" width=32/>Game plugins (esp/esm)<br><img src=":/MO/gui/content/interface" width=32/>interface<br><img src=":/MO/gui/content/mesh" width=32/>Meshes<br><img src=":/MO/gui/content/texture" width=32/>Textures<br><img src=":/MO/gui/content/sound" width=32/>Sounds<br><img src=":/MO/gui/content/music" width=32/>Music<br><img src=":/MO/gui/content/string" width=32/>Strings<br><img src=":/MO/gui/content/script" width=32/>Scripts (Papyrus)<br><img src=":/MO/gui/content/skse" width=32/>Script Extender plugins<br><img src=":/MO/gui/content/skyproc" width=32/>SkyProc Patcher<br> - + Time this mod was installed - + @@ -3722,22 +3773,22 @@ p, li { white-space: pre-wrap; } NXMAccessManager - + Logging into Nexus - + - + timeout 超時 - + Unknown error - + - + Please check your password 請檢查您的密碼 @@ -3747,7 +3798,7 @@ p, li { white-space: pre-wrap; } Failed to guess mod id for "%1", please pick the correct one - + @@ -3770,7 +3821,7 @@ p, li { white-space: pre-wrap; } You can use drag&drop to move files and directories to regular mods. - + @@ -3795,7 +3846,7 @@ p, li { white-space: pre-wrap; } %1 not found - 找不到 %1 + @@ -3833,116 +3884,116 @@ p, li { white-space: pre-wrap; } PluginList - + Name 名稱 - + Priority 優先級 - + Mod Index - + - + Flags - + - - + + unknown - 未知 + - + Name of your mods - + - + Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority. - + - + The modindex determins the formids of objects originating from this mods. - + - + failed to update esp info for file %1 (source id: %2), error: %3 - + - + esp not found: %1 - + - - + + Confirm 確認 - + Really enable all plugins? - + - + Really disable all plugins? - + - + The file containing locked plugin indices is broken - + - + Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them. 您的一些插件名稱無效!這些插件無法被遊戲載入。請查看 mo_interface.log 來確認那些受影響的插件並重新命名它們。 - <b>Origin</b>: %1 - + This plugin can't be disabled (enforced by the game) + 這個插件不能被禁用 (由遊戲執行) + <b>Origin</b>: %1 + + + + Author 作者 - + Description 描述 - - This plugin can't be disabled (enforced by the game) - 這個插件不能被禁用 (由遊戲執行) - - - + Missing Masters - + - + Enabled Masters - + - + failed to restore load order for %1 - + 為 %1 回復載入順序失敗 @@ -3950,7 +4001,7 @@ p, li { white-space: pre-wrap; } Preview - + @@ -3963,16 +4014,16 @@ p, li { white-space: pre-wrap; } Problems - 問題 + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.8pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - + @@ -3983,12 +4034,12 @@ p, li { white-space: pre-wrap; } Fix - + No guided fix - + @@ -3996,32 +4047,32 @@ p, li { white-space: pre-wrap; } invalid profile name %1 - + failed to create %1 - 無法建立 %1 + failed to write mod list: %1 - 無法更新 Mod 列表: %1 + failed to update tweaked ini file, wrong settings may be used: %1 - + failed to create tweaked ini: %1 - + "%1" is missing or inaccessible - + @@ -4035,7 +4086,7 @@ p, li { white-space: pre-wrap; } Overwrite directory couldn't be parsed - + @@ -4045,7 +4096,7 @@ p, li { white-space: pre-wrap; } failed to parse ini file (%1) - 無法解析 Ini 檔案 (%1) + @@ -4056,17 +4107,17 @@ p, li { white-space: pre-wrap; } failed to modify "%1" - 未能找到 "%1" + Delete savegames? - + Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames) - + @@ -4114,28 +4165,28 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This is the list of profiles. Each Profile contains its own list and installation order of enabled mods (from a shared pool), a configuration of enabled esps/esms, a copy of the games ini-file and an optional savegame filter.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Note</span> For technical reasons it's currently not possible to have seperate load-orders for esps. This means you can't load moda.esp before modb.esp in one profile and the other way around in another.</p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">這是配置檔案的列表,每個配置檔案都包含了它們自己的已激活 Mod 的列表和安裝順序 (從共享區域)、一個已激活的 esp 或 esm 的配置、一個遊戲 Ini 檔案的拷貝和一個可選的存檔過濾器。</p> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">這是配置檔案的列表,每個配置檔案都包含了它們自己的已啟動 Mod 的列表和安裝順序 (從共享區域)、一個已啟動的 esp 或 esm 的配置、一個遊戲 Ini 檔案的拷貝和一個可選的存檔過濾器。</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由于技術上的原因,目前不可能有分開儲存的插件加載順序。這意味著您不能同时在兩個配置檔案裡使用兩種不同的插件配置方案。</p></body></html> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">注意: </span>由於技術上的原因,目前不可能有分開儲存的插件載入順序。這意味著您不能同時在兩個配置檔案裡使用兩種不同的插件配置方案。</p></body></html> If checked, savegames are local to this profile and will not appear when starting with a different profile. - + Local Savegames - + @@ -4147,7 +4198,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The games Oblivion, Fallout 3 and Fallout NV contain a bug which prevents texture and mesh replacers (that is: all modifications to meshes and textures already in game) from working.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Mod Organizer uses a workaround called &quot;BSA redirection&quot; (google is your friend) to fix this issue reliably and without further work. Simply activate and forget.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"></p> @@ -4206,18 +4257,18 @@ p, li { white-space: pre-wrap; } Rename - &重新命名 + Transfer save games to the selected profile. - + Transfer Saves - + @@ -4253,12 +4304,12 @@ p, li { white-space: pre-wrap; } Invalid name - + Invalid profile name - + @@ -4268,27 +4319,27 @@ p, li { white-space: pre-wrap; } Are you sure you want to remove this profile (including local savegames if any)? - + Profile broken - + This profile you're about to delete seems to be broken or the path is invalid. I'm about to delete the following folder: "%1". Proceed? - + Rename Profile - + New Name - + @@ -4324,47 +4375,47 @@ p, li { white-space: pre-wrap; } invalid field name "%1" - + invalid type for "%1" (should be integer) - + invalid type for "%1" (should be string) - + invalid type for "%1" (should be float) - + no fields set up yet! - + field not set "%1" - + invalid character in field "%1" - + empty field name - + invalid game type %1 - + @@ -4402,7 +4453,7 @@ p, li { white-space: pre-wrap; } Failed to deactivate script extender loading - 無法停用脚本扩展加載 + 停用腳本擴充載入失敗 @@ -4418,7 +4469,7 @@ p, li { white-space: pre-wrap; } Failed to deactivate proxy-dll loading - 無法停用代理DLL加載 + 停用代理DLL載入失敗 @@ -4430,7 +4481,7 @@ p, li { white-space: pre-wrap; } Failed to set up script extender loading - 無法設定腳本拓展加載 + 設定腳本擴充載入失敗 @@ -4445,65 +4496,65 @@ p, li { white-space: pre-wrap; } Failed to set up proxy-dll loading - 無法設定代理DLL加載 + 設定代理DLL載入失敗 - + Permissions required 需要權限 - + The current user account doesn't have the required access rights to run Mod Organizer. The neccessary changes can be made automatically (the MO directory will be made writable for the current user account). You will be asked to run "helper.exe" with administrative rights. - + - - + + Woops 糟糕 - + ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file (%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. Please include a short description of what you were doing when the crash happened - + - + ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1 Mod Organizer 崩潰了!遺憾的是,我無法生成診斷檔案: %1 - + Mod Organizer Mod Organizer - + An instance of Mod Organizer is already running Mod Organizer 的一個實例正在運行 - + No game identified in "%1". The directory is required to contain the game binary and its launcher. "%1" 中未檢測到遊戲。請確保該路徑中包含遊戲執行程式以及對應的 Launcher 檔案。 - - + + Please select the game to manage 請選擇想要管理的遊戲 - + Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!) - + - + failed to start application: %1 - + @@ -4511,28 +4562,28 @@ p, li { white-space: pre-wrap; } 請使用工具列上的“幫助”來獲得所有元素的使用說明 - - + + <Manage...> <管理...> - + failed to parse profile %1: %2 無法解析配置檔案 %1: %2 - + failed to find "%1" 未能找到 "%1" - + failed to access %1 無法訪問 %1 - + failed to set file time %1 無法設定檔案時間 %1 @@ -4544,8 +4595,7 @@ p, li { white-space: pre-wrap; } "%1" is missing or inaccessible - "%1" is missing - + @@ -4558,6 +4608,11 @@ p, li { white-space: pre-wrap; } Error 錯誤 + + + failed to open temporary file + + @@ -4581,87 +4636,82 @@ p, li { white-space: pre-wrap; } 代理DLL - + failed to spawn "%1" 無法生成 "%1" - + Elevation required - + - + This process requires elevation to run. This is a potential security risk so I highly advice you to investigate if "%1" can be installed to work without elevation. Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe to make changes to the system) - + - + failed to spawn "%1": %2 無法生成 "%1": %2 - + "%1" doesn't exist "%1" 不存在 - + failed to inject dll into "%1": %2 無法注入 dll 到 "%1": %2 - + failed to run "%1" 無法運行 "%1" - - - failed to open temporary file - - QueryOverwriteDialog Mod Exists - + This mod seems to be installed already. Do you want to add files from this archive (overwriting existing ones) or do you want to completely replace the existing files (old files are deleted)? Alternatively you can install this mod under a different name. - + Keep Backup - + Merge - + Replace - 取代 + Rename - &重新命名 + Cancel - 取消 + @@ -4669,27 +4719,27 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Save # - + Character - + Level - + Location - + Date - Data + @@ -4697,7 +4747,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Missing ESPs - 缺失的 ESP + @@ -4705,37 +4755,37 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Dialog - 對話方塊 + Copy To Clipboard - + Save As... - + Close - 關閉 + Save CSV - + Text Files - 文字文件 + failed to open "%1" for writing - + @@ -4748,7 +4798,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Placeholder - 占位符 + 預留位置 @@ -4761,7 +4811,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe archive.dll not loaded: "%1" - + archive.dll 並未載入: "%1" @@ -4799,7 +4849,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe failed to move outdated files: %1. Please update manually. - + @@ -4819,12 +4869,12 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe No incremental update available for this version, the complete package needs to be downloaded (%1 kB) - 沒有可用于此版本的更新檔案,需要下載完整的安裝包 (%1 KB) + 沒有可用於此版本的更新檔案,需要下載完整的安裝包 (%1 kB) no file for update found. Please update manually. - + @@ -4834,7 +4884,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe No download server available. Please try again later. - 沒有可用的下載伺服器,請稍後再嘗試下載。 + 沒有可用的下載伺服器,請稍後再嘗試下載。 @@ -4842,18 +4892,18 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe Failed - 失敗 + Sorry, failed to start the helper application - + attempt to store setting for unknown plugin "%1" - + @@ -4893,7 +4943,7 @@ Start elevated anyway? (you will be asked if you want to allow ModOrganizer.exe <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The display language. This will only displaye languages for which you have a translation installed.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -4904,48 +4954,48 @@ p, li { white-space: pre-wrap; } Style - + graphical style - + graphical style of the MO user interface - + Log Level - + Decides the amount of data printed to "ModOrganizer.log" - + Decides the amount of data printed to "ModOrganizer.log". "Debug" produces very useful information for finding problems. There is usually no noteworthy performance impact but the file may become rather large. If this is a problem you may prefer the "Info" level for regluar use. On the "Error" level the log file usually remains empty. - + Debug - + Info - + Error - 錯誤 + @@ -4986,37 +5036,37 @@ p, li { white-space: pre-wrap; } User interface - + If checked, the download interface will be more compact. - + 如果勾選,下載介面將會更簡潔。 Compact Download Interface - + 壓縮下載介面 If checked, the download list will display meta information instead of file names. - + Download Meta Information - + Reset stored information from dialogs. - + This will make all dialogs show up again where you checked the "Remember selection"-box. - + @@ -5050,7 +5100,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Allows automatic log-in when the Nexus-Page for the game is clicked. Please note that the obfuscation with which the password is stored in modorganizer.ini is not very strong. If you're worried someone might steal your password, don't store it here.</span></p></body></html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> @@ -5061,7 +5111,7 @@ p, li { white-space: pre-wrap; } If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic. - + @@ -5081,82 +5131,82 @@ p, li { white-space: pre-wrap; } Disable automatic internet features - + Disable automatic internet features. This does not affect features that are explicitly invoked by the user (like checking mods for updates, endorsing, opening the web browser) - + Offline Mode - + Use a proxy for network connections. - + Use a proxy for network connections. This uses the system-wide settings which can be configured in Internet Explorer. Please note that MO will start up a few seconds slower on some systems when using a proxy. - + Use HTTP Proxy (Uses System Settings) - + Associate with "Download with manager" links - + Known Servers (updated on download) - + Preferred Servers (Drag & Drop) - + Plugins - + Author: - 作者 + Version: - 版本 + Description: - 描述 + Key - + Value - + Blacklisted Plugins (use <del> to remove): - + @@ -5178,7 +5228,7 @@ p, li { white-space: pre-wrap; } <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The Steam App ID is required to directly start some games. For Skyrim, if this is not set or wrong, the &quot;Mod Organizer&quot; load mechanism may not work properly.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">The preset for this is the App ID of the &quot;regular&quot; version so in most cases, you should be set.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">If you think you have a different version (GotY or something), follow these steps to get to the id:</span></p> @@ -5191,8 +5241,8 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必須的,它被用來直接啟動一些遊戲。對於天際,如果沒有設定或設定錯誤,&quot;Mod Organizer&quot; 的加載機制可能會無法正常工作。</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此預設是應用程式 ID 的“常規”版本,因此在大多數情況下,您應該要重新設定一下。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Steam App ID 是必須的,它被用來直接啟動一些遊戲。對於 Skyrim,如果沒有設定或設定錯誤,&quot;Mod Organizer&quot; 的加載機制可能會無法正常工作。</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">此預設是應用程式 ID 的“正常”版本,因此在大多數情況下,您應該要重新設定一下。</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">如果您認為您有不同的版本 (年度版或其它版本),那麼請參照下列的步驟來獲取 ID: </span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">1. 進入 Steam 裡的遊戲庫</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">2. 右鍵點擊您想要獲取 ID 的遊戲,選擇</span><span style=" font-size:9pt; font-weight:600;">建立桌面捷徑</span></p> @@ -5203,12 +5253,12 @@ p, li { white-space: pre-wrap; } Load Mechanism - 加載機制 + 載入機制 Select loading mechanism. See help for details. - 選擇加載機制,使用幫助查看更多細節。 + 選擇載入機制,使用幫助查看更多細節。 @@ -5216,10 +5266,10 @@ p, li { white-space: pre-wrap; } There are several means to do this: *Mod Organizer* (default) In this mode the Mod Organizer itself injects the dll. The disadvantage is that you always have to start the game through MO or a link created by it. *Script Extender* In this mode, MO is installed as a Script Extender (obse, fose, nvse, skse) plugin. -*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. +*Proxy DLL* In this mode, MO replaces one of the game's dlls with one that loads MO (and the original dll of course). This will ONLY work with Steam games and it has only been tested with Skyrim. Please use this only if the other mechanisms don't work. If you use the Steam version of Oblivion the default will NOT work. In this case, please install obse and use "Script Extender" as the load mechanism. Also you can then not start Oblivion from MO. Instead, use MO only to set up your mods, then quit and start Oblivion through Steam. - + @@ -5234,11 +5284,11 @@ If you use the Steam version of Oblivion the default will NOT work. In this case Mod Organizer uses an API provided by the Nexus to provide features like checking for updates and downloading files. Unfortunately this API has not been made available officially to third party tools like MO so we have to impersonate the Nexus Mod Manager to be allowed in. -On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. -Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. +On top of this Nexus has used the client identification to lock out outdated versions of NMM to force users to update. This means that MO also needs to impersonate the new version of NMM even if MO doesn't need an update. Therefore you can configure the version to identify as here. +Please note that MO does identify itself as MO to the webserver, it's not lying about what it is. It is merely adding a "compatible" NMM version to the user agent. tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - Mod Organizer 使用了一個N網所提供的 API 來進行類似於檢查更新和下載檔案這樣的操作。遺憾的是這個 API 並沒有給第三方工具 (比如 MO) 正式的授權,所以我們需要模擬 NMM 來進行這些操作。 + Mod Organizer 使用了N網所提供的 API 來進行類似於檢查更新和下載檔案這樣的操作。遺憾的是這個 API 並沒有給第三方工具 (比如 MO) 正式的授權,所以我們需要模擬 NMM 來進行這些操作。 在此之前,N網使用了客戶端辨識系統鎖定了舊版本的 NMM,強制用戶更新版本。這意味著 MO 也要模擬新版本的 NMM,即便 MO 自己並不需要更新。因此您需要在這裡配置版本號來進行辨識。 請注意: MO 辨識自己為 MO 到網路伺服器,這並不是欺騙。它僅僅是為用戶代理添加了一個“兼容”的 NMM 版本。 @@ -5247,13 +5297,13 @@ tl;dr-version: If Nexus-features don't work, insert the current version num Enforces that inactive ESPs and ESMs are never loaded. - 強制執行,未激活的 ESP 和 ESM 將不會被加載。 + 強制執行,未啟用的 ESP 和 ESM 將不會被載入。 - It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. + It seems that the Games occasionally load ESP or ESM files even if they haven't been activated as plugins. I don't yet know what the circumstances are, but user reports imply it is in some cases unwanted. If this is checked, ESPs and ESMs not checked in the List are invisible to the game and can not be loaded. - 看來,遊戲偶爾會加載一些沒有被激活成插件的 ESP 或 ESM 檔案。 + 看來遊戲偶爾會載入一些沒有被啟動成插件的 ESP 或 ESM 檔案。 我還尚不知道它在什麼情況下會這樣,但是有用戶報告說它在某些情況下是很不必要的。如果這個選項被選中,那麼在列表中沒有被勾選的 ESP 和 ESM 將不會在遊戲中出現,並且也不會被載入。 @@ -5264,36 +5314,36 @@ I don't yet know what the circumstances are, but user reports imply it is i If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) - + If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on) Uncheck this if you want to use Mod Organizer with total conversions (like Nehrim) but be aware that the game will crash if required files are not enabled. - + Force-enable game files - + Disable this to no longer display mods installed outside MO in the mod list (left pane). Assets from those mods will then be treated as having lowest mod priority together with the original game content. - + By default Mod Organizer will display esp+bsa bundles installed with foreign tools as mods (left pane). This allows you to control their priority in relation to other mods. This is particularly useful if you also use Steam Workshop to install mods. -However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. +However, if you installed loose file mods outside MO which conflict with BSAs also installed outside MO those conflicts can't be resolved correctly. If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps and esms) displayed in the right pane are completely unaffected by this feature. - + Display mods installed outside MO - + @@ -5311,7 +5361,7 @@ For the other games this is not a sufficient replacement for AI! These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here. - + @@ -5388,7 +5438,7 @@ For the other games this is not a sufficient replacement for AI! failed to communicate with running instance: %1 - 無法連接到正在運行的實例: %1 + @@ -5434,17 +5484,17 @@ For the other games this is not a sufficient replacement for AI! Transfer Savegames - + Global Characters - + This is a list of characters in the global location. - + @@ -5456,7 +5506,7 @@ On Windows Vista/Windows 7: On Windows XP: C:\Documents and Settings\[UserName]\My Documents\My Games\Skyrim\Saves - + @@ -5469,47 +5519,47 @@ On Windows XP: C:\Documents and Settings[UserName]\My Documents\My Games\Skyrim\Saves - + Move -> - + Copy -> - + <- Move - + <- Copy - + Done - 完成 + 完成 Profile Characters - + Overwrite - 覆蓋 + 覆蓋 Overwrite the file "%1" - + 覆蓋檔案 "%1" @@ -5517,23 +5567,23 @@ On Windows XP: Confirm - 確認 + 確認 Copy all save games of character "%1" to the profile? - + Move all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + Copy all save games of character "%1" to the global location? Please be aware that this will mess up the running number of save games. - + - + \ No newline at end of file -- cgit v1.3.1 From d23466f036618760de16ee874a3c076f05fe6087 Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 14 Nov 2014 23:33:49 +0100 Subject: - fixed manifest for qt5 - download messages can now be sent from different user accounts --- src/dlls.manifest.qt5 | 12 ++++++++++-- src/singleinstance.cpp | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dlls.manifest.qt5 b/src/dlls.manifest.qt5 index 5a22c2a8..59918f2b 100644 --- a/src/dlls.manifest.qt5 +++ b/src/dlls.manifest.qt5 @@ -7,12 +7,20 @@ - + + + + + + + + + + - \ No newline at end of file diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index a1928523..dcb0fc78 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -54,6 +54,8 @@ SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : } if (m_PrimaryInstance) { connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); + // has to be called before listen + m_Server.setSocketOptions(QLocalServer::WorldAccessOption); m_Server.listen(s_Key); } } -- cgit v1.3.1 From f2f9e11fdd876821107cff0c1c5b9d8ecf66691f Mon Sep 17 00:00:00 2001 From: Tannin Date: Fri, 21 Nov 2014 14:45:30 +0100 Subject: - started on a refactoring moving functionality out of the MainWindow class - started on support for game-plugins --- src/browserdialog.cpp | 1 - src/editexecutablesdialog.cpp | 18 +- src/editexecutablesdialog.ui | 3 + src/executableslist.cpp | 32 +- src/executableslist.h | 13 +- src/installationmanager.cpp | 13 +- src/installationmanager.h | 4 +- src/iuserinterface.h | 39 + src/main.cpp | 36 +- src/mainwindow.cpp | 1851 +++++++++-------------------------------- src/mainwindow.h | 152 +--- src/messagedialog.cpp | 2 +- src/organizer.pro | 11 +- src/organizercore.cpp | 1249 +++++++++++++++++++++++++++ src/organizercore.h | 239 ++++++ src/organizerproxy.cpp | 108 +-- src/organizerproxy.h | 12 +- src/plugincontainer.cpp | 329 ++++++++ src/plugincontainer.h | 102 +++ src/selfupdater.cpp | 11 +- src/selfupdater.h | 4 +- src/shared/fallout3info.cpp | 4 +- src/shared/fallout3info.h | 2 +- src/shared/falloutnvinfo.cpp | 4 +- src/shared/falloutnvinfo.h | 2 +- src/shared/gameinfo.h | 10 +- src/shared/oblivioninfo.cpp | 4 +- src/shared/oblivioninfo.h | 2 +- src/shared/skyriminfo.cpp | 4 +- src/shared/skyriminfo.h | 2 +- 30 files changed, 2509 insertions(+), 1754 deletions(-) create mode 100644 src/iuserinterface.h create mode 100644 src/organizercore.cpp create mode 100644 src/organizercore.h create mode 100644 src/plugincontainer.cpp create mode 100644 src/plugincontainer.h diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index f93ffcae..933b4bc0 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -202,7 +202,6 @@ void BrowserDialog::unsupportedContent(QNetworkReply *reply) return; } - qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData()); emit requestDownload(view->url(), reply); } catch (const std::exception &e) { if (isVisible()) { diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index fe548a9a..3cc749d6 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -53,9 +53,7 @@ ExecutablesList EditExecutablesDialog::getExecutablesList() const void EditExecutablesDialog::refreshExecutablesWidget() { - QListWidget *executablesWidget = findChild("executablesListBox"); - - executablesWidget->clear(); + ui->executablesListBox->clear(); std::vector::const_iterator current, end; m_ExecutablesList.getExecutables(current, end); @@ -65,7 +63,7 @@ void EditExecutablesDialog::refreshExecutablesWidget() temp.setValue(*current); newItem->setData(Qt::UserRole, temp); newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); - executablesWidget->addItem(newItem); + ui->executablesListBox->addItem(newItem); } ui->addButton->setEnabled(false); @@ -96,7 +94,8 @@ void EditExecutablesDialog::saveExecutable() { m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), - (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, + (ui->closeCheckBox->checkState() == Qt::Checked) ? ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE + : ExecutableInfo::CloseMOStyle::DEFAULT_STAY, ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", true, false); } @@ -212,14 +211,14 @@ bool EditExecutablesDialog::executableChanged() || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); + || (selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); } else { return false; } } -void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) +void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) { if (current == NULL) { resetInput(); @@ -249,8 +248,8 @@ void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidget ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); ui->argumentsEdit->setText(selectedExecutable.m_Arguments); ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); - ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); - if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { + ui->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE); + if (selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::NEVER_CLOSE) { ui->closeCheckBox->setEnabled(false); ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); } else { @@ -286,4 +285,3 @@ void EditExecutablesDialog::on_closeButton_clicked() } this->accept(); } - diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index 4f0462db..8e70c1c0 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -28,6 +28,9 @@ Qt::MoveAction + + QAbstractItemView::SingleSelection + diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 2c6754d7..11158c5b 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -31,8 +31,14 @@ using namespace MOShared; QDataStream &operator<<(QDataStream &out, const Executable &obj) { - out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO - << obj.m_SteamAppID << obj.m_WorkingDirectory << obj.m_Custom << obj.m_Toolbar; + out << obj.m_Title + << obj.m_BinaryInfo.absoluteFilePath() + << obj.m_Arguments + << static_cast::type>(obj.m_CloseMO) + << obj.m_SteamAppID + << obj.m_WorkingDirectory + << obj.m_Custom + << obj.m_Toolbar; return out; } @@ -43,7 +49,7 @@ QDataStream &operator>>(QDataStream &in, Executable &obj) in >> obj.m_Title >> binaryTemp >> obj.m_Arguments >> closeStyleTemp >> obj.m_SteamAppID >> obj.m_WorkingDirectory >> obj.m_Custom >> obj.m_Toolbar; - obj.m_CloseMO = (CloseMOStyle)closeStyleTemp; + obj.m_CloseMO = static_cast(closeStyleTemp); obj.m_BinaryInfo.setFile(binaryTemp); return in; } @@ -64,14 +70,16 @@ ExecutablesList::~ExecutablesList() { } -void ExecutablesList::init() +void ExecutablesList::init(IPluginGame *game) { - std::vector executables = GameInfo::instance().getExecutables(); - for (std::vector::const_iterator iter = executables.begin(); iter != executables.end(); ++iter) { - addExecutableInternal(ToQString(iter->title), - QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())).append("/").append(ToQString(iter->binary)), - ToQString(iter->arguments), ToQString(iter->workingDirectory), - iter->closeMO, ToQString(iter->steamAppID)); + m_Executables.clear(); + for (const ExecutableInfo &info : game->executables()) { + addExecutableInternal(info.title(), + info.binary().absoluteFilePath(), + info.arguments().join(" "), + info.workingDirectory().absolutePath(), + info.closeMO(), + info.steamAppID()); } } @@ -143,7 +151,7 @@ void ExecutablesList::addExecutable(const Executable &executable) void ExecutablesList::addExecutable(const QString &title, const QString &executableName, const QString &arguments, - const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID, + const QString &workingDirectory, ExecutableInfo::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar) { QFileInfo file(executableName); @@ -185,7 +193,7 @@ void ExecutablesList::remove(const QString &title) void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, - CloseMOStyle closeMO, const QString &steamAppID) + ExecutableInfo::CloseMOStyle closeMO, const QString &steamAppID) { QFileInfo file(executableName); if (file.exists()) { diff --git a/src/executableslist.h b/src/executableslist.h index 6f7771e5..207190c4 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include /*! @@ -34,7 +35,7 @@ struct Executable { QString m_Title; QFileInfo m_BinaryInfo; QString m_Arguments; - MOShared::CloseMOStyle m_CloseMO; + MOBase::ExecutableInfo::CloseMOStyle m_CloseMO; QString m_SteamAppID; QString m_WorkingDirectory; @@ -64,7 +65,7 @@ public: /** * @brief initialise the list with the executables preconfigured for this game **/ - void init(); + void init(MOBase::IPluginGame *game); /** * @brief retrieve an executable by index @@ -114,7 +115,9 @@ public: * @param arguments arguments to pass to the executable * @param closeMO if true, MO will be closed when the binary is started **/ - void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); + void addExecutable(const QString &title, const QString &executableName, const QString &arguments, + const QString &workingDirectory, MOBase::ExecutableInfo::CloseMOStyle closeMO, + const QString &steamAppID, bool custom, bool toolbar); /** * @brief remove the executable with the specified file name. This needs to be an absolute file path @@ -144,7 +147,9 @@ private: Executable *findExe(const QString &title); - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID); + void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, + const QString &workingDirectory, MOBase::ExecutableInfo::CloseMOStyle closeMO, + const QString &steamAppID); private: diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index d6435e57..0fb1b78d 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -67,9 +67,8 @@ template T resolveFunction(QLibrary &lib, const char *name) } -InstallationManager::InstallationManager(QWidget *parent) - : QObject(parent), m_ParentWidget(parent), - m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) +InstallationManager::InstallationManager() + : m_InstallationProgress(nullptr), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) { QLibrary archiveLib("dlls\\archive.dll"); if (!archiveLib.load()) { @@ -92,6 +91,14 @@ InstallationManager::~InstallationManager() delete m_CurrentArchive; } +void InstallationManager::setParentWidget(QWidget *widget) +{ + m_InstallationProgress.setParent(widget); + for (IPluginInstaller *installer : m_Installers) { + installer->setParentWidget(widget); + } +} + void InstallationManager::queryPassword(LPSTR password) { diff --git a/src/installationmanager.h b/src/installationmanager.h index 336c1ce3..cd20f0dd 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -53,10 +53,12 @@ public: * * @param parent parent object. **/ - explicit InstallationManager(QWidget *parent); + explicit InstallationManager(); virtual ~InstallationManager(); + void setParentWidget(QWidget *widget); + /** * @brief update the directory where mods are to be installed * @param modsDirectory the mod directory diff --git a/src/iuserinterface.h b/src/iuserinterface.h new file mode 100644 index 00000000..76d4c75a --- /dev/null +++ b/src/iuserinterface.h @@ -0,0 +1,39 @@ +#ifndef IUSERINTERFACE_H +#define IUSERINTERFACE_H + + +#include "modinfo.h" +#include +#include + + +class IUserInterface +{ +public: + + void storeSettings(QSettings &settings); + + virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = "") = 0; + + virtual bool waitForProcessOrJob(HANDLE processHandle, LPDWORD exitCode = NULL) = 0; + + virtual void registerPluginTool(MOBase::IPluginTool *tool) = 0; + virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; + + virtual void installTranslator(const QString &name) = 0; + + virtual void disconnectPlugins() = 0; + + virtual bool close() = 0; + + virtual void setEnabled(bool enabled) = 0; + + virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; + + virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; + + virtual bool saveArchiveList() = 0; + +}; + +#endif // IUSERINTERFACE_H diff --git a/src/main.cpp b/src/main.cpp index b4139f09..f292eac7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,6 +42,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include "mainwindow.h" #include "report.h" @@ -398,6 +399,11 @@ int main(int argc, char *argv[]) QSettings settings(dataPath + "/ModOrganizer.ini", QSettings::IniFormat); + OrganizerCore organizer(settings); + + PluginContainer pluginContainer(&organizer); + pluginContainer.loadPlugins(); + QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); bool done = false; @@ -473,9 +479,7 @@ int main(int argc, char *argv[]) #pragma message("edition isn't used?") qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); - ExecutablesList executablesList; - - executablesList.init(); + organizer.updateExecutablesList(settings); if (!bootstrap()) { // requires gameinfo to be initialised! return -1; @@ -483,24 +487,6 @@ int main(int argc, char *argv[]) cleanupDir(); - qDebug("setting up configured executables"); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - CloseMOStyle closeMO = settings.value("closeOnStart").toBool() ? DEFAULT_CLOSE : DEFAULT_STAY; - executablesList.addExecutable(settings.value("title").toString(), - settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - closeMO, - settings.value("steamAppID", "").toString(), - settings.value("custom", true).toBool(), - settings.value("toolbar", false).toBool()); - } - - settings.endArray(); - qDebug("initializing tutorials"); TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/")); @@ -512,11 +498,11 @@ int main(int argc, char *argv[]) int res = 1; { // scope to control lifetime of mainwindow // set up main window and its data structures - MainWindow mainWindow(argv[0], settings); + MainWindow mainWindow(argv[0], settings, organizer, pluginContainer); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); - mainWindow.setExecutablesList(executablesList); mainWindow.readSettings(); QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); @@ -571,7 +557,7 @@ int main(int argc, char *argv[]) if ((arguments.size() > 1) && (isNxmLink(arguments.at(1)))) { qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - mainWindow.externalMessage(arguments.at(1)); + organizer.externalMessage(arguments.at(1)); } splash.finish(&mainWindow); res = application.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 024e2510..4762358e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -16,10 +16,10 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ +#ifndef Q_MOC_RUN #include "mainwindow.h" #include "ui_mainwindow.h" -#include #include "spawn.h" #include "report.h" #include "modlist.h" @@ -61,14 +61,16 @@ along with Mod Organizer. If not, see . #include "aboutdialog.h" #include "safewritefile.h" #include "organizerproxy.h" +#include #include #include #include #include #include +#include +#endif // Q_MOC_RUN #include #include -#include #include #include #include @@ -123,6 +125,7 @@ along with Mod Organizer. If not, see . #include #endif #include +#include #ifdef TEST_MODELS #include "modeltest.h" @@ -136,46 +139,28 @@ using namespace MOShared; -static bool isOnline() -{ - QList interfaces = QNetworkInterface::allInterfaces(); - - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) { - if ( (iter->flags() & QNetworkInterface::IsUp) && - (iter->flags() & QNetworkInterface::IsRunning) && - !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; - } - qDebug("interface %s seems to be up (address: %s)", - qPrintable(iter->humanReadableName()), - qPrintable(addresses[0].ip().toString())); - connected = true; - } - } - - return connected; -} - - -MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), - m_ExeName(exeName), m_OldProfileIndex(-1), - m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), - m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), - m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), - m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), - m_CurrentProfile(NULL), m_AskForNexusPW(false), - m_ArchivesInit(false), m_DirectoryUpdate(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL), - m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled(), m_DidUpdateMasterList(false) +MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, OrganizerCore &organizerCore, PluginContainer &pluginContainer, QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_Tutorial(this, "MainWindow") + , m_ExeName(exeName) + , m_OldProfileIndex(-1) + , m_ModListGroupingProxy(nullptr) + , m_ModListSortProxy(nullptr) + , m_OldExecutableIndex(-1) + , m_GamePath(ToQString(GameInfo::instance().getGameDirectory())) + , m_CategoryFactory(CategoryFactory::instance()) + , m_ContextItem(nullptr) + , m_ContextAction(nullptr) + , m_CurrentSaveView(nullptr) + , m_OrganizerCore(organizerCore) + , m_PluginContainer(pluginContainer) + , m_DidUpdateMasterList(false) { ui->setupUi(this); - this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); + this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_OrganizerCore.getVersion().displayString()); - languageChange(m_Settings.language()); + languageChange(m_OrganizerCore.settings().language()); ui->logList->setModel(LogBuffer::instance()); ui->logList->setColumnWidth(0, 100); @@ -202,16 +187,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget updateToolBar(); - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - // set up mod list - m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); - m_ModListSortProxy->setSourceModel(&m_ModList); - -#ifdef TEST_MODELS - new ModelTest(&m_ModList, this); - new ModelTest(m_ModListSortProxy, this); -#endif //TEST_MODELS + m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); ui->modList->setModel(m_ModListSortProxy); @@ -221,7 +198,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); //ui->modList->setAcceptDrops(true); - ui->modList->header()->installEventFilter(&m_ModList); + ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); if (initSettings.contains("mod_list_state")) { ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); @@ -236,11 +213,10 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget } ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden - ui->modList->installEventFilter(&m_ModList); + ui->modList->installEventFilter(m_OrganizerCore.modList()); // set up plugin list - m_PluginListSortProxy = new PluginListSortProxy(this); - m_PluginListSortProxy->setSourceModel(&m_PluginList); + m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); ui->espList->setModel(m_PluginListSortProxy); ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); @@ -248,7 +224,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget if (initSettings.contains("plugin_list_state")) { ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); } - ui->espList->installEventFilter(&m_PluginList); + ui->espList->installEventFilter(m_OrganizerCore.pluginList()); ui->bsaList->setLocalMoveOnly(true); @@ -265,70 +241,50 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ui->listOptionsBtn->setMenu(modListContextMenu()); - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - updateDownloadListDelegate(); ui->savegameList->installEventFilter(this); ui->savegameList->setMouseTracking(true); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int))); - connect(&m_DownloadManager, SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom())); connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - connect(&m_ModList, SIGNAL(modorder_changed()), this, SLOT(modorder_changed())); - connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(&m_ModList, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), this, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); + connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex))); connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); + connect(m_OrganizerCore.pluginList(), SIGNAL(saveTimer()), this, SLOT(savePluginList())); connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved())); connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); - connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); - connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString))); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); - connect(&m_Settings, SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_Settings, SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); - connect(&m_Updater, SIGNAL(restart()), this, SLOT(close())); - connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); - connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); + connect(m_OrganizerCore.updater(), SIGNAL(restart()), this, SLOT(close())); + connect(m_OrganizerCore.updater(), SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); + connect(m_OrganizerCore.updater(), SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); connect(NexusInterface::instance(), SIGNAL(needLogin()), this, SLOT(nexusLogin())); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); + connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); + connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); @@ -343,42 +299,44 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); m_SaveMetaTimer.start(5000); - m_DirectoryRefresher.moveToThread(&m_RefresherThread); - m_RefresherThread.start(); - - m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); FileDialogMemory::restore(initSettings); fixCategories(); - if (isOnline() && !m_Settings.offlineMode()) { - m_Updater.testForUpdate(); - } else { - qDebug("user doesn't seem to be connected to the internet"); - } - m_StartTime = QTime::currentTime(); - m_Tutorial.expose("modList", &m_ModList); - m_Tutorial.expose("espList", &m_PluginList); + m_Tutorial.expose("modList", m_OrganizerCore.modList()); + m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); // before we start loading plugins we, add the dll path to the dll search order ::SetDllDirectoryW(ToWString(QDir::toNativeSeparators(qApp->applicationDirPath() + "/dlls")).c_str()); - loadPlugins(); + + m_OrganizerCore.setUserInterface(this, this); + + for (const QString &fileName : m_PluginContainer.pluginFileNames()) { + installTranslator(QFileInfo(fileName).baseName()); + } + + refreshExecutablesList(); + updateToolBar(); } MainWindow::~MainWindow() { - m_AboutToRun.disconnect_all_slots(); - m_ModInstalled.disconnect_all_slots(); - m_RefresherThread.exit(); - m_RefresherThread.wait(); + m_PluginContainer.setUserInterface(nullptr, nullptr); + m_OrganizerCore.setUserInterface(nullptr, nullptr); m_IntegratedBrowser.close(); delete ui; - delete m_GameInfo; - delete m_DirectoryStructure; +} + + +void MainWindow::disconnectPlugins() +{ + if (ui->actionTool->menu() != NULL) { + ui->actionTool->menu()->clear(); + } } @@ -537,7 +495,7 @@ void MainWindow::updateToolBar() ui->toolBar->insertWidget(action, spacer); std::vector::iterator begin, end; - m_ExecutablesList.getExecutables(begin, end); + m_OrganizerCore.executablesList()->getExecutables(begin, end); for (auto iter = begin; iter != end; ++iter) { if (iter->m_Toolbar) { QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), @@ -623,7 +581,7 @@ bool MainWindow::errorReported(QString &logFile) int MainWindow::checkForProblems() { int numProblems = 0; - foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) { + for (IPluginDiagnose *diagnose : m_PluginContainer.plugins()) { numProblems += diagnose->activeProblems().size(); } return numProblems; @@ -631,7 +589,7 @@ int MainWindow::checkForProblems() void MainWindow::about() { - AboutDialog dialog(m_Updater.getVersion().displayString(), this); + AboutDialog dialog(m_OrganizerCore.getVersion().displayString(), this); dialog.exec(); } @@ -701,47 +659,10 @@ void MainWindow::createHelpWidget() } -bool MainWindow::saveArchiveList() -{ - if (m_ArchivesInit) { - SafeWriteFile archiveFile(m_CurrentProfile->getArchivesFileName()); - for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { - QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); - for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem *item = tlItem->child(j); - if (item->checkState(0) == Qt::Checked) { - // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini - if (ui->manageArchivesBox->isChecked() - || item->data(0, Qt::UserRole).toBool()) { - archiveFile->write(item->text(0).toUtf8().append("\r\n")); - } - } - } - } - if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); - return true; - } - } else { - qWarning("archive list not initialised"); - } - return false; -} - -void MainWindow::savePluginList() -{ - m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName(), - m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); - m_PluginList.saveLoadOrder(*m_DirectoryStructure); -} - void MainWindow::modFilterActive(bool filterActive) { if (filterActive) { - m_ModList.setOverwriteMarkers(std::set(), std::set()); + m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); } else if (ui->groupCombo->currentIndex() != 0) { ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); @@ -781,22 +702,6 @@ void MainWindow::expandModList(const QModelIndex &index) } } -bool MainWindow::saveCurrentLists() -{ - if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); - return false; - } - - try { - savePluginList(); - saveArchiveList(); - } catch (const std::exception &e) { - reportError(tr("failed to save load order: %1").arg(e.what())); - } - - return true; -} bool MainWindow::addProfile() { @@ -834,7 +739,7 @@ void MainWindow::hookUpWindowTutorials() QString firstLine = QString::fromUtf8(file.readLine()); if (firstLine.startsWith("//WIN")) { QString windowName = firstLine.mid(6).trimmed(); - if (!m_Settings.directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { + if (!m_OrganizerCore.settings().directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { TutorialManager::instance().activateTutorial(windowName, fileName); } } @@ -850,7 +755,7 @@ void MainWindow::showEvent(QShowEvent *event) hookUpWindowTutorials(); - if (m_Settings.directInterface().value("first_start", true).toBool()) { + if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { if (QMessageBox::question(this, tr("Show tutorial?"), @@ -869,48 +774,35 @@ void MainWindow::showEvent(QShowEvent *event) QObject::tr("Please use \"Help\" from the toolbar to get usage instructions to all elements")); } - m_Settings.directInterface().setValue("first_start", false); + m_OrganizerCore.settings().directInterface().setValue("first_start", false); } // this has no visible impact when called before the ui is visible - int grouping = m_Settings.directInterface().value("group_state").toInt(); + int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt(); ui->groupCombo->setCurrentIndex(grouping); allowListResize(); - m_Settings.registerAsNXMHandler(false); + m_OrganizerCore.settings().registerAsNXMHandler(false); } void MainWindow::closeEvent(QCloseEvent* event) { - if (m_DownloadManager.downloadsInProgress()) { + if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Cancel) { event->ignore(); return; } else { - m_DownloadManager.pauseAll(); + m_OrganizerCore.downloadManager()->pauseAll(); } } setCursor(Qt::WaitCursor); m_IntegratedBrowser.close(); - - storeSettings(); - -// unloadPlugins(); - - // profile has to be cleaned up before the modinfo-buffer is cleared - delete m_CurrentProfile; - m_CurrentProfile = NULL; - - ModInfo::clear(); - LogBuffer::cleanQuit(); - m_ModList.setProfile(NULL); - NexusInterface::instance()->cleanup(); } @@ -952,7 +844,7 @@ SaveGameGamebryo *MainWindow::getSaveGame(QListWidgetItem *item) void MainWindow::displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos) { if (m_CurrentSaveView == NULL) { - m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, &m_PluginList, this); + m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, m_OrganizerCore.pluginList(), this); } else { m_CurrentSaveView->setSave(save); } @@ -1012,53 +904,6 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) } -bool MainWindow::testForSteam() -{ - DWORD processIDs[1024]; - DWORD bytesReturned; - if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) { - qWarning("failed to determine if steam is running"); - return true; - } - - TCHAR processName[MAX_PATH]; - for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { - memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); - if (processIDs[i] != 0) { - HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); - - if (process != NULL) { - HMODULE module; - DWORD ignore; - - // first module in a process is always the binary - if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) { - GetModuleBaseName(process, module, processName, MAX_PATH); - if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) || - (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { - return true; - } - } - } - } - } - - return false; -} - - -bool MainWindow::verifyPlugin(IPlugin *plugin) -{ - if (plugin == NULL) { - return false; - } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) { - qWarning("plugin failed to initialize"); - return false; - } - return true; -} - - void MainWindow::toolPluginInvoke() { QAction *triggeredAction = qobject_cast(sender()); @@ -1073,55 +918,6 @@ void MainWindow::toolPluginInvoke() } -void MainWindow::requestDownload(const QUrl &url, QNetworkReply *reply) -{ - QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - if (browserBtn->menu() != NULL) { - // go through modpage plugins, find one to handle the download. - QList browserActions = browserBtn->menu()->actions(); - foreach (QAction *action, browserActions) { - // the nexus action doesn't have a plugin connected currently - if (action->data().isValid()) { - IPluginModPage *plugin = qobject_cast(qvariant_cast(action->data())); - if (plugin == NULL) { - qCritical("invalid mod page. This is a bug"); - continue; - } - ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); - if (plugin->handlesDownload(url, reply->url(), *fileInfo)) { - fileInfo->repository = plugin->name(); - m_DownloadManager.addDownload(reply, fileInfo); - return; - } - } - } - } - - // no mod found that could handle the download. Is it a nexus mod? - if (url.host() == "www.nexusmods.com") { - int modID = 0; - int fileID = 0; - QRegExp modExp("mods/(\\d+)"); - if (modExp.indexIn(url.toString()) != -1) { - modID = modExp.cap(1).toInt(); - } - QRegExp fileExp("fid=(\\d+)"); - if (fileExp.indexIn(reply->url().toString()) != -1) { - fileID = fileExp.cap(1).toInt(); - } - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID)); - } else { - if (QMessageBox::question(this, tr("Download?"), - tr("A download has been started but no installed page plugin recognizes it.\n" - "If you download anyway no information (i.e. version) will be associated with the download.\n" - "Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); - } - } -} - - void MainWindow::modPagePluginInvoke() { QAction *triggeredAction = qobject_cast(sender()); @@ -1134,6 +930,7 @@ void MainWindow::modPagePluginInvoke() } } + void MainWindow::registerPluginTool(IPluginTool *tool) { QAction *action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); @@ -1171,366 +968,24 @@ void MainWindow::registerModPage(IPluginModPage *modPage) } -bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) -{ - { // generic treatment for all plugins - IPlugin *pluginObj = qobject_cast(plugin); - if (pluginObj == NULL) { - qDebug("not an IPlugin"); - return false; - } - plugin->setProperty("filename", fileName); - m_Settings.registerPlugin(pluginObj); - installTranslator(QFileInfo(fileName).baseName()); - } - - { // diagnosis plugins - IPluginDiagnose *diagnose = qobject_cast(plugin); - if (diagnose != NULL) { - m_DiagnosisPlugins.push_back(diagnose); - m_DiagnosisConnections.push_back( - diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }) - ); - } - } - { // mod page plugin - IPluginModPage *modPage = qobject_cast(plugin); - if (verifyPlugin(modPage)) { - registerModPage(modPage); - return true; - } - } - { // tool plugins - IPluginTool *tool = qobject_cast(plugin); - if (verifyPlugin(tool)) { - registerPluginTool(tool); - return true; - } - } - { // installer plugins - IPluginInstaller *installer = qobject_cast(plugin); - if (verifyPlugin(installer)) { - installer->setParentWidget(this); - m_InstallationManager.registerInstaller(installer); - return true; - } - } - { // preview plugins - IPluginPreview *preview = qobject_cast(plugin); - if (verifyPlugin(preview)) { - m_PreviewGenerator.registerPlugin(preview); - return true; - } - } - { // proxy plugins - IPluginProxy *proxy = qobject_cast(plugin); - if (verifyPlugin(proxy)) { - proxy->setParentWidget(this); - QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - foreach (const QString &pluginName, pluginNames) { - try { - QObject *proxiedPlugin = proxy->instantiate(pluginName); - if (proxiedPlugin != NULL) { - if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); - } else { - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } catch (const std::exception &e) { - reportError(tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what())); - } - } - return true; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin *dummy = qobject_cast(plugin); - if (verifyPlugin(dummy)) { - return true; - } - } - - qDebug("no matching plugin interface"); - - return false; -} - -void MainWindow::unloadPlugins() -{ - // disconnect all slots before unloading plugins so plugins don't have to take care of that - m_AboutToRun.disconnect_all_slots(); - m_ModInstalled.disconnect_all_slots(); - m_ModList.disconnectSlots(); - m_PluginList.disconnectSlots(); - - m_DiagnosisPlugins.clear(); - - foreach (const boost::signals2::connection &connection, m_DiagnosisConnections) { - connection.disconnect(); - } - m_DiagnosisConnections.clear(); - - m_Settings.clearPlugins(); - - if (ui->actionTool->menu() != NULL) { - ui->actionTool->menu()->clear(); - } - - while (!m_PluginLoaders.empty()) { - QPluginLoader *loader = m_PluginLoaders.back(); - m_PluginLoaders.pop_back(); - if (!loader->unload()) { - qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); - } - delete loader; - } -} - -void MainWindow::loadPlugins() -{ - unloadPlugins(); - - foreach (QObject *plugin, QPluginLoader::staticInstances()) { - registerPlugin(plugin, ""); - } - - QFile loadCheck(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - if (QMessageBox::question(this, tr("Plugin error"), - tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)").arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Settings.addBlacklistPlugin(fileName); - } - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - - QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); - QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - if (m_Settings.pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); - continue; - } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - QString pluginName = iter.filePath(); - if (QLibrary::isLibrary(pluginName)) { - QPluginLoader *pluginLoader = new QPluginLoader(pluginName, this); - if (pluginLoader->instance() == NULL) { - m_FailedPlugins.push_back(pluginName); - qCritical("failed to load plugin %s: %s", - qPrintable(pluginName), qPrintable(pluginLoader->errorString())); - } else { - if (registerPlugin(pluginLoader->instance(), pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); - m_PluginLoaders.push_back(pluginLoader); - } else { - m_FailedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } - } - - // remove the load check file on success - loadCheck.remove(); - - m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); - - m_DiagnosisPlugins.push_back(this); -} - - -void MainWindow::startSteam() -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - QString temp = QString("\"%1\"").arg(exe); - if (!QProcess::startDetached(temp)) { - reportError(tr("Failed to start \"%1\"").arg(temp)); - } else { - QMessageBox::information(this, tr("Waiting"), tr("Please press OK once you're logged into steam.")); - } - } -} - - -HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID) -{ - storeSettings(); - - if (!binary.exists()) { - reportError(tr("Executable \"%1\" not found").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); - } - - if ((GameInfo::instance().requiresSteam()) && - (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { - if (!testForSteam()) { - if (QuestionBoxMemory::query(this->isVisible() ? this : NULL, - "steamQuery", tr("Start Steam?"), - tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { - startSteam(); - } - } - } - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - // need to make sure all data is saved before we start the application - if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); - } else { - qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); - return INVALID_HANDLE_VALUE; - } -} - -std::wstring getProcessName(DWORD processId) -{ - HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId); - - wchar_t buffer[MAX_PATH]; - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - wchar_t *fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } else { - fileName += 1; - } - return fileName; - } else { - return std::wstring(L"unknown"); - } -} - -void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) -{ - LockedDialog *dialog = new LockedDialog(this); - dialog->show(); - ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); }); - - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID); - if (processHandle != INVALID_HANDLE_VALUE) { - if (closeAfterStart) { - close(); - } else { - this->setEnabled(false); - // re-enable the locked dialog because what'd be the point otherwise? - dialog->setEnabled(true); - - QCoreApplication::processEvents(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - - { - DWORD currentProcess = 0UL; - bool isJobHandle = true; - - DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { - if (isJobHandle) { - if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - break; - } else { - if (info.ProcessIdList[0] != currentProcess) { - currentProcess = info.ProcessIdList[0]; - dialog->setProcessName(ToQString(getProcessName(currentProcess))); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there - // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. - // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without - // the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - - res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - } - } - ::CloseHandle(processHandle); - - this->setEnabled(true); - refreshDirectoryStructure(); - // need to remove our stored load order because it may be outdated if a foreign tool changed the - // file time. After removing that file, refreshESPList will use the file time as the order - if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - refreshESPList(); - } - } - } -} - - void MainWindow::startExeAction() { QAction *action = qobject_cast(sender()); if (action != NULL) { Executable selectedExecutable = action->data().value(); - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); + m_OrganizerCore.spawnBinary( + selectedExecutable.m_BinaryInfo, + selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE, + selectedExecutable.m_SteamAppID); } else { qCritical("not an action?"); } } -void MainWindow::setExecutablesList(const ExecutablesList &executablesList) -{ - m_ExecutablesList = executablesList; - refreshExecutablesList(); - updateToolBar(); -} - void MainWindow::setExecutableIndex(int index) { QComboBox *executableBox = findChild("executablesListBox"); @@ -1548,13 +1003,11 @@ void MainWindow::activateSelectedProfile() qDebug("activate profile \"%s\"", qPrintable(profileName)); QString profileDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) .append("/").append(profileName); - delete m_CurrentProfile; - m_CurrentProfile = new Profile(QDir(profileDir)); - m_ModList.setProfile(m_CurrentProfile); + m_OrganizerCore.setCurrentProfile(new Profile(QDir(profileDir))); - m_ModListSortProxy->setProfile(m_CurrentProfile); + m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile()); - connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); + connect(m_OrganizerCore.currentProfile(), SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); refreshSaveList(); refreshModList(); @@ -1567,9 +1020,9 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) m_OldProfileIndex = index; if ((previousIndex != -1) && - (m_CurrentProfile != NULL) && - m_CurrentProfile->exists()) { - saveCurrentLists(); + (m_OrganizerCore.currentProfile() != NULL) && + m_OrganizerCore.currentProfile()->exists()) { + m_OrganizerCore.saveCurrentLists(); } // ensure the new index is valid @@ -1590,7 +1043,6 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) } } - void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly) { { @@ -1605,7 +1057,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director QStringList columns(fileName); bool isArchive = false; int originID = current->getOrigin(isArchive); - FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); + FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); QString source("data"); unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); if (modIndex != UINT_MAX) { @@ -1645,7 +1097,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director if (altIter != alternatives.begin()) { altString << " , "; } - altString << "" << m_DirectoryStructure->getOriginByID(*altIter).getName() << ""; + altString << "" << m_OrganizerCore.directoryStructure()->getOriginByID(*altIter).getName() << ""; } fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); fileChild->setForeground(1, QBrush(Qt::red)); @@ -1706,7 +1158,7 @@ void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(virtualPath); + DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); if (dir != NULL) { updateTo(item, path, *dir, conflictsOnly); } else { @@ -1769,31 +1221,6 @@ bool MainWindow::refreshProfiles(bool selectProfile) } } -std::set MainWindow::enabledArchives() -{ - std::set result; - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); - } - archiveFile.close(); - } - return result; -} - -void MainWindow::refreshDirectoryStructure() -{ - m_DirectoryUpdate = true; - std::vector > activeModList = m_CurrentProfile->getActiveMods(); - - m_DirectoryRefresher.setMods(activeModList, enabledArchives()); - - statusBar()->show(); - m_RefreshProgress->setRange(0, 100); - - QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); -} #if QT_VERSION >= 0x050000 extern QPixmap qt_pixmapFromWinHICON(HICON icon); @@ -1824,7 +1251,7 @@ void MainWindow::refreshExecutablesList() QAbstractItemModel *model = executablesList->model(); std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); + m_OrganizerCore.executablesList()->getExecutables(current, end); for(int i = 0; current != end; ++current, ++i) { QVariant temp; temp.setValue(*current); @@ -1846,7 +1273,7 @@ void MainWindow::refreshDataTree() QStringList columns("data"); columns.append(""); QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - updateTo(subTree, L"", *m_DirectoryStructure, conflictsBox->isChecked()); + updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked()); tree->insertTopLevelItem(0, subTree); subTree->setExpanded(true); tree->header()->resizeSection(0, 200); @@ -1866,13 +1293,13 @@ void MainWindow::refreshSaveList() ui->savegameList->clear(); QDir savesDir; - if (m_CurrentProfile->localSavesEnabled()) { - savesDir.setPath(m_CurrentProfile->getPath() + "/saves"); + if (m_OrganizerCore.currentProfile()->localSavesEnabled()) { + savesDir.setPath(m_OrganizerCore.currentProfile()->getPath() + "/saves"); } else { wchar_t path[MAX_PATH]; ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves", path, MAX_PATH, - (ToWString(m_CurrentProfile->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str()); + (ToWString(m_OrganizerCore.currentProfile()->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str()); savesDir.setPath(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getDocumentsDir() + L"\\" + path))); } @@ -1895,67 +1322,15 @@ void MainWindow::refreshSaveList() } -void MainWindow::refreshLists() -{ - if ((m_CurrentProfile != NULL) && m_DirectoryStructure->isPopulated()) { - refreshESPList(); - refreshBSAList(); - } // no point in refreshing lists if no files have been added to the directory tree -} - - -void MainWindow::refreshESPList() -{ - m_CurrentProfile->writeModlist(); - - // clear list - try { - m_PluginList.refresh(m_CurrentProfile->getName(), - *m_DirectoryStructure, - m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName()); - } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); - } -} - -void MainWindow::refreshModList(bool saveChanges) -{ - // don't lose changes! - if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); - } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - - m_CurrentProfile->refreshModStatus(); - - m_ModList.notifyChange(-1); - - refreshDirectoryStructure(); -} - - static bool BySortValue(const std::pair &LHS, const std::pair &RHS) { return LHS.first < RHS.first; } -template -QStringList toStringList(InputIterator current, InputIterator end) +void MainWindow::updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) { - QStringList result; - for (; current != end; ++current) { - result.append(*current); - } - return result; -} - -void MainWindow::refreshBSAList() -{ - m_ArchivesInit = false; ui->bsaList->clear(); #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); @@ -1963,40 +1338,9 @@ void MainWindow::refreshBSAList() ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents); #endif - m_DefaultArchives.clear(); - - wchar_t buffer[256]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName())); - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives = ToQString(buffer).split(','); - } else { - std::vector vanillaBSAs = GameInfo::instance().getVanillaBSAs(); - for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) { - m_DefaultArchives.append(ToQString(*iter)); - } - } - - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives.append(ToQString(buffer).split(',')); - } - - for (int i = 0; i < m_DefaultArchives.count(); ++i) { - m_DefaultArchives[i] = m_DefaultArchives[i].trimmed(); - } - - m_ActiveArchives.clear(); - - auto iter = enabledArchives(); - m_ActiveArchives = toStringList(iter.begin(), iter.end()); - if (m_ActiveArchives.isEmpty()) { - m_ActiveArchives = m_DefaultArchives; - } - std::vector > items; - std::vector files = m_DirectoryStructure->getFiles(); + std::vector files = m_OrganizerCore.directoryStructure()->getFiles(); for (auto iter = files.begin(); iter != files.end(); ++iter) { FileEntry::Ptr current = *iter; @@ -2004,7 +1348,7 @@ void MainWindow::refreshBSAList() QString extension = filename.right(3).toLower(); if (extension == "bsa") { - int index = m_ActiveArchives.indexOf(filename); + int index = activeArchives.indexOf(filename); if (index == -1) { index = 0xFFFF; } @@ -2012,20 +1356,20 @@ void MainWindow::refreshBSAList() QStringList strings(filename); bool isArchive = false; int origin = current->getOrigin(isArchive); - strings.append(ToQString(m_DirectoryStructure->getOriginByID(origin).getName())); + strings.append(ToQString(m_OrganizerCore.directoryStructure()->getOriginByID(origin).getName())); QTreeWidgetItem *newItem = new QTreeWidgetItem(strings); newItem->setData(0, Qt::UserRole, index); newItem->setData(1, Qt::UserRole, origin); newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); newItem->setData(0, Qt::UserRole, false); - if (m_Settings.forceEnableCoreFiles() - && m_DefaultArchives.contains(filename)) { + if (m_OrganizerCore.settings().forceEnableCoreFiles() + && defaultArchives.contains(filename)) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); newItem->setData(0, Qt::UserRole, true); - } else if ((m_PluginList.state(basename + ".esp") == IPluginList::STATE_ACTIVE) - || (m_PluginList.state(basename + ".esm") == IPluginList::STATE_ACTIVE)) { + } else if ((m_OrganizerCore.pluginList()->state(basename + ".esp") == IPluginList::STATE_ACTIVE) + || (m_OrganizerCore.pluginList()->state(basename + ".esm") == IPluginList::STATE_ACTIVE)) { newItem->setCheckState(0, Qt::Checked); newItem->setDisabled(true); } else { @@ -2039,7 +1383,7 @@ void MainWindow::refreshBSAList() if (index < 0) index = 0; - UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); + UINT32 sortValue = ((m_OrganizerCore.directoryStructure()->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); items.push_back(std::make_pair(sortValue, newItem)); } } @@ -2049,7 +1393,7 @@ void MainWindow::refreshBSAList() for (std::vector >::iterator iter = items.begin(); iter != items.end(); ++iter) { int originID = iter->second->data(1, Qt::UserRole).toInt(); - FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); + FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); QString modName("data"); unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); if (modIndex != UINT_MAX) { @@ -2069,12 +1413,11 @@ void MainWindow::refreshBSAList() subItem->setExpanded(true); } - checkBSAList(); - m_ArchivesInit = true; + m_OrganizerCore.checkBSAList(); } -void MainWindow::checkBSAList() +void MainWindow::checkBSAList(const QStringList &defaultArchives) { ui->bsaList->blockSignals(true); @@ -2090,7 +1433,7 @@ void MainWindow::checkBSAList() item->setToolTip(0, QString()); if (item->checkState(0) == Qt::Unchecked) { - if (m_DefaultArchives.contains(filename)) { + if (defaultArchives.contains(filename)) { item->setIcon(0, QIcon(":/MO/gui/warning")); item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!")); modWarning = true; @@ -2183,130 +1526,51 @@ void MainWindow::readSettings() } bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); - - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); - } - - ui->manageArchivesBox->blockSignals(true); - ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool()); - ui->manageArchivesBox->blockSignals(false); -} - - -bool renameFile(const QString &oldName, const QString &newName, bool overwrite = true) -{ - if (overwrite && QFile::exists(newName)) { - QFile::remove(newName); - } - return QFile::rename(oldName, newName); -} - - -void MainWindow::storeSettings() -{ - if (m_CurrentProfile == NULL) { - return; - } - m_CurrentProfile->writeModlist(); - m_CurrentProfile->createTweakedIniFile(); - saveCurrentLists(); - m_Settings.setupLoadMechanism(); - - QString iniFile = ToQString(GameInfo::instance().getIniFilename()); - shellCopy(iniFile, iniFile + ".new", true, this); - - QSettings::Status result = QSettings::NoError; - { - QSettings settings(iniFile + ".new", QSettings::IniFormat); - settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); - - settings.setValue("mod_list_state", ui->modList->header()->saveState()); - settings.setValue("plugin_list_state", ui->espList->header()->saveState()); + setCategoryListVisible(filtersVisible); + ui->displayCategoriesBtn->setChecked(filtersVisible); - settings.setValue("group_state", ui->groupCombo->currentIndex()); + int selectedExecutable = settings.value("selected_executable").toInt(); + setExecutableIndex(selectedExecutable); - settings.setValue("ask_for_nexuspw", m_AskForNexusPW); + if (settings.value("Settings/use_proxy", false).toBool()) { + activateProxy(true); + } - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); + ui->manageArchivesBox->blockSignals(true); + ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool()); + ui->manageArchivesBox->blockSignals(false); +} - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked()); +void MainWindow::storeSettings(QSettings &settings) +{ + settings.setValue("mod_list_state", ui->modList->header()->saveState()); + settings.setValue("plugin_list_state", ui->espList->header()->saveState()); - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - int count = 0; - for (; current != end; ++current) { - const Executable &item = *current; - if (item.m_Custom || item.m_Toolbar) { - settings.setArrayIndex(count++); - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("title", item.m_Title); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); - settings.setValue("steamAppID", item.m_SteamAppID); - settings.setValue("custom", item.m_Custom); - settings.setValue("toolbar", item.m_Toolbar); - } - } - settings.endArray(); + settings.setValue("group_state", ui->groupCombo->currentIndex()); - QComboBox *executableBox = findChild("executablesListBox"); - settings.setValue("selected_executable", executableBox->currentIndex()); + settings.setValue("window_geometry", saveGeometry()); + settings.setValue("window_split", ui->splitter->saveState()); + settings.setValue("log_split", ui->topLevelSplitter->saveState()); - FileDialogMemory::save(settings); + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.sync(); - result = settings.status(); - } - if (result == QSettings::NoError) { - if (!shellRename(iniFile + ".new", iniFile, true, this)) { - DWORD err = ::GetLastError(); - // make a second attempt using qt functions but if that fails print the error from the first attempt - if (!renameFile(iniFile + ".new", iniFile)) { - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(err))); - } - } - } else { - QString reason = result == QSettings::AccessError ? tr("File is write protected") - : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(reason)); - } + settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked()); } void MainWindow::on_btnRefreshData_clicked() { - if (!m_DirectoryUpdate) { - // save the mod list so changes don't get lost - m_CurrentProfile->writeModlistNow(true); - refreshDirectoryStructure(); - } else { - qDebug("directory update"); - } + m_OrganizerCore.refreshDirectoryStructure(); } void MainWindow::on_tabWidget_currentChanged(int index) { if (index == 0) { - refreshESPList(); + m_OrganizerCore.refreshESPList(); } else if (index == 1) { - refreshBSAList(); + m_OrganizerCore.refreshBSAList(); } else if (index == 2) { refreshDataTree(); } else if (index == 3) { @@ -2316,67 +1580,11 @@ void MainWindow::on_tabWidget_currentChanged(int index) } } -std::vector MainWindow::activeProblems() const -{ - std::vector problems; - if (m_FailedPlugins.size() != 0) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - if (m_PluginList.enabledCount() > 255) { - problems.push_back(PROBLEM_TOOMANYPLUGINS); - } - return problems; -} - -QString MainWindow::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("Too many esps and esms enabled"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString MainWindow::fullDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
                "; - foreach (const QString &plugin, m_FailedPlugins) { - result += "
              • " + plugin + "
              • "; - } - result += "
                  "; - return result; - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or " - "merge some plugins into one. You can find a guide here: http://wiki.step-project.com/Guide:Merging_Plugins"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool MainWindow::hasGuidedFix(unsigned int) const -{ - return false; -} - -void MainWindow::startGuidedFix(unsigned int) const -{ -} void MainWindow::installMod() { try { - QStringList extensions = m_InstallationManager.getSupportedExtensions(); + QStringList extensions = m_OrganizerCore.installationManager()->getSupportedExtensions(); for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { *iter = "*." + *iter; } @@ -2387,114 +1595,13 @@ void MainWindow::installMod() if (fileName.length() == 0) { return; } else { - installMod(fileName); + m_OrganizerCore.installMod(fileName); } } catch (const std::exception &e) { reportError(e.what()); } } -IModInterface *MainWindow::installMod(const QString &fileName) -{ - if (m_CurrentProfile == NULL) { - return NULL; - } - - bool hasIniTweaks = false; - GuessedValue modName; - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); - } - m_ModInstalled(modName); - return modInfo.data(); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } - return NULL; -} - -IModInterface *MainWindow::getMod(const QString &name) -{ - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - return NULL; - } else { - return ModInfo::getByIndex(index).data(); - } -} - -IModInterface *MainWindow::createMod(GuessedValue &name) -{ - if (!m_InstallationManager.testOverwrite(name)) { - return NULL; - } - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - - QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); - - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - settingsFile.setValue("modid", 0); - settingsFile.setValue("version", ""); - settingsFile.setValue("newestVersion", ""); - settingsFile.setValue("category", 0); - settingsFile.setValue("installationFile", ""); - return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); -} - -bool MainWindow::removeMod(IModInterface *mod) -{ - unsigned int index = ModInfo::getIndex(mod->name()); - if (index == UINT_MAX) { - return mod->remove(); - } else { - return ModInfo::removeMod(index); - } -} - -QList MainWindow::findFileInfos(const QString &path, const std::function &filter) const -{ - QList result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - IOrganizer::FileInfo info; - info.filePath = ToQString(file->getFullPath()); - bool fromArchive = false; - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); - info.archive = fromArchive ? ToQString(file->getArchive()) : ""; - foreach (int idx, file->getAlternatives()) { - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); - } - - if (filter(info)) { - result.append(info); - } - } - } - return result; -} void MainWindow::on_startButton_clicked() { @@ -2502,12 +1609,13 @@ void MainWindow::on_startButton_clicked() Executable selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value(); - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); + m_OrganizerCore.spawnBinary( + selectedExecutable.m_BinaryInfo, + selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE, + selectedExecutable.m_SteamAppID); } @@ -2584,9 +1692,9 @@ bool MainWindow::modifyExecutablesDialog() { bool result = false; try { - EditExecutablesDialog dialog(m_ExecutablesList); + EditExecutablesDialog dialog(m_OrganizerCore.executablesList()); if (dialog.exec() == QDialog::Accepted) { - m_ExecutablesList = dialog.getExecutablesList(); + m_OrganizerCore.setExecutablesDialog(dialog.getExecutablesList()); result = true; } refreshExecutablesList(); @@ -2698,26 +1806,6 @@ void MainWindow::setESPListSorting(int index) } -bool MainWindow::queryLogin(QString &username, QString &password) -{ - CredentialsDialog dialog(this); - int res = dialog.exec(); - if (dialog.neverAsk()) { - m_AskForNexusPW = false; - } - if (res == QDialog::Accepted) { - username = dialog.username(); - password = dialog.password(); - if (dialog.store()) { - m_Settings.setNexusLogin(username, password); - } - return true; - } else { - return false; - } -} - - bool MainWindow::setCurrentProfile(int index) { QComboBox *profilesBox = findChild("profileBox"); @@ -2746,136 +1834,45 @@ bool MainWindow::setCurrentProfile(const QString &name) void MainWindow::refresher_progress(int percent) { - m_RefreshProgress->setValue(percent); + if (percent == 100) { + m_RefreshProgress->setVisible(false); + } else if (!m_RefreshProgress->isVisible()) { + m_RefreshProgress->setVisible(true); + m_RefreshProgress->setRange(0, 100); + m_RefreshProgress->setValue(percent); + } } - void MainWindow::directory_refreshed() { - DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); - Q_ASSERT(newStructure != m_DirectoryStructure); - if (newStructure != NULL) { - std::swap(m_DirectoryStructure, newStructure); - delete newStructure; - refreshDataTree(); - } else { - // TODO: don't know why this happens, this slot seems to get called twice with only one emit - return; - } - m_DirectoryUpdate = false; - if (m_CurrentProfile != NULL) { - refreshLists(); - } - // some problem-reports may rely on the virtual directory tree so they need to be updated // now + refreshDataTree(); updateProblemsButton(); - - for (int i = 0; i < m_ModList.rowCount(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->clearCaches(); - } statusBar()->hide(); } -void MainWindow::externalMessage(const QString &message) -{ - if (message.left(6).toLower() == "nxm://") { - MessageDialog::showMessage(tr("Download started"), this); - downloadRequestedNXM(message); - } -} - -void MainWindow::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) -{ - // add files of the bsa to the directory structure - m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->stealFiles() - ); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - // need to refresh plugin list now so we can activate esps - refreshESPList(); - // activate all esps of the specified mod so the bsas get activated along with it - updateModActiveState(index, true); - // now we need to refresh the bsa list and save it so there is no confusion about what archives are avaiable and active - refreshBSAList(); - saveArchiveList(); - m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); - - // finally also add files from bsas to the directory structure - m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->archives() - ); -} - -void MainWindow::modStatusChanged(unsigned int index) -{ - try { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (m_CurrentProfile->modEnabled(index)) { - updateModInDirectoryStructure(index, modInfo); - } else { - updateModActiveState(index, false); - refreshESPList(); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - } - } - modInfo->clearCaches(); - - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - int priority = m_CurrentProfile->getModPriority(i); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); - } - } - m_DirectoryStructure->getFileRegister()->sortOrigins(); - - refreshLists(); - } catch (const std::exception& e) { - reportError(tr("failed to update mod list: %1").arg(e.what())); - } -} - - -void MainWindow::removeOrigin(const QString &name) -{ - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); - origin.enable(false); - refreshLists(); -} - - void MainWindow::modorder_changed() { - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - int priority = m_CurrentProfile->getModPriority(i); - if (m_CurrentProfile->modEnabled(i)) { + for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { + int priority = m_OrganizerCore.currentProfile()->getModPriority(i); + if (m_OrganizerCore.currentProfile()->modEnabled(i)) { ModInfo::Ptr modInfo = ModInfo::getByIndex(i); // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1); + m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1); } } - refreshBSAList(); - m_CurrentProfile->writeModlist(); - saveArchiveList(); - m_DirectoryStructure->getFileRegister()->sortOrigins(); + m_OrganizerCore.refreshBSAList(); + m_OrganizerCore.currentProfile()->writeModlist(); + m_OrganizerCore.saveArchiveList(); + m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); { // refresh selection QModelIndex current = ui->modList->currentIndex(); if (current.isValid()) { ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); modInfo->doConflictCheck(); - m_ModList.setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); + m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); if (m_ModListSortProxy != NULL) { m_ModListSortProxy->invalidate(); } @@ -2884,6 +1881,16 @@ void MainWindow::modorder_changed() } } +void MainWindow::modInstalled() +{ + QModelIndexList posList = + m_OrganizerCore.modList().match(m_OrganizerCore.modList().index(0, 0), + Qt::DisplayRole, static_cast(modName)); + if (posList.count() == 1) { + ui->modList->scrollTo(posList.at(0)); + } +} + void MainWindow::procError(QProcess::ProcessError error) { reportError(tr("failed to spawn notepad.exe: %1").arg(error)); @@ -2895,15 +1902,6 @@ void MainWindow::procFinished(int, QProcess::ExitStatus) this->sender()->deleteLater(); } -void MainWindow::profileRefresh() -{ - // have to refresh mods twice (again in refreshModList), otherwise the refresh isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - m_CurrentProfile->refreshModStatus(); - - refreshModList(); -} - void MainWindow::showMessage(const QString &message) { MessageDialog::showMessage(message, this); @@ -2987,12 +1985,12 @@ void MainWindow::modRenamed(const QString &oldName, const QString &newName) } // immediately refresh the active profile because the data in memory is invalid - m_CurrentProfile->refreshModStatus(); + m_OrganizerCore.currentProfile()->refreshModStatus(); // also fix the directory structure try { - if (m_DirectoryStructure->originExists(ToWString(oldName))) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(oldName)); + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldName))) { + FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldName)); origin.setName(ToWString(newName)); } else { @@ -3011,11 +2009,11 @@ void MainWindow::modlistChanged(int) void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) { - const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath)); + const FileEntry::Ptr filePtr = m_OrganizerCore.directoryStructure()->findFile(ToWString(filePath)); if (filePtr.get() != NULL) { try { - if (m_DirectoryStructure->originExists(ToWString(newOriginName))) { - FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName)); + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(newOriginName))) { + FilesOrigin &newOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(newOriginName)); QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; WIN32_FIND_DATAW findData; @@ -3023,8 +2021,8 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); } - if (m_DirectoryStructure->originExists(ToWString(oldOriginName))) { - FilesOrigin &oldOrigin = m_DirectoryStructure->getOriginByName(ToWString(oldOriginName)); + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldOriginName))) { + FilesOrigin &oldOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldOriginName)); filePtr->removeOrigin(oldOrigin.getID()); } } catch (const std::exception &e) { @@ -3140,7 +2138,7 @@ void MainWindow::restoreBackup_clicked() ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); if (backupRegEx.indexIn(modInfo->name()) != -1) { QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); if (!modDir.exists(regName) || (QMessageBox::question(this, tr("Overwrite?"), tr("This will replace the existing mod \"%1\". Continue?").arg(regName), @@ -3148,7 +2146,7 @@ void MainWindow::restoreBackup_clicked() if (modDir.exists(regName) && !shellDelete(QStringList(modDir.absoluteFilePath(regName)))) { reportError(tr("failed to remove mod \"%1\"").arg(regName)); } else { - QString destinationPath = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + "/" + regName; + QString destinationPath = QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory()) + "/" + regName; if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); } @@ -3158,43 +2156,18 @@ void MainWindow::restoreBackup_clicked() } } -void MainWindow::updateModActiveState(int index, bool active) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - - QDir dir(modInfo->absolutePath()); - foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) { - m_PluginList.enableESP(esm, active); - } - int enabled = 0; - QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files); - foreach (const QString &esp, esps) { - if (active != m_PluginList.isEnabled(esp)) { - m_PluginList.enableESP(esp, active); - ++enabled; - } - } - if (active && (enabled > 1)) { - MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), this); - } - m_PluginList.refreshLoadOrder(); - // immediately save affected lists - savePluginList(); -// refreshBSAList(); -} - void MainWindow::modlistChanged(const QModelIndex&, int) { - m_CurrentProfile->writeModlist(); + m_OrganizerCore.currentProfile()->writeModlist(); } void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) { if (current.isValid()) { ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); } else { - m_ModList.setOverwriteMarkers(std::set(), std::set()); + m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); } if ((m_ModListSortProxy != NULL) && !m_ModListSortProxy->beingInvalidated()) { @@ -3229,11 +2202,11 @@ void MainWindow::removeMod_clicked() QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { // use mod names instead of indexes because those become invalid during the removal foreach (QString name, modNames) { - m_ModList.removeRowForce(ModInfo::getIndex(name)); + m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name)); } } } else { - m_ModList.removeRow(m_ContextRow, QModelIndex()); + m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); } } catch (const std::exception &e) { reportError(tr("failed to remove mod: %1").arg(e.what())); @@ -3244,9 +2217,9 @@ void MainWindow::removeMod_clicked() void MainWindow::modRemoved(const QString &fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { - int index = m_DownloadManager.indexByName(fileName); + int index = m_OrganizerCore.downloadManager()->indexByName(fileName); if (index >= 0) { - m_DownloadManager.markUninstalled(index); + m_OrganizerCore.downloadManager()->markUninstalled(index); } } } @@ -3263,10 +2236,10 @@ void MainWindow::reinstallMod_clicked() if (fileInfo.exists()) { fullInstallationFile = installationFile; } else { - fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(fileInfo.fileName()); + fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory().append("/").append(fileInfo.fileName()); } } else { - fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(installationFile); + fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory().append("/").append(installationFile); } if (QFile::exists(fullInstallationFile)) { installMod(fullInstallationFile); @@ -3283,11 +2256,12 @@ void MainWindow::reinstallMod_clicked() void MainWindow::resumeDownload(int downloadIndex) { if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_DownloadManager.resumeDownload(downloadIndex); + m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex); } else { QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + //m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); + m_OrganizerCore.doAfterLogin(std::bind(&MainWindow::resumeDownload, this, downloadIndex)); NexusInterface::instance()->getAccessManager()->login(username, password); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); @@ -3302,7 +2276,7 @@ void MainWindow::endorseMod(ModInfo::Ptr mod) mod->endorse(true); } else { QString username, password; - if (m_Settings.getNexusLogin(username, password)) { + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod)); NexusInterface::instance()->getAccessManager()->login(username, password); } else { @@ -3329,7 +2303,7 @@ void MainWindow::unendorse_clicked() if (NexusInterface::instance()->getAccessManager()->loggedIn()) { ModInfo::getByIndex(m_ContextRow)->endorse(false); } else { - if (m_Settings.getNexusLogin(username, password)) { + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::unendorse_clicked)); NexusInterface::instance()->getAccessManager()->login(username, password); } else { @@ -3338,12 +2312,21 @@ void MainWindow::unendorse_clicked() } } +void MainWindow::loginFailed(const QString &message) +{ + statusBar()->hide(); +} + +void MainWindow::windowTutorialFinished(const QString &windowName) +{ + m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, this); +} void MainWindow::overwriteClosed(int) { OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); if (dialog != NULL) { - m_ModList.modInfoChanged(dialog->modInfo()); + m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); dialog->deleteLater(); } } @@ -3351,7 +2334,7 @@ void MainWindow::overwriteClosed(int) void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) { - m_ModList.modInfoAboutToChange(modInfo); + m_OrganizerCore.modList()->modInfoAboutToChange(modInfo); std::vector flags = modInfo->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { QDialog *dialog = this->findChild("__overwriteDialog"); @@ -3371,7 +2354,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } } else { modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); + ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), this, SLOT(downloadRequestedNXM(QString))); connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); @@ -3381,31 +2364,31 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); dialog.openTab(tab); - dialog.restoreTabState(m_Settings.directInterface().value("mod_info_tabs").toByteArray()); + dialog.restoreTabState(m_OrganizerCore.settings().directInterface().value("mod_info_tabs").toByteArray()); dialog.exec(); - m_Settings.directInterface().setValue("mod_info_tabs", dialog.saveTabState()); + m_OrganizerCore.settings().directInterface().setValue("mod_info_tabs", dialog.saveTabState()); modInfo->saveMeta(); emit modInfoDisplayed(); - m_ModList.modInfoChanged(modInfo); + m_OrganizerCore.modList()->modInfoChanged(modInfo); } - if (m_CurrentProfile->modEnabled(index)) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + if (m_OrganizerCore.currentProfile()->modEnabled(index)) { + FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(modInfo->name()))) { + FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); origin.enable(false); - m_DirectoryRefresher.addModToStructure(m_DirectoryStructure + m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure() , modInfo->name() - , m_CurrentProfile->getModPriority(index) + , m_OrganizerCore.currentProfile()->getModPriority(index) , modInfo->absolutePath() , modInfo->stealFiles() , modInfo->archives()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - refreshLists(); + DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); + m_OrganizerCore.refreshLists(); } } } @@ -3413,7 +2396,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, void MainWindow::modOpenNext() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); + QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); @@ -3430,7 +2413,7 @@ void MainWindow::modOpenNext() void MainWindow::modOpenPrev() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); + QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); int row = index.row() - 1; if (row == -1) { row = m_ModListSortProxy->rowCount() - 1; @@ -3473,15 +2456,15 @@ void MainWindow::ignoreMissingData_clicked() ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); QDir(info->absolutePath()).mkdir("textures"); info->testValid(); - connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), &m_ModList, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex,QModelIndex))); - emit modListDataChanged(m_ModList.index(m_ContextRow, 0), m_ModList.index(m_ContextRow, m_ModList.columnCount() - 1)); + emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); } void MainWindow::visitOnNexus_clicked() { - int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); + int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt(); if (modID > 0) { nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID)); } else { @@ -3508,9 +2491,9 @@ void MainWindow::information_clicked() void MainWindow::syncOverwrite() { ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, this); + SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_OrganizerCore.directoryStructure(), this); if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + syncDialog.apply(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); modInfo->testValid(); refreshDirectoryStructure(); } @@ -3533,12 +2516,12 @@ void MainWindow::createModFromOverwrite() } } - if (getMod(name) != NULL) { + if (m_OrganizerCore.getMod(name) != NULL) { reportError(tr("A mod with this name already exists")); return; } - IModInterface *newMod = createMod(name); + IModInterface *newMod = m_OrganizerCore.createMod(name); if (newMod == NULL) { return; } @@ -3561,7 +2544,7 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) if (!index.isValid()) { return; } - QModelIndex sourceIdx = mapToModel(&m_ModList, index); + QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); if (!sourceIdx.isValid()) { return; } @@ -3675,14 +2658,14 @@ void MainWindow::addRemoveCategories_MenuHandler() { if (selected.size() > 0) { foreach (const QPersistentModelIndex &idx, selected) { qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString())); - QModelIndex modIdx = mapToModel(&m_ModList, idx); + QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); if (modIdx.row() != m_ContextIdx.row()) { addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); } } replaceCategoriesFromMenu(menu, m_ContextIdx.row()); - m_ModList.notifyChange(-1); + m_OrganizerCore.modList()->notifyChange(-1); foreach (const QPersistentModelIndex &idx, selected) { ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); @@ -3690,7 +2673,7 @@ void MainWindow::addRemoveCategories_MenuHandler() { } else { //For single mod selections, just do a replace replaceCategoriesFromMenu(menu, m_ContextRow); - m_ModList.notifyChange(m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); } refreshFilters(); @@ -3708,12 +2691,12 @@ void MainWindow::replaceCategories_MenuHandler() { if (selected.size() > 0) { QStringList selectedMods; for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); + QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row()); + replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); } - m_ModList.notifyChange(-1); + m_OrganizerCore.modList()->notifyChange(-1); // find mods by their name because indices are invalidated QAbstractItemModel *model = ui->modList->model(); @@ -3727,7 +2710,7 @@ void MainWindow::replaceCategories_MenuHandler() { } else { //For single mod selections, just do a replace replaceCategoriesFromMenu(menu, m_ContextRow); - m_ModList.notifyChange(m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); } refreshFilters(); @@ -3757,6 +2740,33 @@ void MainWindow::savePrimaryCategory() } } +bool MainWindow::saveArchiveList() +{ + if (m_ArchivesInit) { + SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName()); + for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) { + QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem *item = tlItem->child(j); + if (item->checkState(0) == Qt::Checked) { + // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini + if (ui->manageArchivesBox->isChecked() + || item->data(0, Qt::UserRole).toBool()) { + archiveFile->write(item->text(0).toUtf8().append("\r\n")); + } + } + } + } + if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + return true; + } + } else { + qWarning("archive list not initialised"); + } + return false; +} + void MainWindow::checkModsForUpdates() { statusBar()->show(); @@ -3765,8 +2775,8 @@ void MainWindow::checkModsForUpdates() m_RefreshProgress->setRange(0, m_ModsToUpdate); } else { QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates)); + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + m_OrganizerCore.doAfterLogin(boost::mem_fn(&MainWindow::checkModsForUpdates)); NexusInterface::instance()->getAccessManager()->login(username, password); } else { // otherwise there will be no endorsement info m_ModsToUpdate = ModInfo::checkAllForUpdate(this); @@ -3891,7 +2901,7 @@ void MainWindow::exportModListCSV() for (unsigned int i = 0; i < numMods; ++i) { ModInfo::Ptr info = ModInfo::getByIndex(i); - bool enabled = m_CurrentProfile->modEnabled(i); + bool enabled = m_OrganizerCore.currentProfile()->modEnabled(i); if ((selection.getChoiceData().toInt() == 1) && !enabled) { continue; } else if ((selection.getChoiceData().toInt() == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { @@ -3936,7 +2946,7 @@ QMenu *MainWindow::modListContextMenu() menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); - menu->addAction(tr("Refresh"), this, SLOT(profileRefresh())); + menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); return menu; @@ -3947,7 +2957,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) try { QTreeView *modList = findChild("modList"); - m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos)); + m_ContextIdx = mapToModel(m_OrganizerCore.modList(), modList->indexAt(pos)); m_ContextRow = m_ContextIdx.row(); QMenu *menu = NULL; @@ -4123,7 +3133,7 @@ void MainWindow::fixMods_clicked() for (int i = 0; i < save->numPlugins(); ++i) { const QString &pluginName = save->plugin(i); - if (!m_PluginList.isEnabled(pluginName)) { + if (!m_OrganizerCore.pluginList()->isEnabled(pluginName)) { missingPlugins[pluginName] = std::vector(); } } @@ -4145,8 +3155,8 @@ void MainWindow::fixMods_clicked() } // search in mods - for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) { - int modIndex = m_CurrentProfile->modIndexByPriority(i); + for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numRegularMods(); ++i) { + int modIndex = m_OrganizerCore.currentProfile()->modIndexByPriority(i); ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); QStringList esps = QDir(modInfo->absolutePath()).entryList(espFilter); @@ -4178,18 +3188,18 @@ void MainWindow::fixMods_clicked() for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { if ((*iter != "") && (*iter != "")) { unsigned int modIndex = ModInfo::getIndex(*iter); - m_CurrentProfile->setModEnabled(modIndex, true); + m_OrganizerCore.currentProfile()->setModEnabled(modIndex, true); } } - m_CurrentProfile->writeModlist(); - refreshLists(); + m_OrganizerCore.currentProfile()->writeModlist(); + m_OrganizerCore.refreshLists(); std::set espsToActivate = dialog.getESPsToActivate(); for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { - m_PluginList.enableESP(*iter); + m_OrganizerCore.pluginList()->enableESP(*iter); } - saveCurrentLists(); + m_OrganizerCore.saveCurrentLists(); } } @@ -4216,7 +3226,7 @@ void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) void MainWindow::linkToolbar() { const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value(); - Executable &exe = m_ExecutablesList.find(selectedExecutable.m_Title); + Executable &exe = m_OrganizerCore.executablesList()->find(selectedExecutable.m_Title); exe.m_Toolbar = !exe.m_Toolbar; ui->linkButton->menu()->actions().at(2)->setIcon(exe.m_Toolbar ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); updateToolBar(); @@ -4289,44 +3299,44 @@ void MainWindow::linkMenu() void MainWindow::downloadSpeed(const QString &serverName, int bytesPerSecond) { - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); + m_OrganizerCore.settings().setDownloadSpeed(serverName, bytesPerSecond); } void MainWindow::on_actionSettings_triggered() { - QString oldModDirectory(m_Settings.getModDirectory()); - QString oldCacheDirectory(m_Settings.getCacheDirectory()); - bool oldDisplayForeign(m_Settings.displayForeign()); - bool proxy = m_Settings.useProxy(); - m_Settings.query(this); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + QString oldModDirectory(m_OrganizerCore.settings().getModDirectory()); + QString oldCacheDirectory(m_OrganizerCore.settings().getCacheDirectory()); + bool oldDisplayForeign(m_OrganizerCore.settings().displayForeign()); + bool proxy = m_OrganizerCore.settings().useProxy(); + m_OrganizerCore.settings().query(this); + m_OrganizerCore.installationManager()->setModsDirectory(m_OrganizerCore.settings().getModDirectory()); + m_OrganizerCore.installationManager()->setDownloadDirectory(m_OrganizerCore.settings().getDownloadDirectory()); fixCategories(); refreshFilters(); - if (QDir::fromNativeSeparators(m_DownloadManager.getOutputDirectory()) != QDir::fromNativeSeparators(m_Settings.getDownloadDirectory())) { - if (m_DownloadManager.downloadsInProgress()) { + if (QDir::fromNativeSeparators(m_OrganizerCore.downloadManager()->getOutputDirectory()) != QDir::fromNativeSeparators(m_OrganizerCore.settings().getDownloadDirectory())) { + if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this); } else { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_OrganizerCore.downloadManager()->setOutputDirectory(m_OrganizerCore.settings().getDownloadDirectory()); } } - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + m_OrganizerCore.downloadManager()->setPreferredServers(m_OrganizerCore.settings().getPreferredServers()); - if ((m_Settings.getModDirectory() != oldModDirectory) - || (m_Settings.displayForeign() != oldDisplayForeign)) { - profileRefresh(); + if ((m_OrganizerCore.settings().getModDirectory() != oldModDirectory) + || (m_OrganizerCore.settings().displayForeign() != oldDisplayForeign)) { + m_OrganizerCore.profileRefresh(); } - if (m_Settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); + if (m_OrganizerCore.settings().getCacheDirectory() != oldCacheDirectory) { + NexusInterface::instance()->setCacheDirectory(m_OrganizerCore.settings().getCacheDirectory()); } - if (proxy != m_Settings.useProxy()) { - activateProxy(m_Settings.useProxy()); + if (proxy != m_OrganizerCore.settings().useProxy()) { + activateProxy(m_OrganizerCore.settings().useProxy()); } - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + NexusInterface::instance()->setNMMVersion(m_OrganizerCore.settings().getNMMVersion()); updateDownloadListDelegate(); } @@ -4351,49 +3361,6 @@ void MainWindow::linkClicked(const QString &url) } -bool MainWindow::nexusLogin() -{ - QString username, password; - - NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); - - if (!accessManager->loginAttempted() - && !accessManager->loggedIn() - && (m_Settings.getNexusLogin(username, password) - || (m_AskForNexusPW - && queryLogin(username, password)))) { - accessManager->login(username, password); - return true; - } else { - return false; - } -} - - -void MainWindow::downloadRequestedNXM(const QString &url) -{ - qDebug("download requested: %s", qPrintable(url)); - if (nexusLogin()) { - m_PendingDownloads.append(url); - } else { - m_DownloadManager.addNXMDownload(url); - } -} - - -void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName) -{ - try { - if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { - MessageDialog::showMessage(tr("Download started"), this); - } - } catch (const std::exception &e) { - MessageDialog::showMessage(tr("Download failed"), this); - qCritical("exception starting download: %s", e.what()); - } -} - - void MainWindow::installTranslator(const QString &name) { QTranslator *translator = new QTranslator(this); @@ -4436,8 +3403,8 @@ void MainWindow::languageChange(const QString &newLanguage) void MainWindow::installDownload(int index) { try { - QString fileName = m_DownloadManager.getFilePath(index); - int modID = m_DownloadManager.getModID(index); + QString fileName = m_OrganizerCore.downloadManager()->getFilePath(index); + int modID = m_OrganizerCore.downloadManager()->getModID(index); GuessedValue modName; // see if there already are mods with the specified mod id @@ -4452,15 +3419,15 @@ void MainWindow::installDownload(int index) } } - m_CurrentProfile->writeModlistNow(); + m_OrganizerCore.currentProfile()->writeModlistNow(); bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + m_OrganizerCore.installationManager()->setModsDirectory(m_OrganizerCore.settings().getModDirectory()); + if (m_OrganizerCore.installationManager()->install(fileName, modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), this); refreshModList(); - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast(modName)); + QModelIndexList posList = m_OrganizerCore.modList()->match(m_OrganizerCore.modList()->index(0, 0), Qt::DisplayRole, static_cast(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -4479,10 +3446,10 @@ void MainWindow::installDownload(int index) } else { reportError(tr("mod \"%1\" not found").arg(modName)); } - m_DownloadManager.markInstalled(index); + m_OrganizerCore.downloadManager()->markInstalled(index); emit modInstalled(); - } else if (m_InstallationManager.wasCancelled()) { + } else if (m_OrganizerCore.installationManager()->wasCancelled()) { QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); } } catch (const std::exception &e) { @@ -4512,7 +3479,7 @@ void MainWindow::writeDataToFile(QFile &file, const QString &directory, const Di file.write(fullName.toUtf8()); file.write("\t("); - file.write(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()).toUtf8()); + file.write(ToQString(m_OrganizerCore.directoryStructure()->getOriginByID(origin).getName()).toUtf8()); file.write(")\r\n"); } } @@ -4535,7 +3502,7 @@ void MainWindow::writeDataToFile() reportError(tr("failed to write to file %1").arg(fileName)); } - writeDataToFile(file, "data", *m_DirectoryStructure); + writeDataToFile(file, "data", *m_OrganizerCore.directoryStructure()); file.close(); MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); @@ -4607,9 +3574,9 @@ void MainWindow::addAsExecutable() tr("Please enter a name for the executable"), QLineEdit::Normal, targetInfo.baseName()); if (!name.isEmpty()) { - m_ExecutablesList.addExecutable(name, binaryInfo.absoluteFilePath(), + m_OrganizerCore.executablesList()->addExecutable(name, binaryInfo.absoluteFilePath(), arguments, targetInfo.absolutePath(), - DEFAULT_STAY, QString(), + ExecutableInfo::CloseMOStyle::DEFAULT_STAY, QString(), true, false); refreshExecutablesList(); } @@ -4627,10 +3594,10 @@ void MainWindow::addAsExecutable() void MainWindow::originModified(int originID) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); + FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); origin.enable(false); - m_DirectoryStructure->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); + m_OrganizerCore.directoryStructure()->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); + DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); } @@ -4691,11 +3658,11 @@ void MainWindow::previewDataFile() // 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_Settings.getModDirectory().size() + 1; + int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; offset = fileName.indexOf("/", offset); fileName = fileName.mid(offset + 1); - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL); + const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), NULL); if (file.get() == NULL) { reportError(tr("file not found: %1").arg(fileName)); @@ -4705,12 +3672,12 @@ void MainWindow::previewDataFile() // set up preview dialog PreviewDialog preview(fileName); auto addFunc = [&] (int originId) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(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_PreviewGenerator.genPreview(filePath); - if (wid == NULL) { + 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); @@ -4737,7 +3704,7 @@ void MainWindow::openDataFile() QString arguments; switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { case 1: { - spawnBinaryDirect(binaryInfo, arguments, m_CurrentProfile->getName(), targetInfo.absolutePath(), ""); + spawnBinaryDirect(binaryInfo, arguments, m_OrganizerCore.currentProfile()->getName(), targetInfo.absolutePath(), ""); } break; case 2: { ::ShellExecuteW(NULL, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); @@ -4769,10 +3736,10 @@ void MainWindow::motdReceived(const QString &motd) // internet connection is faster next time if (m_StartTime.secsTo(QTime::currentTime()) < 5) { uint hash = qHash(motd); - if (hash != m_Settings.getMotDHash()) { + if (hash != m_OrganizerCore.settings().getMotDHash()) { MotDDialog dialog(motd); dialog.exec(); - m_Settings.setMotDHash(hash); + m_OrganizerCore.settings().setMotDHash(hash); } } @@ -4797,7 +3764,7 @@ void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); QString fileName = m_ContextItem->text(0); - if (m_PreviewGenerator.previewSupported(QFileInfo(fileName).completeSuffix())) { + if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).completeSuffix())) { menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } @@ -4826,11 +3793,7 @@ void MainWindow::on_conflictsCheckBox_toggled(bool) void MainWindow::on_actionUpdate_triggered() { - if (nexusLogin()) { - m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); }); - } else { - m_Updater.startUpdate(); - } + m_OrganizerCore.startMOUpdate(); } @@ -4846,16 +3809,22 @@ void MainWindow::on_actionEndorseMO_triggered() void MainWindow::updateDownloadListDelegate() { - if (m_Settings.compactDownloads()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); + if (m_OrganizerCore.settings().compactDownloads()) { + ui->downloadView->setItemDelegate( + new DownloadListWidgetCompactDelegate(m_OrganizerCore.downloadManager(), + m_OrganizerCore.settings().metaDownloads(), + ui->downloadView, + ui->downloadView)); } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); + ui->downloadView->setItemDelegate( + new DownloadListWidgetDelegate(m_OrganizerCore.downloadManager(), + m_OrganizerCore.settings().metaDownloads(), + ui->downloadView, + ui->downloadView)); } - DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); - sortProxy->setSourceModel(new DownloadList(&m_DownloadManager, ui->downloadView)); + DownloadListSortProxy *sortProxy = new DownloadListSortProxy(m_OrganizerCore.downloadManager(), ui->downloadView); + sortProxy->setSourceModel(new DownloadList(m_OrganizerCore.downloadManager(), ui->downloadView)); connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString))); connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); @@ -4864,11 +3833,11 @@ void MainWindow::updateDownloadListDelegate() ui->downloadView->header()->resizeSections(QHeaderView::Fixed); connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), this, SLOT(installDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), &m_DownloadManager, SLOT(queryInfo(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), &m_DownloadManager, SLOT(removeDownload(int, bool))); - connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), SLOT(removeDownload(int, bool))); + connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), SLOT(restoreDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), SLOT(cancelDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), m_OrganizerCore.downloadManager(), SLOT(pauseDownload(int))); connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); } @@ -4957,7 +3926,7 @@ void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) // other keys: ConnectedUsers, Country, URI servers.append(info); } - m_Settings.updateServers(servers); + m_OrganizerCore.settings().updateServers(servers); } @@ -4972,62 +3941,6 @@ void MainWindow::nxmRequestFailed(int modID, int, QVariant, int, const QString & } -void MainWindow::loginSuccessful(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - foreach (auto task, m_PostLoginTasks) { - task(this); - } - - m_PostLoginTasks.clear(); - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginSuccessfulUpdate(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - m_Updater.startUpdate(); -} - - -void MainWindow::loginFailed(const QString &message) -{ - if (!m_PendingDownloads.isEmpty()) { - MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), this); - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - } else { - MessageDialog::showMessage(tr("login failed: %1").arg(message), this); - m_PostLoginTasks.clear(); - statusBar()->hide(); - } - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginFailedUpdate(const QString &message) -{ - MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), this); -} - - -void MainWindow::windowTutorialFinished(const QString &windowName) -{ - m_Settings.directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); -} - - BSA::EErrorCode MainWindow::extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &progress) { @@ -5085,7 +3998,7 @@ void MainWindow::extractBSATriggered() QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); if (!targetFolder.isEmpty()) { BSA::Archive archive; - QString originPath = QDir::fromNativeSeparators(ToQString(m_DirectoryStructure->getOriginByName(ToWString(item->text(1))).getPath())); + 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); @@ -5166,7 +4079,7 @@ void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) void MainWindow::on_actionProblems_triggered() { - ProblemsDialog problems(m_DiagnosisPlugins, this); + ProblemsDialog problems(m_PluginContainer.plugins(), this); if (problems.hasProblems()) { problems.exec(); updateProblemsButton(); @@ -5217,10 +4130,10 @@ void MainWindow::updateESPLock(bool locked) QItemSelection currentSelection = ui->espList->selectionModel()->selection(); if (currentSelection.count() == 0) { // this path is probably useless - m_PluginList.lockESPIndex(m_ContextRow, locked); + m_OrganizerCore.pluginList()->lockESPIndex(m_ContextRow, locked); } else { Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - m_PluginList.lockESPIndex(mapToModel(&m_PluginList, idx).row(), locked); + m_OrganizerCore.pluginList()->lockESPIndex(mapToModel(m_OrganizerCore.pluginList(), idx).row(), locked); } } } @@ -5240,7 +4153,7 @@ void MainWindow::unlockESPIndex() void MainWindow::removeFromToolbar() { try { - Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); + Executable &exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text()); exe.m_Toolbar = false; } catch (const std::runtime_error&) { qDebug("executable doesn't exist any more"); @@ -5268,16 +4181,16 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); QMenu menu; - menu.addAction(tr("Enable all"), &m_PluginList, SLOT(enableAll())); - menu.addAction(tr("Disable all"), &m_PluginList, SLOT(disableAll())); + menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), SLOT(enableAll())); + menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), SLOT(disableAll())); QItemSelection currentSelection = ui->espList->selectionModel()->selection(); bool hasLocked = false; bool hasUnlocked = false; Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { int row = m_PluginListSortProxy->mapToSource(idx).row(); - if (m_PluginList.isEnabled(row)) { - if (m_PluginList.isESPLocked(row)) { + if (m_OrganizerCore.pluginList()->isEnabled(row)) { + if (m_OrganizerCore.pluginList()->isESPLocked(row)) { hasLocked = true; } else { hasUnlocked = true; @@ -5311,11 +4224,11 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) QAbstractProxyModel *newModel = NULL; switch (index) { case 1: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, + newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, 0, Qt::UserRole + 2); } break; case 2: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, + newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, Qt::UserRole + 2); } break; @@ -5333,7 +4246,7 @@ void MainWindow::on_groupCombo_currentIndexChanged(int index) connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); } else { - m_ModListSortProxy->setSourceModel(&m_ModList); + m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); } modFilterActive(m_ModListSortProxy->isFilterActive()); } @@ -5355,7 +4268,7 @@ void MainWindow::on_linkButton_pressed() void MainWindow::on_showHiddenBox_toggled(bool checked) { - m_DownloadManager.setShowHidden(checked); + m_OrganizerCore.downloadManager()->setShowHidden(checked); } @@ -5421,11 +4334,11 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportU if (std::tr1::regex_match(line, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - m_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); + 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)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); - m_PluginList.addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); + m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); } else { qDebug("[loot] %s", line.c_str()); } @@ -5442,8 +4355,8 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList QString currentDirectory = cwd; QString profileName = profile; if (profile.length() == 0) { - if (m_CurrentProfile != NULL) { - profileName = m_CurrentProfile->getName(); + if (m_OrganizerCore.currentProfile() != NULL) { + profileName = m_OrganizerCore.currentProfile()->getName(); } else { throw MyException(tr("No profile set")); } @@ -5459,7 +4372,7 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList } std::vector::iterator current, end; - m_ExecutablesList.getExecutables(current, end); + m_OrganizerCore.executablesList()->getExecutables(current, end); for (; current != end; ++current) { if (current->m_BinaryInfo == binary) { steamAppID = current->m_SteamAppID; @@ -5473,7 +4386,7 @@ HANDLE MainWindow::startApplication(const QString &executable, const QStringList } else { // only a file name, search executables list try { - const Executable &exe = m_ExecutablesList.find(executable); + const Executable &exe = m_OrganizerCore.executablesList()->find(executable); steamAppID = exe.m_SteamAppID; if (arguments == "") { arguments = exe.m_Arguments; @@ -5555,7 +4468,7 @@ void MainWindow::on_bossButton_clicked() std::string reportURL; std::string errorMessages; - m_CurrentProfile->writeModlistNow(); + m_OrganizerCore.currentProfile()->writeModlistNow(); bool success = false; @@ -5587,8 +4500,8 @@ void MainWindow::on_bossButton_clicked() createStdoutPipe(&stdOutRead, &stdOutWrite); HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), - m_CurrentProfile->getName(), - m_Settings.logLevel(), + m_OrganizerCore.currentProfile()->getName(), + m_OrganizerCore.settings().logLevel(), qApp->applicationDirPath() + "/loot", true, stdOutWrite); @@ -5596,7 +4509,7 @@ void MainWindow::on_bossButton_clicked() // we don't use the write end ::CloseHandle(stdOutWrite); - m_PluginList.clearAdditionalInformation(); + m_OrganizerCore.pluginList()->clearAdditionalInformation(); DWORD retLen; JOBOBJECT_BASIC_PROCESS_ID_LIST info; @@ -5669,11 +4582,11 @@ void MainWindow::on_bossButton_clicked() QJsonArray pluginMessages = pluginObj["messages"].toArray(); for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { QJsonObject msg = (*msgIter).toObject(); - m_PluginList.addInformation(pluginObj["name"].toString(), + m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); } if (pluginObj["dirty"].toString() == "yes") - m_PluginList.addInformation(pluginObj["name"].toString(), "dirty"); + m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); } } @@ -5708,9 +4621,9 @@ void MainWindow::on_bossButton_clicked() // 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 (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName()); } - refreshESPList(); + m_OrganizerCore.refreshESPList(); } } @@ -5734,11 +4647,11 @@ bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) void MainWindow::on_saveButton_clicked() { - savePluginList(); + m_OrganizerCore.savePluginList(); QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getPluginsFileName(), now) - && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) - && createBackup(m_CurrentProfile->getLockedOrderFileName(), now)) { + if (createBackup(m_OrganizerCore.currentProfile()->getPluginsFileName(), now) + && createBackup(m_OrganizerCore.currentProfile()->getLoadOrderFileName(), now) + && createBackup(m_OrganizerCore.currentProfile()->getLockedOrderFileName(), now)) { MessageDialog::showMessage(tr("Backup of load order created"), this); } } @@ -5775,32 +4688,32 @@ QString MainWindow::queryRestore(const QString &filePath) void MainWindow::on_restoreButton_clicked() { - QString pluginName = m_CurrentProfile->getPluginsFileName(); + QString pluginName = m_OrganizerCore.currentProfile()->getPluginsFileName(); QString choice = queryRestore(pluginName); if (!choice.isEmpty()) { - QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); - QString lockedName = m_CurrentProfile->getLockedOrderFileName(); + QString loadOrderName = m_OrganizerCore.currentProfile()->getLoadOrderFileName(); + QString lockedName = m_OrganizerCore.currentProfile()->getLockedOrderFileName(); if (!shellCopy(pluginName + "." + choice, pluginName, true, this) || !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || !shellCopy(lockedName + "." + choice, lockedName, true, this)) { QMessageBox::critical(this, tr("Restore failed"), tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); } - refreshESPList(); + m_OrganizerCore.refreshESPList(); } } void MainWindow::on_saveModsButton_clicked() { - m_CurrentProfile->writeModlistNow(true); + m_OrganizerCore.currentProfile()->writeModlistNow(true); QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { + if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { MessageDialog::showMessage(tr("Backup of modlist created"), this); } } void MainWindow::on_restoreModsButton_clicked() { - QString modlistName = m_CurrentProfile->getModlistFileName(); + QString modlistName = m_OrganizerCore.currentProfile()->getModlistFileName(); QString choice = queryRestore(modlistName); if (!choice.isEmpty()) { if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { @@ -5845,5 +4758,5 @@ void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) void MainWindow::on_manageArchivesBox_toggled(bool) { - refreshBSAList(); + m_OrganizerCore.refreshBSAList(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index ea79fc37..c8cb8152 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -29,18 +29,16 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include "executableslist.h" #include "modlist.h" #include "pluginlist.h" +#include "plugincontainer.h" #define WIN32_LEAN_AND_MEAN #include #include #include "directoryrefresher.h" #include -#include -#include -#include +#include #include "settings.h" #include "downloadmanager.h" #include "installationmanager.h" @@ -52,6 +50,7 @@ along with Mod Organizer. If not, see . #include "savegameinfowidgetgamebryo.h" #include "previewgenerator.h" #include "browserdialog.h" +#include "iuserinterface.h" #include #include #ifndef Q_MOC_RUN @@ -67,42 +66,24 @@ class ModListSortProxy; class ModListGroupCategoriesProxy; -class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose +class MainWindow : public QMainWindow, public IUserInterface { Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) friend class OrganizerProxy; -private: - - struct SignalCombinerAnd - { - typedef bool result_type; - template - bool operator()(InputIterator first, InputIterator last) const - { - while (first != last) { - if (!(*first)) { - return false; - } - ++first; - } - return true; - } - }; - - typedef boost::signals2::signal SignalAboutToRunApplication; - typedef boost::signals2::signal SignalModInstalled; public: - explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0); + explicit MainWindow(const QString &exeName, QSettings &initSettings, + OrganizerCore &organizerCore, PluginContainer &pluginContainer, + QWidget *parent = 0); ~MainWindow(); + void storeSettings(QSettings &settings); void readSettings(); bool addProfile(); - void refreshBSAList(); + void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); void refreshDataTree(); void refreshSaveList(); @@ -116,21 +97,13 @@ public: void createFirstProfile(); -/* void spawnProgram(const QString &fileName, const QString &argumentsArg, - const QString &profileName, const QDir ¤tDirectory);*/ - - void loadPlugins(); + bool saveArchiveList(); - virtual std::vector activeProblems() const; - virtual QString shortDescription(unsigned int key) const; - virtual QString fullDescription(unsigned int key) const; - virtual bool hasGuidedFix(unsigned int key) const; - virtual void startGuidedFix(unsigned int key) const; + void registerPluginTool(MOBase::IPluginTool *tool); + void registerModPage(MOBase::IPluginModPage *modPage); void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - bool saveArchiveList(); - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); std::string readFromPipe(HANDLE stdOutRead); void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog); @@ -141,16 +114,21 @@ public: void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - QString getOriginDisplayName(int originID); - void unloadPlugins(); -public slots: - void refreshLists(); + void installTranslator(const QString &name); + + virtual void disconnectPlugins(); + + virtual bool close(); + virtual void setEnabled(bool enabled); + + void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + +public slots: void displayColumnSelection(const QPoint &pos); - void externalMessage(const QString &message); void modorder_changed(); void refresher_progress(int percent); void directory_refreshed(); @@ -160,11 +138,6 @@ public slots: signals: - /** - * @brief emitted after a mod has been installed - * @node this is currently only used for tutorials - */ - void modInstalled(); /** * @brief emitted after the information dialog has been closed @@ -188,41 +161,28 @@ protected: private: - void refreshESPList(); void refreshModList(bool saveChanges = true); void actionToToolButton(QAction *&sourceAction); - bool verifyPlugin(MOBase::IPlugin *plugin); - void registerPluginTool(MOBase::IPluginTool *tool); - void registerModPage(MOBase::IPluginModPage *modPage); - bool registerPlugin(QObject *pluginObj, const QString &fileName); - bool unregisterPlugin(QObject *pluginObj, const QString &fileName); void updateToolBar(); void activateSelectedProfile(); void setExecutableIndex(int index); - bool testForSteam(); void startSteam(); HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); - void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); void refreshDirectoryStructure(); bool refreshProfiles(bool selectProfile = true); void refreshExecutablesList(); void installMod(); - MOBase::IModInterface *installMod(const QString &fileName); - MOBase::IModInterface *getMod(const QString &name); - MOBase::IModInterface *createMod(MOBase::GuessedValue &name); - bool removeMod(MOBase::IModInterface *mod); QList findFileInfos(const QString &path, const std::function &filter) const; bool modifyExecutablesDialog(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); void displayModInformation(int row, int tab = 0); void testExtractBSA(int modIndex); @@ -256,10 +216,6 @@ private: // remove invalid category-references from mods void fixCategories(); - void storeSettings(); - - bool queryLogin(QString &username, QString &password); - void createHelpWidget(); bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); @@ -288,7 +244,6 @@ private: static void setupNetworkProxy(bool activate); void activateProxy(bool activate); - void installTranslator(const QString &name); void setBrowserGeometry(const QByteArray &geometry); bool createBackup(const QString &filePath, const QDateTime &time); @@ -300,13 +255,8 @@ private: void scheduleUpdateButton(); - void updateModActiveState(int index, bool active); - private: - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; - static const char *PATTERN_BACKUP_GLOB; static const char *PATTERN_BACKUP_REGEX; static const char *PATTERN_BACKUP_DATE; @@ -321,21 +271,15 @@ private: int m_OldProfileIndex; - QThread m_RefresherThread; - DirectoryRefresher m_DirectoryRefresher; - MOShared::DirectoryEntry *m_DirectoryStructure; std::vector m_ModNameList; // the mod-list to go with the directory structure QProgressBar *m_RefreshProgress; bool m_Refreshing; - ModList m_ModList; QAbstractItemModel *m_ModListGroupingProxy; ModListSortProxy *m_ModListSortProxy; - PluginList m_PluginList; PluginListSortProxy *m_PluginListSortProxy; - ExecutablesList m_ExecutablesList; int m_OldExecutableIndex; QString m_GamePath; @@ -347,28 +291,12 @@ private: //int m_SelectedSaveGame; - Settings m_Settings; - - DownloadManager m_DownloadManager; - InstallationManager m_InstallationManager; - - SelfUpdater m_Updater; - CategoryFactory &m_CategoryFactory; - Profile *m_CurrentProfile; - int m_ModsToUpdate; - QStringList m_PendingDownloads; - QList > m_PostLoginTasks; - bool m_AskForNexusPW; bool m_LoginAttempted; - QStringList m_DefaultArchives; - QStringList m_ActiveArchives; - bool m_DirectoryUpdate; - bool m_ArchivesInit; QTimer m_CheckBSATimer; QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; @@ -376,23 +304,14 @@ private: QTime m_StartTime; SaveGameInfoWidget *m_CurrentSaveView; - MOBase::IGameInfo *m_GameInfo; - - std::vector m_DiagnosisPlugins; - std::vector m_DiagnosisConnections; - std::vector m_ModPages; - std::vector m_FailedPlugins; - std::vector m_PluginLoaders; + OrganizerCore &m_OrganizerCore; + PluginContainer &m_PluginContainer; - QFile m_PluginsCheck; - - SignalAboutToRunApplication m_AboutToRun; - SignalModInstalled m_ModInstalled; + MOBase::IPluginGame *m_ActiveGame; QString m_CurrentLanguage; std::vector m_Translators; - PreviewGenerator m_PreviewGenerator; BrowserDialog m_IntegratedBrowser; QFileSystemWatcher m_SavesWatcher; @@ -447,8 +366,6 @@ private slots: void modStatusChanged(unsigned int index); void saveSelectionChanged(QListWidgetItem *newItem); - bool saveCurrentLists(); - void windowTutorialFinished(const QString &windowName); BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); @@ -457,8 +374,6 @@ private slots: void createModFromOverwrite(); - void removeOrigin(const QString &name); - void procError(QProcess::ProcessError error); void procFinished(int exitCode, QProcess::ExitStatus exitStatus); @@ -466,17 +381,9 @@ private slots: void checkModsForUpdates(); void nexusLinkActivated(const QString &link); - void linkClicked(const QString &url); - - bool nexusLogin(); - - void loginSuccessful(bool necessary); - void loginSuccessfulUpdate(bool necessary); void loginFailed(const QString &message); - void loginFailedUpdate(const QString &message); - void downloadRequestedNXM(const QString &url); - void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName); + void linkClicked(const QString &url); void installDownload(int index); void updateAvailable(); @@ -495,6 +402,8 @@ private slots: void modDetailsUpdated(bool success); void modlistChanged(int row); + void modInstalled(); + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); @@ -527,7 +436,7 @@ private slots: void startExeAction(); - void checkBSAList(); + void checkBSAList(const QStringList &defaultArchives); void updateProblemsButton(); @@ -538,7 +447,6 @@ private slots: void modlistChanged(const QModelIndex &index, int role); void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - void savePluginList(); void modFilterActive(bool active); void espFilterChanged(const QString &filter); @@ -576,7 +484,6 @@ private slots: void modListSortIndicatorChanged(int column, Qt::SortOrder order); private slots: // ui slots - void profileRefresh(); // actions void on_actionAdd_Profile_triggered(); void on_actionInstallMod_triggered(); @@ -619,6 +526,7 @@ private slots: // ui slots void on_categoriesOrBtn_toggled(bool checked); void on_managedArchiveLabel_linkHovered(const QString &link); void on_manageArchivesBox_toggled(bool checked); + }; diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 863d7628..36902bbf 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -82,7 +82,7 @@ void MessageDialog::resizeEvent(QResizeEvent *event) void MessageDialog::showMessage(const QString &text, QWidget *reference, bool bringToFront) { qDebug("%s", qPrintable(text)); - if (reference != NULL) { + if (reference != nullptr) { if (bringToFront || (qApp->activeWindow() != NULL)) { MessageDialog *dialog = new MessageDialog(text, reference); dialog->show(); diff --git a/src/organizer.pro b/src/organizer.pro index fd1e6aad..98dbcd4a 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -94,7 +94,9 @@ SOURCES += \ modflagicondelegate.cpp \ genericicondelegate.cpp \ organizerproxy.cpp \ - viewmarkingscrollbar.cpp + viewmarkingscrollbar.cpp \ + plugincontainer.cpp \ + organizercore.cpp HEADERS += \ @@ -174,7 +176,10 @@ HEADERS += \ modflagicondelegate.h \ genericicondelegate.h \ organizerproxy.h \ - viewmarkingscrollbar.h + viewmarkingscrollbar.h \ + plugincontainer.h \ + organizercore.h \ + iuserinterface.h FORMS += \ transfersavesdialog.ui \ @@ -279,7 +284,7 @@ CONFIG(debug, debug|release) { #QMAKE_CXXFLAGS_WARN_ON -= -W3 #QMAKE_CXXFLAGS_WARN_ON += -W4 -QMAKE_CXXFLAGS += -wd4100 -wd4127 -wd4512 -wd4189 +QMAKE_CXXFLAGS += /wd4100 -wd4127 -wd4512 -wd4189 CONFIG += embed_manifest_exe diff --git a/src/organizercore.cpp b/src/organizercore.cpp new file mode 100644 index 00000000..68989c8c --- /dev/null +++ b/src/organizercore.cpp @@ -0,0 +1,1249 @@ +#include "organizercore.h" +#include "mainwindow.h" +#include "gameinfoimpl.h" +#include "messagedialog.h" +#include "logbuffer.h" +#include "credentialsdialog.h" +#include "filedialogmemory.h" +#include "lockeddialog.h" +#include "modinfodialog.h" +#include "report.h" +#include "spawn.h" +#include "safewritefile.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace MOShared; +using namespace MOBase; + + +static bool isOnline() +{ + QList interfaces = QNetworkInterface::allInterfaces(); + + bool connected = false; + for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) { + if ( (iter->flags() & QNetworkInterface::IsUp) && + (iter->flags() & QNetworkInterface::IsRunning) && + !(iter->flags() & QNetworkInterface::IsLoopBack)) { + auto addresses = iter->addressEntries(); + if (addresses.count() == 0) { + continue; + } + qDebug("interface %s seems to be up (address: %s)", + qPrintable(iter->humanReadableName()), + qPrintable(addresses[0].ip().toString())); + connected = true; + } + } + + return connected; +} + +static bool renameFile(const QString &oldName, const QString &newName, bool overwrite = true) +{ + if (overwrite && QFile::exists(newName)) { + QFile::remove(newName); + } + return QFile::rename(oldName, newName); +} + +static std::wstring getProcessName(DWORD processId) +{ + HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId); + + wchar_t buffer[MAX_PATH]; + if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { + wchar_t *fileName = wcsrchr(buffer, L'\\'); + if (fileName == nullptr) { + fileName = buffer; + } else { + fileName += 1; + } + return fileName; + } else { + return std::wstring(L"unknown"); + } +} + +static bool testForSteam() +{ + DWORD processIDs[1024]; + DWORD bytesReturned; + if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) { + qWarning("failed to determine if steam is running"); + return true; + } + + TCHAR processName[MAX_PATH]; + for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { + memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); + if (processIDs[i] != 0) { + HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); + + if (process != NULL) { + HMODULE module; + DWORD ignore; + + // first module in a process is always the binary + if (::EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) { + ::GetModuleBaseName(process, module, processName, MAX_PATH); + if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) || + (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { + return true; + } + } + } + } + } + + return false; +} + +static void startSteam(QWidget *widget) +{ + QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", QSettings::NativeFormat); + QString exe = steamSettings.value("SteamExe", "").toString(); + if (!exe.isEmpty()) { + QString temp = QString("\"%1\"").arg(exe); + if (!QProcess::startDetached(temp)) { + reportError(QObject::tr("Failed to start \"%1\"").arg(temp)); + } else { + QMessageBox::information(widget, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); + } + } +} + +template +QStringList toStringList(InputIterator current, InputIterator end) +{ + QStringList result; + for (; current != end; ++current) { + result.append(*current); + } + return result; +} + + +OrganizerCore::OrganizerCore(const QSettings &initSettings) + : m_GameInfo(new GameInfoImpl()) + , m_UserInterface(nullptr) + , m_PluginContainer(nullptr) + , m_CurrentProfile(nullptr) + , m_Settings() + , m_Updater(NexusInterface::instance()) + , m_AboutToRun() + , m_FinishedRun() + , m_ModInstalled() + , m_ModList(this) + , m_PluginList(this) + , m_DirectoryRefresher() + , m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)) + , m_DownloadManager(NexusInterface::instance(), this) + , m_InstallationManager() + , m_RefresherThread() + , m_AskForNexusPW(false) + , m_DirectoryUpdate(false) + , m_ArchivesInit(false) +{ + m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + + NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + + connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int))); + connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); + + connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); + + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); + + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + + // make directory refresher run in a separate thread + m_RefresherThread.start(); + m_DirectoryRefresher.moveToThread(&m_RefresherThread); + + m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); +} + +OrganizerCore::~OrganizerCore() +{ + m_RefresherThread.exit(); + m_RefresherThread.wait(); + + prepareStart(); + + // profile has to be cleaned up before the modinfo-buffer is cleared + delete m_CurrentProfile; + m_CurrentProfile = nullptr; + + ModInfo::clear(); + LogBuffer::cleanQuit(); + m_ModList.setProfile(nullptr); + NexusInterface::instance()->cleanup(); + + delete m_GameInfo; + delete m_DirectoryStructure; +} + +void OrganizerCore::storeSettings() +{ + QString iniFile = ToQString(GameInfo::instance().getIniFilename()); + shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow()); + + QSettings::Status result = QSettings::NoError; + { + QSettings settings(iniFile + ".new", QSettings::IniFormat); + if (m_UserInterface != nullptr) { + m_UserInterface->storeSettings(settings); + } + settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); + settings.setValue("ask_for_nexuspw", m_AskForNexusPW); + + settings.remove("customExecutables"); + settings.beginWriteArray("customExecutables"); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + int count = 0; + for (; current != end; ++current) { + const Executable &item = *current; + if (item.m_Custom || item.m_Toolbar) { + settings.setArrayIndex(count++); + settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); + settings.setValue("title", item.m_Title); + settings.setValue("arguments", item.m_Arguments); + settings.setValue("workingDirectory", item.m_WorkingDirectory); + settings.setValue("closeOnStart", item.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE); + settings.setValue("steamAppID", item.m_SteamAppID); + settings.setValue("custom", item.m_Custom); + settings.setValue("toolbar", item.m_Toolbar); + } + } + settings.endArray(); + + QComboBox *executableBox = findChild("executablesListBox"); + settings.setValue("selected_executable", executableBox->currentIndex()); + + FileDialogMemory::save(settings); + + settings.sync(); + result = settings.status(); + } + if (result == QSettings::NoError) { + if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { + DWORD err = ::GetLastError(); + // make a second attempt using qt functions but if that fails print the error from the first attempt + if (!renameFile(iniFile + ".new", iniFile)) { + QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(err))); + } + } + } else { + QString reason = result == QSettings::AccessError ? tr("File is write protected") + : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") + : tr("Unknown error %1").arg(result); + QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occured trying to write back MO settings: %1").arg(reason)); + } +} + +void OrganizerCore::updateExecutablesList(QSettings &settings) +{ + m_ExecutablesList.init(m_PluginContainer->managedGame(ToQString(GameInfo::instance().getGameName()))); + + qDebug("setting up configured executables"); + + int numCustomExecutables = settings.beginReadArray("customExecutables"); + for (int i = 0; i < numCustomExecutables; ++i) { + settings.setArrayIndex(i); + ExecutableInfo::CloseMOStyle closeMO = + settings.value("closeOnStart").toBool() ? ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE + : ExecutableInfo::CloseMOStyle::DEFAULT_STAY; + m_ExecutablesList.addExecutable(settings.value("title").toString(), + settings.value("binary").toString(), + settings.value("arguments").toString(), + settings.value("workingDirectory", "").toString(), + closeMO, + settings.value("steamAppID", "").toString(), + settings.value("custom", true).toBool(), + settings.value("toolbar", false).toBool()); + } + + settings.endArray(); +} + +void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *widget) +{ + storeSettings(); + + m_UserInterface = userInterface; + + connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex,int)), widget, SLOT(modorder_changed())); + connect(&m_ModList, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); + connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), widget, SLOT(modRenamed(QString,QString))); + connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, SLOT(modRemoved(QString))); + connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int))); + connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, SLOT(removeMod_clicked())); + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, SLOT(displayColumnSelection(QPoint))); + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, SLOT(fileMoved(QString, QString, QString))); + connect(&m_DownloadManager, SIGNAL(downloadAdded()), widget, SLOT(scrollToBottom())); + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); + + m_InstallationManager.setParentWidget(widget); + m_Updater.setUserInterface(widget); + + // this currently wouldn't work reliably if the ui isn't initialized yet to display the result + if (isOnline() && !m_Settings.offlineMode()) { + m_Updater.testForUpdate(); + } else { + qDebug("user doesn't seem to be connected to the internet"); + } +} + +void OrganizerCore::connectPlugins(PluginContainer *container) +{ + m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); + m_PluginContainer = container; +} + +void OrganizerCore::disconnectPlugins() +{ + m_AboutToRun.disconnect_all_slots(); + m_FinishedRun.disconnect_all_slots(); + m_ModInstalled.disconnect_all_slots(); + m_ModList.disconnectSlots(); + m_PluginList.disconnectSlots(); + + m_Settings.clearPlugins(); + m_PluginContainer = nullptr; +} + +Settings &OrganizerCore::settings() +{ + return m_Settings; +} + +bool OrganizerCore::nexusLogin() +{ + QString username, password; + + NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); + + if (!accessManager->loginAttempted() + && !accessManager->loggedIn() + && (m_Settings.getNexusLogin(username, password) + || (m_AskForNexusPW + && queryLogin(username, password)))) { + accessManager->login(username, password); + return true; + } else { + return false; + } +} + +bool OrganizerCore::queryLogin(QString &username, QString &password) +{ + CredentialsDialog dialog(qApp->activeWindow()); + int res = dialog.exec(); + if (dialog.neverAsk()) { + m_AskForNexusPW = false; + } + if (res == QDialog::Accepted) { + username = dialog.username(); + password = dialog.password(); + if (dialog.store()) { + m_Settings.setNexusLogin(username, password); + } + return true; + } else { + return false; + } +} + +void OrganizerCore::startMOUpdate() +{ + if (nexusLogin()) { + m_PostLoginTasks.append([&]() { m_Updater.startUpdate(); }); + } else { + m_Updater.startUpdate(); + } +} + +void OrganizerCore::downloadRequestedNXM(const QString &url) +{ + qDebug("download requested: %s", qPrintable(url)); + if (nexusLogin()) { + m_PendingDownloads.append(url); + } else { + m_DownloadManager.addNXMDownload(url); + } +} + +void OrganizerCore::externalMessage(const QString &message) +{ + if (message.left(6).toLower() == "nxm://") { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); + downloadRequestedNXM(message); + } +} + +void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName) +{ + try { + if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); + } + } catch (const std::exception &e) { + MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); + qCritical("exception starting download: %s", e.what()); + } +} + +void OrganizerCore::removeOrigin(const QString &name) +{ + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); + origin.enable(false); + refreshLists(); +} + +InstallationManager *OrganizerCore::installationManager() +{ + return &m_InstallationManager; +} + +void OrganizerCore::setCurrentProfile(Profile *profile) { + delete m_CurrentProfile; + m_CurrentProfile = profile; + m_ModList.setProfile(profile); +} + +MOBase::IGameInfo &OrganizerCore::gameInfo() const +{ + return *m_GameInfo; +} + +MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const +{ + return new NexusBridge(); +} + +QString OrganizerCore::profileName() const +{ + if (m_CurrentProfile != NULL) { + return m_CurrentProfile->getName(); + } else { + return ""; + } +} + +QString OrganizerCore::profilePath() const +{ + if (m_CurrentProfile != NULL) { + return m_CurrentProfile->getPath(); + } else { + return ""; + } +} + +QString OrganizerCore::downloadsPath() const +{ + return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); +} + +MOBase::VersionInfo OrganizerCore::appVersion() const +{ + return m_Updater.getVersion(); +} + +MOBase::IModInterface *OrganizerCore::getMod(const QString &name) +{ + unsigned int index = ModInfo::getIndex(name); + if (index == UINT_MAX) { + return NULL; + } else { + return ModInfo::getByIndex(index).data(); + } +} + +MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) +{ + if (!m_InstallationManager.testOverwrite(name)) { + return NULL; + } + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + + QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); + + QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); + + settingsFile.setValue("modid", 0); + settingsFile.setValue("version", ""); + settingsFile.setValue("newestVersion", ""); + settingsFile.setValue("category", 0); + settingsFile.setValue("installationFile", ""); + return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); +} + +bool OrganizerCore::removeMod(MOBase::IModInterface *mod) +{ + unsigned int index = ModInfo::getIndex(mod->name()); + if (index == UINT_MAX) { + return mod->remove(); + } else { + return ModInfo::removeMod(index); + } +} + +void OrganizerCore::modDataChanged(MOBase::IModInterface *mod) +{ + refreshModList(false); +} + +QVariant OrganizerCore::pluginSetting(const QString &pluginName, const QString &key) const +{ + return m_Settings.pluginSetting(pluginName, key); +} + +void OrganizerCore::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + m_Settings.setPluginSetting(pluginName, key, value); +} + +QVariant OrganizerCore::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + return m_Settings.pluginPersistent(pluginName, key, def); +} + +void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + m_Settings.setPluginPersistent(pluginName, key, value, sync); +} + +QString OrganizerCore::pluginDataPath() const +{ + QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); + return pluginPath + "/data"; +} + +MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName) +{ + if (m_CurrentProfile == nullptr) { + return nullptr; + } + + bool hasIniTweaks = false; + GuessedValue modName; + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); + refreshModList(); + + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (hasIniTweaks + && (m_UserInterface != nullptr) + && (QMessageBox::question(qApp->activeWindow(), tr("Configure Mod"), + tr("This mod contains ini tweaks. Do you want to configure them now?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { + m_UserInterface->displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); + } + m_ModInstalled(modName); + return modInfo.data(); + } else { + reportError(tr("mod \"%1\" not found").arg(modName)); + } + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), + tr("The mod was not installed completely."), QMessageBox::Ok); + } + return nullptr; +} + +QString OrganizerCore::resolvePath(const QString &fileName) const +{ + if (m_DirectoryStructure == nullptr) { + return QString(); + } + const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); + if (file.get() != nullptr) { + return ToQString(file->getFullPath()); + } else { + return QString(); + } +} + +QStringList OrganizerCore::listDirectories(const QString &directoryName) const +{ + QStringList result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName)); + if (dir != NULL) { + std::vector::iterator current, end; + dir->getSubDirectories(current, end); + for (; current != end; ++current) { + result.append(ToQString((*current)->getName())); + } + } + return result; +} + +QStringList OrganizerCore::findFiles(const QString &path, const std::function &filter) const +{ + QStringList result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != NULL) { + std::vector files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + if (filter(ToQString(file->getFullPath()))) { + result.append(ToQString(file->getFullPath())); + } + } + } else { + qWarning("directory %s not found", qPrintable(path)); + } + return result; +} + +QStringList OrganizerCore::getFileOrigins(const QString &fileName) const +{ + QStringList result; + const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(QFileInfo(fileName).fileName()), NULL); + + if (file.get() != NULL) { + result.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); + foreach (int i, file->getAlternatives()) { + result.append(ToQString(m_DirectoryStructure->getOriginByID(i).getName())); + } + } else { + qDebug("%s not found", qPrintable(fileName)); + } + return result; +} + +QList OrganizerCore::findFileInfos(const QString &path, const std::function &filter) const +{ + QList result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != NULL) { + std::vector files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + IOrganizer::FileInfo info; + info.filePath = ToQString(file->getFullPath()); + bool fromArchive = false; + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); + info.archive = fromArchive ? ToQString(file->getArchive()) : ""; + foreach (int idx, file->getAlternatives()) { + info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); + } + + if (filter(info)) { + result.append(info); + } + } + } + return result; +} + +DownloadManager *OrganizerCore::downloadManager() +{ + return &m_DownloadManager; +} + +PluginList *OrganizerCore::pluginList() +{ + return &m_PluginList; +} + +ModList *OrganizerCore::modList() +{ + return &m_ModList; +} + +void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) +{ + LockedDialog *dialog = new LockedDialog(qApp->activeWindow()); + dialog->show(); + ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); }); + + HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID); + if (processHandle != INVALID_HANDLE_VALUE) { + if (closeAfterStart && (m_UserInterface != nullptr)) { + m_UserInterface->close(); + } else { + if (m_UserInterface != nullptr) { + m_UserInterface->setEnabled(false); + } + // re-enable the locked dialog because what'd be the point otherwise? + dialog->setEnabled(true); + + QCoreApplication::processEvents(); + + DWORD processExitCode; + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + + { + DWORD currentProcess = 0UL; + bool isJobHandle = true; + + DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { + if (isJobHandle) { + if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + break; + } else { + if (info.ProcessIdList[0] != currentProcess) { + currentProcess = info.ProcessIdList[0]; + dialog->setProcessName(ToQString(getProcessName(currentProcess))); + } + } + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; + } + } + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + + res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); + } + ::GetExitCodeProcess(processHandle, &processExitCode); + } + ::CloseHandle(processHandle); + + if (m_UserInterface != nullptr) { + m_UserInterface->setEnabled(true); + } + refreshDirectoryStructure(); + // need to remove our stored load order because it may be outdated if a foreign tool changed the + // file time. After removing that file, refreshESPList will use the file time as the order + if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + refreshESPList(); + } + + m_FinishedRun(binary.absoluteFilePath(), processExitCode); + } + } +} + +HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, + const QDir ¤tDirectory, const QString &steamAppID) +{ + prepareStart(); + + if (!binary.exists()) { + reportError(tr("Executable \"%1\" not found").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; + } + + if (!steamAppID.isEmpty()) { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + } else { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); + } + + if ((GameInfo::instance().requiresSteam()) + && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + if (!testForSteam()) { + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + if (QuestionBoxMemory::query(window, "steamQuery", + tr("Start Steam?"), + tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { + startSteam(qApp->activeWindow()); + } + } + } + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (m_AboutToRun(binary.absoluteFilePath())) { + return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); + } else { + qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); + return INVALID_HANDLE_VALUE; + } +} + +HANDLE OrganizerCore::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) +{ + if (m_UserInterface != nullptr) { + return m_UserInterface->startApplication(executable, args, cwd, profile); + } +} + +bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + if (m_UserInterface != nullptr) { + return m_UserInterface->waitForProcessOrJob(handle, exitCode); + } +} + +bool OrganizerCore::onAboutToRun(const std::function &func) +{ + auto conn = m_AboutToRun.connect(func); + return conn.connected(); +} + +bool OrganizerCore::onFinishedRun(const std::function &func) +{ + auto conn = m_FinishedRun.connect(func); + return conn.connected(); +} + +bool OrganizerCore::onModInstalled(const std::function &func) +{ + auto conn = m_ModInstalled.connect(func); + return conn.connected(); +} + +void OrganizerCore::refreshModList(bool saveChanges) +{ + // don't lose changes! + if (saveChanges) { + m_CurrentProfile->writeModlistNow(true); + } + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + + m_CurrentProfile->refreshModStatus(); + + m_ModList.notifyChange(-1); + + refreshDirectoryStructure(); +} + +void OrganizerCore::refreshESPList() +{ + m_CurrentProfile->writeModlist(); + + // clear list + try { + m_PluginList.refresh(m_CurrentProfile->getName(), + *m_DirectoryStructure, + m_CurrentProfile->getPluginsFileName(), + m_CurrentProfile->getLoadOrderFileName(), + m_CurrentProfile->getLockedOrderFileName()); + } catch (const std::exception &e) { + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); + } +} + + +void OrganizerCore::refreshBSAList() +{ + m_ArchivesInit = false; + + m_DefaultArchives.clear(); + + wchar_t buffer[256]; + std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName())); + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), + L"", buffer, 256, iniFileName.c_str()) != 0) { + m_DefaultArchives = ToQString(buffer).split(','); + } else { + std::vector vanillaBSAs = GameInfo::instance().getVanillaBSAs(); + for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) { + m_DefaultArchives.append(ToQString(*iter)); + } + } + + if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(), + L"", buffer, 256, iniFileName.c_str()) != 0) { + m_DefaultArchives.append(ToQString(buffer).split(',')); + } + + for (int i = 0; i < m_DefaultArchives.count(); ++i) { + m_DefaultArchives[i] = m_DefaultArchives[i].trimmed(); + } + + m_ActiveArchives.clear(); + + auto iter = enabledArchives(); + m_ActiveArchives = toStringList(iter.begin(), iter.end()); + if (m_ActiveArchives.isEmpty()) { + m_ActiveArchives = m_DefaultArchives; + } + + if (m_UserInterface != nullptr) { + m_UserInterface->updateBSAList(); + } + + m_ArchivesInit = true; +} + +void OrganizerCore::refreshLists() +{ + if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) { + refreshESPList(); + refreshBSAList(); + } // no point in refreshing lists if no files have been added to the directory tree +} + +void OrganizerCore::updateModActiveState(int index, bool active) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + + QDir dir(modInfo->absolutePath()); + foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) { + m_PluginList.enableESP(esm, active); + } + int enabled = 0; + QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files); + foreach (const QString &esp, esps) { + if (active != m_PluginList.isEnabled(esp)) { + m_PluginList.enableESP(esp, active); + ++enabled; + } + } + if (active && (enabled > 1)) { + MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), qApp->activeWindow()); + } + m_PluginList.refreshLoadOrder(); + // immediately save affected lists + savePluginList(); +// refreshBSAList(); +} + +void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) +{ + // add files of the bsa to the directory structure + m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath() + , modInfo->stealFiles() + ); + DirectoryRefresher::cleanStructure(m_DirectoryStructure); + // need to refresh plugin list now so we can activate esps + refreshESPList(); + // activate all esps of the specified mod so the bsas get activated along with it + updateModActiveState(index, true); + // now we need to refresh the bsa list and save it so there is no confusion about what archives are avaiable and active + refreshBSAList(); + if (m_UserInterface != nullptr) { + m_UserInterface->saveArchiveList(); + } + m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); + + // finally also add files from bsas to the directory structure + m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure + , modInfo->name() + , m_CurrentProfile->getModPriority(index) + , modInfo->absolutePath() + , modInfo->archives() + ); +} + +void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) +{ + if (m_PluginContainer != nullptr) { + for (IPluginModPage *modPage : m_PluginContainer->plugins()) { + ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); + if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { + fileInfo->repository = modPage->name(); + m_DownloadManager.addDownload(reply, fileInfo); + return; + } + } + } + + // no mod found that could handle the download. Is it a nexus mod? + if (url.host() == "www.nexusmods.com") { + int modID = 0; + int fileID = 0; + QRegExp modExp("mods/(\\d+)"); + if (modExp.indexIn(url.toString()) != -1) { + modID = modExp.cap(1).toInt(); + } + QRegExp fileExp("fid=(\\d+)"); + if (fileExp.indexIn(reply->url().toString()) != -1) { + fileID = fileExp.cap(1).toInt(); + } + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID)); + } else { + if (QMessageBox::question(qApp->activeWindow(), tr("Download?"), + tr("A download has been started but no installed page plugin recognizes it.\n" + "If you download anyway no information (i.e. version) will be associated with the download.\n" + "Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); + } + } +} + +ModListSortProxy *OrganizerCore::createModListProxyModel() +{ + ModListSortProxy *result = new ModListSortProxy(m_CurrentProfile, this); + result->setSourceModel(&m_ModList); + return result; +} + +PluginListSortProxy *OrganizerCore::createPluginListProxyModel() +{ + PluginListSortProxy *result = new PluginListSortProxy(this); + result->setSourceModel(&m_PluginList); + return result; +} + +std::set OrganizerCore::enabledArchives() +{ + std::set result; + QFile archiveFile(m_CurrentProfile->getArchivesFileName()); + if (archiveFile.open(QIODevice::ReadOnly)) { + while (!archiveFile.atEnd()) { + result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); + } + archiveFile.close(); + } + return result; +} + +void OrganizerCore::refreshDirectoryStructure() +{ + if (!m_DirectoryUpdate) { + m_CurrentProfile->writeModlistNow(true); + + m_DirectoryUpdate = true; + std::vector > activeModList = m_CurrentProfile->getActiveMods(); + + m_DirectoryRefresher.setMods(activeModList, enabledArchives()); + + QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); + } else { + qDebug("directory update"); + } +} + +void OrganizerCore::directory_refreshed() +{ + DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); + Q_ASSERT(newStructure != m_DirectoryStructure); + if (newStructure != nullptr) { + std::swap(m_DirectoryStructure, newStructure); + delete newStructure; + } else { + // TODO: don't know why this happens, this slot seems to get called twice with only one emit + return; + } + m_DirectoryUpdate = false; + if (m_CurrentProfile != nullptr) { + refreshLists(); + } + + for (int i = 0; i < m_ModList.rowCount(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->clearCaches(); + } +} + +void OrganizerCore::profileRefresh() +{ + // have to refresh mods twice (again in refreshModList), otherwise the refresh isn't complete. Not sure why + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); + m_CurrentProfile->refreshModStatus(); + + refreshModList(); +} + +void OrganizerCore::modStatusChanged(unsigned int index) +{ + try { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (m_CurrentProfile->modEnabled(index)) { + updateModInDirectoryStructure(index, modInfo); + } else { + updateModActiveState(index, false); + refreshESPList(); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + } + } + modInfo->clearCaches(); + + for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + int priority = m_CurrentProfile->getModPriority(i); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + // priorities in the directory structure are one higher because data is 0 + m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); + } + } + m_DirectoryStructure->getFileRegister()->sortOrigins(); + + refreshLists(); + } catch (const std::exception& e) { + reportError(tr("failed to update mod list: %1").arg(e.what())); + } +} + +void OrganizerCore::loginSuccessful(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); + } + foreach (QString url, m_PendingDownloads) { + downloadRequestedNXM(url); + } + m_PendingDownloads.clear(); + for (auto task : m_PostLoginTasks) { + task(); + } + + m_PostLoginTasks.clear(); + NexusInterface::instance()->loginCompleted(); +} + +void OrganizerCore::loginSuccessfulUpdate(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); + } + m_Updater.startUpdate(); +} + +void OrganizerCore::loginFailed(const QString &message) +{ + if (!m_PendingDownloads.isEmpty()) { + MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), qApp->activeWindow()); + foreach (QString url, m_PendingDownloads) { + downloadRequestedNXM(url); + } + m_PendingDownloads.clear(); + } else { + MessageDialog::showMessage(tr("login failed: %1").arg(message), qApp->activeWindow()); + m_PostLoginTasks.clear(); + } + NexusInterface::instance()->loginCompleted(); +} + + +void OrganizerCore::loginFailedUpdate(const QString &message) +{ + MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), qApp->activeWindow()); +} + + +std::vector OrganizerCore::activeProblems() const +{ + std::vector problems; + if (enabledCount() > 255) { + problems.push_back(PROBLEM_TOOMANYPLUGINS); + } + return problems; +} + +QString OrganizerCore::shortDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_TOOMANYPLUGINS: { + return tr("Too many esps and esms enabled"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +QString OrganizerCore::fullDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_TOOMANYPLUGINS: { + return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or " + "merge some plugins into one. You can find a guide here: http://wiki.step-project.com/Guide:Merging_Plugins"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +bool OrganizerCore::hasGuidedFix(unsigned int) const +{ + return false; +} + +void OrganizerCore::startGuidedFix(unsigned int) const +{ +} + +bool OrganizerCore::saveCurrentLists() +{ + if (m_DirectoryUpdate) { + qWarning("not saving lists during directory update"); + return false; + } + + try { + savePluginList(); + if (m_UserInterface != nullptr) { + m_UserInterface->saveArchiveList(); + } + } catch (const std::exception &e) { + reportError(tr("failed to save load order: %1").arg(e.what())); + } + + return true; +} + +void OrganizerCore::savePluginList() +{ + m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(), + m_CurrentProfile->getLoadOrderFileName(), + m_CurrentProfile->getLockedOrderFileName(), + m_CurrentProfile->getDeleterFileName(), + m_Settings.hideUncheckedPlugins()); + m_PluginList.saveLoadOrder(*m_DirectoryStructure); +} + +void OrganizerCore::prepareStart() { + if (m_CurrentProfile == nullptr) { + return; + } + m_CurrentProfile->writeModlist(); + m_CurrentProfile->createTweakedIniFile(); + saveCurrentLists(); + m_Settings.setupLoadMechanism(); + storeSettings(); +} + diff --git a/src/organizercore.h b/src/organizercore.h new file mode 100644 index 00000000..b362a002 --- /dev/null +++ b/src/organizercore.h @@ -0,0 +1,239 @@ +#ifndef ORGANIZERCORE_H +#define ORGANIZERCORE_H + + +#include "profile.h" +#include "selfupdater.h" +#include "iuserinterface.h" +#include "settings.h" +#include "modlist.h" +#include "pluginlist.h" +#include "directoryrefresher.h" +#include "installationmanager.h" +#include "downloadmanager.h" +#include "modlistsortproxy.h" +#include "pluginlistsortproxy.h" +#include "executableslist.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class PluginContainer; + + +class OrganizerCore : public QObject, public MOBase::IPluginDiagnose +{ + + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) + +private: + + struct SignalCombinerAnd + { + typedef bool result_type; + template + bool operator()(InputIterator first, InputIterator last) const + { + while (first != last) { + if (!(*first)) { + return false; + } + ++first; + } + return true; + } + }; + +private: + + typedef boost::signals2::signal SignalAboutToRunApplication; + typedef boost::signals2::signal SignalFinishedRunApplication; + typedef boost::signals2::signal SignalModInstalled; + +public: + + OrganizerCore(const QSettings &initSettings); + + ~OrganizerCore(); + + void setUserInterface(IUserInterface *userInterface, QWidget *widget); + void connectPlugins(PluginContainer *container); + void disconnectPlugins(); + + void updateExecutablesList(QSettings &settings); + + void startMOUpdate(); + + Settings &settings(); + SelfUpdater *updater() { return &m_Updater; } + InstallationManager *installationManager(); + MOShared::DirectoryEntry *directoryStructure() { return m_DirectoryStructure; } + DirectoryRefresher *directoryRefresher() { return &m_DirectoryRefresher; } + ExecutablesList *executablesList() { return &m_ExecutablesList; } + void setExecutablesDialog(const ExecutablesList &executablesList) { m_ExecutablesList = executablesList; } + + Profile *currentProfile() { return m_CurrentProfile; } + void setCurrentProfile(Profile *profile); + + void setExecutablesList(const ExecutablesList &executablesList); + + std::set enabledArchives(); + + MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } + + ModListSortProxy *createModListProxyModel(); + PluginListSortProxy *createPluginListProxyModel(); + + bool isArchivesInit() const { return m_ArchivesInit; } + + bool saveCurrentLists(); + void savePluginList(); + + void prepareStart(); + + void refreshESPList(); + void refreshBSAList(); + + void refreshDirectoryStructure(); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + + void requestDownload(const QUrl &url, QNetworkReply *reply); + + void doAfterLogin(std::function &function) { m_PostLoginTasks.append(function); } + + void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); + + void modStatusChanged(unsigned int index); + + void loginSuccessful(bool necessary); + void loginSuccessfulUpdate(bool necessary); + void loginFailed(const QString &message); + void loginFailedUpdate(const QString &message); + +public: + virtual MOBase::IGameInfo &gameInfo() const; + virtual MOBase::IModRepositoryBridge *createNexusBridge() const; + virtual QString profileName() const; + virtual QString profilePath() const; + virtual QString downloadsPath() const; + virtual MOBase::VersionInfo appVersion() const; + virtual MOBase::IModInterface *getMod(const QString &name); + virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + virtual bool removeMod(MOBase::IModInterface *mod); + virtual void modDataChanged(MOBase::IModInterface *mod); + virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; + virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def) const; + virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + virtual QString pluginDataPath() const; + virtual MOBase::IModInterface *installMod(const QString &fileName); + virtual QString resolvePath(const QString &fileName) const; + virtual QStringList listDirectories(const QString &directoryName) const; + virtual QStringList findFiles(const QString &path, const std::function &filter) const; + virtual QStringList getFileOrigins(const QString &fileName) const; + virtual QList findFileInfos(const QString &path, const std::function &filter) const; + virtual DownloadManager *downloadManager(); + virtual PluginList *pluginList(); + virtual ModList *modList(); + virtual HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode) const; + virtual bool onModInstalled(const std::function &func); + virtual bool onAboutToRun(const std::function &func); + virtual bool onFinishedRun(const std::function &func); + virtual void refreshModList(bool saveChanges = true); + +public: // IPluginDiagnose interface + + virtual std::vector activeProblems() const; + virtual QString shortDescription(unsigned int key) const; + virtual QString fullDescription(unsigned int key) const; + virtual bool hasGuidedFix(unsigned int key) const; + virtual void startGuidedFix(unsigned int key) const; + +public slots: + + void profileRefresh(); + void externalMessage(const QString &message); + + void refreshLists(); + +signals: + + /** + * @brief emitted after a mod has been installed + * @node this is currently only used for tutorials + */ + void modInstalled(); + +private: + + void storeSettings(); + + bool queryLogin(QString &username, QString &password); + + bool nexusLogin(); + + HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); + void updateModActiveState(int index, bool active); + +private slots: + + void directory_refreshed(); + void downloadRequestedNXM(const QString &url); + void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName); + void removeOrigin(const QString &name); + +private: + + static const unsigned int PROBLEM_TOOMANYPLUGINS = 1; + +private: + + MOBase::IGameInfo *m_GameInfo; + + IUserInterface *m_UserInterface; + PluginContainer *m_PluginContainer; + + Profile *m_CurrentProfile; + + Settings m_Settings; + + SelfUpdater m_Updater; + + SignalAboutToRunApplication m_AboutToRun; + SignalFinishedRunApplication m_FinishedRun; + SignalModInstalled m_ModInstalled; + + ModList m_ModList; + PluginList m_PluginList; + + QList> m_PostLoginTasks; + + ExecutablesList m_ExecutablesList; + QStringList m_PendingDownloads; + QStringList m_DefaultArchives; + QStringList m_ActiveArchives; + + DirectoryRefresher m_DirectoryRefresher; + MOShared::DirectoryEntry *m_DirectoryStructure; + + DownloadManager m_DownloadManager; + InstallationManager m_InstallationManager; + + QThread m_RefresherThread; + + bool m_AskForNexusPW; + bool m_DirectoryUpdate; + bool m_ArchivesInit; + +}; + +#endif // ORGANIZERCORE_H diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index 903c979e..07a006f5 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -7,50 +7,40 @@ using namespace MOBase; using namespace MOShared; -OrganizerProxy::OrganizerProxy(MainWindow *window, const QString &pluginName) - : m_Proxied(window) +OrganizerProxy::OrganizerProxy(OrganizerCore *organizer, const QString &pluginName) + : m_Proxied(organizer) , m_PluginName(pluginName) { } IGameInfo &OrganizerProxy::gameInfo() const { - return *m_Proxied->m_GameInfo; + return m_Proxied->gameInfo(); } - IModRepositoryBridge *OrganizerProxy::createNexusBridge() const { return new NexusBridge(m_PluginName); } - QString OrganizerProxy::profileName() const { - if (m_Proxied->m_CurrentProfile != NULL) { - return m_Proxied->m_CurrentProfile->getName(); - } else { - return ""; - } + return m_Proxied->profileName(); } QString OrganizerProxy::profilePath() const { - if (m_Proxied->m_CurrentProfile != NULL) { - return m_Proxied->m_CurrentProfile->getPath(); - } else { - return ""; - } + return m_Proxied->profilePath(); } QString OrganizerProxy::downloadsPath() const { - return QDir::fromNativeSeparators(m_Proxied->m_Settings.getDownloadDirectory()); + return m_Proxied->downloadsPath(); } VersionInfo OrganizerProxy::appVersion() const { - return m_Proxied->m_Updater.getVersion(); + return m_Proxied->appVersion(); } IModInterface *OrganizerProxy::getMod(const QString &name) @@ -68,35 +58,34 @@ bool OrganizerProxy::removeMod(IModInterface *mod) return m_Proxied->removeMod(mod); } -void OrganizerProxy::modDataChanged(IModInterface*) +void OrganizerProxy::modDataChanged(IModInterface *mod) { - m_Proxied->refreshModList(); + m_Proxied->modDataChanged(mod); } QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const { - return m_Proxied->m_Settings.pluginSetting(pluginName, key); + return m_Proxied->pluginSetting(pluginName, key); } void OrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) { - m_Proxied->m_Settings.setPluginSetting(pluginName, key, value); + m_Proxied->setPluginSetting(pluginName, key, value); } QVariant OrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const { - return m_Proxied->m_Settings.pluginPersistent(pluginName, key, def); + return m_Proxied->persistent(pluginName, key, def); } void OrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) { - m_Proxied->m_Settings.setPluginPersistent(pluginName, key, value, sync); + m_Proxied->setPersistent(pluginName, key, value, sync); } QString OrganizerProxy::pluginDataPath() const { - QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); - return pluginPath + "/data"; + return m_Proxied->pluginDataPath(); } HANDLE OrganizerProxy::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) @@ -106,26 +95,31 @@ HANDLE OrganizerProxy::startApplication(const QString &executable, const QString bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForProcessOrJob(handle, exitCode); + return m_Proxied->waitForApplication(handle, exitCode); } bool OrganizerProxy::onAboutToRun(const std::function &func) { - auto conn = m_Proxied->m_AboutToRun.connect(func); - return conn.connected(); + return m_Proxied->onAboutToRun(func); +} + +bool OrganizerProxy::onFinishedRun(const std::function &func) +{ + return m_Proxied->onFinishedRun(func); } bool OrganizerProxy::onModInstalled(const std::function &func) { - auto conn = m_Proxied->m_ModInstalled.connect(func); - return conn.connected(); + return m_Proxied->onModInstalled(func); } + void OrganizerProxy::refreshModList(bool saveChanges) { m_Proxied->refreshModList(saveChanges); } + IModInterface *OrganizerProxy::installMod(const QString &fileName) { return m_Proxied->installMod(fileName); @@ -133,62 +127,22 @@ IModInterface *OrganizerProxy::installMod(const QString &fileName) QString OrganizerProxy::resolvePath(const QString &fileName) const { - if (m_Proxied->m_DirectoryStructure == NULL) { - return QString(); - } - const FileEntry::Ptr file = m_Proxied->m_DirectoryStructure->searchFile(ToWString(fileName), NULL); - if (file.get() != NULL) { - return ToQString(file->getFullPath()); - } else { - return QString(); - } + return m_Proxied->resolvePath(fileName); } QStringList OrganizerProxy::listDirectories(const QString &directoryName) const { - QStringList result; - DirectoryEntry *dir = m_Proxied->m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName)); - if (dir != NULL) { - std::vector::iterator current, end; - dir->getSubDirectories(current, end); - for (; current != end; ++current) { - result.append(ToQString((*current)->getName())); - } - } - return result; + return m_Proxied->listDirectories(directoryName); } QStringList OrganizerProxy::findFiles(const QString &path, const std::function &filter) const { - QStringList result; - DirectoryEntry *dir = m_Proxied->m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - if (filter(ToQString(file->getFullPath()))) { - result.append(ToQString(file->getFullPath())); - } - } - } else { - qWarning("directory %s not found", qPrintable(path)); - } - return result; + return m_Proxied->findFiles(path, filter); } QStringList OrganizerProxy::getFileOrigins(const QString &fileName) const { - QStringList result; - const FileEntry::Ptr file = m_Proxied->m_DirectoryStructure->searchFile(ToWString(QFileInfo(fileName).fileName()), NULL); - - if (file.get() != NULL) { - result.append(ToQString(m_Proxied->m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); - foreach (int i, file->getAlternatives()) { - result.append(ToQString(m_Proxied->m_DirectoryStructure->getOriginByID(i).getName())); - } - } else { - qDebug("%s not found", qPrintable(fileName)); - } - return result; + return m_Proxied->getFileOrigins(fileName); } QList OrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const @@ -198,15 +152,15 @@ QList OrganizerProxy::findFileInfos(const QString MOBase::IDownloadManager *OrganizerProxy::downloadManager() { - return &m_Proxied->m_DownloadManager; + return m_Proxied->downloadManager(); } MOBase::IPluginList *OrganizerProxy::pluginList() { - return &m_Proxied->m_PluginList; + return m_Proxied->pluginList(); } MOBase::IModList *OrganizerProxy::modList() { - return &m_Proxied->m_ModList; + return m_Proxied->modList(); } diff --git a/src/organizerproxy.h b/src/organizerproxy.h index affa2d55..625e86aa 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -7,8 +7,10 @@ class OrganizerProxy : public MOBase::IOrganizer { + public: - OrganizerProxy(MainWindow *window, const QString &pluginName); + + OrganizerProxy(OrganizerCore *organizer, const QString &pluginName); virtual MOBase::IGameInfo &gameInfo() const; virtual MOBase::IModRepositoryBridge *createNexusBridge() const; @@ -40,10 +42,14 @@ public: virtual void refreshModList(bool saveChanges); virtual bool onAboutToRun(const std::function &func); - virtual bool onModInstalled(const std::function &func); + virtual bool onFinishedRun(const std::function &func); + virtual bool onModInstalled(const std::function &func); + private: - MainWindow *m_Proxied; + + OrganizerCore *m_Proxied; + const QString &m_PluginName; }; diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp new file mode 100644 index 00000000..bd96828e --- /dev/null +++ b/src/plugincontainer.cpp @@ -0,0 +1,329 @@ +#include "plugincontainer.h" +#include "organizerproxy.h" +#include "report.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace MOBase; +using namespace MOShared; + +namespace bf = boost::fusion; + + +PluginContainer::PluginContainer(OrganizerCore *organizer) + : m_Organizer(organizer) +{ +} + + +void PluginContainer::setUserInterface(IUserInterface *userInterface, QWidget *widget) +{ + for (IPluginProxy *proxy : bf::at_key(m_Plugins)) { + proxy->setParentWidget(widget); + } + m_UserInterface = userInterface; +} + + +QStringList PluginContainer::pluginFileNames() const +{ + QStringList result; + for (QPluginLoader *loader : m_PluginLoaders) { + result.append(loader->fileName()); + } + return result; +} + + +bool PluginContainer::verifyPlugin(IPlugin *plugin) +{ + if (plugin == NULL) { + return false; + } else if (!plugin->init(new OrganizerProxy(m_Organizer, plugin->name()))) { + qWarning("plugin failed to initialize"); + return false; + } + return true; +} + + +void PluginContainer::registerGame(IPluginGame *game) +{ + m_SupportedGames.insert({ game->gameName(), game }); +} + + +bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) +{ + { // generic treatment for all plugins + IPlugin *pluginObj = qobject_cast(plugin); + if (pluginObj == NULL) { + qDebug("not an IPlugin"); + return false; + } + plugin->setProperty("filename", fileName); + m_Organizer->settings().registerPlugin(pluginObj); + } + + { // diagnosis plugins + IPluginDiagnose *diagnose = qobject_cast(plugin); + if (diagnose != NULL) { + bf::at_key(m_Plugins).push_back(diagnose); + m_DiagnosisConnections.push_back( + diagnose->onInvalidated([&] () { emit diagnosisUpdate(); }) + ); + } + } + { // mod page plugin + IPluginModPage *modPage = qobject_cast(plugin); + if (verifyPlugin(modPage)) { + bf::at_key(m_Plugins).push_back(modPage); + registerModPage(modPage); + return true; + } + } + { // game plugin + IPluginGame *game = qobject_cast(plugin); + if (verifyPlugin(game)) { + bf::at_key(m_Plugins).push_back(game); + registerGame(game); + return true; + } + } + { // tool plugins + IPluginTool *tool = qobject_cast(plugin); + if (verifyPlugin(tool)) { + bf::at_key(m_Plugins).push_back(tool); + registerPluginTool(tool); + return true; + } + } + { // installer plugins + IPluginInstaller *installer = qobject_cast(plugin); + if (verifyPlugin(installer)) { + bf::at_key(m_Plugins).push_back(installer); + m_Organizer->installationManager()->registerInstaller(installer); + return true; + } + } + { // preview plugins + IPluginPreview *preview = qobject_cast(plugin); + if (verifyPlugin(preview)) { + bf::at_key(m_Plugins).push_back(preview); + m_PreviewGenerator.registerPlugin(preview); + return true; + } + } + { // proxy plugins + IPluginProxy *proxy = qobject_cast(plugin); + if (verifyPlugin(proxy)) { + bf::at_key(m_Plugins).push_back(proxy); + QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); + foreach (const QString &pluginName, pluginNames) { + try { + QObject *proxiedPlugin = proxy->instantiate(pluginName); + if (proxiedPlugin != NULL) { + if (registerPlugin(proxiedPlugin, pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); + } else { + qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); + } + } + } catch (const std::exception &e) { + reportError(QObject::tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what())); + } + } + return true; + } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin *dummy = qobject_cast(plugin); + if (verifyPlugin(dummy)) { + bf::at_key(m_Plugins).push_back(dummy); + return true; + } + } + + qDebug("no matching plugin interface"); + + return false; +} + +struct clearPlugins +{ + template + void operator()(T& t) const + { + t.second.clear(); + } +}; + +void PluginContainer::unloadPlugins() +{ + if (m_UserInterface != nullptr) { + m_UserInterface->disconnectPlugins(); + } + + // disconnect all slots before unloading plugins so plugins don't have to take care of that + m_Organizer->disconnectPlugins(); + + bf::for_each(m_Plugins, clearPlugins()); + + foreach (const boost::signals2::connection &connection, m_DiagnosisConnections) { + connection.disconnect(); + } + m_DiagnosisConnections.clear(); + + while (!m_PluginLoaders.empty()) { + QPluginLoader *loader = m_PluginLoaders.back(); + m_PluginLoaders.pop_back(); + if (!loader->unload()) { + qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); + } + delete loader; + } +} + +IPluginGame *PluginContainer::managedGame(const QString &name) const +{ + auto iter = m_SupportedGames.find(name); + if (iter != m_SupportedGames.end()) { + return iter->second; + } else { + return nullptr; + } +} + +const PreviewGenerator &PluginContainer::previewGenerator() const +{ + return m_PreviewGenerator; +} + +void PluginContainer::loadPlugins() +{ + unloadPlugins(); + + foreach (QObject *plugin, QPluginLoader::staticInstances()) { + registerPlugin(plugin, ""); + } + + QFile loadCheck(qApp->property("dataPath").toString() + "/plugin_loadcheck.tmp"); + if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { + // oh, there was a failed plugin load last time. Find out which plugin was loaded last + QString fileName; + while (!loadCheck.atEnd()) { + fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); + } + if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), + QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Organizer->settings().addBlacklistPlugin(fileName); + } + loadCheck.close(); + } + + loadCheck.open(QIODevice::WriteOnly); + + QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); + qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); + QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); + + while (iter.hasNext()) { + iter.next(); + if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { + qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); + continue; + } + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + QString pluginName = iter.filePath(); + if (QLibrary::isLibrary(pluginName)) { + QPluginLoader *pluginLoader = new QPluginLoader(pluginName, this); + if (pluginLoader->instance() == NULL) { + m_FailedPlugins.push_back(pluginName); + qCritical("failed to load plugin %s: %s", + qPrintable(pluginName), qPrintable(pluginLoader->errorString())); + } else { + if (registerPlugin(pluginLoader->instance(), pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); + m_PluginLoaders.push_back(pluginLoader); + } else { + m_FailedPlugins.push_back(pluginName); + qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); + } + } + } + } + + // remove the load check file on success + loadCheck.remove(); + + bf::at_key(m_Plugins).push_back(this); + + m_Organizer->connectPlugins(this); +} + + +std::vector PluginContainer::activeProblems() const +{ + std::vector problems; + if (m_FailedPlugins.size()) { + problems.push_back(PROBLEM_PLUGINSNOTLOADED); + } + return problems; +} + +QString PluginContainer::shortDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + return tr("Some plugins could not be loaded"); + } break; + default: { + return tr("Description missing"); + } break; + } +} + +QString PluginContainer::fullDescription(unsigned int key) const +{ + switch (key) { + case PROBLEM_PLUGINSNOTLOADED: { + QString result = tr("The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:") + "
                    "; + for (const QString &plugin : m_FailedPlugins) { + result += "
                  • " + plugin + "
                  • "; + } + result += "
                      "; + return result; + } break; + default: { + return tr("Description missing"); + } break; + } +} + +bool PluginContainer::hasGuidedFix(unsigned int) const +{ + return false; +} + +void PluginContainer::startGuidedFix(unsigned int) const +{ +} diff --git a/src/plugincontainer.h b/src/plugincontainer.h new file mode 100644 index 00000000..213b4154 --- /dev/null +++ b/src/plugincontainer.h @@ -0,0 +1,102 @@ +#ifndef PLUGINCONTAINER_H +#define PLUGINCONTAINER_H + + +#include "organizercore.h" +#include "previewgenerator.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef Q_MOC_RUN +#include +#endif // Q_MOC_RUN +#include + + +class PluginContainer : public QObject, public MOBase::IPluginDiagnose +{ + + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) + +public: + + PluginContainer(OrganizerCore *organizer); + + void setUserInterface(IUserInterface *userInterface, QWidget *widget); + + void loadPlugins(); + void unloadPlugins(); + + MOBase::IPluginGame *managedGame(const QString &name) const; + + template + std::vector plugins() const { + return boost::fusion::at_key(m_Plugins); + } + + const PreviewGenerator &previewGenerator() const; + + QStringList pluginFileNames() const; + +public: // IPluginDiagnose interface + + virtual std::vector activeProblems() const; + virtual QString shortDescription(unsigned int key) const; + virtual QString fullDescription(unsigned int key) const; + virtual bool hasGuidedFix(unsigned int key) const; + virtual void startGuidedFix(unsigned int key) const; + +signals: + + void diagnosisUpdate(); + +private: + + bool verifyPlugin(MOBase::IPlugin *plugin); + void registerPluginTool(MOBase::IPluginTool *tool); + void registerModPage(MOBase::IPluginModPage *modPage); + void registerGame(MOBase::IPluginGame *game); + bool registerPlugin(QObject *pluginObj, const QString &fileName); + bool unregisterPlugin(QObject *pluginObj, const QString &fileName); + +private: + + typedef boost::fusion::map< + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair>, + boost::fusion::pair> + > PluginMap; + + static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; + +private: + + OrganizerCore *m_Organizer; + + IUserInterface *m_UserInterface; + + PluginMap m_Plugins; + + std::map m_SupportedGames; + std::vector m_DiagnosisConnections; + QStringList m_FailedPlugins; + std::vector m_PluginLoaders; + + PreviewGenerator m_PreviewGenerator; + + QFile m_PluginsCheck; +}; + +#endif // PLUGINCONTAINER_H diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 6037422c..803b2cfa 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -55,9 +55,9 @@ template T resolveFunction(QLibrary &lib, const char *name) } -SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) - : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), - m_Reply(NULL), m_Progress(parent), m_Attempts(3) +SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) + : m_Parent(nullptr), m_Interface(nexusInterface), m_UpdateRequestID(-1), + m_Reply(NULL), m_Progress(nullptr), m_Attempts(3) { m_Progress.setMaximum(100); @@ -88,6 +88,11 @@ SelfUpdater::~SelfUpdater() delete m_CurrentArchive; } +void SelfUpdater::setUserInterface(QWidget *widget) +{ + m_Progress.setParent(widget); + m_Parent = widget; +} void SelfUpdater::testForUpdate() { diff --git a/src/selfupdater.h b/src/selfupdater.h index 75b0ff45..3490b58a 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -64,10 +64,12 @@ public: * @param parent parent widget * @todo passing the nexus interface is unneccessary **/ - SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); + SelfUpdater(NexusInterface *nexusInterface); virtual ~SelfUpdater(); + void setUserInterface(QWidget *widget); + /** * @brief start the update process * @note this should not be called if there is no update available diff --git a/src/shared/fallout3info.cpp b/src/shared/fallout3info.cpp index 22db91ac..82b285b7 100644 --- a/src/shared/fallout3info.cpp +++ b/src/shared/fallout3info.cpp @@ -267,7 +267,7 @@ bool Fallout3Info::rerouteToProfile(const wchar_t *fileName, const wchar_t*) } -std::vector Fallout3Info::getExecutables() +/*std::vector Fallout3Info::getExecutables() { std::vector result; result.push_back(ExecutableInfo(L"FOSE", L"fose_loader.exe", L"", L"", DEFAULT_CLOSE)); @@ -278,5 +278,5 @@ std::vector Fallout3Info::getExecutables() result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", NEVER_CLOSE)); return result; -} +}*/ } // namespace MOShared diff --git a/src/shared/fallout3info.h b/src/shared/fallout3info.h index d1356de1..7e39cce4 100644 --- a/src/shared/fallout3info.h +++ b/src/shared/fallout3info.h @@ -87,7 +87,7 @@ public: // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to // the game directory - virtual std::vector getExecutables(); + //virtual std::vector getExecutables(); virtual std::wstring archiveListKey() { return L"SArchiveList"; } diff --git a/src/shared/falloutnvinfo.cpp b/src/shared/falloutnvinfo.cpp index 0dde4db1..178ce8b4 100644 --- a/src/shared/falloutnvinfo.cpp +++ b/src/shared/falloutnvinfo.cpp @@ -257,7 +257,7 @@ bool FalloutNVInfo::rerouteToProfile(const wchar_t *fileName, const wchar_t*) return false; } - +/* std::vector FalloutNVInfo::getExecutables() { std::vector result; @@ -269,5 +269,5 @@ std::vector FalloutNVInfo::getExecutables() result.push_back(ExecutableInfo(L"BOSS", L"BOSS/BOSS.exe", L"", L"", NEVER_CLOSE)); return result; -} +}*/ } // namespace MOShared diff --git a/src/shared/falloutnvinfo.h b/src/shared/falloutnvinfo.h index 50a0d00d..231311de 100644 --- a/src/shared/falloutnvinfo.h +++ b/src/shared/falloutnvinfo.h @@ -88,7 +88,7 @@ public: // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to // the game directory - virtual std::vector getExecutables(); + //virtual std::vector getExecutables(); virtual std::wstring archiveListKey() { return L"SArchiveList"; } diff --git a/src/shared/gameinfo.h b/src/shared/gameinfo.h index d719a073..c11679f3 100644 --- a/src/shared/gameinfo.h +++ b/src/shared/gameinfo.h @@ -28,7 +28,7 @@ along with Mod Organizer. If not, see . #include namespace MOShared { - +/* enum CloseMOStyle { DEFAULT_CLOSE, DEFAULT_STAY, @@ -53,7 +53,7 @@ struct ExecutableInfo { CloseMOStyle closeMO; std::wstring steamAppID; }; - +*/ /** Class to manage information that depends on the used game type. The intention is to keep @@ -160,13 +160,9 @@ public: virtual bool rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPath) = 0; - // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to - // the game directory. the boolean says whether omo should be closed when the executable is started - virtual std::vector getExecutables() = 0; - public: - // initialise with the path to the omo directory (needs to be where hook.dll is stored). This + // initialise with the path to the mo directory (needs to be where hook.dll is stored). This // needs to be called before the instance can be retrieved static bool init(const std::wstring &moDirectory, const std::wstring &moDataDirectory, const std::wstring &gamePath = L""); diff --git a/src/shared/oblivioninfo.cpp b/src/shared/oblivioninfo.cpp index 790fcdb0..89a795a5 100644 --- a/src/shared/oblivioninfo.cpp +++ b/src/shared/oblivioninfo.cpp @@ -273,7 +273,7 @@ std::wstring OblivionInfo::getSteamAPPId(int) const return L"22330"; } - +/* std::vector OblivionInfo::getExecutables() { std::vector result; @@ -286,5 +286,5 @@ std::vector OblivionInfo::getExecutables() result.push_back(ExecutableInfo(L"BOSS (old)", L"Data/BOSS.exe", L"", L"", NEVER_CLOSE)); return result; -} +}*/ } // namespace MOShared diff --git a/src/shared/oblivioninfo.h b/src/shared/oblivioninfo.h index e64ae37b..cb506c01 100644 --- a/src/shared/oblivioninfo.h +++ b/src/shared/oblivioninfo.h @@ -84,7 +84,7 @@ public: // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to // the game directory - virtual std::vector getExecutables(); + //virtual std::vector getExecutables(); virtual std::wstring archiveListKey() { return L"SArchiveList"; } diff --git a/src/shared/skyriminfo.cpp b/src/shared/skyriminfo.cpp index 319e58d5..1203e3ed 100644 --- a/src/shared/skyriminfo.cpp +++ b/src/shared/skyriminfo.cpp @@ -306,7 +306,7 @@ bool SkyrimInfo::rerouteToProfile(const wchar_t *fileName, const wchar_t *fullPa } -std::vector SkyrimInfo::getExecutables() +/*std::vector SkyrimInfo::getExecutables() { std::vector result; result.push_back(ExecutableInfo(L"SKSE", L"skse_loader.exe", L"", L"", DEFAULT_CLOSE)); @@ -317,6 +317,6 @@ std::vector SkyrimInfo::getExecutables() result.push_back(ExecutableInfo(L"Creation Kit", L"CreationKit.exe", L"", L"", DEFAULT_STAY, L"202480")); return result; -} +}*/ } // namespace MOShared diff --git a/src/shared/skyriminfo.h b/src/shared/skyriminfo.h index a7aff8dc..ad6ab95d 100644 --- a/src/shared/skyriminfo.h +++ b/src/shared/skyriminfo.h @@ -94,7 +94,7 @@ public: // get a list of executables (game binary and known-to-work 3rd party tools). All of these are relative to // the game directory - virtual std::vector getExecutables(); + //virtual std::vector getExecutables(); virtual std::wstring archiveListKey() { return L"SResourceArchiveList"; } -- cgit v1.3.1