diff options
| author | Brian Munro <brian.alexander.munro@gmail.com> | 2018-03-05 11:35:58 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-03-05 11:35:58 +0200 |
| commit | c7e1d9738f06c6c6e7c9b41cdd8d35062d7c06b6 (patch) | |
| tree | c9c92380fcc82a64df8956face14e22bc3d2b4f7 /src | |
| parent | 7dfe0c9a8feae35ba7554493eea4fdb4e33a64d3 (diff) | |
| parent | a540a542a47fe75d0d0e4594158422aef4340b3a (diff) | |
Merge pull request #252 from LePresidente/new_vfs_library
Update master to latest stable code.
Diffstat (limited to 'src')
135 files changed, 7335 insertions, 8644 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94b75031..ed6c836a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ SET(organizer_SRCS profile.cpp pluginlistsortproxy.cpp pluginlist.cpp + pluginlistview.cpp overwriteinfodialog.cpp nxmaccessmanager.cpp nexusinterface.cpp @@ -31,17 +32,19 @@ SET(organizer_SRCS modlist.cpp modinfodialog.cpp modinfo.cpp - modinfobackup.cpp - modinfoforeign.cpp - modinfooverwrite.cpp - modinforegular.cpp - modinfowithconflictinfo.cpp + modinfobackup.cpp + modinfoforeign.cpp + modinfooverwrite.cpp + modinforegular.cpp + modinfowithconflictinfo.cpp messagedialog.cpp mainwindow.cpp main.cpp loghighlighter.cpp logbuffer.cpp + lockeddialogbase.cpp lockeddialog.cpp + waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp helper.cpp @@ -76,15 +79,17 @@ SET(organizer_SRCS previewdialog.cpp aboutdialog.cpp json.cpp - safewritefile.cpp modflagicondelegate.cpp genericicondelegate.cpp organizerproxy.cpp viewmarkingscrollbar.cpp plugincontainer.cpp organizercore.cpp + instancemanager.cpp + usvfsconnector.cpp + eventfilter.cpp + moshortcut.cpp - shared/inject.cpp shared/windows_error.cpp shared/error_report.cpp shared/directoryentry.cpp @@ -108,6 +113,7 @@ SET(organizer_HDRS profile.h pluginlistsortproxy.h pluginlist.h + pluginlistview.h overwriteinfodialog.h nxmaccessmanager.h nexusinterface.h @@ -116,16 +122,18 @@ SET(organizer_HDRS modlist.h modinfodialog.h modinfo.h - modinfobackup.h - modinfoforeign.h - modinfooverwrite.h - modinforegular.h - modinfowithconflictinfo.h + modinfobackup.h + modinfoforeign.h + modinfooverwrite.h + modinforegular.h + modinfowithconflictinfo.h messagedialog.h mainwindow.h loghighlighter.h logbuffer.h + lockeddialogbase.h lockeddialog.h + waitingonclosedialog.h loadmechanism.h installationmanager.h helper.h @@ -160,16 +168,20 @@ SET(organizer_HDRS previewdialog.h aboutdialog.h json.h - safewritefile.h modflagicondelegate.h genericicondelegate.h organizerproxy.h viewmarkingscrollbar.h plugincontainer.h organizercore.h + ilockedwaitingforprocess.h iuserinterface.h + instancemanager.h + usvfsconnector.h + eventfilter.h + descriptionpage.h + moshortcut.h - shared/inject.h shared/windows_error.h shared/error_report.h shared/directoryentry.h @@ -194,6 +206,7 @@ SET(organizer_UIS messagedialog.ui mainwindow.ui lockeddialog.ui + waitingonclosedialog.ui installdialog.ui finddialog.ui editexecutablesdialog.ui @@ -231,19 +244,22 @@ SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTOUIC ON) FIND_PACKAGE(Qt5Widgets REQUIRED) -FIND_PACKAGE(Qt5Declarative REQUIRED) +FIND_PACKAGE(Qt5QuickWidgets REQUIRED) +FIND_PACKAGE(Qt5Quick REQUIRED) FIND_PACKAGE(Qt5Network REQUIRED) FIND_PACKAGE(Qt5WinExtras REQUIRED) -FIND_PACKAGE(Qt5WebKitWidgets REQUIRED) +FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED) +FIND_PACKAGE(Qt5LinguistTools) QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS}) QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS}) +QT5_CREATE_TRANSLATION(organizer_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/organizer_en.ts) INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES}) ADD_DEFINITIONS(-DQT_MESSAGELOGCONTEXT) # Boost -SET(Boost_USE_STATIC_LIBS ON) +SET(Boost_USE_STATIC_LIBS OFF) SET(Boost_USE_MULTITHREADED ON) SET(Boost_USE_STATIC_RUNTIME OFF) FIND_PACKAGE(Boost REQUIRED) @@ -264,34 +280,52 @@ SET(project_path "${default_project_path}" CACHE PATH "path to the other mo proj SET(lib_path "${project_path}/../../install/libs") -MESSAGE(STATUS ${lib_path}) -MESSAGE(STATUS ${project_path}) - INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/bsatk/src ${project_path}/esptk/src ${project_path}/archive/src - ${project_path}/game_features/src) + ${project_path}/../usvfs/include + ${project_path}/game_gamebryo/src + ${project_path}/game_features/src + ${project_path}/githubpp/src) + + INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS}) LINK_DIRECTORIES(${lib_path} ${project_path}/../zlib/lib) -ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) +EXECUTE_PROCESS( + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +ADD_DEFINITIONS(-D_UNICODE -DUNICODE -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE -DGITID="${GIT_COMMIT_HASH}") -ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS}) +IF("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + SET(usvfs_name usvfs_x64) +ELSE() + SET(usvfs_name usvfs_x86) +ENDIF() + +ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm}) TARGET_LINK_LIBRARIES(ModOrganizer - Qt5::Widgets Qt5::WinExtras Qt5::WebKitWidgets + Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick ${Boost_LIBRARIES} zlibstatic - uibase esptk bsatk + uibase esptk bsatk githubpp + ${usvfs_name} Dbghelp advapi32 Version Shlwapi) - -SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS /GL) +IF (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "") + SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO + ${OPTIMIZE_COMPILE_FLAGS}) +ENDIF() SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES LINK_FLAGS_RELWITHDEBINFO - "/LTCG /INCREMENTAL:NO /LARGEADDRESSAWARE /OPT:REF /OPT:ICF") + "/LARGEADDRESSAWARE ${OPTIMIZE_LINK_FLAGS}") -QT5_USE_MODULES(ModOrganizer Widgets Declarative Network WebKitWidgets) +QT5_USE_MODULES(ModOrganizer Widgets Script Qml QuickWidgets Quick Network WebEngineWidgets) ############### @@ -303,23 +337,33 @@ FIND_PROGRAM(WINDEPLOYQT_COMMAND windeployqt PATHS ${qt5bin} NO_DEFAULT_PATH) INSTALL(TARGETS ModOrganizer RUNTIME DESTINATION bin) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ModOrganizer.pdb +INSTALL(FILES $<TARGET_PDB_FILE:ModOrganizer> DESTINATION pdb) INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/dlls.manifest.qt5 DESTINATION bin/dlls RENAME dlls.manifest) # use windeployqt.exe to install all required libraries -SET(windeploy_parameters --no-translations --no-plugins --libdir dlls --release-with-debug-info --no-compiler-runtime) +SET(windeploy_parameters "--no-translations --plugindir qtplugins --libdir dlls --release-with-debug-info --no-compiler-runtime") INSTALL( CODE "EXECUTE_PROCESS( COMMAND - ${qt5bin}/windeployqt.exe ModOrganizer.exe ${windeploy_parameters} + ${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets ${windeploy_parameters} + # run it a second time because on the first run it misses some files + COMMAND + ${qt5bin}/windeployqt.exe ModOrganizer.exe --webenginewidgets ${windeploy_parameters} COMMAND ${qt5bin}/windeployqt.exe uibase.dll ${windeploy_parameters} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin - )" + ) + file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/platforms) + file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/styles) + file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/dlls/imageformats) + file(RENAME ${CMAKE_INSTALL_PREFIX}/bin/qtplugins/platforms ${CMAKE_INSTALL_PREFIX}/bin/platforms) + file(RENAME ${CMAKE_INSTALL_PREFIX}/bin/qtplugins/styles ${CMAKE_INSTALL_PREFIX}/bin/styles) + file(RENAME ${CMAKE_INSTALL_PREFIX}/bin/qtplugins/imageformats ${CMAKE_INSTALL_PREFIX}/bin/dlls/imageformats) + file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/qtplugins)" ) INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/stylesheets diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index 3657a10d..ed57a217 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -54,10 +54,13 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) addLicense("RRZE Icon Set", LICENSE_CCBY3);
addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3);
addLicense("Castle Core", LICENSE_APACHE2);
+ addLicense("LOOT", LICENSE_GPL3);
ui->nameLabel->setText(QString("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").arg(ui->nameLabel->text()).arg(version));
-#ifdef HGID
+#if defined(HGID)
ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID);
+#elif defined(GITID)
+ ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID);
#else
ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown");
#endif
diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index ea5d2141..50a9de5f 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -6,8 +6,8 @@ <rect>
<x>0</x>
<y>0</y>
- <width>508</width>
- <height>335</height>
+ <width>604</width>
+ <height>369</height>
</rect>
</property>
<property name="windowTitle">
@@ -19,12 +19,49 @@ <item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QLabel" name="iconLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>250</width>
+ <height>250</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>250</width>
+ <height>250</height>
+ </size>
+ </property>
<property name="text">
<string/>
</property>
<property name="pixmap">
- <pixmap resource="resources.qrc">:/MO/gui/mo_icon.ico</pixmap>
+ <pixmap resource="resources.qrc">:/MO/gui/splash</pixmap>
+ </property>
+ <property name="scaledContents">
+ <bool>true</bool>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
@@ -83,7 +120,7 @@ <item>
<widget class="QLabel" name="label_3">
<property name="text">
- <string notr="true">Copyright 2011-2015 Sebastian Herbord</string>
+ <string notr="true">Copyright 2011-2016 Sebastian Herbord</string>
</property>
</widget>
</item>
@@ -114,12 +151,48 @@ </widget>
<widget class="QWidget" name="credits">
<attribute name="title">
- <string>Credits</string>
+ <string>Thanks</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
+ <string>Current Maintainers</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QListWidget" name="listWidget_2">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::NoSelection</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string notr="true">LePresidente</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Silarn</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">erasmux</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Diana</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
<string>Translators</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
@@ -200,9 +273,9 @@ </widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
+ <widget class="QGroupBox" name="groupBox_3">
<property name="title">
- <string>Others</string>
+ <string>Other Supporter</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
@@ -210,6 +283,11 @@ <property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
+ <item>
+ <property name="text">
+ <string notr="true">Al12 (Discord) </string>
+ </property>
+ </item>
<item>
<property name="text">
<string notr="true">blacksol</string>
@@ -265,6 +343,11 @@ <string notr="true">thosrtanner</string>
</property>
</item>
+ <item>
+ <property name="text">
+ <string>ogrotten</string>
+ </property>
+ </item>
</widget>
</item>
</layout>
diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index 5ef0a4e5..c7e3dca2 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -35,13 +35,8 @@ ActivateModsDialog::ActivateModsDialog(SaveGameInfo::MissingAssets const &missin QTableWidget *modsTable = findChild<QTableWidget*>("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;
diff --git a/src/activatemodsdialog.ui b/src/activatemodsdialog.ui index f6124bbc..cd8b0eec 100644 --- a/src/activatemodsdialog.ui +++ b/src/activatemodsdialog.ui @@ -17,14 +17,14 @@ <item>
<widget class="QTableWidget" name="modsTable">
<property name="toolTip">
- <string>This is a list of esps and esms that were active when the save game was created.</string>
+ <string>This is a list of esps, esms, and esls that were active when the save game was created.</string>
</property>
<property name="whatsThis">
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
-<p style=" 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;">This is a list of esps, esms, and esls 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></string>
diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index e5f0f21d..c2c65acc 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -28,13 +28,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <utility.h>
#include "settings.h"
+#include <QWebEngineSettings>
#include <QNetworkCookieJar>
#include <QNetworkCookie>
#include <QMenu>
#include <QInputDialog>
-#include <QWebHistory>
+#include <QWebEngineHistory>
#include <QDir>
-#include <QWebFrame>
#include <QDesktopWidget>
#include <QKeyEvent>
@@ -77,8 +77,8 @@ void BrowserDialog::closeEvent(QCloseEvent *event) void BrowserDialog::initTab(BrowserView *newView)
{
- newView->page()->setNetworkAccessManager(m_AccessManager);
- newView->page()->setForwardUnsupportedContent(true);
+ //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)));
@@ -86,14 +86,14 @@ void BrowserDialog::initTab(BrowserView *newView) 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*)));
+ connect(newView, SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest)));
+ connect(newView, 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);
+ newView->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
+ newView->settings()->setAttribute(QWebEngineSettings::AutoLoadImages, true);
}
@@ -133,10 +133,10 @@ void BrowserDialog::openUrl(const QUrl &url) void BrowserDialog::maximizeWidth()
{
- int viewportWidth = getCurrentView()->page()->viewportSize().width();
+ int viewportWidth = getCurrentView()->page()->contentsSize ().width();
int frameWidth = width() - viewportWidth;
- int contentWidth = getCurrentView()->page()->mainFrame()->contentsSize().width();
+ int contentWidth = getCurrentView()->page()->contentsSize().width();
QDesktopWidget screen;
int currentScreen = screen.screenNumber(this);
@@ -190,7 +190,7 @@ QString BrowserDialog::guessFileName(const QString &url) void BrowserDialog::unsupportedContent(QNetworkReply *reply)
{
try {
- QWebPage *page = qobject_cast<QWebPage*>(sender());
+ QWebEnginePage *page = qobject_cast<QWebEnginePage*>(sender());
if (page == nullptr) {
qCritical("sender not a page");
return;
@@ -252,10 +252,10 @@ void BrowserDialog::startSearch() void BrowserDialog::on_searchEdit_returnPressed()
{
- BrowserView *currentView = getCurrentView();
- if (currentView != nullptr) {
- currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument);
- }
+// BrowserView *currentView = getCurrentView();
+// if (currentView != nullptr) {
+// currentView->findText(ui->searchEdit->text(), QWebEnginePage::FindWrapsAroundDocument);
+// }
}
void BrowserDialog::on_refreshBtn_clicked()
@@ -274,7 +274,7 @@ void BrowserDialog::on_browserTabWidget_currentChanged(int index) void BrowserDialog::on_urlEdit_returnPressed()
{
- QWebView *currentView = getCurrentView();
+ QWebEngineView *currentView = getCurrentView();
if (currentView != nullptr) {
currentView->setUrl(QUrl(ui->urlEdit->text()));
}
diff --git a/src/browserdialog.h b/src/browserdialog.h index 04567f5f..354a377b 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -24,7 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QNetworkRequest>
#include <QNetworkReply>
#include <QTimer>
-#include <QWebView>
+#include <QWebEngineView>
#include <QQueue>
#include <QTabWidget>
#include <QAtomicInt>
diff --git a/src/browserview.cpp b/src/browserview.cpp index aeb16520..0b871e23 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -21,23 +21,23 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QEvent>
#include <QKeyEvent>
-#include <QWebFrame>
-#include <QWebElement>
#include <QNetworkDiskCache>
+#include <QWebEngineContextMenuData>
+#include <QWebEngineSettings>
#include <QMenu>
#include <Shlwapi.h>
#include "utility.h"
BrowserView::BrowserView(QWidget *parent)
- : QWebView(parent)
+ : QWebEngineView(parent)
{
installEventFilter(this);
- page()->settings()->setMaximumPagesInCache(10);
+ //page()->settings()->setMaximumPagesInCache(10);
}
-QWebView *BrowserView::createWindow(QWebPage::WebWindowType)
+QWebEngineView *BrowserView::createWindow(QWebEnginePage::WebWindowType)
{
BrowserView *newView = new BrowserView(parentWidget());
emit initTab(newView);
@@ -59,17 +59,18 @@ bool BrowserView::eventFilter(QObject *obj, QEvent *event) mouseEvent->ignore();
return true;
}
- } else if (event->type() == QEvent::MouseButtonRelease) {
- QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(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;
- }
+// TODO This is due to that QTWebEnginePage doesn't support QWebFrame anymore
+// } else if (event->type() == QEvent::MouseButtonRelease) {
+// QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+// if (mouseEvent->button() == Qt::MidButton) {
+// QWebEngineContextMenuData hitTest = page()->hitTestContent(mouseEvent->pos());
+// if (hitTest.linkUrl().isValid()) {
+// emit openUrlInNewTab(hitTest.linkUrl());
+// }
+// mouseEvent->ignore();
+//
+// return true;
+// }
}
- return QWebView::eventFilter(obj, event);
+ return QWebEngineView::eventFilter(obj, event);
}
diff --git a/src/browserview.h b/src/browserview.h index 6a89752a..24be21c1 100644 --- a/src/browserview.h +++ b/src/browserview.h @@ -24,13 +24,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. class QEvent;
class QUrl;
class QWidget;
-#include <QWebView>
-#include <QWebPage>
+#include <QWebEngineView>
+#include <QWebEnginePage>
/**
* @brief web view used to display a nexus page
**/
-class BrowserView : public QWebView
+class BrowserView : public QWebEngineView
{
Q_OBJECT
@@ -66,7 +66,7 @@ signals: protected:
- virtual QWebView *createWindow(QWebPage::WebWindowType type);
+ virtual QWebEngineView *createWindow(QWebEnginePage::WebWindowType type);
virtual bool eventFilter(QObject *obj, QEvent *event);
diff --git a/src/categories.cpp b/src/categories.cpp index 59291a49..d8cd49fb 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -44,6 +44,10 @@ QString CategoryFactory::categoriesFilePath() CategoryFactory::CategoryFactory()
{
atexit(&cleanup);
+}
+
+void CategoryFactory::loadCategories()
+{
reset();
QFile categoryFile(categoriesFilePath());
@@ -188,7 +192,7 @@ int CategoryFactory::addCategory(const QString &name, const std::vector<int> &ne void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
{
- int index = m_Categories.size();
+ int index = static_cast<int>(m_Categories.size());
m_Categories.push_back(Category(index, id, name, nexusIDs, parentID));
for (int nexusID : nexusIDs) {
m_NexusMap[nexusID] = index;
@@ -201,42 +205,63 @@ 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<int>(1, 51), 0);
- addCategory(2, "Armour", MakeVector<int>(1, 54), 0);
- addCategory(3, "Sound & Music", MakeVector<int>(1, 61), 0);
- addCategory(5, "Clothing", MakeVector<int>(1, 60), 0);
- addCategory(6, "Collectables", MakeVector<int>(1, 92), 0);
- addCategory(28, "Companions", MakeVector<int>(2, 66, 96), 0);
- addCategory(7, "Creatures & Mounts", MakeVector<int>(2, 83, 65), 0);
- addCategory(8, "Factions", MakeVector<int>(1, 25), 0);
- addCategory(9, "Gameplay", MakeVector<int>(1, 24), 0);
- addCategory(10, "Hair", MakeVector<int>(1, 26), 0);
+ addCategory(1, "Animations", MakeVector<int>(2, 4, 51), 0);
+ addCategory(52, "Poses", MakeVector<int>(1, 29), 1);
+ addCategory(2, "Armour", MakeVector<int>(2, 5, 54), 0);
+ addCategory(53, "Power Armor", MakeVector<int>(1, 53), 2);
+ addCategory(3, "Audio", MakeVector<int>(3, 33, 35, 106), 0);
+ addCategory(38, "Music", MakeVector<int>(2, 34, 61), 0);
+ addCategory(39, "Voice", MakeVector<int>(2, 36, 107), 0);
+ addCategory(5, "Clothing", MakeVector<int>(2, 9, 60), 0);
+ addCategory(41, "Jewelry", MakeVector<int>(1, 102), 5);
+ addCategory(42, "Backpacks", MakeVector<int>(1, 49), 5);
+ addCategory(6, "Collectables", MakeVector<int>(2, 10, 92), 0);
+ addCategory(28, "Companions", MakeVector<int>(3, 11, 66, 96), 0);
+ addCategory(7, "Creatures, Mounts, & Vehicles", MakeVector<int>(4, 12, 65, 83, 101), 0);
+ addCategory(8, "Factions", MakeVector<int>(2, 16, 25), 0);
+ addCategory(9, "Gameplay", MakeVector<int>(2, 15, 24), 0);
+ addCategory(27, "Combat", MakeVector<int>(1, 77), 9);
+ addCategory(43, "Crafting", MakeVector<int>(2, 50, 100), 9);
+ addCategory(48, "Overhauls", MakeVector<int>(2, 24, 79), 9);
+ addCategory(49, "Perks", MakeVector<int>(1, 27), 9);
+ addCategory(54, "Radio", MakeVector<int>(1, 31), 9);
+ addCategory(55, "Shouts", MakeVector<int>(1, 104), 9);
+ addCategory(22, "Skills & Levelling", MakeVector<int>(2, 46, 73), 9);
+ addCategory(58, "Weather & Lighting", MakeVector<int>(1, 56), 9);
+ addCategory(44, "Equipment", MakeVector<int>(1, 44), 43);
+ addCategory(45, "Home/Settlement", MakeVector<int>(1, 45), 43);
+ addCategory(10, "Body, Face, & Hair", MakeVector<int>(2, 17, 26), 0);
+ addCategory(39, "Tattoos", MakeVector<int>(1, 57), 10);
+ addCategory(40, "Character Presets", MakeVector<int>(1, 58), 0);
addCategory(11, "Items", MakeVector<int>(2, 27, 85), 0);
- addCategory(32, "Mercantile", MakeVector<int>(1, 69), 0);
- addCategory(19, "Weapons", MakeVector<int>(1, 55), 11);
- addCategory(36, "Weapon & Armour Sets", MakeVector<int>(1, 39), 11);
- addCategory(12, "Locations", MakeVector<int>(7, 22, 30, 70, 88, 89, 90, 91), 0);
- addCategory(31, "Landscape Changes", MakeVector<int>(1, 58), 0);
- addCategory(4, "Cities", MakeVector<int>(1, 53), 12);
- addCategory(29, "Environment", MakeVector<int>(1, 74), 0);
- addCategory(30, "Immersion", MakeVector<int>(1, 78), 0);
+ addCategory(32, "Mercantile", MakeVector<int>(2, 23, 69), 0);
+ addCategory(37, "Ammo", MakeVector<int>(1, 3), 11);
+ addCategory(19, "Weapons", MakeVector<int>(2, 41, 55), 11);
+ addCategory(36, "Weapon & Armour Sets", MakeVector<int>(1, 42), 11);
+ addCategory(23, "Player Homes", MakeVector<int>(2, 28, 67), 0);
addCategory(25, "Castles & Mansions", MakeVector<int>(1, 68), 23);
+ addCategory(51, "Settlements", MakeVector<int>(1, 48), 23);
+ addCategory(12, "Locations", MakeVector<int>(10, 20, 21, 22, 30, 47, 70, 88, 89, 90, 91), 0);
+ addCategory(4, "Cities", MakeVector<int>(1, 53), 12);
+ addCategory(31, "Landscape Changes", MakeVector<int>(1, 58), 0);
+ addCategory(29, "Environment", MakeVector<int>(2, 14, 74), 0);
+ addCategory(30, "Immersion", MakeVector<int>(2, 51, 78), 0);
addCategory(20, "Magic", MakeVector<int>(3, 75, 93, 94), 0);
- addCategory(21, "Models & Textures", MakeVector<int>(1, 29), 0);
- addCategory(33, "Modders resources", MakeVector<int>(1, 82), 0);
- addCategory(13, "NPCs", MakeVector<int>(1, 33), 0);
- addCategory(14, "Patches", MakeVector<int>(2, 79, 84), 0);
- addCategory(24, "Bugfixes", MakeVector<int>(1, 95), 0);
- addCategory(35, "Utilities", MakeVector<int>(1, 39), 0);
- addCategory(26, "Cheats", MakeVector<int>(1, 40), 0);
- addCategory(23, "Player Homes", MakeVector<int>(1, 67), 0);
- addCategory(15, "Quests", MakeVector<int>(1, 35), 0);
+ addCategory(21, "Models & Textures", MakeVector<int>(2, 19, 29), 0);
+ addCategory(33, "Modders resources", MakeVector<int>(2, 18, 82), 0);
+ addCategory(13, "NPCs", MakeVector<int>(3, 22, 33, 99), 0);
+ addCategory(24, "Bugfixes", MakeVector<int>(2, 6, 95), 0);
+ addCategory(14, "Patches", MakeVector<int>(2, 25, 84), 24);
+ addCategory(35, "Utilities", MakeVector<int>(2, 38, 39), 0);
+ addCategory(26, "Cheats", MakeVector<int>(1, 8), 0);
+ addCategory(15, "Quests", MakeVector<int>(2, 30, 35), 0);
addCategory(16, "Races & Classes", MakeVector<int>(1, 34), 0);
- addCategory(27, "Combat", MakeVector<int>(1, 77), 0);
- addCategory(22, "Skills", MakeVector<int>(1, 73), 0);
addCategory(34, "Stealth", MakeVector<int>(1, 76), 0);
- addCategory(17, "UI", MakeVector<int>(1, 42), 0);
- addCategory(18, "Visuals", MakeVector<int>(1, 62), 0);
+ addCategory(17, "UI", MakeVector<int>(2, 37, 42), 0);
+ addCategory(18, "Visuals", MakeVector<int>(2, 40, 62), 0);
+ addCategory(50, "Pip-Boy", MakeVector<int>(1, 52), 18);
+ addCategory(46, "Shader Presets", MakeVector<int>(3, 13, 97, 105), 0);
+ addCategory(47, "Miscellaneous", MakeVector<int>(2, 2, 28), 0);
}
@@ -317,9 +342,11 @@ int CategoryFactory::getCategoryIndex(int ID) const int CategoryFactory::getCategoryID(const QString &name) const
{
- auto iter = std::find_if(m_Categories.begin(), m_Categories.end(), [name] (const Category &cat) -> bool {
+ auto iter = std::find_if(m_Categories.begin(), m_Categories.end(),
+ [name] (const Category &cat) -> bool {
return cat.m_Name == name;
});
+
if (iter != m_Categories.end()) {
return iter->m_ID;
} else {
diff --git a/src/categories.h b/src/categories.h index 29e794d8..66299c30 100644 --- a/src/categories.h +++ b/src/categories.h @@ -76,6 +76,11 @@ public: void reset();
/**
+ * @brief read categories from file
+ */
+ void loadCategories();
+
+ /**
* @brief save the categories to the categories.dat file
**/
void saveCategories();
diff --git a/src/credentialsdialog.ui b/src/credentialsdialog.ui index 3dd9a62a..918441d8 100644 --- a/src/credentialsdialog.ui +++ b/src/credentialsdialog.ui @@ -6,7 +6,7 @@ <rect>
<x>0</x>
<y>0</y>
- <width>282</width>
+ <width>294</width>
<height>156</height>
</rect>
</property>
@@ -25,29 +25,25 @@ </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
- <item>
+ <item row="0" column="1">
<widget class="QLineEdit" name="usernameEdit"/>
</item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
+ <item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Password</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="1">
<widget class="QLineEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
diff --git a/src/descriptionpage.h b/src/descriptionpage.h new file mode 100644 index 00000000..f6158ee0 --- /dev/null +++ b/src/descriptionpage.h @@ -0,0 +1,28 @@ +#include <QWebEnginePage> + +#ifndef DESCRIPTIONPAGE_H +#define DESCRIPTIONPAGE_H + +class DescriptionPage : public QWebEnginePage +{ + Q_OBJECT + +public: + DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent){} + + bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame) + { + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { + emit linkClicked(url); + return false; + } + return true; + } + +signals: + void linkClicked(const QUrl&); + +}; + +#endif //DESCRIPTIONPAGE_H diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index f50a717e..272b0596 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -88,8 +88,8 @@ void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure QFileInfo fileInfo(archive);
if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) {
try {
- directoryStructure->addFromBSA(ToWString(modName), directoryW,
- ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority);
+ //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()));
}
@@ -144,7 +144,7 @@ void DirectoryRefresher::refresh() m_DirectoryStructure = new DirectoryEntry(L"data", nullptr, 0);
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame*>();
std::wstring dataDirectory = QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString();
m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0);
@@ -159,7 +159,7 @@ void DirectoryRefresher::refresh() } catch (const std::exception &e) {
emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what()));
}
- emit progress((i * 100) / m_Mods.size() + 1);
+ emit progress((i * 100) / static_cast<int>(m_Mods.size()) + 1);
}
emit progress(100);
diff --git a/src/dlls.manifest.qt5 b/src/dlls.manifest.qt5 index 5c64e4f9..924512d9 100644 --- a/src/dlls.manifest.qt5 +++ b/src/dlls.manifest.qt5 @@ -1,29 +1,26 @@ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
- <assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
- <file name="icuin54.dll"/>
- <file name="icuuc54.dll"/>
- <file name="icudt54.dll"/>
- <file name="Qt5Core.dll"/>
- <file name="Qt5Declarative.dll"/>
- <file name="Qt5Gui.dll"/>
- <file name="Qt5Multimedia.dll"/>
- <file name="Qt5MultimediaWidgets.dll"/>
- <file name="Qt5Network.dll"/>
- <file name="Qt5OpenGL.dll"/>
- <file name="Qt5Positioning.dll"/>
- <file name="Qt5PrintSupport.dll"/>
- <file name="Qt5Qml.dll"/>
- <file name="Qt5Quick.dll"/>
- <file name="Qt5Sensors.dll"/>
- <file name="Qt5Script.dll"/>
- <file name="Qt5Sql.dll"/>
- <file name="Qt5Svg.dll"/>
- <file name="Qt5WebChannel.dll"/>
- <file name="Qt5WebKit.dll"/>
- <file name="Qt5WebKitWidgets.dll"/>
- <file name="Qt5Widgets.dll"/>
- <file name="Qt5WinExtras.dll"/>
- <file name="Qt5Xml.dll"/>
- <file name="Qt5XmlPatterns.dll"/>
-</assembly>
+ <assemblyIdentity type="win32" name="dlls" version="1.0.0.0" processorArchitecture="x86"/>
+ <file name="7z.dll"/>
+ <file name="archive.dll"/>
+ <file name="d3dcompiler_47.dll"/>
+ <file name="libEGL.dll"/>
+ <file name="libGLESV2.dll"/>
+ <file name="liblz4.dll"/>
+ <file name="opengl32sw.dll"/>
+ <file name="Qt5Core.dll"/>
+ <file name="Qt5Gui.dll"/>
+ <file name="Qt5Network.dll"/>
+ <file name="Qt5Positioning.dll"/>
+ <file name="Qt5PrintSupport.dll"/>
+ <file name="Qt5Qml.dll"/>
+ <file name="Qt5Quick.dll"/>
+ <file name="Qt5QuickWidgets.dll"/>
+ <file name="Qt5WebChannel.dll"/>
+ <file name="Qt5SerialPort.dll"/>
+ <file name="Qt5Svg.dll"/>
+ <file name="Qt5WebEngineCore.dll"/>
+ <file name="Qt5WebEngineWidgets.dll"/>
+ <file name="Qt5Widgets.dll"/>
+ <file name="Qt5WinExtras.dll"/>
+</assembly>
\ No newline at end of file diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 78bdf1aa..9a8ef572 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -318,7 +318,7 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * if (hidden) {
menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView()));
} else {
- menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView()));
+ menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView()));
}
} else if (state == DownloadManager::STATE_DOWNLOADING){
menu.addAction(tr("Cancel"), this, SLOT(issueCancel()));
@@ -334,8 +334,8 @@ bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel * 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.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
+ menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll()));
}
menu.exec(mouseEvent->globalPos());
diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 32a3f10d..f7975150 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -303,7 +303,7 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem if (hidden) {
menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView()));
} else {
- menu.addAction(tr("Remove from View"), this, SLOT(issueRemoveFromView()));
+ menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView()));
}
} else if (state == DownloadManager::STATE_DOWNLOADING){
menu.addAction(tr("Cancel"), this, SLOT(issueCancel()));
@@ -319,8 +319,8 @@ bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItem 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.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
+ menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll()));
}
menu.exec(mouseEvent->globalPos());
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index e327e55a..a4fde093 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -88,7 +88,8 @@ DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(con QString fileName = QFileInfo(filePath).fileName();
if (fileName.endsWith(UNFINISHED)) {
- info->m_FileName = fileName.mid(0, fileName.length() - strlen(UNFINISHED));
+ info->m_FileName = fileName.mid(
+ 0, fileName.length() - static_cast<int>(strlen(UNFINISHED)));
info->m_State = STATE_PAUSED;
} else {
info->m_FileName = fileName;
@@ -471,7 +472,7 @@ void DownloadManager::addNXMDownload(const QString &url) void DownloadManager::removeFile(int index, bool deleteFile)
{
if (index >= m_ActiveDownloads.size()) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("remove: invalid download index %1").arg(index));
}
DownloadInfo *download = m_ActiveDownloads.at(index);
@@ -538,7 +539,7 @@ void DownloadManager::refreshAlphabeticalTranslation() void DownloadManager::restoreDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("restore: invalid download index: %1").arg(index));
}
DownloadInfo *download = m_ActiveDownloads.at(index);
@@ -557,21 +558,39 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit aboutToUpdate();
if (index < 0) {
- DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY;
- index = 0;
- for (QVector<DownloadInfo*>::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;
- }
- }
+ if (index == -1) {
+ DownloadState minState = STATE_READY;
+ index = 0;
+ for (QVector<DownloadInfo*>::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 {
+ DownloadState minState = STATE_INSTALLED;
+ index = 0;
+ for (QVector<DownloadInfo*>::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));
+ reportError(tr("remove: invalid download index %1").arg(index));
return;
}
@@ -589,7 +608,7 @@ void DownloadManager::removeDownload(int index, bool deleteFile) void DownloadManager::cancelDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("cancel: invalid download index %1").arg(index));
return;
}
@@ -602,7 +621,7 @@ void DownloadManager::cancelDownload(int index) void DownloadManager::pauseDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("pause: invalid download index %1").arg(index));
return;
}
@@ -622,7 +641,7 @@ void DownloadManager::pauseDownload(int index) void DownloadManager::resumeDownload(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("resume: invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -633,7 +652,7 @@ void DownloadManager::resumeDownload(int index) void DownloadManager::resumeDownloadInt(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("resume (int): invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -673,7 +692,7 @@ DownloadManager::DownloadInfo *DownloadManager::downloadInfoByID(unsigned int id void DownloadManager::queryInfo(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
+ reportError(tr("query: invalid download index %1").arg(index));
return;
}
DownloadInfo *info = m_ActiveDownloads[index];
@@ -693,19 +712,14 @@ void DownloadManager::queryInfo(int 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(nullptr, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal,
- QString(), nullptr, 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(nullptr, 10);
+ bool ok = false;
+ int modId = QInputDialog::getInt(
+ nullptr, tr("Please enter the nexus mod id"), tr("Mod ID:"), 1, 1,
+ std::numeric_limits<int>::max(), 1, &ok);
+ // careful now: while the dialog was displayed, events were processed.
+ // the download list might have changed and our info-ptr invalidated.
+ m_ActiveDownloads[index]->m_FileInfo->modID = modId;
+ return;
}
}
info->m_ReQueried = true;
@@ -726,7 +740,7 @@ int DownloadManager::numPendingDownloads() const std::pair<int, int> DownloadManager::getPendingDownload(int index)
{
if ((index < 0) || (index >= m_PendingDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("get pending: invalid download index %1").arg(index));
}
return m_PendingDownloads.at(index);
@@ -735,7 +749,7 @@ std::pair<int, int> DownloadManager::getPendingDownload(int index) QString DownloadManager::getFilePath(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("get path: invalid download index %1").arg(index));
}
return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName;
@@ -756,24 +770,27 @@ QString DownloadManager::getFileTypeString(int fileType) QString DownloadManager::getDisplayName(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("display name: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
+ QTextDocument doc;
if (!info->m_FileInfo->name.isEmpty()) {
- return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name)
+ doc.setHtml(info->m_FileInfo->name);
+ return QString("%1 (%2, v%3)").arg(doc.toPlainText())
.arg(getFileTypeString(info->m_FileInfo->fileCategory))
.arg(info->m_FileInfo->version.displayString());
} else {
- return info->m_FileName;
+ doc.setHtml(info->m_FileName);
+ return doc.toPlainText();
}
}
QString DownloadManager::getFileName(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file name: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileName;
@@ -782,7 +799,7 @@ QString DownloadManager::getFileName(int index) const QDateTime DownloadManager::getFileTime(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file time: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -796,7 +813,7 @@ QDateTime DownloadManager::getFileTime(int index) const qint64 DownloadManager::getFileSize(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file size: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_TotalSize;
@@ -806,7 +823,7 @@ qint64 DownloadManager::getFileSize(int index) const int DownloadManager::getProgress(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("progress: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_Progress;
@@ -816,7 +833,7 @@ int DownloadManager::getProgress(int index) const DownloadManager::DownloadState DownloadManager::getState(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("state: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_State;
@@ -826,7 +843,7 @@ DownloadManager::DownloadState DownloadManager::getState(int index) const bool DownloadManager::isInfoIncomplete(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("infocomplete: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -841,7 +858,7 @@ bool DownloadManager::isInfoIncomplete(int index) const int DownloadManager::getModID(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mod id: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileInfo->modID;
}
@@ -849,7 +866,7 @@ int DownloadManager::getModID(int index) const bool DownloadManager::isHidden(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("ishidden: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_Hidden;
}
@@ -858,7 +875,7 @@ bool DownloadManager::isHidden(int index) const const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("file info: invalid download index %1").arg(index));
}
return m_ActiveDownloads.at(index)->m_FileInfo;
@@ -868,7 +885,7 @@ const ModRepositoryFileInfo *DownloadManager::getFileInfo(int index) const void DownloadManager::markInstalled(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mark installed: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -883,7 +900,7 @@ void DownloadManager::markInstalled(int index) void DownloadManager::markUninstalled(int index)
{
if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
+ throw MyException(tr("mark uninstalled: invalid download index %1").arg(index));
}
DownloadInfo *info = m_ActiveDownloads.at(index);
@@ -1062,7 +1079,9 @@ void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant r DownloadInfo *info = downloadInfoByID(userData.toInt());
if (info == nullptr) return;
info->m_FileInfo->categoryID = result["category_id"].toInt();
- info->m_FileInfo->modName = result["name"].toString().trimmed();
+ QTextDocument doc;
+ doc.setHtml(result["name"].toString().trimmed());
+ info->m_FileInfo->modName = doc.toPlainText();
info->m_FileInfo->newestVersion.parse(result["version"].toString());
if (info->m_FileInfo->fileID != 0) {
setState(info, STATE_READY);
@@ -1120,7 +1139,7 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD bool found = false;
- foreach(QVariant file, result) {
+ for (QVariant file : result) {
QVariantMap fileInfo = file.toMap();
QString fileName = fileInfo["uri"].toString();
QString fileNameVariant = fileName.mid(0).replace(' ', '_');
@@ -1131,12 +1150,10 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD if (!info->m_FileInfo->version.isValid()) {
info->m_FileInfo->version = info->m_FileInfo->newestVersion;
}
- //Nexus has HTMLd these so unhtml them if necessary
+ // we receive some names html-encoded. This is used to decode it
QTextDocument doc;
- doc.setHtml(info->m_FileInfo->modName);
+ doc.setHtml(fileInfo["modName"].toString());
info->m_FileInfo->modName = doc.toPlainText();
- doc.setHtml(info->m_FileInfo->name);
- info->m_FileInfo->name = doc.toPlainText();
info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt());
info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString());
info->m_FileInfo->fileID = fileInfo["id"].toInt();
@@ -1152,7 +1169,7 @@ void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultD 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) {
+ for (QVariant file : result) {
QVariantMap fileInfo = file.toMap();
selection.addChoice(fileInfo["uri"].toString(), "", file);
}
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index b9d548a6..fde6b397 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "ui_editexecutablesdialog.h" #include "filedialogmemory.h" #include "stackdata.h" +#include "modlist.h" #include <QMessageBox> #include <Shellapi.h> #include <utility.h> @@ -29,15 +30,20 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase; using namespace MOShared; -EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) +EditExecutablesDialog::EditExecutablesDialog( + const ExecutablesList &executablesList, const ModList &modList, + Profile *profile, QWidget *parent) : TutorableDialog("EditExecutables", parent) , ui(new Ui::EditExecutablesDialog) , m_CurrentItem(nullptr) , m_ExecutablesList(executablesList) + , m_Profile(profile) { ui->setupUi(this); refreshExecutablesWidget(); + + ui->newFilesModBox->addItems(modList.allMods()); } EditExecutablesDialog::~EditExecutablesDialog() @@ -85,28 +91,34 @@ void EditExecutablesDialog::resetInput() ui->argumentsEdit->setText(""); ui->appIDOverwriteEdit->clear(); ui->overwriteAppIDBox->setChecked(false); - ui->closeCheckBox->setChecked(false); ui->useAppIconCheckBox->setChecked(false); + ui->newFilesModCheckBox->setChecked(false); m_CurrentItem = nullptr; } void EditExecutablesDialog::saveExecutable() { - m_ExecutablesList.updateExecutable(ui->titleEdit->text(), - QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), - QDir::fromNativeSeparators(ui->workingDirEdit->text()), - (ui->closeCheckBox->checkState() == Qt::Checked) ? - ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE - : ExecutableInfo::CloseMOStyle::DEFAULT_STAY, - ui->overwriteAppIDBox->isChecked() ? - ui->appIDOverwriteEdit->text() : "", - Executable::UseApplicationIcon | Executable::CustomExecutable, - (ui->useAppIconCheckBox->isChecked() ? - Executable::UseApplicationIcon : Executable::Flags()) - | Executable::CustomExecutable); + m_ExecutablesList.updateExecutable( + ui->titleEdit->text(), + QDir::fromNativeSeparators(ui->binaryEdit->text()), + ui->argumentsEdit->text(), + QDir::fromNativeSeparators(ui->workingDirEdit->text()), + ui->overwriteAppIDBox->isChecked() ? + ui->appIDOverwriteEdit->text() : "", + Executable::UseApplicationIcon | Executable::CustomExecutable, + (ui->useAppIconCheckBox->isChecked() ? + Executable::UseApplicationIcon : Executable::Flags()) + | Executable::CustomExecutable); + + if (ui->newFilesModCheckBox->isChecked()) { + m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), + ui->newFilesModBox->currentText()); } + else { + m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); + } +} void EditExecutablesDialog::delayedRefresh() @@ -130,15 +142,17 @@ void EditExecutablesDialog::on_addButton_clicked() void EditExecutablesDialog::on_browseButton_clicked() { - QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, - tr("Select a binary"), QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); + 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(), nullptr, buffer) > (HINSTANCE)32) { + if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer) + > reinterpret_cast<HINSTANCE>(32)) { DWORD binaryType = 0UL; if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); @@ -214,11 +228,16 @@ bool EditExecutablesDialog::executableChanged() { if (m_CurrentItem != nullptr) { Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); - return selectedExecutable.m_Arguments != ui->argumentsEdit->text() + + QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + + return selectedExecutable.m_Title != ui->titleEdit->text() + || selectedExecutable.m_Arguments != ui->argumentsEdit->text() || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() + || !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked() + || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText()) || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || (selectedExecutable.m_CloseMO == ExecutableInfo::CloseMOStyle::DEFAULT_CLOSE) != ui->closeCheckBox->isChecked() || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked(); } else { QFileInfo fileInfo(ui->binaryEdit->text()); @@ -291,14 +310,6 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur 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 == 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 { - ui->closeCheckBox->setEnabled(true); - ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); - } ui->removeButton->setEnabled(selectedExecutable.isCustom()); ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty()); if (!selectedExecutable.m_SteamAppID.isEmpty()) { @@ -307,5 +318,23 @@ void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex &cur ui->appIDOverwriteEdit->clear(); } ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon()); + + int index = -1; + + QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + if (!customOverwrite.isEmpty()) { + index = ui->newFilesModBox->findText(customOverwrite); + qDebug("find %s -> %d", qPrintable(customOverwrite), index); + } + + ui->newFilesModCheckBox->setChecked(index != -1); + if (index != -1) { + ui->newFilesModBox->setCurrentIndex(index); + } } } + +void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked) +{ + ui->newFilesModBox->setEnabled(checked); +} diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 4f6c5315..0f3dbaff 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -24,11 +24,16 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QListWidgetItem>
#include <QTimer>
#include "executableslist.h"
+#include "profile.h"
namespace Ui {
class EditExecutablesDialog;
}
+
+class ModList;
+
+
/**
* @brief Dialog to manage the list of executables
**/
@@ -44,7 +49,10 @@ public: * @param executablesList current list of executables
* @param parent parent widget
**/
- explicit EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent = 0);
+ explicit EditExecutablesDialog(const ExecutablesList &executablesList,
+ const ModList &modList,
+ Profile *profile,
+ QWidget *parent = 0);
~EditExecutablesDialog();
@@ -56,6 +64,10 @@ public: ExecutablesList getExecutablesList() const;
void saveExecutable();
+
+private slots:
+ void on_newFilesModCheckBox_toggled(bool checked);
+
private slots:
void on_binaryEdit_textChanged(const QString &arg1);
@@ -89,12 +101,13 @@ private: bool executableChanged();
private:
- Ui::EditExecutablesDialog *ui;
+ Ui::EditExecutablesDialog *ui;
- QListWidgetItem *m_CurrentItem;
+ QListWidgetItem *m_CurrentItem;
- ExecutablesList m_ExecutablesList;
+ ExecutablesList m_ExecutablesList;
+ Profile *m_Profile;
};
#endif // EDITEXECUTABLESDIALOG_H
diff --git a/src/editexecutablesdialog.ui b/src/editexecutablesdialog.ui index abb6fe68..bdbb8bd1 100644 --- a/src/editexecutablesdialog.ui +++ b/src/editexecutablesdialog.ui @@ -6,10 +6,16 @@ <rect>
<x>0</x>
<y>0</y>
- <width>384</width>
- <height>446</height>
+ <width>426</width>
+ <height>460</height>
</rect>
</property>
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>200</height>
+ </size>
+ </property>
<property name="windowTitle">
<string>Modify Executables</string>
</property>
@@ -165,22 +171,37 @@ Right now the only case I know of where this needs to be overwritten is for the </layout>
</item>
<item>
- <widget class="QCheckBox" name="closeCheckBox">
- <property name="toolTip">
- <string>If checked, MO will be closed once the specified executable is run.</string>
- </property>
- <property name="whatsThis">
- <string>If checked, MO will be closed once the specified executable is run.</string>
- </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
+ <item>
+ <widget class="QCheckBox" name="newFilesModCheckBox">
+ <property name="toolTip">
+ <string>If this is enabled, new files are created in the specified mod instead of the "Overwrite" mod.</string>
+ </property>
+ <property name="text">
+ <string>Create Files in Mod instead of Overwrite (*)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="newFilesModBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useAppIconCheckBox">
<property name="text">
- <string>Close MO when started</string>
+ <string>Use Application's Icon for shortcuts</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useAppIconCheckBox">
+ <widget class="QLabel" name="label_5">
<property name="text">
- <string>Use Application's Icon for shortcuts</string>
+ <string>(*) This setting is profile-specific</string>
</property>
</widget>
</item>
@@ -247,11 +268,24 @@ Right now the only case I know of where this needs to be overwritten is for the </layout>
</item>
</layout>
- <zorder>executablesListBox</zorder>
- <zorder>closeButton</zorder>
- <zorder>closeCheckBox</zorder>
- <zorder>useAppIconCheckBox</zorder>
</widget>
+ <tabstops>
+ <tabstop>executablesListBox</tabstop>
+ <tabstop>titleEdit</tabstop>
+ <tabstop>binaryEdit</tabstop>
+ <tabstop>browseButton</tabstop>
+ <tabstop>workingDirEdit</tabstop>
+ <tabstop>browseDirButton</tabstop>
+ <tabstop>argumentsEdit</tabstop>
+ <tabstop>overwriteAppIDBox</tabstop>
+ <tabstop>appIDOverwriteEdit</tabstop>
+ <tabstop>newFilesModCheckBox</tabstop>
+ <tabstop>newFilesModBox</tabstop>
+ <tabstop>useAppIconCheckBox</tabstop>
+ <tabstop>addButton</tabstop>
+ <tabstop>removeButton</tabstop>
+ <tabstop>closeButton</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/src/shared/inject.h b/src/eventfilter.cpp index 7f4f2962..69327f79 100644 --- a/src/shared/inject.h +++ b/src/eventfilter.cpp @@ -1,31 +1,33 @@ -/*
-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 <http://www.gnu.org/licenses/>.
-*/
-
-#pragma once
-
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-
-#include <string>
-
-namespace MOShared {
-
-void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel);
-
-}
+/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "eventfilter.h" + +EventFilter::EventFilter(QObject *parent, + const EventFilter::HandlerFunc &handler) + : QObject(parent) + , m_Handler(handler) +{ +} + +bool EventFilter::eventFilter(QObject *obj, QEvent *event) +{ + return m_Handler(obj, event); +} diff --git a/src/eventfilter.h b/src/eventfilter.h new file mode 100644 index 00000000..2400a853 --- /dev/null +++ b/src/eventfilter.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + + +#include <QObject> +#include <functional> + + +class EventFilter : public QObject { + + Q_OBJECT + + typedef std::function<bool (QObject*, QEvent*)> HandlerFunc; + +public: + + EventFilter(QObject *parent, const HandlerFunc &handler); + + virtual bool eventFilter(QObject *obj , QEvent *event) override; + +private: + + HandlerFunc m_Handler; + +}; + diff --git a/src/executableslist.cpp b/src/executableslist.cpp index a4511ade..21cb6ed4 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -50,7 +50,6 @@ void ExecutablesList::init(IPluginGame const *game) info.binary().absoluteFilePath(),
info.arguments().join(" "),
info.workingDirectory().absolutePath(),
- info.closeMO(),
info.steamAppID());
}
}
@@ -87,7 +86,7 @@ Executable &ExecutablesList::find(const QString &title) return exe;
}
}
- throw std::runtime_error(QString("invalid name %1").arg(title).toLocal8Bit().constData());
+ throw std::runtime_error(QString("invalid executable name %1").arg(title).toLocal8Bit().constData());
}
@@ -135,7 +134,6 @@ void ExecutablesList::updateExecutable(const QString &title, const QString &executableName,
const QString &arguments,
const QString &workingDirectory,
- ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags mask,
Executable::Flags flags)
@@ -146,7 +144,6 @@ void ExecutablesList::updateExecutable(const QString &title, if (existingExe != m_Executables.end()) {
existingExe->m_Title = title;
- existingExe->m_CloseMO = closeMO;
existingExe->m_Flags &= ~mask;
existingExe->m_Flags |= flags;
// for pre-configured executables don't overwrite settings we didn't store
@@ -162,7 +159,6 @@ void ExecutablesList::updateExecutable(const QString &title, } else {
Executable newExe;
newExe.m_Title = title;
- newExe.m_CloseMO = closeMO;
newExe.m_BinaryInfo = file;
newExe.m_Arguments = arguments;
newExe.m_WorkingDirectory = workingDirectory;
@@ -186,18 +182,17 @@ void ExecutablesList::remove(const QString &title) void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName,
const QString &arguments, const QString &workingDirectory,
- ExecutableInfo::CloseMOStyle closeMO, const QString &steamAppID)
+ 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_Flags = 0;
+ newExe.m_Flags = Executable::UseApplicationIcon;
m_Executables.push_back(newExe);
}
}
diff --git a/src/executableslist.h b/src/executableslist.h index 3d5ba0ed..0534c09e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -36,7 +36,6 @@ struct Executable { QString m_Title;
QFileInfo m_BinaryInfo;
QString m_Arguments;
- MOBase::ExecutableInfo::CloseMOStyle m_CloseMO;
QString m_SteamAppID;
QString m_WorkingDirectory;
@@ -126,17 +125,16 @@ public: * @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,
- MOBase::ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags flags)
{
- updateExecutable(title, executableName, arguments, workingDirectory, closeMO, steamAppID, Executable::AllFlags, flags);
+ updateExecutable(title, executableName, arguments, workingDirectory,
+ steamAppID, Executable::AllFlags, flags);
}
/**
@@ -151,7 +149,6 @@ public: const QString &executableName,
const QString &arguments,
const QString &workingDirectory,
- MOBase::ExecutableInfo::CloseMOStyle closeMO,
const QString &steamAppID,
Executable::Flags mask,
Executable::Flags flags);
@@ -192,7 +189,7 @@ private: std::vector<Executable>::iterator findExe(const QString &title);
void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments,
- const QString &workingDirectory, MOBase::ExecutableInfo::CloseMOStyle closeMO,
+ const QString &workingDirectory,
const QString &steamAppID);
private:
diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp new file mode 100644 index 00000000..61a4c523 --- /dev/null +++ b/src/gameinfoimpl.cpp @@ -0,0 +1,56 @@ +/*
+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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "gameinfoimpl.h"
+#include <gameinfo.h>
+#include <utility.h>
+#include <QDir>
+
+
+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_FALLOUT4: return IGameInfo::TYPE_FALLOUT4;
+ case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV;
+ case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM;
+ case GameInfo::TYPE_SKYRIMSE: return IGameInfo::TYPE_SKYRIMSE;
+ 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/genericicondelegate.cpp b/src/genericicondelegate.cpp index 3711b428..1e00f0e9 100644 --- a/src/genericicondelegate.cpp +++ b/src/genericicondelegate.cpp @@ -24,7 +24,7 @@ QList<QString> GenericIconDelegate::getIcons(const QModelIndex &index) const {
QList<QString> result;
if (index.isValid()) {
- foreach (const QVariant &var, index.data(m_Role).toList()) {
+ for (const QVariant &var : index.data(m_Role).toList()) {
if (!m_Compact || !var.toString().isEmpty()) {
result.append(var.toString());
}
diff --git a/src/helper.cpp b/src/helper.cpp index 41784fe5..b7fc866c 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -62,7 +62,7 @@ static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine) }
-bool init(const std::wstring &moDirectory)
+bool init(const std::wstring &moPath, const std::wstring &dataPath)
{
DWORD userNameLen = UNLEN + 1;
wchar_t userName[UNLEN + 1];
@@ -74,22 +74,22 @@ bool init(const std::wstring &moDirectory) wchar_t *commandLine = new wchar_t[32768];
_snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"",
- moDirectory.c_str(), userName);
+ dataPath.c_str(), userName);
- bool res = helperExec(moDirectory.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine);
delete [] commandLine;
return res;
}
-bool backdateBSAs(const std::wstring &moDirectory, const std::wstring &dataPath)
+bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath)
{
wchar_t *commandLine = new wchar_t[32768];
_snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"",
dataPath.c_str());
- bool res = helperExec(moDirectory.c_str(), commandLine);
+ bool res = helperExec(moPath.c_str(), commandLine);
delete [] commandLine;
return res;
diff --git a/src/helper.h b/src/helper.h index 410e2527..cd4b7883 100644 --- a/src/helper.h +++ b/src/helper.h @@ -38,17 +38,17 @@ namespace Helper { * This will create all required sub-directories and give the user running ModOrganizer
* write-access
*
- * @param moDirectory absolute path to the ModOrganizer base directory
+ * @param moPath absolute path to the ModOrganizer base directory
* @return true on success
**/
-bool init(const std::wstring &moDirectory);
+bool init(const std::wstring &moPath, const std::wstring &dataPath);
/**
* @brief sets the last modified time for all .bsa-files in the target directory well into the past
- * @param moDirectory absolute path to the modOrganizer base directory
+ * @param moPath absolute path to the modOrganizer base directory
* @param dataPath the path taht contains the .bsa-files, usually the data directory of the game
**/
-bool backdateBSAs(const std::wstring &moDirectory, const std::wstring &dataPath);
+bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath);
}
diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h new file mode 100644 index 00000000..9475ddb9 --- /dev/null +++ b/src/ilockedwaitingforprocess.h @@ -0,0 +1,13 @@ +#ifndef ILOCKEDWAITINGFORPROCESS_H +#define ILOCKEDWAITINGFORPROCESS_H + +class QString; + +class ILockedWaitingForProcess +{ +public: + virtual bool unlockForced() const = 0; + virtual void setProcessName(QString const &) = 0; +}; + +#endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 7b1f3f37..9a70f8bd 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -47,6 +47,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QApplication>
#include <QDateTime>
#include <QDirIterator>
+#include <QDebug>
+#include <QTextDocument>
#include <Shellapi.h>
@@ -67,22 +69,26 @@ static T resolveFunction(QLibrary &lib, const char *name) {
T temp = reinterpret_cast<T>(lib.resolve(name));
if (temp == nullptr) {
- throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
+ throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1")
+ .arg(lib.errorString())
+ .toLatin1()
+ .constData());
}
return temp;
}
-
InstallationManager::InstallationManager()
- : m_ParentWidget(nullptr)
- , m_SupportedExtensions({ "zip", "rar", "7z", "fomod", "001" })
-{
- QLibrary archiveLib("dlls\\archive.dll");
+ : m_ParentWidget(nullptr),
+ m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}) {
+ QLibrary archiveLib(QCoreApplication::applicationDirPath() +
+ "\\dlls\\archive.dll");
if (!archiveLib.load()) {
- throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
+ throw MyException(QObject::tr("archive.dll not loaded: \"%1\"")
+ .arg(archiveLib.errorString()));
}
- CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
+ CreateArchiveType CreateArchiveFunc
+ = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
m_ArchiveHandler = CreateArchiveFunc();
if (!m_ArchiveHandler->isValid()) {
@@ -90,7 +96,6 @@ InstallationManager::InstallationManager() }
}
-
InstallationManager::~InstallationManager()
{
delete m_ArchiveHandler;
@@ -110,10 +115,10 @@ void InstallationManager::setURL(QString const &url) void InstallationManager::queryPassword(QString *password)
{
- *password = QInputDialog::getText(nullptr, tr("Password required"), tr("Password"), QLineEdit::Password);
+ *password = QInputDialog::getText(nullptr, tr("Password required"),
+ tr("Password"), QLineEdit::Password);
}
-
void InstallationManager::mapToArchive(const DirectoryTree::Node *node, QString path, FileData * const *data)
{
if (path.length() > 0) {
@@ -182,6 +187,7 @@ bool InstallationManager::unpackSingleFile(const QString &fileName) m_InstallationProgress->setWindowTitle(tr("Extracting files"));
m_InstallationProgress->setWindowModality(Qt::WindowModal);
+ m_InstallationProgress->setFixedSize(600, 100);
m_InstallationProgress->show();
bool res = m_ArchiveHandler->extract(QDir::tempPath(),
@@ -269,6 +275,7 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint));
m_InstallationProgress->setWindowTitle(tr("Extracting files"));
m_InstallationProgress->setWindowModality(Qt::WindowModal);
+ m_InstallationProgress->setFixedSize(600, 100);
m_InstallationProgress->show();
// unpack only the files we need for the installer
@@ -340,7 +347,8 @@ DirectoryTree *InstallationManager::createFilesTree() // 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));
+ newNode->setData(
+ DirectoryTreeInformation(*componentIter, static_cast<int>(i)));
currentNode->addNode(newNode, false);
currentNode = newNode;
} else {
@@ -413,9 +421,10 @@ void InstallationManager::updateProgress(float percentage) void InstallationManager::updateProgressFile(QString const &fileName)
{
- if (m_InstallationProgress != nullptr) {
- m_InstallationProgress->setLabelText(fileName);
- }
+ if (m_InstallationProgress != nullptr) {
+ m_InstallationProgress->setLabelText(fileName);
+ QCoreApplication::processEvents();
+ }
}
@@ -557,6 +566,7 @@ bool InstallationManager::doInstall(GuessedValue<QString> &modName, int modID, m_InstallationProgress->setWindowFlags(
m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint));
m_InstallationProgress->setWindowModality(Qt::WindowModal);
+ m_InstallationProgress->setFixedSize(600, 100);
m_InstallationProgress->show();
if (!m_ArchiveHandler->extract(targetDirectory,
new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
@@ -632,13 +642,14 @@ void InstallationManager::postInstallCleanup() m_TempFilesToDelete.clear();
// 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) {
+ for (const QString &dir : directoriesToRemove) {
QDir().rmdir(dir);
}
}
-
-bool InstallationManager::install(const QString &fileName, GuessedValue<QString> &modName, bool &hasIniTweaks)
+bool InstallationManager::install(const QString &fileName,
+ GuessedValue<QString> &modName,
+ bool &hasIniTweaks)
{
QFileInfo fileInfo(fileName);
if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) {
@@ -661,12 +672,15 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> if (QFile(metaName).exists()) {
QSettings metaFile(metaName, QSettings::IniFormat);
modID = metaFile.value("modID", 0).toInt();
- modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK);
+ QTextDocument doc;
+ doc.setHtml(metaFile.value("name", "").toString());
+ modName.update(doc.toPlainText(), 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());
+ unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(
+ metaFile.value("category", 0).toInt());
categoryID = CategoryFactory::instance().getCategoryID(categoryIndex);
repository = metaFile.value("repository", "").toString();
}
@@ -679,12 +693,13 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> { // 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);
+ NexusInterface::interpretNexusFileName(QFileInfo(fileName).fileName(), 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);
}
@@ -703,7 +718,10 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> bool archiveOpen = m_ArchiveHandler->open(fileName,
new MethodCallback<InstallationManager, void, QString *>(this, &InstallationManager::queryPassword));
if (!archiveOpen) {
- qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_ArchiveHandler->getLastError());
+ qDebug("integrated archiver can't open %s: %s (%d)",
+ qPrintable(fileName),
+ qPrintable(getErrorString(m_ArchiveHandler->getLastError())),
+ m_ArchiveHandler->getLastError());
}
ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
@@ -714,7 +732,7 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> return LHS->priority() > RHS->priority();
});
- foreach (IPluginInstaller *installer, m_Installers) {
+ for (IPluginInstaller *installer : m_Installers) {
// don't use inactive installers (installer can't be null here but vc static code analysis thinks it could)
if ((installer == nullptr) || !installer->isActive()) {
continue;
@@ -731,14 +749,18 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> try {
{ // simple case
- IPluginInstallerSimple *installerSimple = dynamic_cast<IPluginInstallerSimple*>(installer);
- if ((installerSimple != nullptr) &&
- (filesTree != nullptr) && (installer->isArchiveSupported(*filesTree))) {
- installResult = installerSimple->install(modName, *filesTree, version, modID);
+ IPluginInstallerSimple *installerSimple
+ = dynamic_cast<IPluginInstallerSimple *>(installer);
+ if ((installerSimple != nullptr) && (filesTree != nullptr)
+ && (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, repository)) {
+ // the simple installer only prepares the installation, the rest
+ // works the same for all installers
+ if (!doInstall(modName, modID, version, newestVersion, categoryID,
+ repository)) {
installResult = IPluginInstaller::RESULT_FAILED;
}
}
@@ -746,13 +768,18 @@ bool InstallationManager::install(const QString &fileName, GuessedValue<QString> }
{ // custom case
- IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer);
- if ((installerCustom != nullptr) &&
- (((filesTree != nullptr) && installer->isArchiveSupported(*filesTree)) ||
- ((filesTree == nullptr) && installerCustom->isArchiveSupported(fileName)))) {
- std::set<QString> installerExtensions = installerCustom->supportedExtensions();
- if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) {
- installResult = installerCustom->install(modName, fileName, version, modID);
+ IPluginInstallerCustom *installerCustom
+ = dynamic_cast<IPluginInstallerCustom *>(installer);
+ if ((installerCustom != nullptr)
+ && (((filesTree != nullptr)
+ && installer->isArchiveSupported(*filesTree))
+ || ((filesTree == nullptr)
+ && installerCustom->isArchiveSupported(fileName)))) {
+ std::set<QString> installerExt
+ = installerCustom->supportedExtensions();
+ if (installerExt.find(fileInfo.suffix()) != installerExt.end()) {
+ installResult
+ = installerCustom->install(modName, fileName, version, modID);
unsigned int idx = ModInfo::getIndex(modName);
if (idx != UINT_MAX) {
ModInfo::Ptr info = ModInfo::getByIndex(idx);
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp new file mode 100644 index 00000000..d12ec119 --- /dev/null +++ b/src/instancemanager.cpp @@ -0,0 +1,298 @@ +/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "instancemanager.h" +#include "selectiondialog.h" +#include <utility.h> +#include <appconfig.h> +#include <QCoreApplication> +#include <QDir> +#include <QStandardPaths> +#include <QInputDialog> +#include <QMessageBox> +#include <cstdint> + + +static const char COMPANY_NAME[] = "Tannin"; +static const char APPLICATION_NAME[] = "Mod Organizer"; +static const char INSTANCE_KEY[] = "CurrentInstance"; + + + +InstanceManager::InstanceManager() + : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) +{ +} + +InstanceManager &InstanceManager::instance() +{ + static InstanceManager s_Instance; + return s_Instance; +} + +void InstanceManager::overrideInstance(const QString& instanceName) +{ + m_overrideInstanceName = instanceName; + m_overrideInstance = true; +} + + +QString InstanceManager::currentInstance() const +{ + if (m_overrideInstance) + return m_overrideInstanceName; + else + return m_AppSettings.value(INSTANCE_KEY, "").toString(); +} + +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); + m_Reset = true; +} + +void InstanceManager::setCurrentInstance(const QString &name) +{ + m_AppSettings.setValue(INSTANCE_KEY, name); +} + +bool InstanceManager::deleteLocalInstance(const QString &instanceId) const +{ + bool result = true; + QString instancePath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId); + + if (QMessageBox::warning(nullptr, QObject::tr("Deleting folder"), + QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No ){ + return false; + } + + if (!MOBase::shellDelete(QStringList(instancePath),true)) + { + qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(instancePath), ::GetLastError()); + if (!MOBase::removeDir(instancePath)) + { + qWarning("regular delete failed too"); + result = false; + } + } + + return result; +} + +QString InstanceManager::manageInstances(const QStringList &instanceList) const +{ + SelectionDialog selection( + QString("<h3>%1</h3><br>%2") + .arg(QObject::tr("Choose Instance to Delete")) + .arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), + nullptr); + for (const QString &instance : instanceList) + { + selection.addChoice(instance, "", instance); + } + + if (selection.exec() == QDialog::Rejected) { + return(chooseInstance(instances())); + } + else { + QString choice = selection.getChoiceData().toString(); + { + if (QMessageBox::warning(nullptr, QObject::tr("Are you sure?"), + QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?").arg(choice), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + if (!deleteLocalInstance(choice)) + { + QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"), + QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok); + } + } + } + } + return(manageInstances(instances())); +} + +QString InstanceManager::queryInstanceName(const QStringList &instanceList) const +{ + QString instanceId; + while (instanceId.isEmpty()) { + QInputDialog dialog; + + dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); + dialog.setLabelText(QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):")); + // would be neat if we could take the names from the game plugins but + // the required initialization order requires the ini file to be + // available *before* we load plugins + dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "Fallout 3", + "Fallout NV", "FO4VR", "Oblivion" }); + dialog.setComboBoxEditable(true); + + if (dialog.exec() == QDialog::Rejected) { + throw MOBase::MyException(QObject::tr("Canceled")); + } + instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), ""); + + bool alreadyExists=false; + for (const QString &instance : instanceList) { + if(instanceId==instance) + alreadyExists=true; + } + if(alreadyExists) + { + QMessageBox msgBox; + msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) ); + msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId)); + msgBox.exec(); + instanceId=""; + } + } + return instanceId; +} + + +QString InstanceManager::chooseInstance(const QStringList &instanceList) const +{ + enum class Special : uint8_t { + NewInstance, + Portable, + Manage + }; + + SelectionDialog selection( + QString("<h3>%1</h3><br>%2") + .arg(QObject::tr("Choose Instance")) + .arg(QObject::tr( + "Each Instance is a full set of MO data files (mods, " + "downloads, profiles, configuration, ...). You can use multiple " + "instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. " + "If your MO folder is writable, you can also store a single instance locally (called " + "a Portable install, and all the MO data files will be inside the installation folder).")), + nullptr); + selection.disableCancel(); + for (const QString &instance : instanceList) { + selection.addChoice(instance, "", instance); + } + + selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), + QObject::tr("Create a new instance."), + static_cast<uint8_t>(Special::NewInstance)); + + if (QFileInfo(qApp->applicationDirPath()).isWritable()) { + selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), + QObject::tr("Use MO folder for data."), + static_cast<uint8_t>(Special::Portable)); + } + + selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), + QObject::tr("Delete an Instance."), + static_cast<uint8_t>(Special::Manage)); + + if (selection.exec() == QDialog::Rejected) { + qDebug("rejected"); + throw MOBase::MyException(QObject::tr("Canceled")); + } + + QVariant choice = selection.getChoiceData(); + + if (choice.type() == QVariant::String) { + return choice.toString(); + } else { + switch (choice.value<uint8_t>()) { + case Special::NewInstance: return queryInstanceName(instanceList); + case Special::Portable: return QString(); + case Special::Manage: { + + return(manageInstances(instances())); + } + default: throw std::runtime_error("invalid selection"); + } + } +} + + +QString InstanceManager::instancePath() const +{ + return QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation)); +} + + +QStringList InstanceManager::instances() const +{ + return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot); +} + + +bool InstanceManager::portableInstall() const +{ + return QFile::exists(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::iniFileName())); +} + + +void InstanceManager::createDataPath(const QString &dataPath) const +{ + if (!QDir(dataPath).exists()) { + if (!QDir().mkpath(dataPath)) { + throw MOBase::MyException( + QObject::tr("failed to create %1").arg(dataPath)); + } else { + QMessageBox::information( + nullptr, QObject::tr("Data directory created"), + QObject::tr("New data directory created at %1. If you don't want to " + "store a lot of data there, reconfigure the storage " + "directories via settings.").arg(dataPath)); + } + } +} + + +QString InstanceManager::determineDataPath() +{ + QString instanceId = currentInstance(); + if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) + { + // startup, apparently using portable mode before + return qApp->applicationDirPath(); + } + + QString dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); + + + if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { + instanceId = chooseInstance(instances()); + setCurrentInstance(instanceId); + if (!instanceId.isEmpty()) { + dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); + } + } + + if (instanceId.isEmpty()) { + return qApp->applicationDirPath(); + } else { + createDataPath(dataPath); + + return dataPath; + } +} + diff --git a/src/instancemanager.h b/src/instancemanager.h new file mode 100644 index 00000000..adedd78f --- /dev/null +++ b/src/instancemanager.h @@ -0,0 +1,67 @@ +/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + + +#pragma once + + +#include <QString> +#include <QSettings> + + +class InstanceManager { + +public: + + static InstanceManager &instance(); + + QString determineDataPath(); + void clearCurrentInstance(); + + void overrideInstance(const QString& instanceName); + + QString currentInstance() const; + +private: + + InstanceManager(); + + QString instancePath() const; + + QStringList instances() const; + + bool deleteLocalInstance(const QString &instanceId) const; + + QString manageInstances(const QStringList &instanceList) const; + + void setCurrentInstance(const QString &name); + + QString queryInstanceName(const QStringList &instanceList) const; + QString chooseInstance(const QStringList &instanceList) const; + + void createDataPath(const QString &dataPath) const; + bool portableInstall() const; + +private: + + QSettings m_AppSettings; + bool m_Reset {false}; + bool m_overrideInstance{false}; + QString m_overrideInstanceName; +}; diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 540839c6..7ce71c24 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -3,6 +3,7 @@ #include "modinfo.h"
+#include "ilockedwaitingforprocess.h"
#include <iplugintool.h>
#include <ipluginmodpage.h>
#include <delayedfilewriter.h>
@@ -31,12 +32,8 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0;
- virtual void lock() = 0;
+ virtual ILockedWaitingForProcess* lock() = 0;
virtual void unlock() = 0;
- virtual bool unlockClicked() = 0;
- virtual void setProcessName(QString const &) = 0;
-
-
};
#endif // IUSERINTERFACE_H
diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 47587467..7ad36ac7 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -65,7 +65,7 @@ void LoadMechanism::removeHintFile(QDir targetDirectory) bool LoadMechanism::isDirectLoadingSupported()
{
//FIXME: Seriously? isn't there a 'do i need steam' thing?
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
if (game->gameName().compare("oblivion", Qt::CaseInsensitive) == 0) {
// oblivion can be loaded directly if it's not the steam variant
return !game->gameDirectory().exists("steam_api.dll");
@@ -77,7 +77,7 @@ bool LoadMechanism::isDirectLoadingSupported() bool LoadMechanism::isScriptExtenderSupported()
{
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
ScriptExtender *extender = game->feature<ScriptExtender>();
// test if there even is an extender for the managed game and if so whether it's installed
@@ -87,11 +87,11 @@ bool LoadMechanism::isScriptExtenderSupported() bool LoadMechanism::isProxyDLLSupported()
{
// using steam_api.dll as the proxy is way too game specific as many games will have different
- // versions of that game.
+ // versions of that dll.
// plus: the proxy dll hasn't been working for at least the whole 1.12.x versions of MO and
// noone reported it so why maintain an unused feature?
return false;
-/* IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+/* IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
return game->gameDirectory().exists(QString::fromStdWString(AppConfig::proxyDLLTarget()));*/
}
@@ -123,21 +123,33 @@ bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fil void LoadMechanism::deactivateScriptExtender()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame*>();
ScriptExtender *extender = game->feature<ScriptExtender>();
if (extender == nullptr) {
throw MyException(QObject::tr("game doesn't support a script extender"));
}
- QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->name() + "/plugins");
+ QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath());
- 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)));
- }
- }
+#pragma message("implement this for usvfs")
+
+ QString vfsDLLName = "";
+ if (extender->getArch() == IMAGE_FILE_MACHINE_I386) {
+ vfsDLLName = ToQString(AppConfig::vfs32DLLName());
+ }
+ else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64)
+ {
+ vfsDLLName = ToQString(AppConfig::vfs64DLLName());
+ }
+ qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1());
+ if (vfsDLLName != "") {
+ if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) {
+ // remove dll from SE plugins directory
+ if (!pluginsDir.remove(vfsDLLName)) {
+ throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(vfsDLLName)));
+ }
+ }
+ }
removeHintFile(pluginsDir);
} catch (const std::exception &e) {
@@ -149,7 +161,7 @@ void LoadMechanism::deactivateScriptExtender() void LoadMechanism::deactivateProxyDLL()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget()));
@@ -178,36 +190,50 @@ void LoadMechanism::deactivateProxyDLL() void LoadMechanism::activateScriptExtender()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
ScriptExtender *extender = game->feature<ScriptExtender>();
if (extender == nullptr) {
throw MyException(QObject::tr("game doesn't support a script extender"));
}
- QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->name() + "/plugins");
+ QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath());
if (!pluginsDir.exists()) {
pluginsDir.mkpath(".");
}
- QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName()));
- QString hookDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::hookDLLName());
+#pragma message("implement this for usvfs")
+ std::wstring vfsDLL = L"";
+ if (extender->getArch() == IMAGE_FILE_MACHINE_I386) {
+ vfsDLL = AppConfig::vfs32DLLName();
+ }
+ else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64)
+ {
+ vfsDLL = AppConfig::vfs64DLLName();
+ }
+ if (vfsDLL != L"") {
+ QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL));
+ QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL);
- QFile dllFile(targetPath);
+ qDebug("DLL USVFS Target Path: " + targetPath.toLatin1());
+ qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1());
- if (dllFile.exists()) {
- // may be outdated
- if (!hashIdentical(targetPath, hookDLLPath)) {
- dllFile.remove();
- }
- }
+ QFile dllFile(targetPath);
- 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));
- }
- }
+ if (dllFile.exists()) {
+ // may be outdated
+ if (!hashIdentical(targetPath, vfsDLLPath)) {
+ dllFile.remove();
+ }
+ }
+
+ if (!dllFile.exists()) {
+ // install dll to SE plugins
+ if (!QFile::copy(vfsDLLPath, targetPath)) {
+ throw MyException(QObject::tr("Failed to copy %1 to %2").arg(vfsDLLPath, targetPath));
+ }
+ }
+ }
writeHintFile(pluginsDir);
} catch (const std::exception &e) {
QMessageBox::critical(nullptr, QObject::tr("Failed to set up script extender loading"), e.what());
@@ -218,7 +244,7 @@ void LoadMechanism::activateScriptExtender() void LoadMechanism::activateProxyDLL()
{
try {
- IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>();
+ IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>();
QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget()));
@@ -271,14 +297,17 @@ void LoadMechanism::activate(EMechanism mechanism) {
switch (mechanism) {
case LOAD_MODORGANIZER: {
+ qDebug("Load Mechanism: Mod Organizer");
deactivateProxyDLL();
deactivateScriptExtender();
} break;
case LOAD_SCRIPTEXTENDER: {
+ qDebug("Load Mechanism: ScriptExtender");
deactivateProxyDLL();
activateScriptExtender();
} break;
case LOAD_PROXYDLL: {
+ qDebug("Load Mechanism: Proxy DLL");
deactivateScriptExtender();
activateProxyDLL();
} break;
diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 519abd5b..143d5838 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -25,27 +25,26 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QWidget>
#include <Qt> // for Qt::FramelessWindowHint, etc
-LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton)
- : QDialog(parent)
+LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton)
+ : LockedDialogBase(parent, !unlockByButton)
, ui(new Ui::LockedDialog)
- , m_UnlockClicked(false)
{
ui->setupUi(this);
- this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint);
+ // Supposedly the Qt::CustomizeWindowHint should use a customized window
+ // allowing us to select if there is a close button. In practice this doesn't
+ // seem to work. We will ignore pressing the close button if unlockByButton == true
+ Qt::WindowFlags flags =
+ this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint;
+ if (m_allowClose)
+ flags |= Qt::WindowCloseButtonHint;
+ this->setWindowFlags(flags);
- if (parent != nullptr) {
- 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) {
+ if (!unlockByButton)
+ {
ui->unlockButton->hide();
+ ui->verticalLayout->addItem(
+ new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
}
}
@@ -60,19 +59,13 @@ void LockedDialog::setProcessName(const QString &name) ui->processLabel->setText(name);
}
-
-void LockedDialog::resizeEvent(QResizeEvent *event)
+void LockedDialog::on_unlockButton_clicked()
{
- QWidget *par = parentWidget();
- if (par != nullptr) {
- 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);
- }
+ unlock();
}
-void LockedDialog::on_unlockButton_clicked()
-{
- m_UnlockClicked = true;
+void LockedDialog::unlock() {
+ LockedDialogBase::unlock();
+ ui->label->setText("unlocking may take a few seconds");
+ ui->unlockButton->setEnabled(false);
}
diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 29ac459b..36c16429 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,15 +17,9 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef LOCKEDDIALOG_H
-#define LOCKEDDIALOG_H
+#pragma once
-#include <QDialog> // for QDialog
-#include <QObject> // for Q_OBJECT, slots
-#include <QString> // for QString
-
-class QResizeEvent;
-class QWidget;
+#include "lockeddialogbase.h"
namespace Ui {
class LockedDialog;
@@ -39,38 +33,25 @@ namespace Ui { * 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
+class LockedDialog : public LockedDialogBase
{
Q_OBJECT
public:
- explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true);
+ explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false);
~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; }
-
- /**
- * @brief set the name of the process being run
- * @param name of process
- */
- void setProcessName(const QString &name);
+ void setProcessName(const QString &name) override;
protected:
- virtual void resizeEvent(QResizeEvent *event);
+ void unlock() override;
private slots:
void on_unlockButton_clicked();
private:
+
Ui::LockedDialog *ui;
- bool m_UnlockClicked;
};
-
-#endif // LOCKEDDIALOG_H
diff --git a/src/lockeddialog.ui b/src/lockeddialog.ui index 2175f8ac..0ec2e467 100644 --- a/src/lockeddialog.ui +++ b/src/lockeddialog.ui @@ -11,7 +11,7 @@ </rect>
</property>
<property name="windowTitle">
- <string>Locked</string>
+ <string>Running virtualized processes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0">
<item>
@@ -30,7 +30,19 @@ </property>
</widget>
</item>
- <item>
+ <item>
+ <spacer name="verticalSpacer1">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QLabel" name="processLabel">
<property name="font">
<font>
@@ -48,13 +60,37 @@ </property>
</widget>
</item>
- <item>
+ <item>
+ <spacer name="verticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
<widget class="QPushButton" name="unlockButton">
<property name="text">
<string>Unlock</string>
</property>
</widget>
</item>
+ <item>
+ <spacer name="verticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp new file mode 100644 index 00000000..b18f7429 --- /dev/null +++ b/src/lockeddialogbase.cpp @@ -0,0 +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 <http://www.gnu.org/licenses/>. +*/ + +#include "lockeddialogbase.h" + +#include <QPoint> +#include <QResizeEvent> +#include <QWidget> +#include <Qt> // for Qt::FramelessWindowHint, etc + +LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) + : QDialog(parent) + , m_Unlocked(false) + , m_Canceled(false) + , m_allowClose(allowClose) +{ + if (parent != nullptr) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } +} + +void LockedDialogBase::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != nullptr) { + 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 LockedDialogBase::reject() +{ + if (m_allowClose) + unlock(); +} + +bool LockedDialogBase::unlockForced() const { + return m_Unlocked; +} + +bool LockedDialogBase::canceled() const { + return m_Canceled; +} + +void LockedDialogBase::unlock() { + m_Unlocked = true; +} + +void LockedDialogBase::cancel() { + m_Canceled = true; +} + diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h new file mode 100644 index 00000000..3c974a38 --- /dev/null +++ b/src/lockeddialogbase.h @@ -0,0 +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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "ilockedwaitingforprocess.h" +#include <QDialog> // for QDialog +#include <QObject> // for Q_OBJECT, slots +#include <QString> // for QString + +class QResizeEvent; +class QWidget; + +/** + * 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 LockedDialogBase : public QDialog, public ILockedWaitingForProcess +{ + Q_OBJECT + +public: + explicit LockedDialogBase(QWidget *parent, bool allowClose); + + bool unlockForced() const override; + + virtual bool canceled() const; + +protected: + + virtual void resizeEvent(QResizeEvent *event); + + virtual void reject(); + + virtual void unlock(); + + virtual void cancel(); + + bool m_Unlocked; + bool m_Canceled; + bool m_allowClose; +}; diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index a07ae264..522ce3c8 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -26,45 +26,42 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDateTime>
#include <Windows.h>
-
using MOBase::reportError;
QScopedPointer<LogBuffer> LogBuffer::s_Instance;
QMutex LogBuffer::s_Mutex;
-
-LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName)
- : QAbstractItemModel(nullptr), m_OutFileName(outputFileName), m_ShutDown(false),
- m_MinMsgType(minMsgType), m_NumMessages(0)
+LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType,
+ const QString &outputFileName)
+ : QAbstractItemModel(nullptr)
+ , 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();
-// }
+ write();
}
-
void LogBuffer::logMessage(QtMsgType type, const QString &message)
{
if (type >= m_MinMsgType) {
- Message msg = { type, QTime::currentTime(), message };
+ Message msg = {type, QTime::currentTime(), message};
if (m_NumMessages < m_Messages.size()) {
- beginInsertRows(QModelIndex(), m_NumMessages, m_NumMessages + 1);
+ beginInsertRows(QModelIndex(), static_cast<int>(m_NumMessages),
+ static_cast<int>(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));
+ emit dataChanged(createIndex(0, 0),
+ createIndex(static_cast<int>(m_Messages.size()), 0));
}
++m_NumMessages;
if (type >= QtCriticalMsg) {
@@ -73,7 +70,6 @@ void LogBuffer::logMessage(QtMsgType type, const QString &message) }
}
-
void LogBuffer::write() const
{
if (m_NumMessages == 0) {
@@ -84,12 +80,16 @@ void LogBuffer::write() const QFile file(m_OutFileName);
if (!file.open(QIODevice::WriteOnly)) {
- reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString()));
+ 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;
+ unsigned int i
+ = (m_NumMessages > m_Messages.size())
+ ? static_cast<unsigned int>(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");
@@ -97,84 +97,68 @@ void LogBuffer::write() const ::SetLastError(lastError);
}
-
-void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString &outputFileName)
+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 '?';
+ 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)
+void LogBuffer::log(QtMsgType type, const QMessageLogContext &context,
+ const QString &message)
{
// QMutexLocker doesn't support timeout...
if (!s_Mutex.tryLock(100)) {
fprintf(stderr, "failed to log: %s", qPrintable(message));
return;
}
- ON_BLOCK_EXIT([] () {
- s_Mutex.unlock();
- });
+ ON_BLOCK_EXIT([]() { s_Mutex.unlock(); });
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));
+ fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()),
+ msgTypeID(type), qPrintable(message));
} else {
if (context.line != 0) {
- fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type),
+ fprintf(stdout, "%s [%c] (%s:%u) %s\n",
+ qPrintable(QTime::currentTime().toString()), msgTypeID(type),
context.file, context.line, qPrintable(message));
} else {
- fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message));
+ fprintf(stdout, "%s [%c] %s\n",
+ qPrintable(QTime::currentTime().toString()), msgTypeID(type),
+ 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
+QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const
{
return createIndex(row, column, row);
}
-QModelIndex LogBuffer::parent(const QModelIndex&) const
+QModelIndex LogBuffer::parent(const QModelIndex &) const
{
return QModelIndex();
}
@@ -184,19 +168,20 @@ int LogBuffer::rowCount(const QModelIndex &parent) const if (parent.isValid())
return 0;
else
- return std::min(m_NumMessages, m_Messages.size());
+ return static_cast<int>(std::min(m_NumMessages, m_Messages.size()));
}
-int LogBuffer::columnCount(const QModelIndex&) const
+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 offset
+ = m_NumMessages < m_Messages.size()
+ ? 0
+ : static_cast<unsigned int>(m_NumMessages - m_Messages.size());
unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size();
switch (role) {
case Qt::DisplayRole: {
@@ -214,20 +199,28 @@ QVariant LogBuffer::data(const QModelIndex &index, int role) const case Qt::DecorationRole: {
if (index.column() == 1) {
switch (m_Messages[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");
+ 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[msgIndex].type) {
- case QtDebugMsg: return "D";
- case QtWarningMsg: return "W";
- case QtCriticalMsg: return "C";
- case QtFatalMsg: return "F";
+ case QtDebugMsg:
+ return "D";
+ case QtWarningMsg:
+ return "W";
+ case QtCriticalMsg:
+ return "C";
+ case QtFatalMsg:
+ return "F";
}
}
} break;
@@ -243,7 +236,6 @@ void LogBuffer::writeNow() }
}
-
void LogBuffer::cleanQuit()
{
QMutexLocker guard(&s_Mutex);
@@ -269,9 +261,10 @@ void log(const char *format, ...) va_end(argList);
}
-
-
QString LogBuffer::Message::toString() const
{
- return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message);
+ return QString("%1 [%2] %3")
+ .arg(time.toString())
+ .arg(msgTypeID(type))
+ .arg(message);
}
diff --git a/src/logbuffer.h b/src/logbuffer.h index 748aaf31..0cfecfa2 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -35,11 +35,7 @@ class LogBuffer : public QAbstractItemModel 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();
diff --git a/src/main.cpp b/src/main.cpp index 3469e9c3..935b7404 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <windows.h>
#include <DbgHelp.h>
-#include <inject.h>
#include <appconfig.h>
#include <utility.h>
#include <scopeguard.h>
@@ -45,8 +44,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "moapplication.h"
#include "tutorialmanager.h"
#include "nxmaccessmanager.h"
+#include "instancemanager.h"
+#include "moshortcut.h"
+
#include <eh.h>
#include <windows_error.h>
+#include <usvfs.h>
#include <QApplication>
#include <QPushButton>
@@ -75,6 +78,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <cstdarg>
#include <iostream>
+#include <sstream>
#include <stdexcept>
@@ -84,36 +88,21 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase;
using namespace MOShared;
-bool createAndMakeWritable(const std::wstring &subPath)
-{
+bool createAndMakeWritable(const std::wstring &subPath) {
QString const dataPath = qApp->property("dataPath").toString();
QString fullPath = dataPath + "/" + QString::fromStdWString(subPath);
- if (!QDir(fullPath).exists()) {
- QDir().mkdir(fullPath);
- }
-
- QFileInfo fileInfo(fullPath);
- if (!fileInfo.exists() || !fileInfo.isWritable()) {
- if (QMessageBox::question(nullptr, 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(dataPath.toStdWString())) {
- return false;
- }
- } else {
- return false;
- }
- // no matter which directory didn't exist/wasn't writable, the helper
- // should have created them all so we don't have to worry this message box would appear repeatedly
+ if (!QDir(fullPath).exists() && !QDir().mkdir(fullPath)) {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("Failed to create \"%1\". Your user "
+ "account probably lacks permission.")
+ .arg(fullPath));
+ return false;
+ } else {
+ return true;
}
- return true;
}
-
bool bootstrap()
{
// remove the temporary backup directory in case we're restarting after an update
@@ -124,138 +113,85 @@ bool bootstrap() // cycle logfile
removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()),
- "ModOrganizer*.log", 5, QDir::Name);
-
- createAndMakeWritable(AppConfig::profilesPath());
- createAndMakeWritable(AppConfig::modsPath());
- createAndMakeWritable(AppConfig::downloadPath());
- createAndMakeWritable(AppConfig::overwritePath());
- createAndMakeWritable(AppConfig::logPath());
+ "usvfs*.log", 5, QDir::Name);
- // verify the hook-dll exists
- QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName());
-
- if (::GetModuleHandleW(ToWString(dllName).c_str()) != nullptr) {
- throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)");
+ if (!createAndMakeWritable(AppConfig::logPath())) {
+ return false;
}
- HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str());
- if (dllMod == nullptr) {
- throw windows_error("hook.dll is missing or invalid");
- }
- ::FreeLibrary(dllMod);
-
return true;
}
-void cleanupDir()
+LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr;
+
+static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
{
- // files from previous versions of MO that are no longer
- // required (in that location)
- QStringList fileNames {
- "imageformats/",
- "loot/resources/",
- "plugins/previewDDS.dll",
- "dlls/boost_python-vc100-mt-1_55.dll",
- "dlls/QtCore4.dll",
- "dlls/QtDeclarative4.dll",
- "dlls/QtGui4.dll",
- "dlls/QtNetwork4.dll",
- "dlls/QtOpenGL4.dll",
- "dlls/QtScript4.dll",
- "dlls/QtSql4.dll",
- "dlls/QtSvg4.dll",
- "dlls/QtWebKit4.dll",
- "dlls/QtXml4.dll",
- "dlls/QtXmlPatterns4.dll",
- "msvcp100.dll",
- "msvcr100.dll",
- "proxy.dll"
- };
+ const std::wstring& dumpPath = OrganizerCore::crashDumpsPath();
+ int dumpRes =
+ CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str());
+ if (!dumpRes)
+ qCritical("ModOrganizer has crashed, crash dump created.");
+ else
+ qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError());
- for (const QString &fileName : fileNames) {
- QString fullPath = qApp->applicationDirPath() + "/" + fileName;
- if (QFile::exists(fullPath)) {
- if (shellDelete(QStringList(fullPath), true)) {
- qDebug("removed obsolete file %s", qPrintable(fullPath));
- } else {
- qDebug("failed to remove obsolete %s", qPrintable(fullPath));
- }
- }
- }
+ if (prevUnhandledExceptionFilter)
+ return prevUnhandledExceptionFilter(exceptionPtrs);
+ else
+ return EXCEPTION_CONTINUE_SEARCH;
}
-
-bool isNxmLink(const QString &link)
+// Parses the first parseArgCount arguments of the current process command line and returns
+// them in parsedArgs, the rest of the command line is returned untouched.
+LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector<std::wstring>& parsedArgs)
{
- return link.startsWith("nxm://", Qt::CaseInsensitive);
+ LPCWSTR cmd = GetCommandLineW();
+ LPCWSTR arg = nullptr; // to skip executable name
+ for (; parseArgCount >= 0 && *cmd; ++cmd)
+ {
+ if (*cmd == '"') {
+ int escaped = 0;
+ for (++cmd; *cmd && (*cmd != '"' || escaped % 2 != 0); ++cmd)
+ escaped = *cmd == '\\' ? escaped + 1 : 0;
+ }
+ if (*cmd == ' ') {
+ if (arg)
+ if (cmd-1 > arg && *arg == '"' && *(cmd-1) == '"')
+ parsedArgs.push_back(std::wstring(arg+1, cmd-1));
+ else
+ parsedArgs.push_back(std::wstring(arg, cmd));
+ arg = cmd + 1;
+ --parseArgCount;
+ }
+ }
+ return cmd;
}
-static 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 int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) {
+ PROCESS_INFORMATION pi{ 0 };
+ STARTUPINFO si{ 0 };
+ si.cb = sizeof(si);
+ std::wstring commandLineCopy = commandLine;
- 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(nullptr, 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"));
+ if (!CreateProcessW(NULL, &commandLineCopy[0], NULL, NULL, FALSE, 0, NULL, workingDirectory, &si, &pi)) {
+ // A bit of a problem where to log the error message here, at least this way you can get the message
+ // using a either DebugView or a live debugger:
+ std::wostringstream ost;
+ ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError();
+ OutputDebugStringW(ost.str().c_str());
+ return -1;
+ }
- HANDLE dumpFile = ::CreateFile(dumpName.c_str(),
- GENERIC_WRITE, FILE_SHARE_WRITE, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
- if (dumpFile != INVALID_HANDLE_VALUE) {
- _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
- exceptionInfo.ThreadId = ::GetCurrentThreadId();
- exceptionInfo.ExceptionPointers = exceptionPtrs;
- exceptionInfo.ClientPointers = false;
+ WaitForSingleObject(pi.hProcess, INFINITE);
- BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile,
- MiniDumpNormal, &exceptionInfo, nullptr, nullptr);
+ DWORD exitCode = (DWORD)-1;
+ ::GetExitCodeProcess(pi.hProcess, &exitCode);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+ return static_cast<int>(exitCode);
+}
- ::FlushFileBuffers(dumpFile);
- ::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");
- }
+static DWORD WaitForProcess() {
- QMessageBox::critical(nullptr, QObject::tr("Woops"),
- QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer));
- return result;
}
static bool HaveWriteAccess(const std::wstring &path)
@@ -382,6 +318,9 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett }
}
+ //The following code would try to determine the right game to mange but it would usually find the wrong one
+ //so it was commented out.
+ /*
//OK, we are in a new setup or existing info is useless.
//See if MO has been installed inside a game directory
for (IPluginGame * const game : plugins.plugins<IPluginGame>()) {
@@ -405,6 +344,7 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett //OK, chop off the last directory and try again
} while (gameDir.cdUp());
}
+ */
//Then try a selection dialogue.
if (!gamePath.isEmpty() || !gameName.isEmpty()) {
@@ -448,139 +388,162 @@ MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &sett return nullptr;
}
-int main(int argc, char *argv[])
-{
- MOApplication application(argc, argv);
-
- qDebug("application name: %s", qPrintable(application.applicationName()));
- QString instanceID;
- QFile instanceFile(application.applicationDirPath() + "/INSTANCE");
- if (instanceFile.open(QIODevice::ReadOnly)) {
- instanceID = instanceFile.readAll().trimmed();
- }
+// extend path to include dll directory so plugins don't need a manifest
+// (using AddDllDirectory would be an alternative to this but it seems fairly
+// complicated esp.
+// since it isn't easily accessible on Windows < 8
+// SetDllDirectory replaces other search directories and this seems to
+// propagate to child processes)
+void setupPath()
+{
+ static const int BUFSIZE = 4096;
- QString const dataPath =
- instanceID.isEmpty() ? application.applicationDirPath()
- : QDir::fromNativeSeparators(
- QStandardPaths::writableLocation(QStandardPaths::DataLocation)
- + "/" + instanceID
- );
- application.setProperty("dataPath", dataPath);
+ qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath())));
- if (!QDir(dataPath).exists()) {
- if (!QDir().mkpath(dataPath)) {
- qCritical("failed to create %s", qPrintable(dataPath));
- return 1;
- }
+ boost::scoped_array<TCHAR> 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);
}
- SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+ std::wstring newPath(oldPath.get());
+ newPath += L";";
+ newPath += ToWString(QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath()))
+ .c_str();
+ newPath += L"\\dlls";
- if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) {
- QStringList arguments = application.arguments();
- arguments.pop_front();
- ::ShellExecuteW( nullptr
- , L"runas"
- , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str()
- , ToWString(arguments.join(" ")).c_str()
- , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL);
- return 1;
+ ::SetEnvironmentVariableW(L"PATH", newPath.c_str());
+}
+
+static void preloadSsl()
+{
+ QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath());
+ if (GetModuleHandleA("libeay32.dll"))
+ qWarning("libeay32.dll already loaded?!");
+ else {
+ QString libeay32 = appPath + "\\libeay32.dll";
+ if (!QFile::exists(libeay32))
+ qWarning("libeay32.dll not found: %s", qPrintable(libeay32));
+ else if (!LoadLibraryW(libeay32.toStdWString().c_str()))
+ qWarning("failed to load: %s, %d", qPrintable(libeay32), GetLastError());
}
+ if (GetModuleHandleA("ssleay32.dll"))
+ qWarning("ssleay32.dll already loaded?!");
+ else {
+ QString ssleay32 = appPath + "\\ssleay32.dll";
+ if (!QFile::exists(ssleay32))
+ qWarning("ssleay32.dll not found: %s", qPrintable(ssleay32));
+ else if (!LoadLibraryW(ssleay32.toStdWString().c_str()))
+ qWarning("failed to load: %s, %d", qPrintable(ssleay32), GetLastError());
+ }
+}
- QPixmap pixmap(":/MO/gui/splash");
- QSplashScreen splash(pixmap);
+static QString getVersionDisplayString()
+{
+ VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath()));
+ return VersionInfo(version.dwFileVersionMS >> 16,
+ version.dwFileVersionMS & 0xFFFF,
+ version.dwFileVersionLS >> 16,
+ version.dwFileVersionLS & 0xFFFF).displayString();
+}
- try {
- if (!bootstrap()) {
- return -1;
- }
+int runApplication(MOApplication &application, SingleInstance &instance,
+ const QString &splashPath)
+{
- LogBuffer::init(100, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
+ qDebug("Starting Mod Organizer version %s revision %s", qPrintable(getVersionDisplayString()),
+#if defined(HGID)
+ HGID
+#elif defined(GITID)
+ GITID
+#else
+ "unknown"
+#endif
+ );
-#if QT_VERSION >= 0x050000 && !defined(QT_NO_SSL)
- qDebug("ssl support: %d", QSslSocket::supportsSsl());
+#if !defined(QT_NO_SSL)
+ preloadSsl();
+ qDebug("ssl support: %d", QSslSocket::supportsSsl());
+#else
+ qDebug("non-ssl build");
#endif
- qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
- qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath())));
- splash.show();
+ QString dataPath = application.property("dataPath").toString();
+ qDebug("data path: %s", qPrintable(dataPath));
- cleanupDir();
- } catch (const std::exception &e) {
- reportError(e.what());
+ if (!bootstrap()) {
+ reportError("failed to set up data paths");
return 1;
}
- { // extend path to include dll directory so plugins don't need a manifest
- // (using AddDllDirectory would be an alternative to this but it seems fairly complicated esp.
- // since it isn't easily accessible on Windows < 8
- // SetDllDirectory replaces other search directories and this seems to propagate to child processes)
- static const int BUFSIZE = 4096;
+ QStringList arguments = application.arguments();
- boost::scoped_array<TCHAR> 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);
- }
+ try {
+ qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
- std::wstring newPath(oldPath.get());
- newPath += L";";
- newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str();
- newPath += L"\\dlls";
+ QSettings settings(dataPath + "/"
+ + QString::fromStdWString(AppConfig::iniFileName()),
+ QSettings::IniFormat);
- ::SetEnvironmentVariableW(L"PATH", newPath.c_str());
- }
+ // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime
+ OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt());
- QStringList arguments = application.arguments();
+ qDebug("Loaded settings:");
+ settings.beginGroup("Settings");
+ for (auto k : settings.allKeys())
+ if (!k.contains("username") && !k.contains("password"))
+ qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data());
+ settings.endGroup();
- 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(nullptr, 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
- QSettings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat);
qDebug("initializing core");
OrganizerCore organizer(settings);
+ if (!organizer.bootstrap()) {
+ reportError("failed to set up data paths");
+ return 1;
+ }
qDebug("initialize plugins");
PluginContainer pluginContainer(&organizer);
pluginContainer.loadPlugins();
- MOBase::IPluginGame *game = determineCurrentGame(application.applicationDirPath(), settings, pluginContainer);
+ MOBase::IPluginGame *game = determineCurrentGame(
+ application.applicationDirPath(), settings, pluginContainer);
if (game == nullptr) {
return 1;
}
+ if (splashPath.startsWith(':')) {
+ // currently using MO splash, see if the plugin contains one
+ QString pluginSplash
+ = QString(":/%1/splash").arg(game->gameShortName());
+ QImage image(pluginSplash);
+ if (!image.isNull()) {
+ image.save(dataPath + "/splash.png");
+ } else {
+ qDebug("no plugin splash");
+ }
+ }
organizer.setManagedGame(game);
-
organizer.createDefaultProfile();
- //See the pragma - we apparently don't use this so not sure why we check it
if (!settings.contains("game_edition")) {
QStringList editions = game->gameVariants();
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!)"), nullptr);
+ SelectionDialog selection(
+ QObject::tr("Please select the game edition you have (MO can't "
+ "start the game correctly if this is set "
+ "incorrectly!)"),
+ nullptr);
int index = 0;
for (const QString &edition : editions) {
selection.addChoice(edition, "", index++);
}
if (selection.exec() == QDialog::Rejected) {
- return -1;
+ return 1;
} else {
settings.setValue("game_edition", selection.getChoiceString());
}
@@ -588,9 +551,8 @@ int main(int argc, char *argv[]) }
game->setGameVariant(settings.value("game_edition").toString());
-
-#pragma message("edition isn't used?")
- qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(game->gameDirectory().absolutePath())));
+ qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(
+ game->gameDirectory().absolutePath())));
organizer.updateExecutablesList(settings);
@@ -599,27 +561,48 @@ int main(int argc, char *argv[]) // 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 {
- organizer.startApplication(exeName, arguments, QString(), QString());
- return 0;
- } catch (const std::exception &e) {
- reportError(QObject::tr("failed to start application: %1").arg(e.what()));
- return 1;
+ if (arguments.size() > 1) {
+ if (MOShortcut shortcut{ arguments.at(1) }) {
+ try {
+ organizer.runShortcut(shortcut);
+ return 0;
+ } catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start shortcut: %1").arg(e.what()));
+ return 1;
+ }
+ } else if (OrganizerCore::isNxmLink(arguments.at(1))) {
+ qDebug("starting download from command line: %s",
+ qPrintable(arguments.at(1)));
+ organizer.externalMessage(arguments.at(1));
+ } else {
+ QString exeName = arguments.at(1);
+ qDebug("starting %s from command line", qPrintable(exeName));
+ arguments.removeFirst(); // remove application name (ModOrganizer.exe)
+ arguments.removeFirst(); // remove binary name
+ // pass the remaining parameters to the binary
+ try {
+ organizer.startApplication(exeName, arguments, QString(), QString());
+ return 0;
+ } catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start application: %1").arg(e.what()));
+ return 1;
+ }
}
}
+ QPixmap pixmap(splashPath);
+ QSplashScreen splash(pixmap);
+ splash.show();
+
NexusInterface::instance()->getAccessManager()->startLoginCheck();
qDebug("initializing tutorials");
- TutorialManager::init(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
- &organizer);
+ TutorialManager::init(
+ qApp->applicationDirPath() + "/"
+ + QString::fromStdWString(AppConfig::tutorialsPath()) + "/",
+ &organizer);
if (!application.setStyleFile(settings.value("Settings/style", "").toString())) {
// disable invalid stylesheet
@@ -629,27 +612,98 @@ 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, organizer, pluginContainer);
+ MainWindow mainWindow(settings, organizer, pluginContainer);
- QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString)));
- QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString)));
+ QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application,
+ SLOT(setStyleFile(QString)));
+ QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer,
+ SLOT(externalMessage(QString)));
mainWindow.readSettings();
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)));
- organizer.externalMessage(arguments.at(1));
- }
splash.finish(&mainWindow);
- res = application.exec();
+ return application.exec();
}
- return res;
} catch (const std::exception &e) {
reportError(e.what());
return 1;
}
}
+
+
+int main(int argc, char *argv[])
+{
+ //Should allow for better scaling of ui with higher resolution displays
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+ if (argc >= 4) {
+ std::vector<std::wstring> arg;
+ auto args = UntouchedCommandLineArguments(2, arg);
+ if (arg[0] == L"launch")
+ return SpawnWaitProcess(arg[1].c_str(), args);
+ }
+
+ MOApplication application(argc, argv);
+ QStringList arguments = application.arguments();
+
+ setupPath();
+
+ bool forcePrimary = false;
+ if (arguments.contains("update")) {
+ arguments.removeAll("update");
+ forcePrimary = true;
+ }
+
+ MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" };
+
+ SingleInstance instance(forcePrimary);
+ if (!instance.primaryInstance()) {
+ if (moshortcut ||
+ arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1)))
+ {
+ qDebug("not primary instance, sending shortcut/download message");
+ instance.sendMessage(arguments.at(1));
+ return 0;
+ } else if (arguments.size() == 1) {
+ QMessageBox::information(
+ nullptr, 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 was called with parameters
+
+ do {
+ QString dataPath;
+
+ try {
+ InstanceManager& instanceManager = InstanceManager::instance();
+ if (moshortcut && moshortcut.hasInstance())
+ instanceManager.overrideInstance(moshortcut.instance());
+ dataPath = instanceManager.determineDataPath();
+ } catch (const std::exception &e) {
+ if (strcmp(e.what(),"Canceled"))
+ QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what());
+ return 1;
+ }
+ application.setProperty("dataPath", dataPath);
+
+ // initialize dump collection only after "dataPath" since the crashes are stored under it
+ prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+
+ LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log");
+
+ QString splash = dataPath + "/splash.png";
+ if (!QFile::exists(dataPath + "/splash.png")) {
+ splash = ":/MO/gui/splash";
+ }
+
+ int result = runApplication(application, instance, splash);
+ if (result != INT_MAX) {
+ return result;
+ }
+ argc = 1;
+ } while (true);
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 72ee8d6a..fe93b546 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -38,6 +38,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "savegameinfo.h"
#include "spawn.h"
#include "versioninfo.h"
+#include "instancemanager.h"
#include "report.h"
#include "modlist.h"
@@ -58,6 +59,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "messagedialog.h"
#include "installationmanager.h"
#include "lockeddialog.h"
+#include "waitingonclosedialog.h"
#include "logbuffer.h"
#include "downloadlistsortproxy.h"
#include "motddialog.h"
@@ -72,14 +74,16 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "previewdialog.h"
#include "browserdialog.h"
#include "aboutdialog.h"
-#include "safewritefile.h"
+#include <safewritefile.h>
#include "nxmaccessmanager.h"
#include "appconfig.h"
+#include "eventfilter.h"
#include <utility.h>
#include <dataarchives.h>
#include <bsainvalidation.h>
#include <taskprogressmanager.h>
#include <scopeguard.h>
+#include <usvfs.h>
#include <QAbstractItemDelegate>
#include <QAbstractProxyModel>
@@ -123,6 +127,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QPoint>
#include <QProcess>
#include <QProgressDialog>
+#include <QDialogButtonBox>
#include <QPushButton>
#include <QRadioButton>
#include <QRect>
@@ -138,20 +143,17 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QToolTip>
#include <QTranslator>
#include <QTreeWidget>
+#include <QTreeWidgetItemIterator>
#include <QUrl>
#include <QVariantList>
#include <QWhatsThis>
#include <QWidgetAction>
+#include <QWebEngineProfile>
-#include <QtDebug>
+#include <QDebug>
#include <QtGlobal>
-
-#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtConcurrent/QtConcurrentRun>
-#else
-#include <QtConcurrentRun>
-#endif
#ifndef Q_MOC_RUN
#include <boost/thread.hpp>
@@ -181,8 +183,7 @@ using namespace MOBase; using namespace MOShared;
-MainWindow::MainWindow(const QString &exeName
- , QSettings &initSettings
+MainWindow::MainWindow(QSettings &initSettings
, OrganizerCore &organizerCore
, PluginContainer &pluginContainer
, QWidget *parent)
@@ -190,7 +191,6 @@ MainWindow::MainWindow(const QString &exeName , ui(new Ui::MainWindow)
, m_WasVisible(false)
, m_Tutorial(this, "MainWindow")
- , m_ExeName(exeName)
, m_OldProfileIndex(-1)
, m_ModListGroupingProxy(nullptr)
, m_ModListSortProxy(nullptr)
@@ -204,10 +204,17 @@ MainWindow::MainWindow(const QString &exeName , m_DidUpdateMasterList(false)
, m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this))
{
+ QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies);
+ QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800);
+ QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory());
+ QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory());
ui->setupUi(this);
updateWindowTitle(QString(), false);
+
languageChange(m_OrganizerCore.settings().language());
+ m_CategoryFactory.loadCategories();
+
ui->logList->setModel(LogBuffer::instance());
ui->logList->setColumnWidth(0, 100);
ui->logList->setAutoScroll(true);
@@ -227,6 +234,7 @@ MainWindow::MainWindow(const QString &exeName m_RefreshProgress->setVisible(false);
statusBar()->addWidget(m_RefreshProgress, 1000);
statusBar()->clearMessage();
+ statusBar()->hide();
ui->actionEndorseMO->setVisible(false);
@@ -247,12 +255,14 @@ MainWindow::MainWindow(const QString &exeName ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList));
ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate);
ui->modList->header()->installEventFilter(m_OrganizerCore.modList());
- if (initSettings.contains("mod_list_state")) {
- ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray());
+ bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state");
+
+ if (modListAdjusted) {
// 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);
+ int sectionSize = ui->modList->header()->sectionSize(ModList::COL_CONTENT);
+ ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1);
+ ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize);
} else {
// hide these columns by default
ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true);
@@ -269,17 +279,19 @@ MainWindow::MainWindow(const QString &exeName ui->espList->setModel(m_PluginListSortProxy);
ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder);
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());
- }
ui->espList->installEventFilter(m_OrganizerCore.pluginList());
ui->bsaList->setLocalMoveOnly(true);
+ bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state");
+ registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header());
+ registerWidgetState(ui->downloadView->objectName(),
+ ui->downloadView->header());
+
ui->splitter->setStretchFactor(0, 3);
ui->splitter->setStretchFactor(1, 2);
- resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state"));
+ resizeLists(modListAdjusted, pluginListAdjusted);
QMenu *linkMenu = new QMenu(this);
linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar()));
@@ -289,11 +301,23 @@ MainWindow::MainWindow(const QString &exeName ui->listOptionsBtn->setMenu(modListContextMenu());
+ ui->openFolderMenu->setMenu(openFolderMenu());
+
updateDownloadListDelegate();
ui->savegameList->installEventFilter(this);
ui->savegameList->setMouseTracking(true);
+ // don't allow mouse wheel to switch grouping, too many people accidentally
+ // turn on grouping and then don't understand what happened
+ EventFilter *noWheel
+ = new EventFilter(this, [](QObject *, QEvent *event) -> bool {
+ return event->type() == QEvent::Wheel;
+ });
+
+ ui->groupCombo->installEventFilter(noWheel);
+ ui->profileBox->installEventFilter(noWheel);
+
connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool)));
@@ -305,16 +329,12 @@ MainWindow::MainWindow(const QString &exeName connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString)));
connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString)));
- connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved()));
-
connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*)));
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_OrganizerCore.downloadManager(), SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom()));
-
connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen()));
connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString)));
@@ -328,11 +348,8 @@ MainWindow::MainWindow(const QString &exeName connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int)));
connect(NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusLogin()));
connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
- connect(NexusInterface::instance()->getAccessManager(), &NXMAccessManager::credentialsReceived,
- [this] (const QString &accountName, bool premium) {
- qDebug("user %s premium", premium ? "is" : "is not");
- updateWindowTitle(accountName, premium);
- });
+ connect(NexusInterface::instance()->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)),
+ this, SLOT(updateWindowTitle(const QString&, bool)));
connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int)));
@@ -340,6 +357,7 @@ MainWindow::MainWindow(const QString &exeName connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint)));
connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled);
+ connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close);
connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*)));
@@ -348,6 +366,9 @@ MainWindow::MainWindow(const QString &exeName m_CheckBSATimer.setSingleShot(true);
connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList()));
+ connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection)));
+ connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection)));
+
m_UpdateProblemsTimer.setSingleShot(true);
connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton()));
@@ -389,6 +410,8 @@ MainWindow::MainWindow(const QString &exeName MainWindow::~MainWindow()
{
+ cleanup();
+
m_PluginContainer.setUserInterface(nullptr, nullptr);
m_OrganizerCore.setUserInterface(nullptr, nullptr);
m_IntegratedBrowser.close();
@@ -402,9 +425,7 @@ void MainWindow::updateWindowTitle(const QString &accountName, bool premium) m_OrganizerCore.managedGame()->gameName(),
m_OrganizerCore.getVersion().displayString());
- if (accountName.isEmpty()) {
- title.append(" (not logged in)");
- } else {
+ if (!accountName.isEmpty()) {
title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : ""));
}
@@ -575,7 +596,7 @@ void MainWindow::scheduleUpdateButton() void MainWindow::updateProblemsButton()
{
- int numProblems = checkForProblems();
+ size_t numProblems = checkForProblems();
if (numProblems > 0) {
ui->actionProblems->setEnabled(true);
ui->actionProblems->setIconText(tr("Problems"));
@@ -627,9 +648,9 @@ bool MainWindow::errorReported(QString &logFile) }
-int MainWindow::checkForProblems()
+size_t MainWindow::checkForProblems()
{
- int numProblems = 0;
+ size_t numProblems = 0;
for (IPluginDiagnose *diagnose : m_PluginContainer.plugins<IPluginDiagnose>()) {
numProblems += diagnose->activeProblems().size();
}
@@ -836,12 +857,15 @@ void MainWindow::showEvent(QShowEvent *event) m_OrganizerCore.settings().registerAsNXMHandler(false);
m_WasVisible = true;
+ updateProblemsButton();
}
}
void MainWindow::closeEvent(QCloseEvent* event)
{
+ m_closing = true;
+
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?"),
@@ -853,8 +877,29 @@ void MainWindow::closeEvent(QCloseEvent* event) }
}
+ std::vector<QString> hiddenList;
+ hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
+ HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId());
+ if (injected_process_still_running != INVALID_HANDLE_VALUE)
+ {
+ m_OrganizerCore.waitForApplication(injected_process_still_running);
+ if (!m_closing) { // if operation cancelled
+ event->ignore();
+ return;
+ }
+ }
+
setCursor(Qt::WaitCursor);
+}
+void MainWindow::cleanup()
+{
+ if (ui->logList->model() != nullptr) {
+ disconnect(ui->logList->model(), nullptr, nullptr, nullptr);
+ ui->logList->setModel(nullptr);
+ }
+
+ QWebEngineProfile::defaultProfile()->clearAllVisitedLinks();
m_IntegratedBrowser.close();
}
@@ -1000,14 +1045,15 @@ void MainWindow::startExeAction() {
QAction *action = qobject_cast<QAction*>(sender());
if (action != nullptr) {
- const Executable &selectedExecutable(m_OrganizerCore.executablesList()->find(action->text()));
+ const Executable &selectedExecutable(
+ m_OrganizerCore.executablesList()->find(action->text()));
+ QString customOverwrite= m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString();
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);
+ selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0
+ ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_SteamAppID, customOverwrite);
} else {
qCritical("not an action?");
}
@@ -1068,9 +1114,7 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
{
{
- std::vector<FileEntry::Ptr> files = directoryEntry.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry::Ptr current = *iter;
+ for (const FileEntry::Ptr current : directoryEntry.getFiles()) {
if (conflictsOnly && (current->getAlternatives().size() == 0)) {
continue;
}
@@ -1109,17 +1153,17 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director fileChild->setData(1, Qt::UserRole, source);
fileChild->setData(1, Qt::UserRole + 1, originID);
- std::vector<int> alternatives = current->getAlternatives();
+ std::vector<std::pair<int, std::wstring>> alternatives = current->getAlternatives();
if (!alternatives.empty()) {
std::wostringstream altString;
altString << ToWString(tr("Also in: <br>"));
- for (std::vector<int>::iterator altIter = alternatives.begin();
+ for (std::vector<std::pair<int, std::wstring>>::iterator altIter = alternatives.begin();
altIter != alternatives.end(); ++altIter) {
if (altIter != alternatives.begin()) {
altString << " , ";
}
- altString << "<span style=\"white-space: nowrap;\"><i>" << m_OrganizerCore.directoryStructure()->getOriginByID(*altIter).getName() << "</font></span>";
+ altString << "<span style=\"white-space: nowrap;\"><i>" << m_OrganizerCore.directoryStructure()->getOriginByID(altIter->first).getName() << "</font></span>";
}
fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str())));
fileChild->setForeground(1, QBrush(Qt::red));
@@ -1165,7 +1209,7 @@ void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &director void MainWindow::delayedRemove()
{
- foreach (QTreeWidgetItem *item, m_RemoveWidget) {
+ for (QTreeWidgetItem *item : m_RemoveWidget) {
item->removeChild(item->child(0));
}
m_RemoveWidget.clear();
@@ -1202,7 +1246,7 @@ bool MainWindow::refreshProfiles(bool selectProfile) profileBox->clear();
profileBox->addItem(QObject::tr("<Manage...>"));
- QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()));
+ QDir profilesDir(Settings::instance().getProfileDirectory());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
QDirIterator profileIter(profilesDir);
@@ -1264,7 +1308,38 @@ void MainWindow::refreshDataTree() updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked());
tree->insertTopLevelItem(0, subTree);
subTree->setExpanded(true);
- tree->header()->resizeSection(0, 200);
+}
+
+void MainWindow::refreshDataTreeKeepExpandedNodes()
+{
+ QCheckBox *conflictsBox = findChild<QCheckBox*>("conflictsCheckBox");
+ QTreeWidget *tree = findChild<QTreeWidget*>("dataTree");
+
+ QStringList expandedNodes;
+ QTreeWidgetItemIterator it1(tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren);
+ while (*it1) {
+ QTreeWidgetItem *current = (*it1);
+ if (current->isExpanded() && !(current->text(0)=="data")) {
+ expandedNodes.append(current->text(0)+"/"+current->parent()->text(0));
+ }
+ ++it1;
+ }
+
+ tree->clear();
+ QStringList columns("data");
+ columns.append("");
+ QTreeWidgetItem *subTree = new QTreeWidgetItem(columns);
+ updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked());
+ tree->insertTopLevelItem(0, subTree);
+ subTree->setExpanded(true);
+ QTreeWidgetItemIterator it2(tree, QTreeWidgetItemIterator::HasChildren);
+ while (*it2) {
+ QTreeWidgetItem *current = (*it2);
+ if (!(current->text(0)=="data") && expandedNodes.contains(current->text(0)+"/"+current->parent()->text(0))) {
+ current->setExpanded(true);
+ }
+ ++it2;
+ }
}
@@ -1320,9 +1395,10 @@ void MainWindow::refreshSaveList() QDir savesDir = currentSavesDir();
savesDir.setNameFilters(filters);
+ qDebug("reading save games from %s", qPrintable(savesDir.absolutePath()));
QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time);
- foreach (const QFileInfo &file, files) {
+ for (const QFileInfo &file : files) {
QListWidgetItem *item = new QListWidgetItem(file.fileName());
item->setData(Qt::UserRole, file.absoluteFilePath());
ui->savegameList->addItem(item);
@@ -1349,27 +1425,23 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString {
m_DefaultArchives = defaultArchives;
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
-
std::vector<std::pair<UINT32, QTreeWidgetItem*>> items;
- BSAInvalidation *invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
+ BSAInvalidation * invalidation = m_OrganizerCore.managedGame()->feature<BSAInvalidation>();
std::vector<FileEntry::Ptr> files = m_OrganizerCore.directoryStructure()->getFiles();
- QStringList plugins = m_OrganizerCore.findFiles("", [] (const QString &fileName) -> bool {
+ QStringList plugins = m_OrganizerCore.findFiles("", [](const QString &fileName) -> bool {
return fileName.endsWith(".esp", Qt::CaseInsensitive)
- || fileName.endsWith(".esm", Qt::CaseInsensitive);
+ || fileName.endsWith(".esm", Qt::CaseInsensitive)
+ || fileName.endsWith(".esl", Qt::CaseInsensitive);
});
auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool {
for (const QString &pluginName : plugins) {
QFileInfo pluginInfo(pluginName);
if (bsaName.startsWith(QFileInfo(pluginName).baseName(), Qt::CaseInsensitive)
- && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) {
+ && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) {
return true;
}
}
@@ -1379,11 +1451,12 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString for (FileEntry::Ptr current : files) {
QFileInfo fileInfo(ToQString(current->getName().c_str()));
- if (fileInfo.suffix().toLower() == "bsa") {
+ if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") {
int index = activeArchives.indexOf(fileInfo.fileName());
if (index == -1) {
index = 0xFFFF;
- } else {
+ }
+ else {
index += 2;
}
@@ -1392,18 +1465,18 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString }
int originId = current->getOrigin();
- FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId);
+ FilesOrigin & origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId);
- QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList()
- << fileInfo.fileName()
- << ToQString(origin.getName()));
+ QTreeWidgetItem * newItem = new QTreeWidgetItem(QStringList()
+ << fileInfo.fileName()
+ << ToQString(origin.getName()));
newItem->setData(0, Qt::UserRole, index);
newItem->setData(1, Qt::UserRole, originId);
- newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable);
+ 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_OrganizerCore.settings().forceEnableCoreFiles()
- && defaultArchives.contains(fileInfo.fileName())) {
+ && defaultArchives.contains(fileInfo.fileName())) {
newItem->setCheckState(0, Qt::Checked);
newItem->setDisabled(true);
newItem->setData(0, Qt::UserRole, true);
@@ -1414,21 +1487,15 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString 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);
- }
+ newItem->setCheckState(0, Qt::Unchecked);
+ newItem->setDisabled(true);
}
-
if (index < 0) index = 0;
UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF);
items.push_back(std::make_pair(sortValue, newItem));
}
}
-
std::sort(items.begin(), items.end(), BySortValue);
for (auto iter = items.begin(); iter != items.end(); ++iter) {
@@ -1442,10 +1509,11 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString modName = modInfo->name();
}
QList<QTreeWidgetItem*> items = ui->bsaList->findItems(modName, Qt::MatchFixedString);
- QTreeWidgetItem *subItem = nullptr;
+ QTreeWidgetItem * subItem = nullptr;
if (items.length() > 0) {
subItem = items.at(0);
- } else {
+ }
+ else {
subItem = new QTreeWidgetItem(QStringList(modName));
subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled);
ui->bsaList->addTopLevelItem(subItem);
@@ -1453,18 +1521,16 @@ void MainWindow::updateBSAList(const QStringList &defaultArchives, const QString subItem->addChild(iter->second);
subItem->setExpanded(true);
}
-
checkBSAList();
}
-
void MainWindow::checkBSAList()
{
- DataArchives *archives = m_OrganizerCore.managedGame()->feature<DataArchives>();
+ DataArchives * archives = m_OrganizerCore.managedGame()->feature<DataArchives>();
if (archives != nullptr) {
ui->bsaList->blockSignals(true);
- ON_BLOCK_EXIT([&] () { ui->bsaList->blockSignals(false); });
+ ON_BLOCK_EXIT([&]() { ui->bsaList->blockSignals(false); });
QStringList defaultArchives = archives->archives(m_OrganizerCore.currentProfile());
@@ -1472,9 +1538,9 @@ void MainWindow::checkBSAList() for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
bool modWarning = false;
- QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i);
+ QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i);
for (int j = 0; j < tlItem->childCount(); ++j) {
- QTreeWidgetItem *item = tlItem->child(j);
+ QTreeWidgetItem * item = tlItem->child(j);
QString filename = item->text(0);
item->setIcon(0, QIcon());
item->setToolTip(0, QString());
@@ -1492,7 +1558,6 @@ void MainWindow::checkBSAList() warning = true;
}
}
-
if (warning) {
ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning"));
} else {
@@ -1501,7 +1566,6 @@ void MainWindow::checkBSAList() }
}
-
void MainWindow::saveModMetas()
{
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
@@ -1510,7 +1574,6 @@ void MainWindow::saveModMetas() }
}
-
void MainWindow::fixCategories()
{
for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
@@ -1581,18 +1644,9 @@ void MainWindow::readSettings() 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(QSettings &settings)
-{
- settings.setValue("mod_list_state", ui->modList->header()->saveState());
- settings.setValue("plugin_list_state", ui->espList->header()->saveState());
-
+void MainWindow::storeSettings(QSettings &settings) {
settings.setValue("group_state", ui->groupCombo->currentIndex());
settings.setValue("window_geometry", saveGeometry());
@@ -1602,22 +1656,32 @@ void MainWindow::storeSettings(QSettings &settings) settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry());
settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked());
- settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked());
- settings.setValue("selected_executable", ui->executablesListBox->currentIndex());
+ settings.setValue("selected_executable",
+ ui->executablesListBox->currentIndex());
+
+ for (const std::pair<QString, QHeaderView*> kv : m_PersistedGeometry) {
+ QString key = QString("geometry/") + kv.first;
+ settings.setValue(key, kv.second->saveState());
+ }
}
-void MainWindow::lock()
+ILockedWaitingForProcess* MainWindow::lock()
{
if (m_LockDialog != nullptr) {
++m_LockCount;
- return;
+ return m_LockDialog;
}
- m_LockDialog = new LockedDialog(qApp->activeWindow());
+ if (m_closing)
+ m_LockDialog = new WaitingOnCloseDialog(this);
+ else
+ m_LockDialog = new LockedDialog(this, true);
+ m_LockDialog->setModal(true);
m_LockDialog->show();
setEnabled(false);
m_LockDialog->setEnabled(true); //What's the point otherwise?
++m_LockCount;
+ return m_LockDialog;
}
void MainWindow::unlock()
@@ -1629,6 +1693,8 @@ void MainWindow::unlock() }
--m_LockCount;
if (m_LockCount == 0) {
+ if (m_closing && m_LockDialog->canceled())
+ m_closing = false;
m_LockDialog->hide();
m_LockDialog->deleteLater();
m_LockDialog = nullptr;
@@ -1636,22 +1702,6 @@ void MainWindow::unlock() }
}
-bool MainWindow::unlockClicked()
-{
- if (m_LockDialog != nullptr) {
- return m_LockDialog->unlockClicked();
- } else {
- return false;
- }
-}
-
-void MainWindow::setProcessName(QString const &name)
-{
- if (m_LockDialog != nullptr) {
- m_LockDialog->setProcessName(name);
- }
-}
-
void MainWindow::on_btnRefreshData_clicked()
{
m_OrganizerCore.refreshDirectoryStructure();
@@ -1667,8 +1717,6 @@ void MainWindow::on_tabWidget_currentChanged(int index) refreshDataTree();
} else if (index == 3) {
refreshSaveList();
- } else if (index == 4) {
- ui->downloadView->scrollToBottom();
}
}
@@ -1696,21 +1744,17 @@ void MainWindow::installMod(QString fileName) }
}
-
-void MainWindow::on_startButton_clicked()
-{
+void MainWindow::on_startButton_clicked() {
const Executable &selectedExecutable(getSelectedExecutable());
-
+ QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString();
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);
+ selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0
+ ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_SteamAppID, customOverwrite);
}
-
static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
LPCSTR linkFileName, LPCWSTR description,
LPCTSTR iconFileName, int iconNumber,
@@ -1797,7 +1841,9 @@ bool MainWindow::modifyExecutablesDialog() {
bool result = false;
try {
- EditExecutablesDialog dialog(*m_OrganizerCore.executablesList());
+ EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(),
+ *m_OrganizerCore.modList(),
+ m_OrganizerCore.currentProfile());
if (dialog.exec() == QDialog::Accepted) {
m_OrganizerCore.setExecutablesList(dialog.getExecutablesList());
result = true;
@@ -1840,7 +1886,7 @@ void MainWindow::wikiTriggered() void MainWindow::issueTriggered()
{
- ::ShellExecuteW(nullptr, L"open", L"http://issue.tannin.eu/tbg", nullptr, nullptr, SW_SHOWNORMAL);
+ ::ShellExecuteW(nullptr, L"open", L"http://github.com/LePresidente/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL);
}
void MainWindow::tutorialTriggered()
@@ -1917,8 +1963,10 @@ void MainWindow::setESPListSorting(int index) void MainWindow::refresher_progress(int percent)
{
if (percent == 100) {
-// m_RefreshProgress->setVisible(false);
+ m_RefreshProgress->setVisible(false);
+ statusBar()->hide();
} else if (!m_RefreshProgress->isVisible()) {
+ statusBar()->show();
m_RefreshProgress->setVisible(true);
m_RefreshProgress->setRange(0, 100);
m_RefreshProgress->setValue(percent);
@@ -1945,7 +1993,7 @@ void MainWindow::modorder_changed() }
}
m_OrganizerCore.refreshBSAList();
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
m_ArchiveListWriter.write();
m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins();
@@ -2006,71 +2054,9 @@ 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(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()))
- .arg(profileName);
-
- QFile modList(modlistName);
- if (modList.exists()) {
- renameModInList(modList, oldName, newName);
- }
- }
+ Profile::renameModInAllProfiles(oldName, newName);
// immediately refresh the active profile because the data in memory is invalid
m_OrganizerCore.currentProfile()->refreshModStatus();
@@ -2098,9 +2084,10 @@ void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath;
WIN32_FIND_DATAW findData;
- ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData);
-
+ HANDLE hFind;
+ hFind = ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData);
filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L"");
+ FindClose(hFind);
}
if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldOriginName))) {
FilesOrigin &oldOrigin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldOriginName));
@@ -2137,11 +2124,14 @@ void MainWindow::addContentFilters() void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID)
{
- for (size_t i = 1; i < m_CategoryFactory.numCategories(); ++i) {
+ for (unsigned int i = 1;
+ i < static_cast<unsigned int>(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, ModListSortProxy::TYPE_CATEGORY);
+ QTreeWidgetItem *item =
+ addFilterItem(root, m_CategoryFactory.getCategoryName(i),
+ categoryID, ModListSortProxy::TYPE_CATEGORY);
if (m_CategoryFactory.hasChildren(i)) {
addCategoryFilters(item, categoriesUsed, categoryID);
}
@@ -2247,7 +2237,7 @@ void MainWindow::restoreBackup_clicked() void MainWindow::modlistChanged(const QModelIndex&, int)
{
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
}
void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&)
@@ -2265,6 +2255,18 @@ void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QMode ui->modList->verticalScrollBar()->repaint();
}
+void MainWindow::modlistSelectionsChanged(const QItemSelection &selected)
+{
+ m_OrganizerCore.pluginList()->highlightPlugins(selected, *m_OrganizerCore.directoryStructure(), *m_OrganizerCore.currentProfile());
+ ui->espList->verticalScrollBar()->repaint();
+}
+
+void MainWindow::esplistSelectionsChanged(const QItemSelection &selected)
+{
+ m_OrganizerCore.modList()->highlightMods(selected, *m_OrganizerCore.directoryStructure());
+ ui->modList->verticalScrollBar()->repaint();
+}
+
void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder)
{
ui->modList->verticalScrollBar()->repaint();
@@ -2277,7 +2279,7 @@ void MainWindow::removeMod_clicked() if (selection->hasSelection() && selection->selectedRows().count() > 1) {
QString mods;
QStringList modNames;
- foreach (QModelIndex idx, selection->selectedRows()) {
+ for (QModelIndex idx : selection->selectedRows()) {
QString name = idx.data().toString();
if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) {
continue;
@@ -2289,7 +2291,7 @@ void MainWindow::removeMod_clicked() tr("Remove the following mods?<br><ul>%1</ul>").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) {
+ for (QString name : modNames) {
m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex());
}
}
@@ -2603,7 +2605,34 @@ void MainWindow::information_clicked() }
}
+void MainWindow::createEmptyMod_clicked()
+{
+ GuessedValue<QString> name;
+ name.setFilter(&fixDirectoryName);
+
+ while (name->isEmpty()) {
+ bool ok;
+ name.update(QInputDialog::getText(this, tr("Create Mod..."),
+ tr("This will create an empty mod.\n"
+ "Please enter a name:"), QLineEdit::Normal, "", &ok),
+ GUESS_USER);
+ if (!ok) {
+ return;
+ }
+ }
+
+ if (m_OrganizerCore.getMod(name) != nullptr) {
+ reportError(tr("A mod with this name already exists"));
+ return;
+ }
+ IModInterface *newMod = m_OrganizerCore.createMod(name);
+ if (newMod == nullptr) {
+ return;
+ }
+
+ m_OrganizerCore.refreshModList();
+}
void MainWindow::createModFromOverwrite()
{
@@ -2642,6 +2671,31 @@ void MainWindow::createModFromOverwrite() m_OrganizerCore.refreshModList();
}
+void MainWindow::clearOverwrite()
+{
+ unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
+ std::vector<ModInfo::EFlag> flags = mod->getFlags();
+ return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE)
+ != flags.end();
+ });
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex);
+ if (modInfo)
+ {
+ QDir overwriteDir(modInfo->absolutePath());
+ if (QMessageBox::question(this, tr("Are you sure?"),
+ tr("About to recursively delete:\n") + overwriteDir.absolutePath(),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok)
+ {
+ QStringList delList;
+ for (auto f : overwriteDir.entryList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot))
+ delList.push_back(overwriteDir.absoluteFilePath(f));
+ shellDelete(delList, true);
+ updateProblemsButton();
+ }
+ }
+}
+
void MainWindow::cancelModListEditor()
{
ui->modList->setEnabled(false);
@@ -2682,7 +2736,7 @@ bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) bool childEnabled = false;
- for (size_t i = 1; i < m_CategoryFactory.numCategories(); ++i) {
+ for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) {
if (m_CategoryFactory.getParentID(i) == targetID) {
QMenu *targetMenu = menu;
if (m_CategoryFactory.hasChildren(i)) {
@@ -2716,7 +2770,7 @@ bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow)
{
ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow);
- foreach (QAction* action, menu->actions()) {
+ for (QAction* action : menu->actions()) {
if (action->menu() != nullptr) {
replaceCategoriesFromMenu(action->menu(), modRow);
} else {
@@ -2841,44 +2895,16 @@ void MainWindow::replaceCategories_MenuHandler() { refreshFilters();
}
-void MainWindow::savePrimaryCategory()
-{
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == nullptr) {
- qCritical("not a menu?");
- return;
- }
-
- foreach (QAction* action, menu->actions()) {
- QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
- if (widgetAction != nullptr) {
- QRadioButton *btn = qobject_cast<QRadioButton*>(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::saveArchiveList()
{
if (m_OrganizerCore.isArchivesInit()) {
SafeWriteFile archiveFile(m_OrganizerCore.currentProfile()->getArchivesFileName());
for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
- QTreeWidgetItem *tlItem = ui->bsaList->topLevelItem(i);
+ QTreeWidgetItem * tlItem = ui->bsaList->topLevelItem(i);
for (int j = 0; j < tlItem->childCount(); ++j) {
- QTreeWidgetItem *item = tlItem->child(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"));
- }
+ archiveFile->write(item->text(0).toUtf8().append("\r\n"));
}
}
}
@@ -2949,15 +2975,21 @@ void MainWindow::unignoreUpdate() info->ignoreUpdate(false);
}
-void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info)
-{
+void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu,
+ ModInfo::Ptr info) {
const std::set<int> &categories = info->getCategories();
- foreach (int categoryID, categories) {
+ for (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);
+ QRadioButton *categoryBox = new QRadioButton(
+ m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"),
+ primaryCategoryMenu);
+ connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) {
+ if (enable) {
+ info->setPrimaryCategory(categoryID);
+ }
+ });
categoryBox->setChecked(categoryID == info->getPrimaryCategory());
action->setDefaultWidget(categoryBox);
} catch (const std::exception &e) {
@@ -2998,58 +3030,191 @@ void MainWindow::disableVisibleMods() }
}
+void MainWindow::openInstanceFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+void MainWindow::openInstallFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(qApp->applicationDirPath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+void MainWindow::openProfileFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+void MainWindow::openDownloadsFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+void MainWindow::openGameFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+void MainWindow::openMyGamesFolder()
+{
+ ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
+
void MainWindow::exportModListCSV()
{
- SelectionDialog selection(tr("Choose what to export"));
+ //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);
+ //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();
+ QDialog selection(this);
+ QGridLayout *grid = new QGridLayout;
+ selection.setWindowTitle(tr("Export to csv"));
- try {
- QBuffer buffer;
- buffer.open(QIODevice::ReadWrite);
- CSVBuilder builder(&buffer);
- builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS);
- std::vector<std::pair<QString, CSVBuilder::EFieldType> > 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);
+ QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:"));
+ QRadioButton *all = new QRadioButton(tr("All installed mods"));
+ QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile"));
+ QRadioButton *visible = new QRadioButton(tr("All currently visible mods in the mod list"));
- builder.writeHeader();
+ QVBoxLayout *vbox = new QVBoxLayout;
+ vbox->addWidget(all);
+ vbox->addWidget(active);
+ vbox->addWidget(visible);
+ vbox->addStretch(1);
+ groupBoxRows->setLayout(vbox);
- for (unsigned int i = 0; i < numMods; ++i) {
- ModInfo::Ptr info = ModInfo::getByIndex(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)) {
- continue;
- }
- std::vector<ModInfo::EFlag> 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()));
- }
- }
+
+ grid->addWidget(groupBoxRows);
+
+ QButtonGroup *buttonGroupRows = new QButtonGroup();
+ buttonGroupRows->addButton(all, 0);
+ buttonGroupRows->addButton(active, 1);
+ buttonGroupRows->addButton(visible, 2);
+ buttonGroupRows->button(0)->setChecked(true);
+
+
+
+ QGroupBox *groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:"));
+ groupBoxColumns->setFlat(true);
+
+ QCheckBox *mod_Priority = new QCheckBox(tr("Mod_Priority"));
+ mod_Priority->setChecked(true);
+ QCheckBox *mod_Name = new QCheckBox(tr("Mod_Name"));
+ mod_Name->setChecked(true);
+ QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status"));
+ QCheckBox *primary_Category = new QCheckBox(tr("Primary_Category"));
+ QCheckBox *nexus_ID = new QCheckBox(tr("Nexus_ID"));
+ QCheckBox *mod_Nexus_URL = new QCheckBox(tr("Mod_Nexus_URL"));
+ QCheckBox *mod_Version = new QCheckBox(tr("Mod_Version"));
+ QCheckBox *install_Date = new QCheckBox(tr("Install_Date"));
+ QCheckBox *download_File_Name = new QCheckBox(tr("Download_File_Name"));
+
+ QVBoxLayout *vbox1 = new QVBoxLayout;
+ vbox1->addWidget(mod_Priority);
+ vbox1->addWidget(mod_Name);
+ vbox1->addWidget(mod_Status);
+ vbox1->addWidget(primary_Category);
+ vbox1->addWidget(nexus_ID);
+ vbox1->addWidget(mod_Nexus_URL);
+ vbox1->addWidget(mod_Version);
+ vbox1->addWidget(install_Date);
+ vbox1->addWidget(download_File_Name);
+ groupBoxColumns->setLayout(vbox1);
+
+ grid->addWidget(groupBoxColumns);
+
+ QPushButton *ok = new QPushButton("Ok");
+ QPushButton *cancel = new QPushButton("Cancel");
+ QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+
+ connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept()));
+ connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject()));
+
+ grid->addWidget(buttons);
+
+ selection.setLayout(grid);
+
+
+ if (selection.exec() == QDialog::Accepted) {
+
+ unsigned int numMods = ModInfo::getNumMods();
+ int selectedRowID = buttonGroupRows->checkedId();
+
+ try {
+ QBuffer buffer;
+ buffer.open(QIODevice::ReadWrite);
+ CSVBuilder builder(&buffer);
+ builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS);
+ std::vector<std::pair<QString, CSVBuilder::EFieldType> > fields;
+ if (mod_Priority->isChecked())
+ fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING));
+ if (mod_Name->isChecked())
+ fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING));
+ if (mod_Status->isChecked())
+ fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING));
+ if (primary_Category->isChecked())
+ fields.push_back(std::make_pair(QString("#Primary_Category"), CSVBuilder::TYPE_STRING));
+ if (nexus_ID->isChecked())
+ fields.push_back(std::make_pair(QString("#Nexus_ID"), CSVBuilder::TYPE_INTEGER));
+ if (mod_Nexus_URL->isChecked())
+ fields.push_back(std::make_pair(QString("#Mod_Nexus_URL"), CSVBuilder::TYPE_STRING));
+ if (mod_Version->isChecked())
+ fields.push_back(std::make_pair(QString("#Mod_Version"), CSVBuilder::TYPE_STRING));
+ if (install_Date->isChecked())
+ fields.push_back(std::make_pair(QString("#Install_Date"), CSVBuilder::TYPE_STRING));
+ if (download_File_Name->isChecked())
+ fields.push_back(std::make_pair(QString("#Download_File_Name"), CSVBuilder::TYPE_STRING));
+
+ builder.setFields(fields);
+
+ builder.writeHeader();
+
+ for (unsigned int i = 0; i < numMods; ++i) {
+ ModInfo::Ptr info = ModInfo::getByIndex(i);
+ bool enabled = m_OrganizerCore.currentProfile()->modEnabled(i);
+ if ((selectedRowID == 1) && !enabled) {
+ continue;
+ }
+ else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) {
+ continue;
+ }
+ std::vector<ModInfo::EFlag> 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())) {
+ if (mod_Priority->isChecked())
+ builder.setRowField("#Mod_Priority", QString("%1").arg(m_OrganizerCore.currentProfile()->getModPriority(i), 4, 10, QChar('0')));
+ if (mod_Name->isChecked())
+ builder.setRowField("#Mod_Name", info->name());
+ if (mod_Status->isChecked())
+ builder.setRowField("#Mod_Status", (enabled)? "Enabled" : "Disabled");
+ if (primary_Category->isChecked())
+ builder.setRowField("#Primary_Category", (m_CategoryFactory.categoryExists(info->getPrimaryCategory())) ? m_CategoryFactory.getCategoryName(info->getPrimaryCategory()) : "");
+ if (nexus_ID->isChecked())
+ builder.setRowField("#Nexus_ID", info->getNexusID());
+ if (mod_Nexus_URL->isChecked())
+ builder.setRowField("#Mod_Nexus_URL", info->getURL());
+ if (mod_Version->isChecked())
+ builder.setRowField("#Mod_Version", info->getVersion().canonicalString());
+ if (install_Date->isChecked())
+ builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss"));
+ if (download_File_Name->isChecked())
+ builder.setRowField("#Download_File_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()));
+ }
+ }
}
static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu)
@@ -3061,11 +3226,39 @@ static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) menu->addAction(action);
}
+QMenu *MainWindow::openFolderMenu()
+{
+
+ QMenu *FolderMenu = new QMenu(this);
+
+
+ FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder()));
+
+ FolderMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder()));
+
+ FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder()));
+
+ FolderMenu->addAction(tr("Open MO Install folder"), this, SLOT(openInstallFolder()));
+
+ FolderMenu->addSeparator();
+
+ FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder()));
+
+ FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder()));
+
+
+ return FolderMenu;
+}
+
QMenu *MainWindow::modListContextMenu()
{
QMenu *menu = new QMenu(this);
menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked()));
+ menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked()));
+
+ menu->addSeparator();
+
menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods()));
menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods()));
@@ -3074,6 +3267,8 @@ QMenu *MainWindow::modListContextMenu() menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh()));
menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV()));
+
+
return menu;
}
@@ -3100,7 +3295,9 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (QDir(info->absolutePath()).count() > 2) {
menu->addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite()));
menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite()));
+ menu->addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite()));
}
+ menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked()));
} 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()));
@@ -3119,7 +3316,6 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) 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();
@@ -3138,6 +3334,9 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked()));
menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked()));
menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked()));
+
+ menu->addSeparator();
+
if (info->getNexusID() > 0) {
switch (info->endorsedState()) {
case ModInfo::ENDORSED_TRUE: {
@@ -3158,6 +3357,8 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) }
}
+ menu->addSeparator();
+
std::vector<ModInfo::EFlag> 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()));
@@ -3165,11 +3366,7 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) if (info->getNexusID() > 0) {
menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
- }
-
- //If a URL is specified which is not the game's URL, pop up 'visit web page'
- if (info->getURL() != "" &&
- !NexusInterface::instance()->isModURL(info->getNexusID(), info->getURL())) {
+ } else if ((info->getURL() != "")) {
menu->addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked()));
}
@@ -3196,7 +3393,7 @@ void MainWindow::on_categoriesList_itemSelectionChanged() QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows();
std::vector<int> categories;
std::vector<int> content;
- foreach (const QModelIndex &index, indices) {
+ for (const QModelIndex &index : indices) {
int filterType = index.data(Qt::UserRole + 1).toInt();
if ((filterType == ModListSortProxy::TYPE_CATEGORY)
|| (filterType == ModListSortProxy::TYPE_SPECIAL)) {
@@ -3212,7 +3409,10 @@ void MainWindow::on_categoriesList_itemSelectionChanged() m_ModListSortProxy->setCategoryFilter(categories);
m_ModListSortProxy->setContentFilter(content);
- ui->clickBlankLabel->setEnabled(categories.size() > 0);
+ ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0);
+ //ui->clearFiltersButton->setStyleSheet("border:5px solid #ff0000;");
+ ui->clearFiltersButton->setVisible(categories.size() > 0 || content.size() > 0);
+
if (indices.count() == 0) {
ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>")));
} else if (indices.count() > 1) {
@@ -3260,6 +3460,7 @@ void MainWindow::deleteSavegame_clicked() .arg(savesMsgLabel),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
shellDelete(deleteFiles, true); // recycle bin delete.
+ refreshSaveList();
}
}
@@ -3277,7 +3478,7 @@ void MainWindow::fixMods_clicked(SaveGameInfo::MissingAssets const &missingAsset }
}
- m_OrganizerCore.currentProfile()->modlistWriter().write();
+ m_OrganizerCore.currentProfile()->writeModlist();
m_OrganizerCore.refreshLists();
std::set<QString> espsToActivate = dialog.getESPsToActivate();
@@ -3347,7 +3548,7 @@ QString getStartMenuLinkfile(const Executable &exec) void MainWindow::addWindowsLink(const ShortcutType mapping)
{
- Executable const &selectedExecutable(getSelectedExecutable());
+ const Executable &selectedExecutable(getSelectedExecutable());
QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(),
selectedExecutable);
@@ -3363,11 +3564,11 @@ void MainWindow::addWindowsLink(const ShortcutType mapping) QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath());
std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
- std::wstring parameter = ToWString(QString("\"%1\" %2").arg(executable)
- .arg(selectedExecutable.m_Arguments));
- std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
+ std::wstring parameter = ToWString(
+ QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.m_Title));
+ std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title));
std::wstring iconFile = ToWString(executable);
- std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath()));
+ std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath()));
if (CreateShortcut(targetFile.c_str()
, parameter.c_str()
@@ -3394,40 +3595,59 @@ void MainWindow::linkMenu() void MainWindow::on_actionSettings_triggered()
{
- 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());
+ Settings &settings = m_OrganizerCore.settings();
+
+ QString oldModDirectory(settings.getModDirectory());
+ QString oldCacheDirectory(settings.getCacheDirectory());
+ QString oldProfilesDirectory(settings.getProfileDirectory());
+ bool oldDisplayForeign(settings.displayForeign());
+ bool proxy = settings.useProxy();
+
+ settings.query(this);
+
+ InstallationManager *instManager = m_OrganizerCore.installationManager();
+ instManager->setModsDirectory(settings.getModDirectory());
+ instManager->setDownloadDirectory(settings.getDownloadDirectory());
+
fixCategories();
refreshFilters();
- 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);
+
+ if (settings.getProfileDirectory() != oldProfilesDirectory) {
+ refreshProfiles();
+ }
+
+ DownloadManager *dlManager = m_OrganizerCore.downloadManager();
+
+ if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) {
+ if (dlManager->downloadsInProgress()) {
+ MessageDialog::showMessage(tr("Can't change download directory while "
+ "downloads are in progress!"),
+ this);
} else {
- m_OrganizerCore.downloadManager()->setOutputDirectory(m_OrganizerCore.settings().getDownloadDirectory());
+ dlManager->setOutputDirectory(settings.getDownloadDirectory());
}
}
- m_OrganizerCore.downloadManager()->setPreferredServers(m_OrganizerCore.settings().getPreferredServers());
+ dlManager->setPreferredServers(settings.getPreferredServers());
- if ((m_OrganizerCore.settings().getModDirectory() != oldModDirectory)
- || (m_OrganizerCore.settings().displayForeign() != oldDisplayForeign)) {
+ if ((settings.getModDirectory() != oldModDirectory)
+ || (settings.displayForeign() != oldDisplayForeign)) {
m_OrganizerCore.profileRefresh();
}
- if (m_OrganizerCore.settings().getCacheDirectory() != oldCacheDirectory) {
- NexusInterface::instance()->setCacheDirectory(m_OrganizerCore.settings().getCacheDirectory());
+ if (settings.getCacheDirectory() != oldCacheDirectory) {
+ NexusInterface::instance()->setCacheDirectory(settings.getCacheDirectory());
}
- if (proxy != m_OrganizerCore.settings().useProxy()) {
- activateProxy(m_OrganizerCore.settings().useProxy());
+ if (proxy != settings.useProxy()) {
+ activateProxy(settings.useProxy());
}
- NexusInterface::instance()->setNMMVersion(m_OrganizerCore.settings().getNMMVersion());
+ NexusInterface::instance()->setNMMVersion(settings.getNMMVersion());
updateDownloadListDelegate();
+
+ m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType());
+ m_OrganizerCore.cycleDiagnostics();
}
@@ -3471,7 +3691,7 @@ void MainWindow::installTranslator(const QString &name) void MainWindow::languageChange(const QString &newLanguage)
{
- foreach (QTranslator *trans, m_Translators) {
+ for (QTranslator *trans : m_Translators) {
qApp->removeTranslator(trans);
}
m_Translators.clear();
@@ -3491,6 +3711,9 @@ void MainWindow::languageChange(const QString &newLanguage) updateProblemsButton();
ui->listOptionsBtn->setMenu(modListContextMenu());
+
+ ui->openFolderMenu->setMenu(openFolderMenu());
+
}
void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry)
@@ -3605,7 +3828,6 @@ void MainWindow::addAsExecutable() binaryInfo.absoluteFilePath(),
arguments,
targetInfo.absolutePath(),
- ExecutableInfo::CloseMOStyle::DEFAULT_STAY,
QString(),
Executable::CustomExecutable);
refreshExecutablesList();
@@ -3650,7 +3872,7 @@ void MainWindow::hideFile() if (QFile::rename(oldName, newName)) {
originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt());
- refreshDataTree();
+ refreshDataTreeKeepExpandedNodes();
} else {
reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName)));
}
@@ -3674,7 +3896,7 @@ void MainWindow::unhideFile() }
if (QFile::rename(oldName, newName)) {
originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt());
- refreshDataTree();
+ refreshDataTreeKeepExpandedNodes();
} else {
reportError(tr("failed to rename \"%1\" to \"%2\"").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName)));
}
@@ -3716,8 +3938,8 @@ void MainWindow::previewDataFile() };
addFunc(file->getOrigin());
- foreach (int i, file->getAlternatives()) {
- addFunc(i);
+ for (auto alt : file->getAlternatives()) {
+ addFunc(alt.first);
}
if (preview.numVariants() > 0) {
preview.exec();
@@ -3734,10 +3956,14 @@ void MainWindow::openDataFile() QString arguments;
switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) {
case 1: {
- m_OrganizerCore.spawnBinaryDirect(binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(), targetInfo.absolutePath(), "");
+ m_OrganizerCore.spawnBinaryDirect(
+ binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(),
+ targetInfo.absolutePath(), "", "");
} break;
case 2: {
- ::ShellExecuteW(nullptr, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+ ::ShellExecuteW(nullptr, L"open",
+ ToWString(targetInfo.absoluteFilePath()).c_str(),
+ nullptr, nullptr, SW_SHOWNORMAL);
} break;
default: {
// nop
@@ -3860,7 +4086,7 @@ void MainWindow::updateDownloadListDelegate() connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString)));
ui->downloadView->setModel(sortProxy);
- ui->downloadView->sortByColumn(1, Qt::AscendingOrder);
+ ui->downloadView->sortByColumn(1, Qt::DescendingOrder);
ui->downloadView->header()->resizeSections(QHeaderView::Fixed);
connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int)));
@@ -3875,8 +4101,7 @@ void MainWindow::updateDownloadListDelegate() void MainWindow::modDetailsUpdated(bool)
{
- --m_ModsToUpdate;
- if (m_ModsToUpdate == 0) {
+ 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) {
@@ -3893,7 +4118,7 @@ void MainWindow::modDetailsUpdated(bool) void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int)
{
- m_ModsToUpdate -= modIDs.size();
+ m_ModsToUpdate -= static_cast<int>(modIDs.size());
QVariantList resultList = resultData.toList();
for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) {
QVariantMap result = iter->toMap();
@@ -3947,7 +4172,7 @@ void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) QVariantList serverList = resultData.toList();
QList<ServerInfo> servers;
- foreach (const QVariant &server, serverList) {
+ for (const QVariant &server : serverList) {
QVariantMap serverInfo = server.toMap();
ServerInfo info;
info.name = serverInfo["Name"].toString();
@@ -4070,7 +4295,7 @@ void MainWindow::displayColumnSelection(const QPoint &pos) // view/hide columns depending on check-state
int i = 1;
- foreach (const QAction *action, menu.actions()) {
+ for (const QAction *action : menu.actions()) {
const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
if (widgetAction != nullptr) {
const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
@@ -4082,26 +4307,6 @@ void MainWindow::displayColumnSelection(const QPoint &pos) }
}
-
-void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
-{
- m_ContextItem = ui->bsaList->itemAt(pos);
-
-// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos));
-
- QMenu menu;
- menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered()));
-
- menu.exec(ui->bsaList->mapToGlobal(pos));
-}
-
-void MainWindow::bsaList_itemMoved()
-{
- m_ArchiveListWriter.write();
- m_CheckBSATimer.start(500);
-}
-
-
void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int)
{
m_ArchiveListWriter.write();
@@ -4117,6 +4322,17 @@ void MainWindow::on_actionProblems_triggered() }
}
+void MainWindow::on_actionChange_Game_triggered()
+{
+ if (QMessageBox::question(this, tr("Are you sure?"),
+ tr("This will restart MO, continue?"),
+ QMessageBox::Yes | QMessageBox::Cancel)
+ == QMessageBox::Yes) {
+ InstanceManager::instance().clearCurrentInstance();
+ qApp->exit(INT_MAX);
+ }
+}
+
void MainWindow::setCategoryListVisible(bool visible)
{
if (visible) {
@@ -4395,7 +4611,11 @@ void MainWindow::on_bossButton_clicked() std::string reportURL;
std::string errorMessages;
- m_OrganizerCore.currentProfile()->writeModlistNow();
+ //m_OrganizerCore.currentProfile()->writeModlistNow();
+ m_OrganizerCore.savePluginList();
+ //Create a backup of the load orders w/ LOOT in name
+ //to make sure that any sorting is easily undo-able.
+ //Need to figure out how I want to do that.
bool success = false;
@@ -4408,27 +4628,28 @@ void MainWindow::on_bossButton_clicked() dialog.show();
QString outPath = QDir::temp().absoluteFilePath("lootreport.json");
-
+
QStringList parameters;
- parameters << "--unattended"
- << "--stdout"
- << "--noreport"
- << "--game" << m_OrganizerCore.managedGame()->gameShortName()
+ parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName()
<< "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath())
- << "--out" << outPath;
+ << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath())
+ << "--out" << QString("\"%1\"").arg(outPath);
if (m_DidUpdateMasterList) {
parameters << "--skipUpdateMasterlist";
- } else {
- m_DidUpdateMasterList = true;
}
HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
HANDLE stdOutRead = INVALID_HANDLE_VALUE;
createStdoutPipe(&stdOutRead, &stdOutWrite);
+ try {
+ m_OrganizerCore.prepareVFS();
+ } catch (const std::exception &e) {
+ QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what());
+ return;
+ }
+
HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
parameters.join(" "),
- m_OrganizerCore.currentProfile()->name(),
- m_OrganizerCore.settings().logLevel(),
qApp->applicationDirPath() + "/loot",
true,
stdOutWrite);
@@ -4523,6 +4744,7 @@ void MainWindow::on_bossButton_clicked() } 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);
@@ -4530,20 +4752,17 @@ void MainWindow::on_bossButton_clicked() }
if (success) {
- if (reportURL.length() > 0) {
+ m_DidUpdateMasterList = true;
+ /*if (reportURL.length() > 0) {
m_IntegratedBrowser.setWindowTitle("LOOT Report");
QString report(reportURL.c_str());
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);
- }
+ }*/
// 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.
@@ -4552,10 +4771,7 @@ void MainWindow::on_bossButton_clicked() QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName());
}
m_OrganizerCore.refreshESPList();
- if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- // the load order should have been retrieved from file time, now save it to our own format
- m_OrganizerCore.savePluginList();
- }
+ m_OrganizerCore.savePluginList();
}
}
@@ -4597,7 +4813,7 @@ QString MainWindow::queryRestore(const QString &filePath) SelectionDialog dialog(tr("Choose backup to restore"), this);
QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX);
QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)");
- foreach(const QFileInfo &info, files) {
+ for(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));
@@ -4637,7 +4853,7 @@ void MainWindow::on_restoreButton_clicked() void MainWindow::on_saveModsButton_clicked()
{
- m_OrganizerCore.currentProfile()->modlistWriter().writeImmediately(true);
+ m_OrganizerCore.currentProfile()->writeModlistNow(true);
QDateTime now = QDateTime::currentDateTime();
if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) {
MessageDialog::showMessage(tr("Backup of modlist created"), this);
@@ -4686,12 +4902,7 @@ void MainWindow::on_categoriesOrBtn_toggled(bool checked) void MainWindow::on_managedArchiveLabel_linkHovered(const QString&)
{
QToolTip::showText(QCursor::pos(),
- ui->managedArchiveLabel->toolTip());
-}
-
-void MainWindow::on_manageArchivesBox_toggled(bool)
-{
- m_OrganizerCore.refreshBSAList();
+ ui->managedArchiveLabel->toolTip());
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
@@ -4781,6 +4992,31 @@ void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool m }
}
+bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) {
+ // register the view so it's geometry gets saved at exit
+ m_PersistedGeometry.push_back(std::make_pair(name, view));
+
+ // also, restore the geometry if it was saved before
+ QSettings &settings = m_OrganizerCore.settings().directInterface();
+
+ QString key = QString("geometry/%1").arg(name);
+ QByteArray data;
+
+ if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) {
+ data = settings.value(oldSettingName).toByteArray();
+ settings.remove(oldSettingName);
+ } else if (settings.contains(key)) {
+ data = settings.value(key).toByteArray();
+ }
+
+ if (!data.isEmpty()) {
+ view->restoreState(data);
+ return true;
+ } else {
+ return false;
+ }
+}
+
void MainWindow::dropEvent(QDropEvent *event)
{
Qt::DropAction action = event->proposedAction();
@@ -4799,3 +5035,14 @@ void MainWindow::dropEvent(QDropEvent *event) event->accept();
}
+
+void MainWindow::on_clickBlankButton_clicked()
+{
+ deselectFilters();
+}
+
+void MainWindow::on_clearFiltersButton_clicked()
+{
+ deselectFilters();
+}
+
diff --git a/src/mainwindow.h b/src/mainwindow.h index 8e63a14f..2c2e2723 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -35,7 +35,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. //when I get round to cleaning up main.cpp
struct Executable;
class CategoryFactory;
-class LockedDialog;
+class LockedDialogBase;
class OrganizerCore;
#include "plugincontainer.h" //class PluginContainer;
class PluginListSortProxy;
@@ -60,6 +60,7 @@ namespace MOShared { class DirectoryEntry; } #include <QStringList>
#include <QTime>
#include <QTimer>
+#include <QHeaderView>
#include <QVariant>
#include <Qt>
@@ -105,9 +106,9 @@ class MainWindow : public QMainWindow, public IUserInterface friend class OrganizerProxy;
-
public:
- explicit MainWindow(const QString &exeName, QSettings &initSettings,
+
+ explicit MainWindow(QSettings &initSettings,
OrganizerCore &organizerCore, PluginContainer &pluginContainer,
QWidget *parent = 0);
~MainWindow();
@@ -115,14 +116,13 @@ public: void storeSettings(QSettings &settings) override;
void readSettings();
- virtual void lock() override;
+ virtual ILockedWaitingForProcess* lock() override;
virtual void unlock() override;
- virtual bool unlockClicked() override;
- virtual void setProcessName(QString const &name) override;
bool addProfile();
void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives);
void refreshDataTree();
+ void refreshDataTreeKeepExpandedNodes();
void refreshSaveList();
void setModListSorting(int index);
@@ -154,7 +154,6 @@ public: virtual MOBase::DelayedFileWriterBase &archivesWriter() override { return m_ArchiveListWriter; }
- void updateWindowTitle(const QString &accountName, bool premium);
public slots:
void displayColumnSelection(const QPoint &pos);
@@ -168,7 +167,6 @@ public slots: signals:
-
/**
* @brief emitted after the information dialog has been closed
*/
@@ -191,8 +189,16 @@ protected: virtual void dragEnterEvent(QDragEnterEvent *event);
virtual void dropEvent(QDropEvent *event);
+private slots:
+ void on_actionChange_Game_triggered();
+
+private slots:
+ void on_clickBlankButton_clicked();
+
private:
+ void cleanup();
+
void actionToToolButton(QAction *&sourceAction);
void updateToolBar();
@@ -215,8 +221,6 @@ private: void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry);
- void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
-
void refreshFilters();
/**
@@ -247,7 +251,7 @@ private: bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
- int checkForProblems();
+ size_t checkForProblems();
int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type);
@@ -273,6 +277,8 @@ private: QMenu *modListContextMenu();
+ QMenu *openFolderMenu();
+
std::set<QString> enabledArchives();
void scheduleUpdateButton();
@@ -284,6 +290,8 @@ private: void dropLocalFile(const QUrl &url, const QString &outputDir, bool move);
+ bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr);
+
private:
static const char *PATTERN_BACKUP_GLOB;
@@ -298,8 +306,6 @@ private: MOBase::TutorialControl m_Tutorial;
- QString m_ExeName;
-
int m_OldProfileIndex;
std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure
@@ -350,9 +356,13 @@ private: bool m_DidUpdateMasterList;
- LockedDialog *m_LockDialog { nullptr };
+ LockedDialogBase *m_LockDialog { nullptr };
uint64_t m_LockCount { 0 };
+ bool m_closing{ false };
+
+ std::vector<std::pair<QString, QHeaderView*>> m_PersistedGeometry;
+
MOBase::DelayedFileWriter m_ArchiveListWriter;
enum class ShortcutType {
@@ -368,6 +378,8 @@ private: private slots:
+ void updateWindowTitle(const QString &accountName, bool premium);
+
void showMessage(const QString &message);
void showError(const QString &message);
@@ -380,6 +392,7 @@ private slots: // modlist context menu
void installMod_clicked();
+ void createEmptyMod_clicked();
void restoreBackup_clicked();
void renameMod_clicked();
void removeMod_clicked();
@@ -415,6 +428,7 @@ private slots: BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress);
void createModFromOverwrite();
+ void clearOverwrite();
void procError(QProcess::ProcessError error);
void procFinished(int exitCode, QProcess::ExitStatus exitStatus);
@@ -437,7 +451,6 @@ private slots: void addRemoveCategories_MenuHandler();
void replaceCategories_MenuHandler();
- void savePrimaryCategory();
void addPrimaryCategoryCandidates();
void modDetailsUpdated(bool success);
@@ -473,7 +486,12 @@ private slots: void enableVisibleMods();
void disableVisibleMods();
void exportModListCSV();
-
+ void openInstanceFolder();
+ void openInstallFolder();
+ void openDownloadsFolder();
+ void openProfileFolder();
+ void openGameFolder();
+ void openMyGamesFolder();
void startExeAction();
void checkBSAList();
@@ -520,6 +538,9 @@ private slots: void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous);
void modListSortIndicatorChanged(int column, Qt::SortOrder order);
+ void modlistSelectionsChanged(const QItemSelection ¤t);
+ void esplistSelectionsChanged(const QItemSelection ¤t);
+
private slots: // ui slots
// actions
void on_actionAdd_Profile_triggered();
@@ -531,8 +552,7 @@ private slots: // ui slots void on_actionUpdate_triggered();
void on_actionEndorseMO_triggered();
- void on_bsaList_customContextMenuRequested(const QPoint &pos);
- void bsaList_itemMoved();
+ void on_clearFiltersButton_clicked();
void on_btnRefreshData_clicked();
void on_categoriesList_customContextMenuRequested(const QPoint &pos);
void on_conflictsCheckBox_toggled(bool checked);
@@ -562,7 +582,6 @@ private slots: // ui slots 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);
};
diff --git a/src/mainwindow.ui b/src/mainwindow.ui index ca93ea54..31522c03 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -45,7 +45,10 @@ <property name="title">
<string>Categories</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_10">
+ <layout class="QVBoxLayout" name="verticalLayout_10" stretch="6,0,0">
+ <property name="spacing">
+ <number>0</number>
+ </property>
<property name="leftMargin">
<number>3</number>
</property>
@@ -92,25 +95,37 @@ </widget>
</item>
<item>
- <widget class="QLabel" name="clickBlankLabel">
- <property name="enabled">
- <bool>false</bool>
+ <widget class="QPushButton" name="clickBlankButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
</property>
<property name="text">
- <string>Click blank area to deselect</string>
+ <string>Clear</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
</property>
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string/>
- </property>
- <property name="flat">
- <bool>false</bool>
- </property>
- <property name="checkable">
- <bool>false</bool>
+ <widget class="QWidget" name="widget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
@@ -161,7 +176,7 @@ <number>2</number>
</property>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0">
+ <layout class="QHBoxLayout" name="horizontalLayout_6" stretch="0,1,0,0,0,0,0">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
@@ -233,9 +248,33 @@ p, li { white-space: pre-wrap; } <height>16</height>
</size>
</property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openFolderMenu">
+ <property name="toolTip">
+ <string>Quickly open in Explorer relevant Folders...</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/open_folder</normaloff>:/MO/gui/open_folder</iconset>
+ </property>
</widget>
</item>
<item>
+
+
+
+
+
+
+
+
+
+
<widget class="QPushButton" name="restoreModsButton">
<property name="toolTip">
<string>Restore Backup...</string>
@@ -424,7 +463,7 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,1,0">
<item>
<widget class="QPushButton" name="displayCategoriesBtn">
<property name="maximumSize">
@@ -473,8 +512,49 @@ p, li { white-space: pre-wrap; } </property>
</widget>
</item>
+ <item alignment="Qt::AlignLeft">
+ <widget class="QPushButton" name="clearFiltersButton">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>25</height>
+ </size>
+ </property>
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::RightToLeft</enum>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">border:1px solid #ff0000;</string>
+ </property>
+ <property name="text">
+ <string>Clear all Filters</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>12</width>
+ <height>12</height>
+ </size>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QComboBox" name="groupCombo">
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
<item>
<property name="text">
<string>No groups</string>
@@ -714,9 +794,12 @@ p, li { white-space: pre-wrap; } <layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QPushButton" name="bossButton">
+ <property name="visible">
+ <bool>true</bool>
+ </property>
<property name="text">
<string>Sort</string>
- </property>
+ </property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/MO/gui/sort</normaloff>:/MO/gui/sort</iconset>
@@ -773,7 +856,7 @@ p, li { white-space: pre-wrap; } </layout>
</item>
<item>
- <widget class="QTreeView" name="espList">
+ <widget class="PluginListView" name="espList">
<property name="minimumSize">
<size>
<width>250</width>
@@ -828,7 +911,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;">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></string>
+<p style=" 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, esms, and esls 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></string>
</property>
<property name="dragEnabled">
<bool>true</bool>
@@ -885,9 +968,15 @@ p, li { white-space: pre-wrap; } </layout>
</widget>
<widget class="QWidget" name="bsaTab">
+
+
+
<attribute name="title">
<string>Archives</string>
</attribute>
+ <property name="visible">
+ <bool>true</bool>
+ </property>
<layout class="QVBoxLayout" name="verticalLayout_9">
<property name="leftMargin">
<number>6</number>
@@ -903,23 +992,23 @@ p, li { white-space: pre-wrap; } </property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9" stretch="0,1">
- <item>
- <widget class="QCheckBox" name="manageArchivesBox">
- <property name="text">
- <string/>
- </property>
- <property name="checked">
- <bool>true</bool>
- </property>
- </widget>
- </item>
+ <!--<item>-->
+ <!--<widget class="QCheckBox" name="manageArchivesBox">-->
+ <!--<property name="text">-->
+ <!--<string/>-->
+ <!--</property>-->
+ <!--<property name="checked">-->
+ <!--<bool>true</bool>-->
+ <!--</property>-->
+ <!--</widget>-->
+ <!--</item>-->
<item>
<widget class="QLabel" name="managedArchiveLabel">
<property name="toolTip">
- <string><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></string>
+ <string><html><head/><body><p>BSAs / BA2s are bundles of game assets (textures, scripts, etc.). By default, the engine loads these bundles in a separate step from loose files. <p>Their load order is specified by the priority of the corresponding plugin (right pane, plugins tab).</p><p>If there is a matching plugin, the game will load them no matter what.</p></body></html></string>
</property>
<property name="text">
- <string><html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html></string>
+ <string><html><head/><body><p>Currently detected archives. (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">What is an archive?</span></a>)</p></body></html></string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -938,15 +1027,15 @@ p, li { white-space: pre-wrap; } </property>
<property name="whatsThis">
<string>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!
+ 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.</string>
+ BSAs checked here are loaded in such a way that your installation order is obeyed properly.</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
- <bool>true</bool>
+ <bool>false</bool>
</property>
<property name="dragEnabled">
<bool>false</bool>
@@ -955,10 +1044,10 @@ BSAs checked here are loaded in such a way that your installation order is obeye <bool>false</bool>
</property>
<property name="dragDropMode">
- <enum>QAbstractItemView::DragDrop</enum>
+ <enum>QAbstractItemView::NoDragDrop</enum>
</property>
<property name="defaultDropAction">
- <enum>Qt::MoveAction</enum>
+ <enum>Qt::IgnoreAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
@@ -1288,6 +1377,7 @@ p, li { white-space: pre-wrap; } <attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
+ <addaction name="actionChange_Game"/>
<addaction name="actionInstallMod"/>
<addaction name="actionNexus"/>
<addaction name="actionAdd_Profile"/>
@@ -1474,6 +1564,18 @@ Right now this has very limited functionality</string> <string>Ctrl+C</string>
</property>
</action>
+ <action name="actionChange_Game">
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/instance_switch</normaloff>:/MO/gui/instance_switch</iconset>
+ </property>
+ <property name="text">
+ <string>Change Game</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the Instance selection dialog to manage a different Game</string>
+ </property>
+ </action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@@ -1488,9 +1590,9 @@ Right now this has very limited functionality</string> <header>modlistview.h</header>
</customwidget>
<customwidget>
- <class>MOBase::SortableTreeWidget</class>
- <extends>QTreeWidget</extends>
- <header>sortabletreewidget.h</header>
+ <class>PluginListView</class>
+ <extends>QTreeView</extends>
+ <header>pluginlistview.h</header>
</customwidget>
</customwidgets>
<resources>
diff --git a/src/mo_icon.ico b/src/mo_icon.ico Binary files differindex 59da6c2d..ba54d4dc 100644 --- a/src/mo_icon.ico +++ b/src/mo_icon.ico diff --git a/src/mo_icon.psd b/src/mo_icon.psd Binary files differdeleted file mode 100644 index 1f7b40c9..00000000 --- a/src/mo_icon.psd +++ /dev/null diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 46b9677b..9ddd9e54 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -129,18 +129,9 @@ bool MOApplication::notify(QObject *receiver, QEvent *event) 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)) {
diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index d66c3ac5..61df0a0d 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -12,8 +12,7 @@ ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent) {
}
-QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const
-{
+QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const {
QList<QString> result;
QVariant modid = index.data(Qt::UserRole + 1);
if (modid.isValid()) {
@@ -21,7 +20,8 @@ QList<QString> ModFlagIconDelegate::getIcons(const QModelIndex &index) const std::vector<ModInfo::EFlag> flags = info->getFlags();
{ // insert conflict icon first to provide nicer alignment
- auto iter = std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4);
+ auto iter = std::find_first_of(flags.begin(), flags.end(),
+ m_ConflictFlags, m_ConflictFlags + 4);
if (iter != flags.end()) {
result.append(getFlagIcon(*iter));
flags.erase(iter);
@@ -58,7 +58,7 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const if (modIdx < ModInfo::getNumMods()) {
ModInfo::Ptr info = ModInfo::getByIndex(modIdx);
std::vector<ModInfo::EFlag> flags = info->getFlags();
- int count = flags.size();
+ size_t count = flags.size();
if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) {
++count;
}
@@ -71,11 +71,11 @@ size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const
{
- int count = getNumIcons(modelIndex);
+ size_t count = getNumIcons(modelIndex);
unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt();
QSize result;
if (index < ModInfo::getNumMods()) {
- result = QSize(count * 40, 20);
+ result = QSize(static_cast<int>(count) * 40, 20);
} else {
result = QSize(1, 20);
}
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 1081daf8..c14eedb7 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -35,6 +35,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <versioninfo.h> #include <appconfig.h> #include <scriptextender.h> +#include <unmanagedmods.h> #include <QApplication> #include <QDirIterator> @@ -75,11 +76,14 @@ ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStru return result; } -ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames - , DirectoryEntry ** directoryStructure) -{ +ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, + const QString &espName, + const QStringList &bsaNames, + ModInfo::EModType modType, + DirectoryEntry **directoryStructure) { QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); + ModInfo::Ptr result = ModInfo::Ptr( + new ModInfoForeign(modName, espName, bsaNames, modType, directoryStructure)); s_Collection.push_back(result); return result; } @@ -90,14 +94,13 @@ QString ModInfo::getContentTypeName(int contentType) case CONTENT_PLUGIN: return tr("Plugins"); case CONTENT_TEXTURE: return tr("Textures"); case CONTENT_MESH: return tr("Meshes"); - case CONTENT_BSA: return tr("BSA"); + case CONTENT_BSA: return tr("Bethesda Archive"); 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"); + case CONTENT_MCM: return tr("MCM Data"); default: throw MyException(tr("invalid content type %1").arg(contentType)); } } @@ -112,7 +115,7 @@ void ModInfo::createFromOverwrite() unsigned int ModInfo::getNumMods() { QMutexLocker locker(&s_Mutex); - return s_Collection.size(); + return static_cast<unsigned int>(s_Collection.size()); } @@ -120,9 +123,10 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index) { QMutexLocker locker(&s_Mutex); - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); + if (index >= s_Collection.size() && index != ULONG_MAX) { + throw MyException(tr("invalid mod index %1").arg(index)); } + if (index == ULONG_MAX) return s_Collection[ModInfo::getIndex("Overwrite")]; return s_Collection[index]; } @@ -150,7 +154,7 @@ bool ModInfo::removeMod(unsigned int index) QMutexLocker locker(&s_Mutex); if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); + throw MyException(tr("remove: invalid mod index %1").arg(index)); } // update the indices first ModInfo::Ptr modInfo = s_Collection[index]; @@ -219,27 +223,17 @@ void ModInfo::updateFromDisc(const QString &modDirectory, } } - { // list plugins in the data directory and make a foreign-managed mod out of each - QStringList dlcPlugins = game->DLCPlugins(); - QStringList mainPlugins = game->primaryPlugins(); - QDir dataDir(game->dataDirectory()); - for (const QString &file : dataDir.entryList({ "*.esp", "*.esm" })) { - if (std::find_if(mainPlugins.begin(), mainPlugins.end(), - [&file](QString const &p) { - return p.compare(file, Qt::CaseInsensitive) == 0; }) == mainPlugins.end() - && (displayForeign // show non-dlc bundles only if the user wants them - || std::find_if(dlcPlugins.begin(), dlcPlugins.end(), - [&file](QString const &p) { - return p.compare(file, Qt::CaseInsensitive) == 0; }) != dlcPlugins.end())) { + UnmanagedMods *unmanaged = game->feature<UnmanagedMods>(); + if (unmanaged != nullptr) { + for (const QString &modName : unmanaged->mods(!displayForeign)) { + ModInfo::EModType modType = game->DLCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_DLC : + (game->CCPlugins().contains(unmanaged->referenceFile(modName).fileName(), Qt::CaseInsensitive) ? ModInfo::EModType::MOD_CC : ModInfo::EModType::MOD_DEFAULT); - QFileInfo f(file); //Just so I can get a basename... - QStringList archives; - for (const QString &archiveName : dataDir.entryList({ f.baseName() + "*.bsa" })) { - archives.append(dataDir.absoluteFilePath(archiveName)); - } - - createFromPlugin(file, archives, directoryStructure); - } + createFromPlugin(unmanaged->displayName(modName), + unmanaged->referenceFile(modName).absoluteFilePath(), + unmanaged->secondaryFiles(modName), + modType, + directoryStructure); } } @@ -289,7 +283,7 @@ int ModInfo::checkAllForUpdate(QObject *receiver) std::vector<int> modIDs; //I ought to store this, it's used elsewhere - IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>(); + IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>(); modIDs.push_back(game->nexusModOrganizerID()); @@ -313,6 +307,11 @@ void ModInfo::setVersion(const VersionInfo &version) m_Version = version; } +void ModInfo::setPluginSelected(const bool &isSelected) +{ + m_PluginSelected = isSelected; +} + void ModInfo::addCategory(const QString &categoryName) { int id = CategoryFactory::instance().getCategoryID(categoryName); diff --git a/src/modinfo.h b/src/modinfo.h index c10232da..d00c04e7 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -67,7 +67,8 @@ public: FLAG_CONFLICT_OVERWRITE,
FLAG_CONFLICT_OVERWRITTEN,
FLAG_CONFLICT_MIXED,
- FLAG_CONFLICT_REDUNDANT
+ FLAG_CONFLICT_REDUNDANT,
+ FLAG_PLUGIN_SELECTED
};
enum EContent {
@@ -76,21 +77,21 @@ public: CONTENT_MESH,
CONTENT_BSA,
CONTENT_INTERFACE,
- CONTENT_MUSIC,
CONTENT_SOUND,
CONTENT_SCRIPT,
CONTENT_SKSE,
CONTENT_SKYPROC,
- CONTENT_STRING
+ CONTENT_MCM
};
- static const int NUM_CONTENT_TYPES = CONTENT_STRING + 1;
+ static const int NUM_CONTENT_TYPES = CONTENT_MCM + 1;
enum EHighlight {
HIGHLIGHT_NONE = 0,
HIGHLIGHT_INVALID = 1,
HIGHLIGHT_CENTER = 2,
- HIGHLIGHT_IMPORTANT = 4
+ HIGHLIGHT_IMPORTANT = 4,
+ HIGHLIGHT_PLUGIN = 8
};
enum EEndorsedState {
@@ -100,6 +101,13 @@ public: ENDORSED_NEVER
};
+ enum EModType {
+ MOD_DEFAULT,
+ MOD_DLC,
+ MOD_CC
+ };
+
+
public:
/**
@@ -188,7 +196,7 @@ public: * @param bsaNames names of archives
* @return a new mod
*/
- static ModInfo::Ptr createFromPlugin(const QString &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure);
+ static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure);
/**
* @brief retieve a name for one of the CONTENT_ enums
@@ -270,6 +278,12 @@ public: virtual void setVersion(const MOBase::VersionInfo &version);
/**
+ * @brief Controls if mod should be highlighted based on plugin selection
+ * @param isSelected whether or not the plugin has a selected mod
+ **/
+ virtual void setPluginSelected(const bool &isSelected);
+
+ /**
* @brief set the newest version of this mod on the nexus
*
* this can be used to overwrite the version of a mod without actually
@@ -600,6 +614,8 @@ protected: MOBase::VersionInfo m_Version;
+ bool m_PluginSelected = false;
+
private:
static QMutex s_Mutex;
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index feaac2d4..ccd2a122 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "modinfodialog.h"
#include "ui_modinfodialog.h"
+#include "descriptionpage.h"
#include "iplugingame.h"
#include "nexusinterface.h"
@@ -38,6 +39,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QMenu>
#include <QFileSystemModel>
#include <QInputDialog>
+#include <QPointer>
#include <Shlwapi.h>
@@ -85,11 +87,14 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo ui->notesEdit->setText(modInfo->notes());
+ ui->descriptionView->setPage(new DescriptionPage);
+
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(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
- ui->descriptionView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
+ connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
+ //TODO: No easy way to delegate links
+ //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks);
if (directory->originExists(ToWString(modInfo->name()))) {
m_Origin = &directory->getOriginByName(ToWString(modInfo->name()));
@@ -143,6 +148,8 @@ 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->descriptionView->page();
+ delete ui->descriptionView;
delete ui;
delete m_Settings;
}
@@ -259,22 +266,22 @@ void ModInfoDialog::refreshLists() QString fileName = relativeName.mid(0).prepend(m_RootPath);
bool archive;
if ((*iter)->getOrigin(archive) == m_Origin->getID()) {
- std::vector<int> alternatives = (*iter)->getAlternatives();
+ std::vector<std::pair<int, std::wstring>> alternatives = (*iter)->getAlternatives();
if (!alternatives.empty()) {
std::wostringstream altString;
- for (std::vector<int>::iterator altIter = alternatives.begin();
+ for (std::vector<std::pair<int, std::wstring>>::iterator altIter = alternatives.begin();
altIter != alternatives.end(); ++altIter) {
if (altIter != alternatives.begin()) {
altString << ", ";
}
- altString << m_Directory->getOriginByID(*altIter).getName();
+ altString << m_Directory->getOriginByID(altIter->first).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, ToQString(m_Directory->getOriginByID(alternatives.begin()->first).getName()));
+ item->setData(1, Qt::UserRole + 1, alternatives.begin()->first);
item->setData(1, Qt::UserRole + 2, archive);
ui->overwriteTree->addTopLevelItem(item);
++numOverwrite;
@@ -313,7 +320,8 @@ void ModInfoDialog::refreshLists() ui->iniFileList->addItem(namePart);
}
} else if (fileName.endsWith(".esp", Qt::CaseInsensitive) ||
- fileName.endsWith(".esm", Qt::CaseInsensitive)) {
+ fileName.endsWith(".esm", Qt::CaseInsensitive) ||
+ fileName.endsWith(".esl", Qt::CaseInsensitive)) {
QString relativePath = fileName.mid(m_RootPath.length() + 1);
if (relativePath.contains('/')) {
QFileInfo fileInfo(fileName);
@@ -351,14 +359,18 @@ void ModInfoDialog::refreshLists() void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel)
{
- for (size_t i = 0; i < factory.numCategories(); ++i) {
+ for (int i = 0; i < static_cast<int>(factory.numCategories()); ++i) {
if (factory.getParentID(i) != rootLevel) {
continue;
}
int categoryID = factory.getCategoryID(i);
- QTreeWidgetItem *newItem = new QTreeWidgetItem(QStringList(factory.getCategoryName(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->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);
@@ -820,11 +832,13 @@ void ModInfoDialog::modDetailsUpdated(bool success) "<body>%1</body>"
"</html>").arg(BBCode::convertToHTML(nexusDescription));
+ ui->descriptionView->page()->setHtml(descriptionAsHTML);
+
// QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString());
- ui->descriptionView->setHtml(descriptionAsHTML);
+ // 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)"));
+ ui->descriptionView->page()->setHtml(tr("(description incomplete, please visit nexus)"));
}
updateVersionColor();
@@ -870,7 +884,7 @@ void ModInfoDialog::on_modIDEdit_editingFinished() if (oldID != modID){
m_ModInfo->setNexusID(modID);
- ui->descriptionView->setHtml("");
+ ui->descriptionView->page()->setHtml("");
if (modID != 0) {
m_RequestStarted = false;
refreshNexusData(modID);
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index a03edef7..25822fd0 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -279,10 +279,10 @@ <item row="1" column="1">
<widget class="QListWidget" name="inactiveESPList">
<property name="toolTip">
- <string>List of esps and esms that can not be loaded by the game.</string>
+ <string>List of esps, esms, and esls that can not be loaded by the game.</string>
</property>
<property name="whatsThis">
- <string>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.
+ <string>List of esps, esms, and esls 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.</string>
@@ -678,7 +678,7 @@ p, li { white-space: pre-wrap; } </layout>
</item>
<item>
- <widget class="QWebView" name="descriptionView">
+ <widget class="QWebEngineView" name="descriptionView">
<property name="url">
<url>
<string>about:blank</string>
@@ -807,9 +807,9 @@ p, li { white-space: pre-wrap; } </widget>
<customwidgets>
<customwidget>
- <class>QWebView</class>
+ <class>QQuickWidget</class>
<extends>QWidget</extends>
- <header>QtWebKitWidgets/QWebView</header>
+ <header>QtQuickWidgets/QQuickWidget</header>
</customwidget>
</customwidgets>
<resources>
diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index 4dbe034b..6ac66c67 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -21,7 +21,7 @@ QDateTime ModInfoForeign::creationTime() const QString ModInfoForeign::absolutePath() const { //I ought to store this, it's used elsewhere - IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>(); + IPluginGame const *game = qApp->property("managed_game").value<IPluginGame *>(); return game->dataDirectory().absolutePath(); } @@ -30,12 +30,16 @@ std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); result.push_back(FLAG_FOREIGN); + if (m_PluginSelected) { + result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); + } + return result; } int ModInfoForeign::getHighlight() const { - return 0; + return m_PluginSelected ? ModInfo::HIGHLIGHT_PLUGIN : ModInfo::HIGHLIGHT_NONE; } QString ModInfoForeign::getDescription() const @@ -43,12 +47,22 @@ 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, +ModInfoForeign::ModInfoForeign(const QString &modName, + const QString &referenceFile, + const QStringList &archives, + ModInfo::EModType modType, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_ReferenceFile(referenceFile) - , m_Archives(archives) -{ + : ModInfoWithConflictInfo(directoryStructure), + m_ReferenceFile(referenceFile), m_Archives(archives) { m_CreationTime = QFileInfo(referenceFile).created(); - m_Name = "Unmanaged: " + QFileInfo(m_ReferenceFile).baseName(); + switch (modType) { + case ModInfo::EModType::MOD_DLC: + m_Name = tr("DLC: ") + modName; + break; + case ModInfo::EModType::MOD_CC: + m_Name = tr("Creation Club: ") + modName; + break; + default: + m_Name = tr("Unmanaged: ") + modName; + } } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index b35c099b..d60064f0 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -51,9 +51,9 @@ public: virtual void addInstalledFile(int, int) {} protected: - - ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure); - + ModInfoForeign(const QString &modName, const QString &referenceFile, + const QStringList &archives, ModInfo::EModType modType, + MOShared::DirectoryEntry **directoryStructure); private: QString m_Name; diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index 4380a255..360212c0 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -1,6 +1,7 @@ #include "modinfooverwrite.h" #include "appconfig.h" +#include "settings.h" #include <QApplication> #include <QDirIterator> @@ -21,19 +22,25 @@ bool ModInfoOverwrite::isEmpty() const QString ModInfoOverwrite::absolutePath() const { - return QDir::fromNativeSeparators(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath())); + return Settings::instance().getOverwriteDirectory(); } std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const { std::vector<ModInfo::EFlag> result; result.push_back(FLAG_OVERWRITE); + if (m_PluginSelected) + result.push_back(FLAG_PLUGIN_SELECTED); return result; } int ModInfoOverwrite::getHighlight() const { - return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; + int highlight = (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; + auto flags = getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) + highlight |= HIGHLIGHT_PLUGIN; + return highlight; } QString ModInfoOverwrite::getDescription() const @@ -46,7 +53,7 @@ QStringList ModInfoOverwrite::archives() const { QStringList result; QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { result.append(this->absolutePath() + "/" + archive); } return result; diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index b1f73a37..5486bb2c 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -420,6 +420,9 @@ std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const if (m_Notes.length() != 0) { result.push_back(ModInfo::FLAG_NOTES); } + if (m_PluginSelected) { + result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); + } return result; } @@ -430,28 +433,36 @@ std::vector<ModInfo::EContent> ModInfoRegular::getContents() const if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { m_CachedContent.clear(); QDir dir(absolutePath()); - if (dir.entryList(QStringList() << "*.esp" << "*.esm").size() > 0) { + if (dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl").size() > 0) { m_CachedContent.push_back(CONTENT_PLUGIN); } - if (dir.entryList(QStringList() << "*.bsa").size() > 0) { + if (dir.entryList(QStringList() << "*.bsa" << "*.ba2").size() > 0) { m_CachedContent.push_back(CONTENT_BSA); } - ScriptExtender *extender = qApp->property("managed_game").value<IPluginGame*>()->feature<ScriptExtender>(); + ScriptExtender *extender = qApp->property("managed_game") + .value<IPluginGame *>() + ->feature<ScriptExtender>(); if (extender != nullptr) { - QString sePluginPath = extender->name() + "/plugins"; - if (dir.exists(sePluginPath)) m_CachedContent.push_back(CONTENT_SKSE); + QString sePluginPath = extender->PluginPath(); + if (dir.exists(sePluginPath)) + m_CachedContent.push_back(CONTENT_SKSE); } - if (dir.exists("textures")) m_CachedContent.push_back(CONTENT_TEXTURE); - if (dir.exists("meshes")) m_CachedContent.push_back(CONTENT_MESH); - if (dir.exists("interface") - || dir.exists("menus")) m_CachedContent.push_back(CONTENT_INTERFACE); - if (dir.exists("music")) m_CachedContent.push_back(CONTENT_MUSIC); - if (dir.exists("sound")) m_CachedContent.push_back(CONTENT_SOUND); - if (dir.exists("scripts")) m_CachedContent.push_back(CONTENT_SCRIPT); - if (dir.exists("strings")) m_CachedContent.push_back(CONTENT_STRING); - if (dir.exists("SkyProc Patchers")) m_CachedContent.push_back(CONTENT_SKYPROC); + if (dir.exists("textures")) + m_CachedContent.push_back(CONTENT_TEXTURE); + if (dir.exists("meshes")) + m_CachedContent.push_back(CONTENT_MESH); + if (dir.exists("interface") || dir.exists("menus")) + m_CachedContent.push_back(CONTENT_INTERFACE); + if (dir.exists("music") || dir.exists("sound")) + m_CachedContent.push_back(CONTENT_SOUND); + if (dir.exists("scripts")) + m_CachedContent.push_back(CONTENT_SCRIPT); + if (dir.exists("SkyProc Patchers")) + m_CachedContent.push_back(CONTENT_SKYPROC); + if (dir.exists("MCM")) + m_CachedContent.push_back(CONTENT_MCM); m_LastContentCheck = QTime::currentTime(); } @@ -463,14 +474,19 @@ std::vector<ModInfo::EContent> ModInfoRegular::getContents() const int ModInfoRegular::getHighlight() const { - return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; + if (!isValid()) + return HIGHLIGHT_INVALID; + auto flags = getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) + return HIGHLIGHT_PLUGIN; + return HIGHLIGHT_NONE; } QString ModInfoRegular::getDescription() const { if (!isValid()) { - return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); + return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory").arg(name()); } else { const std::set<int> &categories = getCategories(); std::wostringstream categoryString; @@ -535,7 +551,7 @@ QStringList ModInfoRegular::archives() const { QStringList result; QDir dir(this->absolutePath()); - for (const QString &archive : dir.entryList(QStringList("*.bsa"))) { + for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { result.append(this->absolutePath() + "/" + archive); } return result; diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index fbf2345f..b8ece783 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -57,8 +57,8 @@ void ModInfoWithConflictInfo::doConflictCheck() const std::vector<FileEntry::Ptr> files = origin.getFiles(); // for all files in this origin for (FileEntry::Ptr file : files) { - const std::vector<int> &alternatives = file->getAlternatives(); - if ((alternatives.size() == 0) || (alternatives[0] == dataID)) { + const std::vector<std::pair<int, std::wstring>> &alternatives = file->getAlternatives(); + if ((alternatives.size() == 0) || (alternatives.begin()->first == dataID)) { // no alternatives -> no conflict providesAnything = true; } else { @@ -71,9 +71,9 @@ void ModInfoWithConflictInfo::doConflictCheck() const } // for all non-providing alternative origins - for (int altId : alternatives) { - if ((altId != dataID) && (altId != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altId); + for (auto altInfo : alternatives) { + if ((altInfo.first != dataID) && (altInfo.first != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.first); unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); if (origin.getPriority() > altOrigin.getPriority()) { m_OverwriteList.insert(altIndex); diff --git a/src/modlist.cpp b/src/modlist.cpp index 9d7f32c8..4002a424 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "qtgroupingproxy.h"
#include "viewmarkingscrollbar.h"
#include "modlistsortproxy.h"
+#include "settings.h"
#include <appconfig.h>
#include <utility.h>
#include <report.h>
@@ -60,17 +61,16 @@ ModList::ModList(QObject *parent) , m_FontMetrics(QFont())
, m_DropOnItems(false)
{
- m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game plugins (esp/esm)"));
+ m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)"));
m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface"));
m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes"));
- m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("BSA"));
- m_ContentIcons[ModInfo::CONTENT_MUSIC] = std::make_tuple(":/MO/gui/content/music", tr("Music"));
+ m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive"));
m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)"));
m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin"));
m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher"));
- m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound"));
- m_ContentIcons[ModInfo::CONTENT_STRING] = std::make_tuple(":/MO/gui/content/string", tr("Strings"));
+ m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music"));
m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures"));
+ m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration"));
m_LastCheck.start();
}
@@ -339,6 +339,7 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const 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 (highlight & ModInfo::HIGHLIGHT_PLUGIN) return QBrush(Qt::darkBlue);
} else if (column == COL_VERSION) {
if (!modInfo->getNewestVersion().isValid()) {
return QVariant();
@@ -351,7 +352,9 @@ 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()) {
+ if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) {
+ return QColor(0, 0, 255, 32);
+ } else if (m_Overwrite.find(modIndex) != m_Overwrite.end()) {
return QColor(0, 255, 0, 32);
} else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) {
return QColor(255, 0, 0, 32);
@@ -421,15 +424,18 @@ bool ModList::renameMod(int index, const QString &newName) 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);
+ if (newName != oldName) {
+ // before we rename, ensure there is no scheduled asynchronous to rewrite
+ m_Profile->cancelModlistWrite();
+
+
+ if (modInfo->setName(nameFixed))
+ // Notice there is a good chance that setName() updated the modinfo indexes
+ // the modRenamed() call will refresh the indexes in the current profile
+ // and update the modlists in all profiles
+ emit modRenamed(oldName, nameFixed);
}
// invalidate the currently displayed state of this list
@@ -686,6 +692,37 @@ int ModList::timeElapsedSinceLastChecked() const return m_LastCheck.elapsed();
}
+void ModList::highlightMods(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry)
+{
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ ModInfo::getByIndex(i)->setPluginSelected(false);
+ }
+ for (QModelIndex idx : selected.indexes()) {
+ QString modName = idx.data().toString();
+
+ const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString());
+ if (fileEntry.get() != nullptr) {
+ QString fileName;
+ bool archive = false;
+ std::vector<std::pair<int, std::wstring>> origins;
+ {
+ std::vector<std::pair<int, std::wstring>> alternatives = fileEntry->getAlternatives();
+ origins.insert(origins.end(), std::pair<int, std::wstring>(fileEntry->getOrigin(archive), fileEntry->getArchive()));
+ }
+ for (auto originInfo : origins) {
+ MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first);
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) {
+ ModInfo::getByIndex(i)->setPluginSelected(true);
+ break;
+ }
+ }
+ }
+ }
+ }
+ notifyChange(0, rowCount() - 1);
+}
+
IModList::ModStates ModList::state(unsigned int modIndex) const
{
IModList::ModStates result;
@@ -802,7 +839,7 @@ bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &pa ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
QDir modDirectory(modInfo->absolutePath());
- QDir gameDirectory(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::overwritePath()));
+ QDir gameDirectory(Settings::instance().getOverwriteDirectory());
unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool {
std::vector<ModInfo::EFlag> flags = mod->getFlags();
@@ -906,12 +943,12 @@ void ModList::removeRowForce(int row, const QModelIndex &parent) m_Profile->setModEnabled(row, false);
- m_Profile->modlistWriter().cancel();
+ m_Profile->cancelModlistWrite();
beginRemoveRows(parent, row, row);
ModInfo::removeMod(row);
endRemoveRows();
m_Profile->refreshModStatus(); // removes the mod from the status list
- m_Profile->modlistWriter().write(); // this ensures the modified list gets written back before new mods can be installed
+ m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed
if (wasEnabled) {
emit removeOrigin(modInfo->name());
@@ -1028,17 +1065,18 @@ QString ModList::getColumnToolTip(int column) case COL_FLAGS: return tr("Emblemes to highlight things that might require attention.");
case COL_CONTENT: return tr("Depicts the content of the mod:<br>"
"<table cellspacing=7>"
- "<tr><td><img src=\":/MO/gui/content/plugin\" width=32/></td><td>Game plugins (esp/esm)</tr>"
- "<tr><td><img src=\":/MO/gui/content/interface\" width=32/></td><td>Interface</tr>"
- "<tr><td><img src=\":/MO/gui/content/mesh\" width=32/></td><td>Meshes</tr>"
- "<tr><td><img src=\":/MO/gui/content/bsa\" width=32/></td><td>BSA</tr>"
- "<tr><td><img src=\":/MO/gui/content/texture\" width=32/></td><td>Textures</tr>"
- "<tr><td><img src=\":/MO/gui/content/sound\" width=32/></td><td>Sounds</tr>"
- "<tr><td><img src=\":/MO/gui/content/music\" width=32/></td><td>Music</tr>"
- "<tr><td><img src=\":/MO/gui/content/string\" width=32/></td><td>Strings</tr>"
- "<tr><td><img src=\":/MO/gui/content/script\" width=32/></td><td>Scripts (Papyrus)</tr>"
- "<tr><td><img src=\":/MO/gui/content/skse\" width=32/></td><td>Script Extender plugins</tr>"
- "<tr><td><img src=\":/MO/gui/content/skyproc\" width=32/></td><td>SkyProc Patcher</tr>"
+ "<tr><td><img src=\":/MO/gui/content/plugin\" width=32/></td><td>Game plugins (esp/esm/esl)</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/interface\" width=32/></td><td>Interface</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/mesh\" width=32/></td><td>Meshes</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/bsa\" width=32/></td><td>BSA</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/texture\" width=32/></td><td>Textures</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/sound\" width=32/></td><td>Sounds</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/music\" width=32/></td><td>Music</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/string\" width=32/></td><td>Strings</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/script\" width=32/></td><td>Scripts (Papyrus)</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/skse\" width=32/></td><td>Script Extender plugins</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/skyproc\" width=32/></td><td>SkyProc Patcher</td></tr>"
+ "<tr><td><img src=\":/MO/gui/content/menu\" width=32/></td><td>Mod Configuration Menu</td></tr>"
"</table>");
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 358fd583..bd715107 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "profile.h"
#include <imodlist.h>
+#include <directoryentry.h>
#include <QFile>
#include <QListWidget>
@@ -113,6 +114,8 @@ public: int timeElapsedSinceLastChecked() const;
+ void highlightMods(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry);
+
public:
/// \copydoc MOBase::IModList::displayName
diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp new file mode 100644 index 00000000..c8c2ef6f --- /dev/null +++ b/src/moshortcut.cpp @@ -0,0 +1,39 @@ +/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + + +#include "moshortcut.h" + + +MOShortcut::MOShortcut(const QString& link) + : m_valid(link.startsWith("moshortcut://")) + , m_hasInstance(false) +{ + if (m_valid) { + int start = (int)strlen("moshortcut://"); + int sep = link.indexOf(':', start); + if (sep >= 0) { + m_hasInstance = true; + m_instance = link.mid(start, sep - start); + m_executable = link.mid(sep + 1); + } + else + m_executable = link.mid(start); + } +} diff --git a/src/safewritefile.h b/src/moshortcut.h index 0af6bf98..7b7574b9 100644 --- a/src/safewritefile.h +++ b/src/moshortcut.h @@ -1,57 +1,46 @@ -/*
-Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
-*/
-
-
-#ifndef SAFEWRITEFILE_H
-#define SAFEWRITEFILE_H
-
-
-#include <utility.h>
-#include <QTemporaryFile>
-#include <QString>
-
-/**
- * @brief a wrapper for QFile that ensures the file is only actually (over-)written if writing was successful
- */
-class SafeWriteFile {
-public:
- SafeWriteFile(const QString &fileName);
-
- QFile *operator->();
-
- void commit();
-
- bool commitIfDifferent(QByteArray &hash);
-
-private:
-
- QByteArray hash();
-
-private:
- QString m_FileName;
- QTemporaryFile m_TempFile;
-};
-
-
-#endif // SAFEWRITEFILE_H
-
-
-
-
-
+/* +Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. +*/ + + +#pragma once + + +#include <QString> + + +class MOShortcut { + +public: + MOShortcut(const QString& link); + + /// true iff intialized using a valid moshortcut link + operator bool() const { return m_valid; } + + bool hasInstance() const { return m_hasInstance; } + + const QString& instance() const { return m_instance; } + + const QString& executable() const { return m_executable; } + +private: + QString m_instance; + QString m_executable; + bool m_valid; + bool m_hasInstance; +}; diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index d0eb58a6..eba02a6f 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -27,6 +27,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <util.h>
#include <QApplication>
+#include <QNetworkCookieJar>
#include <regex>
@@ -86,7 +87,7 @@ void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resul QVariantList resultList = resultData.toList();
- foreach(QVariant file, resultList) {
+ for (const QVariant &file : resultList) {
ModRepositoryFileInfo temp;
QVariantMap fileInfo = file.toMap();
temp.uri = fileInfo["uri"].toString();
@@ -193,7 +194,7 @@ void NexusInterface::loginCompleted() void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query)
{
//Look for something along the lines of modulename-Vn-m + any old rubbish.
- static std::regex exp("^([a-zA-Z0-9_'\"\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*");
+ static std::regex exp(R"exp(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\.(zip|rar|7z))exp");
static std::regex simpleexp("^([a-zA-Z0-9_]+)");
QByteArray fileNameUTF8 = fileName.toUtf8();
@@ -206,11 +207,12 @@ void NexusInterface::interpretNexusFileName(const QString &fileName, QString &mo 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(), "-_ ");
+ size_t 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, " *");
+ r2Highlight.insert(result.position(2) + result.length(2), "* ")
+ .insert(result.position(2) + static_cast<int>(offset), " *");
QString r3Highlight(fileName);
r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *");
@@ -250,12 +252,12 @@ bool NexusInterface::isURLGameRelated(const QUrl &url) const QString NexusInterface::getGameURL() const
{
- return "http://www.nexusmods.com/" + m_Game->gameShortName().toLower();
+ return "http://www.nexusmods.com/" + m_Game->gameNexusName().toLower();
}
QString NexusInterface::getOldModsURL() const
{
- return "http://" + m_Game->gameShortName().toLower() + ".nexusmods.com/mods";
+ return "http://" + m_Game->gameNexusName().toLower() + ".nexusmods.com/mods";
}
@@ -264,14 +266,14 @@ QString NexusInterface::getModURL(int modID) const return QString("%1/mods/%2").arg(getGameURL()).arg(modID);
}
-bool NexusInterface::isModURL(int modID, QString const &url) const
+bool NexusInterface::isModURL(int modID, const QString &url) const
{
- if (url == getModURL(modID)) {
+ if (QUrl(url) == QUrl(getModURL(modID))) {
return true;
}
//Try the alternate (old style) mod name
QString alt = QString("%1/%2").arg(getOldModsURL()).arg(modID);
- return alt == url;
+ return QUrl(alt) == QUrl(url);
}
int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData,
@@ -338,10 +340,6 @@ int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData 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;
}
@@ -412,6 +410,12 @@ void NexusInterface::cleanup() m_DiskCache = nullptr;
}
+void NexusInterface::clearCache()
+{
+ m_DiskCache->clear();
+ m_AccessManager->clearCookies();
+}
+
void NexusInterface::nextRequest()
{
if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS)
@@ -564,8 +568,10 @@ void NexusInterface::requestError(QNetworkReply::NetworkError) return;
}
- qCritical("request (%s) error: %s",
- qPrintable(reply->url().toString()), qPrintable(reply->errorString()));
+ qCritical("request (%s) error: %s (%d)",
+ qPrintable(reply->url().toString()),
+ qPrintable(reply->errorString()),
+ reply->error());
}
@@ -593,7 +599,7 @@ void NexusInterface::managedGameChanged(IPluginGame const *game) namespace {
QString get_management_url(MOBase::IPluginGame const *game)
{
- return "http://nmm.nexusmods.com/" + game->gameShortName().toLower();
+ return "http://nmm.nexusmods.com/" + game->gameNexusName().toLower();
}
}
diff --git a/src/nexusinterface.h b/src/nexusinterface.h index c9a81134..9e7f3642 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -143,6 +143,11 @@ public: void cleanup();
/**
+ * @brief clear webcache and cookies associated with this access manager
+ */
+ void clearCache();
+
+ /**
* @brief request description for a mod
*
* @param modID id of the mod caller is interested in (assumed to be for the current game)
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 616b3806..17c50e35 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -59,6 +59,11 @@ NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) m_LoginTimeout.setInterval(30000);
setCookieJar(new PersistentCookieJar(
QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat")));
+
+ if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) {
+ // why is this necessary all of a sudden?
+ setNetworkAccessible(QNetworkAccessManager::Accessible);
+ }
}
NXMAccessManager::~NXMAccessManager()
@@ -105,6 +110,15 @@ void NXMAccessManager::showCookies() const }
}
+void NXMAccessManager::clearCookies()
+{
+ PersistentCookieJar *jar = qobject_cast<PersistentCookieJar*>(cookieJar());
+ if (jar != nullptr) {
+ jar->clear();
+ } else {
+ qWarning("failed to clear cookies, invalid cookie jar");
+ }
+}
void NXMAccessManager::startLoginCheck()
{
@@ -201,7 +215,6 @@ void NXMAccessManager::login(const QString &username, const QString &password) emit loginSuccessful(false);
return;
}
-
m_Username = username;
m_Password = password;
pageLogin();
@@ -223,7 +236,6 @@ QString NXMAccessManager::userAgent(const QString &subModule) const void NXMAccessManager::pageLogin()
{
qDebug("logging %s in on Nexus", qPrintable(m_Username));
-
QString requestString = (Nexus_Management_URL + "/Sessions/?Login&uri=%1")
.arg(QString(QUrl::toPercentEncoding(Nexus_Management_URL)));
@@ -233,8 +245,8 @@ void NXMAccessManager::pageLogin() QByteArray postDataQuery;
QUrlQuery postData;
postData.addQueryItem("username", m_Username);
- postData.addQueryItem("password", m_Password);
- postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8();
+ postData.addQueryItem("password", QUrl::toPercentEncoding(m_Password));
+ postDataQuery = postData.query(QUrl::EncodeReserved).toUtf8();
request.setRawHeader("User-Agent", userAgent().toUtf8());
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 82bd2bd5..c58c4cc3 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -52,6 +52,8 @@ public: void showCookies() const;
+ void clearCookies();
+
QString userAgent(const QString &subModule = QString()) const;
void startLoginCheck();
diff --git a/src/organizer_en_US.ts b/src/organizer_en_US.ts deleted file mode 100644 index 075132c2..00000000 --- a/src/organizer_en_US.ts +++ /dev/null @@ -1,5428 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE TS> -<TS version="2.0" language="en_US"> -<context> - <name>AboutDialog</name> - <message> - <location filename="aboutdialog.ui" line="14"/> - <location filename="aboutdialog.ui" line="53"/> - <source>About</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="66"/> - <source>Revision:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="104"/> - <source>Used Software</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="117"/> - <source>Credits</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="123"/> - <source>Translators</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="189"/> - <source>Others</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.ui" line="266"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="aboutdialog.cpp" line="80"/> - <source>No license</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ActivateModsDialog</name> - <message> - <location filename="activatemodsdialog.ui" line="14"/> - <source>Activate Mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="activatemodsdialog.ui" line="20"/> - <source>This is a list of esps and esms that were active when the save game was created.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="activatemodsdialog.ui" line="23"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="activatemodsdialog.ui" line="37"/> - <source>Missing ESP</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="activatemodsdialog.ui" line="42"/> - <source>Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="activatemodsdialog.cpp" line="49"/> - <source>not found</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>BainComplexInstallerDialog</name> - <message> - <location filename="baincomplexinstallerdialog.ui" line="14"/> - <source>BAIN Package Installer</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="22"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="34"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="44"/> - <source>Components of this package.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="47"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="57"/> - <location filename="baincomplexinstallerdialog.ui" line="60"/> - <source>The package.txt is often part of BAIN packages and contains details about the options available.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="63"/> - <source>Package.txt</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="83"/> - <location filename="baincomplexinstallerdialog.ui" line="86"/> - <source>Opens a Dialog that allows custom modifications.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="89"/> - <source>Manual</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="96"/> - <source>Ok</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="baincomplexinstallerdialog.ui" line="103"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>BrowserDialog</name> - <message> - <location filename="browserdialog.ui" line="14"/> - <source>Some Page</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="browserdialog.ui" line="281"/> - <source>Search</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="browserdialog.cpp" line="95"/> - <source>new</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="browserdialog.cpp" line="209"/> - <source>failed to start download</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>CategoriesDialog</name> - <message> - <location filename="categoriesdialog.ui" line="14"/> - <source>Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="66"/> - <source>ID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="69"/> - <source>Internal ID for the category.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="72"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="77"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="80"/> - <location filename="categoriesdialog.ui" line="83"/> - <source>Name of the Categorie used for display.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="88"/> - <source>Nexus IDs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="91"/> - <source>Comma-Separated list of Nexus IDs to be matched to the internal ID.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="94"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="105"/> - <source>Parent ID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.ui" line="108"/> - <source>If set, the category is defined as a sub-category of another one. Parent ID needs to be a valid category ID.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.cpp" line="239"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categoriesdialog.cpp" line="240"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>CredentialsDialog</name> - <message> - <location filename="credentialsdialog.ui" line="14"/> - <source>Login</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="credentialsdialog.ui" line="20"/> - <source>This feature may not work unless you're logged in with Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="credentialsdialog.ui" line="32"/> - <source>Username</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="credentialsdialog.ui" line="46"/> - <source>Password</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="credentialsdialog.ui" line="64"/> - <source>Remember</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="credentialsdialog.ui" line="75"/> - <source>Never ask again</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DirectoryRefresher</name> - <message> - <location filename="directoryrefresher.cpp" line="146"/> - <source>failed to read bsa: %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadList</name> - <message> - <location filename="downloadlist.cpp" line="64"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlist.cpp" line="65"/> - <source>Filetime</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlist.cpp" line="66"/> - <source>Done</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlist.cpp" line="82"/> - <source>Information missing, please select "Query Info" from the context menu to re-retrieve.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlist.cpp" line="89"/> - <source>pending download</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadListWidget</name> - <message> - <location filename="downloadlistwidget.ui" line="17"/> - <location filename="downloadlistwidget.ui" line="61"/> - <source>Placeholder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.ui" line="99"/> - <location filename="downloadlistwidget.cpp" line="150"/> - <location filename="downloadlistwidget.cpp" line="152"/> - <source>Done - Double Click to install</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="116"/> - <location filename="downloadlistwidget.cpp" line="118"/> - <source>Paused - Double Click to resume</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="136"/> - <location filename="downloadlistwidget.cpp" line="138"/> - <source>Installed - Double Click to re-install</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="143"/> - <location filename="downloadlistwidget.cpp" line="145"/> - <source>Uninstalled - Double Click to re-install</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadListWidgetCompact</name> - <message> - <location filename="downloadlistwidgetcompact.ui" line="17"/> - <location filename="downloadlistwidgetcompact.ui" line="56"/> - <source>Placeholder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.ui" line="122"/> - <source>Done</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadListWidgetCompactDelegate</name> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="92"/> - <source>< mod %1 file %2 ></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="97"/> - <source>Pending</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="120"/> - <source>Paused</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="123"/> - <source>Fetching Info 1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="125"/> - <source>Fetching Info 2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="130"/> - <source>Installed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="133"/> - <source>Uninstalled</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="136"/> - <source>Done</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="245"/> - <location filename="downloadlistwidgetcompact.cpp" line="254"/> - <location filename="downloadlistwidgetcompact.cpp" line="263"/> - <location filename="downloadlistwidgetcompact.cpp" line="272"/> - <source>Are you sure?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="246"/> - <source>This will remove all finished downloads from this list and from disk.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="255"/> - <source>This will remove all installed downloads from this list and from disk.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="264"/> - <source>This will permanently remove all finished downloads from this list (but NOT from disk).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="273"/> - <source>This will permanently remove all installed downloads from this list (but NOT from disk).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="302"/> - <source>Install</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="304"/> - <source>Query Info</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="306"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="308"/> - <source>Un-Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="310"/> - <source>Remove from View</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="313"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="314"/> - <source>Pause</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="316"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="317"/> - <source>Resume</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="322"/> - <source>Delete Installed...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="323"/> - <source>Delete All...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="326"/> - <source>Remove Installed...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidgetcompact.cpp" line="327"/> - <source>Remove All...</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadListWidgetDelegate</name> - <message> - <location filename="downloadlistwidget.cpp" line="93"/> - <source>< mod %1 file %2 ></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="96"/> - <source>Pending</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="123"/> - <source>Fetching Info 1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="126"/> - <source>Fetching Info 2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="258"/> - <location filename="downloadlistwidget.cpp" line="267"/> - <location filename="downloadlistwidget.cpp" line="276"/> - <location filename="downloadlistwidget.cpp" line="285"/> - <source>Are you sure?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="259"/> - <source>This will remove all finished downloads from this list and from disk.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="268"/> - <source>This will remove all installed downloads from this list and from disk.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="277"/> - <source>This will remove all finished downloads from this list (but NOT from disk).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="286"/> - <source>This will remove all installed downloads from this list (but NOT from disk).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="314"/> - <source>Install</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="316"/> - <source>Query Info</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="318"/> - <source>Delete</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="320"/> - <source>Un-Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="322"/> - <source>Remove from View</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="325"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="326"/> - <source>Pause</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="328"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="329"/> - <source>Resume</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="334"/> - <source>Delete Installed...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="335"/> - <source>Delete All...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="338"/> - <source>Remove Installed...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadlistwidget.cpp" line="339"/> - <source>Remove All...</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>DownloadManager</name> - <message> - <location filename="downloadmanager.cpp" line="142"/> - <source>failed to rename "%1" to "%2"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="321"/> - <source>Memory allocation error (in refreshing directory).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="369"/> - <source>Download again?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="369"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="417"/> - <source>failed to download %1: could not open output file: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="454"/> - <source>Wrong Game</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="454"/> - <source>The download link is for a mod for "%1" but this instance of MO has been set up for "%2".</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="471"/> - <location filename="downloadmanager.cpp" line="538"/> - <location filename="downloadmanager.cpp" line="726"/> - <location filename="downloadmanager.cpp" line="735"/> - <location filename="downloadmanager.cpp" line="756"/> - <location filename="downloadmanager.cpp" line="773"/> - <location filename="downloadmanager.cpp" line="782"/> - <location filename="downloadmanager.cpp" line="796"/> - <location filename="downloadmanager.cpp" line="806"/> - <location filename="downloadmanager.cpp" line="816"/> - <location filename="downloadmanager.cpp" line="826"/> - <location filename="downloadmanager.cpp" line="841"/> - <location filename="downloadmanager.cpp" line="849"/> - <location filename="downloadmanager.cpp" line="858"/> - <location filename="downloadmanager.cpp" line="868"/> - <location filename="downloadmanager.cpp" line="883"/> - <source>invalid index</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="489"/> - <source>failed to delete %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="495"/> - <source>failed to delete meta file for %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="571"/> - <location filename="downloadmanager.cpp" line="589"/> - <location filename="downloadmanager.cpp" line="602"/> - <location filename="downloadmanager.cpp" line="622"/> - <location filename="downloadmanager.cpp" line="633"/> - <location filename="downloadmanager.cpp" line="673"/> - <source>invalid index %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="640"/> - <source>No known download urls. Sorry, this download can't be resumed.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="695"/> - <source>Please enter the nexus mod id</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="695"/> - <source>Mod ID:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="744"/> - <source>Main</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="745"/> - <source>Update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="746"/> - <source>Optional</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="747"/> - <source>Old</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="748"/> - <source>Misc</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="749"/> - <source>Unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="999"/> - <source>Memory allocation error (in processing progress event).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1012"/> - <source>Memory allocation error (in processing downloaded data).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1141"/> - <source>Information updated</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1143"/> - <location filename="downloadmanager.cpp" line="1157"/> - <source>No matching file found on Nexus! Maybe this file is no longer available or it was renamed?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1145"/> - <source>No file on Nexus matches the selected file by name. Please manually choose the correct one.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1285"/> - <source>No download server available. Please try again later.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1328"/> - <source>Failed to request file info from nexus: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1356"/> - <source>Download failed. Server reported: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1358"/> - <source>Download failed: %1 (%2)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="downloadmanager.cpp" line="1457"/> - <source>failed to re-open %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>EditExecutablesDialog</name> - <message> - <location filename="editexecutablesdialog.ui" line="14"/> - <source>Modify Executables</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="20"/> - <source>List of configured executables</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="23"/> - <source>This is a list of your configured executables. Executables in grey are automatically recognised. Existing entries can be modified and new ones added.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="38"/> - <source>Title</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="45"/> - <location filename="editexecutablesdialog.ui" line="48"/> - <source>Name of the executable. This is only for display purposes.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="59"/> - <source>Binary</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="66"/> - <location filename="editexecutablesdialog.ui" line="69"/> - <source>Binary to run</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="76"/> - <source>Browse filesystem</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="79"/> - <source>Browse filesystem for the executable to run.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="82"/> - <location filename="editexecutablesdialog.ui" line="103"/> - <source>...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="93"/> - <source>Start in</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="114"/> - <source>Arguments</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="121"/> - <location filename="editexecutablesdialog.ui" line="124"/> - <source>Arguments to pass to the application</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="135"/> - <source>Allow the Steam AppID to be used for this executable to be changed.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="138"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="143"/> - <source>Overwrite Steam AppID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="153"/> - <source>Steam AppID to use for this executable that differs from the games AppID.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="156"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="169"/> - <location filename="editexecutablesdialog.ui" line="172"/> - <location filename="editexecutablesdialog.cpp" line="258"/> - <source>If checked, MO will be closed once the specified executable is run.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="175"/> - <source>Close MO when started</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="182"/> - <location filename="editexecutablesdialog.ui" line="185"/> - <source>Add an executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="188"/> - <location filename="editexecutablesdialog.cpp" line="196"/> - <source>Add</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="199"/> - <location filename="editexecutablesdialog.ui" line="202"/> - <source>Remove the selected executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="205"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.ui" line="233"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="125"/> - <source>Select a binary</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="125"/> - <source>Executable (%1)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="149"/> - <source>Java (32-bit) required</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="150"/> - <source>MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe from that installation as the binary.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="166"/> - <source>Select a directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="175"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="175"/> - <source>Really remove "%1" from executables?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="200"/> - <source>Modify</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="230"/> - <location filename="editexecutablesdialog.cpp" line="278"/> - <source>Save Changes?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="231"/> - <location filename="editexecutablesdialog.cpp" line="279"/> - <source>You made changes to the current executable, do you want to save them?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="editexecutablesdialog.cpp" line="255"/> - <source>MO must be kept running or this application will not work correctly.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>FindDialog</name> - <message> - <location filename="finddialog.ui" line="14"/> - <source>Find</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="finddialog.ui" line="24"/> - <source>Find what:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="finddialog.ui" line="31"/> - <location filename="finddialog.ui" line="34"/> - <source>Search term</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="finddialog.ui" line="47"/> - <location filename="finddialog.ui" line="50"/> - <source>Find next occurence from current file position.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="finddialog.ui" line="53"/> - <source>&Find Next</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="finddialog.ui" line="60"/> - <location filename="finddialog.ui" line="63"/> - <location filename="finddialog.ui" line="66"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>FomodInstallerDialog</name> - <message> - <location filename="fomodinstallerdialog.ui" line="14"/> - <source>FOMOD Installation Dialog</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="22"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="46"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="60"/> - <source>Version</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="74"/> - <source>Website</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="81"/> - <source><a href="#">Link</a></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="160"/> - <source>Manual</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="170"/> - <source>Back</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="177"/> - <source>Next</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="fomodinstallerdialog.ui" line="184"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>InstallDialog</name> - <message> - <location filename="installdialog.ui" line="20"/> - <source>Install Mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="32"/> - <source>New Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="46"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="53"/> - <source>Pick a name for the mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="56"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="65"/> - <source>Content</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="75"/> - <source>Content of the archive. You can change the directory structure by using drag&drop. Hint: Also try right clicking...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="78"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="121"/> - <source>Placeholder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="141"/> - <source>OK</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installdialog.ui" line="148"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>InstallationManager</name> - <message> - <location filename="installationmanager.cpp" line="76"/> - <source>archive.dll not loaded: "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="98"/> - <source>Password required</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="98"/> - <source>Password</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="164"/> - <location filename="installationmanager.cpp" line="248"/> - <location filename="installationmanager.cpp" line="530"/> - <source>Extracting files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="439"/> - <source>failed to create backup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="448"/> - <source>Mod Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="448"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="501"/> - <source>Invalid name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="502"/> - <source>The name you entered is invalid, please enter a different one.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="610"/> - <source>File format "%1" not supported</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="743"/> - <source>None of the available installer plugins were able to handle that archive</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="753"/> - <source>no error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="756"/> - <source>7z.dll not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="759"/> - <source>7z.dll isn't valid</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="762"/> - <source>archive not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="765"/> - <source>failed to open archive</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="768"/> - <source>unsupported archive type</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="771"/> - <source>internal library error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="774"/> - <source>archive invalid</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="778"/> - <source>unknown archive error</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>LockedDialog</name> - <message> - <location filename="lockeddialog.ui" line="14"/> - <source>Locked</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="lockeddialog.ui" line="20"/> - <source>This dialog should disappear automatically if the application/game is done. Click unlock if it didn't.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="lockeddialog.ui" line="23"/> - <source>MO is locked while the executable is running.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="lockeddialog.ui" line="54"/> - <source>Unlock</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>LogBuffer</name> - <message> - <location filename="logbuffer.cpp" line="83"/> - <source>failed to write log to %1: %2</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MOApplication</name> - <message> - <location filename="moapplication.cpp" line="115"/> - <source>an error occured: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="moapplication.cpp" line="120"/> - <source>an error occured</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MainWindow</name> - <message> - <location filename="mainwindow.ui" line="43"/> - <location filename="mainwindow.ui" line="482"/> - <source>Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="97"/> - <source>Click blank area to deselect</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="116"/> - <source>If checked, only mods that match all selected categories are displayed.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="119"/> - <source>And</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="129"/> - <source>If checked, all mods that match at least one of the selected categories are displayed.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="132"/> - <source>Or</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="171"/> - <source>Profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="181"/> - <source>Pick a module collection</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="184"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="215"/> - <source>Open list options...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="218"/> - <source>Refresh list. This is usually not necessary unless you modified data outside the program.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="238"/> - <location filename="mainwindow.ui" line="739"/> - <source>Restore Backup...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="252"/> - <location filename="mainwindow.ui" line="759"/> - <source>Create Backup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="368"/> - <source>List of available mods.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="371"/> - <source>This is a list of installed mods. Use the checkboxes to activate/deactivate mods and drag & drop mods to change their "installation" orders.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="456"/> - <source>Filter</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="477"/> - <source>No groups</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="487"/> - <source>Nexus IDs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="495"/> - <location filename="mainwindow.ui" line="876"/> - <location filename="mainwindow.ui" line="1220"/> - <source>Namefilter</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="530"/> - <source>Pick a program to run.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="533"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="581"/> - <source>Run program</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="584"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="594"/> - <source>Run</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="635"/> - <source>Create a shortcut in your start menu or on the desktop to the specified program</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="638"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="645"/> - <source>Shortcut</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="695"/> - <source>Plugins</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="715"/> - <source>Sort</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="821"/> - <source>List of available esp/esm files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="824"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="886"/> - <source>Archives</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="916"/> - <source><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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="919"/> - <source><html><head/><body><p>Have MO manage archives (<a href="#"><span style=" text-decoration: underline; color:#0000ff;">read more</span></a>)</p></body></html></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="934"/> - <source>List of available BS Archives. Archives not checked here are not managed by MO and ignore installation order.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="937"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="983"/> - <location filename="mainwindow.ui" line="1042"/> - <source>File</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="992"/> - <source>Data</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1010"/> - <source>refresh data-directory overview</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1013"/> - <source>Refresh the overview. This may take a moment.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1016"/> - <location filename="mainwindow.cpp" line="3833"/> - <location filename="mainwindow.cpp" line="4705"/> - <source>Refresh</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1032"/> - <source>This is an overview of your data directory as visible to the game (and tools). </source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1047"/> - <source>Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1057"/> - <location filename="mainwindow.ui" line="1060"/> - <source>Filter the above list so that only conflicts are displayed.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1063"/> - <source>Show only conflicts</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1071"/> - <source>Saves</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1095"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1115"/> - <source>Downloads</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1147"/> - <source>This is a list of mods you downloaded from Nexus. Double click one to install it.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1200"/> - <source>Show Hidden</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1262"/> - <source>Tool Bar</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1304"/> - <source>Install Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1307"/> - <source>Install &Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1310"/> - <source>Install a new mod from an archive</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1313"/> - <source>Ctrl+M</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1322"/> - <source>Profiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1325"/> - <source>&Profiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1328"/> - <source>Configure Profiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1331"/> - <source>Ctrl+P</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1340"/> - <source>Executables</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1343"/> - <source>&Executables</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1346"/> - <source>Configure the executables that can be started through Mod Organizer</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1349"/> - <source>Ctrl+E</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1358"/> - <location filename="mainwindow.ui" line="1364"/> - <source>Tools</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1361"/> - <source>&Tools</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1367"/> - <source>Ctrl+I</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1376"/> - <source>Settings</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1379"/> - <source>&Settings</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1382"/> - <source>Configure settings and workarounds</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1385"/> - <source>Ctrl+S</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1394"/> - <source>Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1397"/> - <source>Search nexus network for more mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1400"/> - <source>Ctrl+N</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1412"/> - <location filename="mainwindow.cpp" line="4646"/> - <source>Update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1415"/> - <source>Mod Organizer is up-to-date</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1427"/> - <location filename="mainwindow.cpp" line="573"/> - <source>No Problems</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1430"/> - <source>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</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1442"/> - <location filename="mainwindow.ui" line="1445"/> - <source>Help</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1448"/> - <source>Ctrl+H</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1457"/> - <source>Endorse MO</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1460"/> - <location filename="mainwindow.cpp" line="4728"/> - <source>Endorse Mod Organizer</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1465"/> - <source>Copy Log to Clipboard</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.ui" line="1468"/> - <source>Ctrl+C</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="255"/> - <source>Toolbar</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="256"/> - <source>Desktop</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="257"/> - <source>Start Menu</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="561"/> - <source>Problems</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="562"/> - <source>There are potential problems with your setup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="574"/> - <source>Everything seems to be in order</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="635"/> - <source>Help on UI</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="639"/> - <source>Documentation Wiki</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="643"/> - <source>Report Issue</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="647"/> - <source>Tutorials</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="686"/> - <source>About</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="687"/> - <source>About Qt</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="782"/> - <source>failed to save load order: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="793"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="794"/> - <source>Please enter a name for the new profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="802"/> - <source>failed to create profile: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="843"/> - <source>Show tutorial?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="844"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="875"/> - <source>Downloads in progress</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="876"/> - <source>There are still downloads in progress, do you really want to quit?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="930"/> - <source>failed to read savegame: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1053"/> - <source>Plugin "%1" failed: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1055"/> - <source>Plugin "%1" failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1098"/> - <source>Download?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1099"/> - <source>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?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1140"/> - <source>Browse Mod Page</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1225"/> - <source>failed to init plugin %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1263"/> - <source>Plugin error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1264"/> - <source>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)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1323"/> - <source>Failed to start "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1325"/> - <source>Waiting</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1325"/> - <source>Please press OK once you're logged into steam.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1337"/> - <source>Executable "%1" not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1351"/> - <source>Start Steam?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1352"/> - <source>Steam is required to be running already to correctly start the game. Should MO try to start steam now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1562"/> - <source>Also in: <br></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1573"/> - <source>No conflict</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1742"/> - <source><Edit...></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1839"/> - <source>Failed to refresh list of esps: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2008"/> - <source>This bsa is enabled in the ini file so it may be required!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2070"/> - <source>Activating Network Proxy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2184"/> - <location filename="mainwindow.cpp" line="2191"/> - <source>Failed to write settings</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2185"/> - <location filename="mainwindow.cpp" line="2192"/> - <source>An error occured trying to write back MO settings: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2188"/> - <source>File is write protected</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2189"/> - <source>Invalid file format (probably a bug)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2190"/> - <source>Unknown error %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2237"/> - <source>Some plugins could not be loaded</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2240"/> - <source>Too many esps and esms enabled</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2243"/> - <location filename="mainwindow.cpp" line="2264"/> - <source>Description missing</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2252"/> - <source>The following plugins could not be loaded. The reason may be missing dependencies (i.e. python) or an outdated version:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2260"/> - <source>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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2286"/> - <source>Choose Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2287"/> - <source>Mod Archive</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2310"/> - <location filename="mainwindow.cpp" line="4349"/> - <source>Installation successful</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2321"/> - <location filename="mainwindow.cpp" line="4361"/> - <source>Configure Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2322"/> - <location filename="mainwindow.cpp" line="4362"/> - <source>This mod contains ini tweaks. Do you want to configure them now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2329"/> - <location filename="mainwindow.cpp" line="4369"/> - <source>mod "%1" not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2332"/> - <location filename="mainwindow.cpp" line="4375"/> - <source>Installation cancelled</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2332"/> - <location filename="mainwindow.cpp" line="4375"/> - <source>The mod was not installed completely.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2541"/> - <source>Start Tutorial?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2542"/> - <source>You're about to start a tutorial. For technical reasons it's not possible to end the tutorial early. Continue?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2686"/> - <location filename="mainwindow.cpp" line="4274"/> - <source>Download started</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2747"/> - <source>failed to update mod list: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2791"/> - <source>failed to spawn notepad.exe: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2828"/> - <source>failed to open %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2903"/> - <source>failed to change origin name: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2933"/> - <source>failed to move "%1" from mod "%2" to "%3": %4</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2983"/> - <source><Checked></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2984"/> - <source><Unchecked></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2985"/> - <source><Update></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2986"/> - <source><Managed by MO></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2987"/> - <source><Managed outside MO></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2988"/> - <source><No category></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2989"/> - <source><Conflicted></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="2990"/> - <source><Not Endorsed></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3026"/> - <source>failed to rename mod: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3039"/> - <source>Overwrite?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3040"/> - <source>This will replace the existing mod "%1". Continue?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3043"/> - <source>failed to remove mod "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3047"/> - <location filename="mainwindow.cpp" line="4547"/> - <location filename="mainwindow.cpp" line="4571"/> - <source>failed to rename "%1" to "%2"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3072"/> - <source>Multiple esps activated, please check that they don't conflict.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3121"/> - <location filename="mainwindow.cpp" line="3746"/> - <location filename="mainwindow.cpp" line="3754"/> - <location filename="mainwindow.cpp" line="3981"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3122"/> - <source>Remove the following mods?<br><ul>%1</ul></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3133"/> - <source>failed to remove mod: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3168"/> - <location filename="mainwindow.cpp" line="3171"/> - <source>Failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3168"/> - <source>Installation file no longer exists</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3172"/> - <source>Mods installed with old versions of MO can't be reinstalled in this way.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3187"/> - <source>You need to be logged in with Nexus to resume a download</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3203"/> - <location filename="mainwindow.cpp" line="3230"/> - <source>You need to be logged in with Nexus to endorse</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3264"/> - <source>Failed to display overwrite dialog: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3382"/> - <source>Nexus ID for this Mod is unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3421"/> - <location filename="mainwindow.cpp" line="3861"/> - <source>Create Mod...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3422"/> - <source>This will move all files from overwrite into a new, regular mod. -Please enter a name:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3431"/> - <source>A mod with this name already exists</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3672"/> - <source>Continue?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3673"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3693"/> - <location filename="mainwindow.cpp" line="4617"/> - <source>Sorry</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3694"/> - <source>I don't know a versioning scheme where %1 is newer than %2.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3746"/> - <source>Really enable all visible mods?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3754"/> - <source>Really disable all visible mods?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3762"/> - <source>Choose what to export</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3764"/> - <source>Everything</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3764"/> - <source>All installed mods are included in the list</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3765"/> - <source>Active Mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3765"/> - <source>Only active (checked) mods from your current profile are included</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3766"/> - <source>Visible</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3766"/> - <source>All mods visible in the mod list are included</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3809"/> - <source>export failed: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3826"/> - <source>Install Mod...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3828"/> - <source>Enable all visible</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3829"/> - <source>Disable all visible</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3831"/> - <source>Check all for update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3835"/> - <source>Export to csv...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3854"/> - <source>All Mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3860"/> - <source>Sync to Mods...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3864"/> - <source>Restore Backup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3865"/> - <source>Remove Backup...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3869"/> - <source>Add/Remove Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3874"/> - <source>Replace Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3879"/> - <source>Primary Category</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3886"/> - <source>Change versioning scheme</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3890"/> - <source>Un-ignore update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3892"/> - <source>Ignore update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3897"/> - <source>Rename Mod...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3898"/> - <source>Remove Mod...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3899"/> - <source>Reinstall Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3902"/> - <source>Un-Endorse</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3905"/> - <location filename="mainwindow.cpp" line="3909"/> - <source>Endorse</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3906"/> - <source>Won't endorse</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3912"/> - <source>Endorsement state unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3919"/> - <source>Ignore missing data</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3922"/> - <source>Visit on Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3923"/> - <source>Open in explorer</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3927"/> - <source>Information...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3934"/> - <location filename="mainwindow.cpp" line="5192"/> - <source>Exception: </source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3936"/> - <location filename="mainwindow.cpp" line="5194"/> - <source>Unknown exception</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3955"/> - <source><All></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="3957"/> - <source><Multiple></source> - <translation type="unfinished"></translation> - </message> - <message numerus="yes"> - <location filename="mainwindow.cpp" line="3981"/> - <source>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.</source> - <translation> - <numerusform>Are you sure you want to remove the following save?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin.</numerusform> - <numerusform>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.</numerusform> - </translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4093"/> - <source>Fix Mods...</source> - <translation type="unfinished"></translation> - </message> - <message numerus="yes"> - <location filename="mainwindow.cpp" line="4095"/> - <source>Delete %n save(s)</source> - <oldsource>Delete save(s)</oldsource> - <translation> - <numerusform>Delete %n save</numerusform> - <numerusform>Delete %n saves</numerusform> - </translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4122"/> - <location filename="mainwindow.cpp" line="4155"/> - <source>failed to remove %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4139"/> - <location filename="mainwindow.cpp" line="4171"/> - <source>failed to create %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4195"/> - <source>Can't change download directory while downloads are in progress!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4277"/> - <source>Download failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4424"/> - <source>failed to write to file %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4430"/> - <source>%1 written</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4469"/> - <source>Select binary</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4469"/> - <source>Binary</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4495"/> - <source>Enter Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4496"/> - <source>Please enter a name for the executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4507"/> - <source>Not an executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4507"/> - <source>This is not a recognized executable.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4532"/> - <location filename="mainwindow.cpp" line="4557"/> - <source>Replace file?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4532"/> - <source>There already is a hidden version of this file. Replace it?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4535"/> - <location filename="mainwindow.cpp" line="4560"/> - <source>File operation failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4535"/> - <location filename="mainwindow.cpp" line="4560"/> - <source>Failed to remove "%1". Maybe you lack the required file permissions?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4557"/> - <source>There already is a visible version of this file. Replace it?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4590"/> - <source>file not found: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4603"/> - <source>failed to generate preview for %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4617"/> - <source>Sorry, can't preview anything. This function currently does not support extracting from bsas.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4648"/> - <source>Update available</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4685"/> - <source>Open/Execute</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4686"/> - <source>Add as Executable</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4690"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4696"/> - <source>Un-Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4698"/> - <source>Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4704"/> - <source>Write To File...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4729"/> - <source>Do you want to endorse Mod Organizer on %1 now?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4825"/> - <source>Thank you!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4825"/> - <source>Thank you for your endorsement!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4860"/> - <source>Request to Nexus failed: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4867"/> - <location filename="mainwindow.cpp" line="4885"/> - <source>login successful</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4894"/> - <source>login failed: %1. Trying to download anyway</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4900"/> - <source>login failed: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4910"/> - <source>login failed: %1. You need to log-in with Nexus to update MO.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4931"/> - <location filename="mainwindow.cpp" line="4982"/> - <source>failed to read %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4943"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4943"/> - <source>failed to extract %1 (errorcode %2)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4974"/> - <source>Extract BSA</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="4993"/> - <source>This archive contains invalid hashes. Some files may be broken.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5038"/> - <source>Extract...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5100"/> - <source>Edit Categories...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5101"/> - <source>Deselect filter</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5152"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5163"/> - <source>Enable all</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5164"/> - <source>Disable all</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5183"/> - <source>Unlock load order</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5186"/> - <source>Lock load order</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5319"/> - <source>depends on missing "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5323"/> - <source>incompatible with "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5343"/> - <source>No profile set</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5450"/> - <source>Please wait while LOOT is running</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5540"/> - <source>loot failed. Exit code was: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5546"/> - <source>failed to start loot</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5549"/> - <source>failed to run loot: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5552"/> - <source>Errors occured</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5601"/> - <source>Backup of load order created</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5611"/> - <source>Choose backup to restore</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5624"/> - <source>No Backups</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5624"/> - <source>There are no backups to restore</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5645"/> - <location filename="mainwindow.cpp" line="5666"/> - <source>Restore failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5646"/> - <location filename="mainwindow.cpp" line="5667"/> - <source>Failed to restore the backup. Errorcode: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="5657"/> - <source>Backup of modlist created</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MessageDialog</name> - <message> - <location filename="messagedialog.ui" line="150"/> - <location filename="messagedialog.ui" line="180"/> - <source>Placeholder</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfo</name> - <message> - <location filename="modinfo.cpp" line="106"/> - <location filename="modinfo.cpp" line="135"/> - <source>invalid index %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfoBackup</name> - <message> - <location filename="modinfo.cpp" line="930"/> - <source>This is the backup of a mod</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfoDialog</name> - <message> - <location filename="modinfodialog.ui" line="14"/> - <source>Mod Info</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="30"/> - <source>Textfiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="42"/> - <source>A list of text-files in the mod directory.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="45"/> - <source>A list of text-files in the mod directory like readmes. </source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="67"/> - <location filename="modinfodialog.ui" line="175"/> - <source>Save</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="77"/> - <source>INI-Files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="91"/> - <source>Ini Files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="104"/> - <source>This is a list of .ini files in the mod.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="107"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="114"/> - <source>Ini Tweaks</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="130"/> - <source>This is a list of ini tweaks (ini modifications that can be toggled).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="133"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="169"/> - <source>Save changes to the file.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="172"/> - <source>Save changes to the file. This overwrites the original. There is no automatic backup!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="185"/> - <source>Images</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="240"/> - <source>Images located in the mod.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="243"/> - <source>This lists all the images (.jpg and .png) in the mod directory, like screenshots and such. Click one to get a larger view.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="276"/> - <location filename="modinfodialog.ui" line="295"/> - <source>Optional ESPs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="282"/> - <source>List of esps and esms that can not be loaded by the game.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="285"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="310"/> - <source>Make the selected mod in the lower list unavailable.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="313"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="339"/> - <source>Move a file to the data directory.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="342"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="377"/> - <source>ESPs in the data directory and thus visible to the game.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="380"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="387"/> - <source>Available ESPs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="395"/> - <source>Conflicts</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="403"/> - <source>The following conflicted files are provided by this mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="453"/> - <location filename="modinfodialog.ui" line="503"/> - <source>File</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="458"/> - <source>Overwritten Mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="468"/> - <source>The following conflicted files are provided by other mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="508"/> - <source>Providing Mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="518"/> - <source>Non-Conflicted files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="538"/> - <source>Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="561"/> - <source>Primary Category</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="578"/> - <source>Nexus Info</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="586"/> - <source>Mod ID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="593"/> - <source>Mod ID for this mod on Nexus.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="596"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="620"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="627"/> - <source>Version</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="657"/> - <source>Refresh</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="660"/> - <source>Refresh all information from Nexus.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="674"/> - <source>Description</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="689"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="724"/> - <source>Endorse</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="738"/> - <source>Notes</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="748"/> - <source>Filetree</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="757"/> - <source>A directory view of this mod</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="760"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="787"/> - <source>Previous</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="794"/> - <source>Next</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.ui" line="814"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="169"/> - <source>&Delete</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="170"/> - <source>&Rename</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="171"/> - <source>&Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="172"/> - <source>&Unhide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="173"/> - <source>&Open</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="174"/> - <source>&New Folder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="420"/> - <location filename="modinfodialog.cpp" line="435"/> - <source>Save changes?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="420"/> - <location filename="modinfodialog.cpp" line="435"/> - <source>Save changes to "%1"?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="625"/> - <source>File Exists</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="625"/> - <source>A file with that name exists, please enter a new one</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="642"/> - <source>failed to move file</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="667"/> - <source>failed to create directory "optional"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="705"/> - <location filename="modinfodialog.cpp" line="1210"/> - <source>Info requested, please wait</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="759"/> - <source>Main</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="760"/> - <source>Update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="761"/> - <source>Optional</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="762"/> - <source>Old</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="763"/> - <source>Misc</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="764"/> - <source>Unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="775"/> - <source>Current Version: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="779"/> - <source>No update available</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="820"/> - <source>(description incomplete, please visit nexus)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="835"/> - <source><a href="%1">Visit on Nexus</a></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="914"/> - <source>Failed to delete %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="925"/> - <location filename="modinfodialog.cpp" line="930"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="925"/> - <source>Are sure you want to delete "%1"?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="930"/> - <source>Are sure you want to delete the selected files?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1004"/> - <location filename="modinfodialog.cpp" line="1010"/> - <source>New Folder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1016"/> - <source>Failed to create "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1120"/> - <location filename="modinfodialog.cpp" line="1144"/> - <source>Replace file?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1120"/> - <source>There already is a hidden version of this file. Replace it?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1123"/> - <location filename="modinfodialog.cpp" line="1147"/> - <source>File operation failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1123"/> - <location filename="modinfodialog.cpp" line="1147"/> - <source>Failed to remove "%1". Maybe you lack the required file permissions?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1134"/> - <location filename="modinfodialog.cpp" line="1157"/> - <source>failed to rename %1 to %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1144"/> - <source>There already is a visible version of this file. Replace it?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1190"/> - <source>Un-Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1192"/> - <source>Hide</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1233"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1233"/> - <source>Please enter a name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1237"/> - <location filename="modinfodialog.cpp" line="1240"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1237"/> - <source>Invalid name. Must be a valid file name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1240"/> - <source>A tweak by that name exists</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfodialog.cpp" line="1254"/> - <source>Create Tweak</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfoForeign</name> - <message> - <location filename="modinfo.cpp" line="1021"/> - <source>This pseudo mod represents content managed outside MO. It isn't modified by MO.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfoOverwrite</name> - <message> - <location filename="modinfo.cpp" line="975"/> - <source>This pseudo mod contains files from the virtual data tree that got modified (i.e. by the construction kit)</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModInfoRegular</name> - <message> - <location filename="modinfo.cpp" line="567"/> - <location filename="modinfo.cpp" line="570"/> - <source>failed to write %1/meta.ini: error %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfo.cpp" line="843"/> - <source>%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modinfo.cpp" line="847"/> - <source>Categories: <br></source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ModList</name> - <message> - <location filename="modlist.cpp" line="55"/> - <source>Game plugins (esp/esm)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="56"/> - <source>Interface</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="57"/> - <source>Meshes</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="58"/> - <source>Music</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="59"/> - <source>Scripts (Papyrus)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="60"/> - <source>Script Extender Plugin</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="61"/> - <source>SkyProc Patcher</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="62"/> - <source>Sound</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="63"/> - <source>Strings</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="64"/> - <source>Textures</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="117"/> - <source>This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="126"/> - <source>Backup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="127"/> - <source>No valid game data</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="128"/> - <source>Not endorsed yet</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="130"/> - <source>Overwrites files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="131"/> - <source>Overwritten files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="132"/> - <source>Overwrites & Overwritten</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="133"/> - <source>Redundant</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="208"/> - <source>Non-MO</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="238"/> - <source>invalid</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="364"/> - <source>installed version: "%1", newest version: "%2"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="366"/> - <source>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".</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="374"/> - <source>Categories: <br></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="403"/> - <source>Invalid name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="814"/> - <source>drag&drop failed: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="877"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="877"/> - <source>Are you sure you want to remove "%1"?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="934"/> - <source>Flags</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="935"/> - <source>Content</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="936"/> - <source>Mod Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="937"/> - <source>Version</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="938"/> - <source>Priority</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="939"/> - <source>Category</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="940"/> - <source>Nexus ID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="941"/> - <source>Installation</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="942"/> - <location filename="modlist.cpp" line="970"/> - <source>unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="950"/> - <source>Name of your mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="951"/> - <source>Version of the mod (if available)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="952"/> - <source>Installation priority of your mod. The higher, the more "important" it is and thus overwrites files from mods with lower priority.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="954"/> - <source>Category of the mod.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="955"/> - <source>Id of the mod as used on Nexus.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="956"/> - <source>Emblemes to highlight things that might require attention.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="957"/> - <source>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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="modlist.cpp" line="969"/> - <source>Time this mod was installed</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MotDDialog</name> - <message> - <location filename="motddialog.ui" line="14"/> - <source>Message of the Day</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="motddialog.ui" line="42"/> - <source>OK</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>MyFileSystemModel</name> - <message> - <location filename="overwriteinfodialog.cpp" line="47"/> - <source>Overwrites</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="59"/> - <source>not implemented</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>NXMAccessManager</name> - <message> - <location filename="nxmaccessmanager.cpp" line="144"/> - <source>Logging into Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="159"/> - <source>timeout</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="178"/> - <source>Unknown error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nxmaccessmanager.cpp" line="204"/> - <source>Please check your password</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>NexusInterface</name> - <message> - <location filename="nexusinterface.cpp" line="216"/> - <source>Failed to guess mod id for "%1", please pick the correct one</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nexusinterface.cpp" line="482"/> - <source>empty response</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="nexusinterface.cpp" line="511"/> - <source>invalid response</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>OverwriteInfoDialog</name> - <message> - <location filename="overwriteinfodialog.ui" line="14"/> - <source>Overwrite</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.ui" line="39"/> - <source>You can use drag&drop to move files and directories to regular mods.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="88"/> - <source>&Delete</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="89"/> - <source>&Rename</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="90"/> - <source>&Open</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="91"/> - <source>&New Folder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="109"/> - <source>%1 not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="144"/> - <source>Failed to delete "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="155"/> - <location filename="overwriteinfodialog.cpp" line="160"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="155"/> - <source>Are sure you want to delete "%1"?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="160"/> - <source>Are sure you want to delete the selected files?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="210"/> - <location filename="overwriteinfodialog.cpp" line="216"/> - <source>New Folder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="overwriteinfodialog.cpp" line="222"/> - <source>Failed to create "%1"</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>PluginList</name> - <message> - <location filename="pluginlist.cpp" line="103"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="104"/> - <source>Priority</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="105"/> - <source>Mod Index</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="106"/> - <source>Flags</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="107"/> - <location filename="pluginlist.cpp" line="119"/> - <source>unknown</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="115"/> - <source>Name of your mods</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="116"/> - <source>Load priority of your mod. The higher, the more "important" it is and thus overwrites data from plugins with lower priority.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="118"/> - <source>The modindex determins the formids of objects originating from this mods.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="159"/> - <source>failed to update esp info for file %1 (source id: %2), error: %3</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="227"/> - <source>esp not found: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="234"/> - <location filename="pluginlist.cpp" line="246"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="234"/> - <source>Really enable all plugins?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="246"/> - <source>Really disable all plugins?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="397"/> - <source>The file containing locked plugin indices is broken</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="438"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="804"/> - <source>This plugin can't be disabled (enforced by the game)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="806"/> - <source><b>Origin</b>: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="808"/> - <source>Author</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="811"/> - <source>Description</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="814"/> - <source>Missing Masters</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="821"/> - <source>Enabled Masters</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="972"/> - <source>failed to restore load order for %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>PreviewDialog</name> - <message> - <location filename="previewdialog.ui" line="14"/> - <source>Preview</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="previewdialog.ui" line="78"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ProblemsDialog</name> - <message> - <location filename="problemsdialog.ui" line="14"/> - <source>Problems</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="problemsdialog.ui" line="49"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="problemsdialog.ui" line="75"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="problemsdialog.cpp" line="44"/> - <location filename="problemsdialog.cpp" line="45"/> - <source>Fix</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="problemsdialog.cpp" line="49"/> - <source>No guided fix</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>Profile</name> - <message> - <location filename="profile.cpp" line="59"/> - <source>invalid profile name %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="63"/> - <source>failed to create %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="184"/> - <source>failed to write mod list: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="195"/> - <source>failed to update tweaked ini file, wrong settings may be used: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="226"/> - <source>failed to create tweaked ini: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="236"/> - <source>"%1" is missing or inaccessible</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="282"/> - <location filename="profile.cpp" line="314"/> - <location filename="profile.cpp" line="416"/> - <location filename="profile.cpp" line="436"/> - <location filename="profile.cpp" line="446"/> - <source>invalid index %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="397"/> - <source>Overwrite directory couldn't be parsed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="406"/> - <source>invalid priority %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="596"/> - <source>failed to parse ini file (%1)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="624"/> - <source>failed to parse ini file (%1): %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="648"/> - <location filename="profile.cpp" line="685"/> - <source>failed to modify "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="713"/> - <source>Delete savegames?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="714"/> - <source>Do you want to delete local savegames? (If you select "No", the save games will show up again if you re-enable local savegames)</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ProfileInputDialog</name> - <message> - <location filename="profileinputdialog.ui" line="14"/> - <source>Dialog</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profileinputdialog.ui" line="20"/> - <source>Please enter a name for the new profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profileinputdialog.ui" line="30"/> - <source>If checked, the new profile will use the default game settings.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profileinputdialog.ui" line="33"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profileinputdialog.ui" line="36"/> - <source>Default Game Settings</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>ProfilesDialog</name> - <message> - <location filename="profilesdialog.ui" line="14"/> - <source>Profiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="22"/> - <source>List of Profiles</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="25"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="38"/> - <location filename="profilesdialog.ui" line="41"/> - <source>If checked, savegames are local to this profile and will not appear when starting with a different profile.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="44"/> - <source>Local Savegames</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="51"/> - <source>This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="54"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="64"/> - <source>Automatic Archive Invalidation</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="75"/> - <location filename="profilesdialog.ui" line="78"/> - <source>Create a new profile from scratch</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="81"/> - <source>Create</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="91"/> - <source>Clone the selected profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="94"/> - <source>This creates a new profile with the same settings and active mods as the selected one.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="97"/> - <source>Copy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="107"/> - <location filename="profilesdialog.ui" line="110"/> - <source>Delete the selected Profile. This can not be un-done!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="113"/> - <source>Remove</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="123"/> - <source>Rename</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="133"/> - <location filename="profilesdialog.ui" line="136"/> - <source>Transfer save games to the selected profile.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="139"/> - <source>Transfer Saves</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.ui" line="162"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="61"/> - <source>Archive invalidation isn't required for this game.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="99"/> - <location filename="profilesdialog.cpp" line="144"/> - <source>failed to create profile: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="152"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="152"/> - <source>Please enter a name for the new profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="162"/> - <source>failed to copy profile: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="165"/> - <source>Invalid name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="165"/> - <source>Invalid profile name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="172"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="172"/> - <source>Are you sure you want to remove this profile (including local savegames if any)?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="182"/> - <source>Profile broken</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="183"/> - <source>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?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="215"/> - <source>Rename Profile</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="215"/> - <source>New Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="252"/> - <source>failed to change archive invalidation state: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="289"/> - <source>failed to determine if invalidation is active: %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>QObject</name> - <message> - <location filename="categories.cpp" line="141"/> - <source>Failed to save custom categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categories.cpp" line="218"/> - <location filename="categories.cpp" line="253"/> - <location filename="categories.cpp" line="263"/> - <location filename="categories.cpp" line="273"/> - <source>invalid index %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="categories.cpp" line="284"/> - <source>invalid category id %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="70"/> - <source>invalid field name "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="76"/> - <source>invalid type for "%1" (should be integer)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="81"/> - <source>invalid type for "%1" (should be string)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="86"/> - <source>invalid type for "%1" (should be float)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="103"/> - <source>no fields set up yet!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="140"/> - <source>field not set "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="237"/> - <source>invalid character in field "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="csvbuilder.cpp" line="240"/> - <source>empty field name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="gameinfoimpl.cpp" line="41"/> - <source>invalid game type %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="helper.cpp" line="53"/> - <source>helper failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="helper.cpp" line="69"/> - <location filename="helper.cpp" line="90"/> - <source>failed to determine account name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="installationmanager.cpp" line="64"/> - <location filename="selfupdater.cpp" line="52"/> - <source>invalid 7-zip32.dll: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="50"/> - <source>failed to open %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="104"/> - <location filename="loadmechanism.cpp" line="113"/> - <source>%1 not found</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="138"/> - <source>Failed to delete %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="144"/> - <source>Failed to deactivate script extender loading</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="165"/> - <source>Failed to remove %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="167"/> - <location filename="loadmechanism.cpp" line="260"/> - <source>Failed to rename %1 to %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="175"/> - <source>Failed to deactivate proxy-dll loading</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="209"/> - <location filename="loadmechanism.cpp" line="243"/> - <location filename="loadmechanism.cpp" line="263"/> - <source>Failed to copy %1 to %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="214"/> - <source>Failed to set up script extender loading</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="240"/> - <source>Failed to delete old proxy-dll %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="256"/> - <source>Failed to overwrite %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="loadmechanism.cpp" line="268"/> - <source>Failed to set up proxy-dll loading</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="121"/> - <source>Permissions required</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="122"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="218"/> - <location filename="main.cpp" line="256"/> - <source>Woops</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="219"/> - <source>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</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="257"/> - <source>ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="378"/> - <location filename="settings.cpp" line="528"/> - <source>Mod Organizer</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="378"/> - <source>An instance of Mod Organizer is already running</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="400"/> - <source>No game identified in "%1". The directory is required to contain the game binary and its launcher.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="403"/> - <location filename="main.cpp" line="432"/> - <source>Please select the game to manage</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="458"/> - <source>Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="main.cpp" line="547"/> - <source>failed to start application: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="856"/> - <source>Please use "Help" from the toolbar to get usage instructions to all elements</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1649"/> - <location filename="mainwindow.cpp" line="4314"/> - <source><Manage...></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="mainwindow.cpp" line="1667"/> - <source>failed to parse profile %1: %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="352"/> - <source>failed to find "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="516"/> - <source>failed to access %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="pluginlist.cpp" line="530"/> - <source>failed to set file time %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="70"/> - <source>failed to create %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profile.cpp" line="96"/> - <source>"%1" is missing or inaccessible</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="profilesdialog.cpp" line="80"/> - <source>Before you can use ModOrganizer, you need to create at least one profile. ATTENTION: Run the game at least once before creating a profile!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="report.cpp" line="33"/> - <location filename="report.cpp" line="36"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="safewritefile.cpp" line="33"/> - <source>failed to open temporary file</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegamegamebryo.cpp" line="130"/> - <location filename="savegamegamebryo.cpp" line="189"/> - <location filename="savegamegamebryo.cpp" line="231"/> - <source>wrong file format</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegamegamebryo.cpp" line="317"/> - <source>failed to open %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="535"/> - <source>Script Extender</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="542"/> - <source>Proxy DLL</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="133"/> - <source>failed to spawn "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="140"/> - <source>Elevation required</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="141"/> - <source>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)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="156"/> - <source>failed to spawn "%1": %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="165"/> - <source>"%1" doesn't exist</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="172"/> - <source>failed to inject dll into "%1": %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="spawn.cpp" line="190"/> - <source>failed to run "%1"</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>QueryOverwriteDialog</name> - <message> - <location filename="queryoverwritedialog.ui" line="14"/> - <source>Mod Exists</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="45"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="63"/> - <source>Keep Backup</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="70"/> - <source>Merge</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="77"/> - <source>Replace</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="84"/> - <source>Rename</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="queryoverwritedialog.ui" line="91"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SaveGameInfoWidget</name> - <message> - <location filename="savegameinfowidget.ui" line="39"/> - <source>Save #</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegameinfowidget.ui" line="51"/> - <source>Character</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegameinfowidget.ui" line="63"/> - <source>Level</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegameinfowidget.ui" line="75"/> - <source>Location</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savegameinfowidget.ui" line="87"/> - <source>Date</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SaveGameInfoWidgetGamebryo</name> - <message> - <location filename="savegameinfowidgetgamebryo.cpp" line="41"/> - <source>Missing ESPs</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SaveTextAsDialog</name> - <message> - <location filename="savetextasdialog.ui" line="14"/> - <source>Dialog</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.ui" line="32"/> - <source>Copy To Clipboard</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.ui" line="39"/> - <source>Save As...</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.ui" line="59"/> - <source>Close</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.cpp" line="36"/> - <source>Save CSV</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.cpp" line="36"/> - <source>Text Files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="savetextasdialog.cpp" line="40"/> - <source>failed to open "%1" for writing</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SelectionDialog</name> - <message> - <location filename="selectiondialog.ui" line="14"/> - <source>Select</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selectiondialog.ui" line="23"/> - <source>Placeholder</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selectiondialog.ui" line="77"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SelfUpdater</name> - <message> - <location filename="selfupdater.cpp" line="66"/> - <source>archive.dll not loaded: "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="117"/> - <location filename="selfupdater.cpp" line="140"/> - <location filename="selfupdater.cpp" line="269"/> - <location filename="selfupdater.cpp" line="416"/> - <source>Update</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="118"/> - <source>An update is available (newest version: %1), do you want to install it?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="141"/> - <source>Download in progress</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="196"/> - <source>Download failed: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="207"/> - <source>Failed to install update: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="228"/> - <source>failed to open archive "%1": %2</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="251"/> - <source>failed to move outdated files: %1. Please update manually.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="269"/> - <source>Update installed, Mod Organizer will now be restarted.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="297"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="352"/> - <source>Failed to parse response. Please report this as a bug and include the file mo_interface.log.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="417"/> - <source>No incremental update available for this version, the complete package needs to be downloaded (%1 kB)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="426"/> - <source>no file for update found. Please update manually.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="441"/> - <source>Failed to retrieve update information: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="selfupdater.cpp" line="461"/> - <source>No download server available. Please try again later.</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>Settings</name> - <message> - <location filename="settings.cpp" line="115"/> - <source>Failed</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="116"/> - <source>Sorry, failed to start the helper application</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="335"/> - <location filename="settings.cpp" line="354"/> - <source>attempt to store setting for unknown plugin "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="647"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settings.cpp" line="647"/> - <source>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?</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SettingsDialog</name> - <message> - <location filename="settingsdialog.ui" line="14"/> - <source>Settings</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="24"/> - <source>General</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="32"/> - <source>Language</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="39"/> - <source>The display language</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="42"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="57"/> - <source>Style</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="64"/> - <source>graphical style</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="67"/> - <source>graphical style of the MO user interface</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="78"/> - <source>Log Level</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="85"/> - <source>Decides the amount of data printed to "ModOrganizer.log"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="88"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="93"/> - <source>Debug</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="98"/> - <source>Info</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="103"/> - <source>Error</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="113"/> - <source>Advanced</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="125"/> - <location filename="settingsdialog.ui" line="128"/> - <source>Directory where downloads are stored.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="148"/> - <source>Mod Directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="155"/> - <source>Directory where mods are stored.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="158"/> - <source>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).</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="172"/> - <source>Download Directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="179"/> - <source>Cache Directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="199"/> - <source>User interface</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="205"/> - <source>If checked, the download interface will be more compact.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="208"/> - <source>Compact Download Interface</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="215"/> - <source>If checked, the download list will display meta information instead of file names.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="218"/> - <source>Download Meta Information</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="234"/> - <source>Reset stored information from dialogs.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="237"/> - <source>This will make all dialogs show up again where you checked the "Remember selection"-box.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="240"/> - <source>Reset Dialogs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="260"/> - <location filename="settingsdialog.ui" line="263"/> - <source>Modify the categories available to arrange your mods.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="266"/> - <source>Configure Mod Categories</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="274"/> - <location filename="settingsdialog.ui" line="290"/> - <source>Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="280"/> - <source>Allows automatic log-in when the Nexus-Page for the game is clicked.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="283"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="299"/> - <source>If checked and if correct credentials are entered below, log-in to Nexus (for browsing and downloading) is automatic.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="302"/> - <source>Automatically Log-In to Nexus</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="311"/> - <source>Username</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="325"/> - <source>Password</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="347"/> - <source>Disable automatic internet features</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="350"/> - <source>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)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="353"/> - <source>Offline Mode</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="360"/> - <source>Use a proxy for network connections.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="363"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="366"/> - <source>Use HTTP Proxy (Uses System Settings)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="375"/> - <source>Associate with "Download with manager" links</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="404"/> - <source>Known Servers (updated on download)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="425"/> - <source>Preferred Servers (Drag & Drop)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="460"/> - <source>Plugins</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="482"/> - <source>Author:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="496"/> - <source>Version:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="510"/> - <source>Description:</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="548"/> - <source>Key</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="553"/> - <source>Value</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="565"/> - <source>Blacklisted Plugins (use <del> to remove):</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="576"/> - <source>Workarounds</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="584"/> - <source>Steam App ID</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="604"/> - <source>The Steam AppID for your game</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="607"/> - <source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" 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></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="638"/> - <source>Load Mechanism</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="658"/> - <source>Select loading mechanism. See help for details.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="661"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="678"/> - <source>NMM Version</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="698"/> - <source>The Version of Nexus Mod Manager to impersonate.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="701"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="723"/> - <source>Enforces that inactive ESPs and ESMs are never loaded.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="726"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="730"/> - <source>Hide inactive ESPs/ESMs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="737"/> - <source>If checked, files (i.e. esps, esms and bsas) belonging to the core game can not be disabled in the UI. (default: on)</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="740"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="744"/> - <source>Force-enable game files</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="754"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="757"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="763"/> - <source>Display mods installed outside MO</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="773"/> - <location filename="settingsdialog.ui" line="777"/> - <source>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!</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="781"/> - <source>Back-date BSAs</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.ui" line="805"/> - <source>These are workarounds for problems with Mod Organizer. Please make sure you read the help text before changing anything here.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="95"/> - <source>Select download directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="103"/> - <source>Select mod directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="111"/> - <source>Select cache directory</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="119"/> - <source>Confirm?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="settingsdialog.cpp" line="120"/> - <source>This will make all dialogs show up again where you checked the "Remember selection"-box. Continue?</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SimpleInstallDialog</name> - <message> - <location filename="simpleinstalldialog.ui" line="14"/> - <source>Quick Install</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="simpleinstalldialog.ui" line="22"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="simpleinstalldialog.ui" line="49"/> - <location filename="simpleinstalldialog.ui" line="52"/> - <source>Opens a Dialog that allows custom modifications.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="simpleinstalldialog.ui" line="55"/> - <source>Manual</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="simpleinstalldialog.ui" line="62"/> - <source>OK</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="simpleinstalldialog.ui" line="72"/> - <source>Cancel</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SingleInstance</name> - <message> - <location filename="singleinstance.cpp" line="50"/> - <source>SHM error: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="singleinstance.cpp" line="82"/> - <source>failed to connect to running instance: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="singleinstance.cpp" line="88"/> - <source>failed to communicate with running instance: %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="singleinstance.cpp" line="100"/> - <source>failed to receive data from secondary instance: %1</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>SyncOverwriteDialog</name> - <message> - <location filename="syncoverwritedialog.ui" line="14"/> - <source>Sync Overwrite</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="syncoverwritedialog.ui" line="27"/> - <source>Name</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="syncoverwritedialog.ui" line="32"/> - <source>Sync To</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="syncoverwritedialog.cpp" line="95"/> - <source><don't sync></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="syncoverwritedialog.cpp" line="147"/> - <source>failed to remove %1</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="syncoverwritedialog.cpp" line="149"/> - <source>failed to move %1 to %2</source> - <translation type="unfinished"></translation> - </message> -</context> -<context> - <name>TransferSavesDialog</name> - <message> - <location filename="transfersavesdialog.ui" line="14"/> - <source>Transfer Savegames</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="22"/> - <source>Global Characters</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="29"/> - <source>This is a list of characters in the global location.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="32"/> - <source>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 -</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="46"/> - <source>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 - -</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="87"/> - <source>Move -></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="97"/> - <source>Copy -></source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="123"/> - <source><- Move</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="133"/> - <source><- Copy</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="156"/> - <source>Done</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.ui" line="167"/> - <source>Profile Characters</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="140"/> - <source>Overwrite</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="141"/> - <source>Overwrite the file "%1"</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="157"/> - <location filename="transfersavesdialog.cpp" line="195"/> - <location filename="transfersavesdialog.cpp" line="230"/> - <location filename="transfersavesdialog.cpp" line="269"/> - <source>Confirm</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="158"/> - <location filename="transfersavesdialog.cpp" line="196"/> - <source>Copy all save games of character "%1" to the profile?</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="231"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> - <message> - <location filename="transfersavesdialog.cpp" line="270"/> - <source>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.</source> - <translation type="unfinished"></translation> - </message> -</context> -</TS> diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 196ef6bf..b84488da 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -17,19 +17,23 @@ #include "logbuffer.h"
#include "credentialsdialog.h"
#include "filedialogmemory.h"
-#include "lockeddialog.h"
#include "modinfodialog.h"
#include "spawn.h"
#include "syncoverwritedialog.h"
#include "nxmaccessmanager.h"
#include <ipluginmodpage.h>
#include <dataarchives.h>
+#include <localsavegames.h>
#include <directoryentry.h>
#include <scopeguard.h>
#include <utility.h>
+#include <usvfs.h>
#include "appconfig.h"
#include <report.h>
#include <questionboxmemory.h>
+#include "lockeddialog.h"
+#include "instancemanager.h"
+#include <scriptextender.h>
#include <QApplication>
#include <QCoreApplication>
@@ -46,6 +50,8 @@ #include <QtGlobal> // for qPrintable, etc
#include <Psapi.h>
+#include <Shlobj.h>
+#include <tlhelp32.h>
#include <tchar.h> // for _tcsicmp
#include <limits.h>
@@ -54,6 +60,7 @@ #include <exception>
#include <functional>
+#include <boost/algorithm/string/predicate.hpp>
#include <memory>
#include <set>
#include <string> //for wstring
@@ -64,16 +71,19 @@ using namespace MOShared;
using namespace MOBase;
+//static
+CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None;
static bool isOnline()
{
QList<QNetworkInterface> 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)) {
+ 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;
@@ -88,7 +98,8 @@ static bool isOnline() return connected;
}
-static bool renameFile(const QString &oldName, const QString &newName, bool overwrite = true)
+static bool renameFile(const QString &oldName, const QString &newName,
+ bool overwrite = true)
{
if (overwrite && QFile::exists(newName)) {
QFile::remove(newName);
@@ -96,31 +107,55 @@ static bool renameFile(const QString &oldName, const QString &newName, bool over return QFile::rename(oldName, newName);
}
-static std::wstring getProcessName(DWORD processId)
+static std::wstring getProcessName(HANDLE process)
{
- HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, processId);
+ wchar_t buffer[MAX_PATH];
+ wchar_t *fileName = L"unknown";
- 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");
- }
+ if (process == nullptr) return fileName;
+
+ if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) {
+ fileName = wcsrchr(buffer, L'\\');
+ if (fileName == nullptr) {
+ fileName = buffer;
+ }
+ else {
+ fileName += 1;
+ }
+ }
+
+ return fileName;
+}
+
+// Get parent PID for the given process, return 0 on failure
+static DWORD getProcessParentID(DWORD pid)
+{
+ HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ PROCESSENTRY32 pe = { 0 };
+ pe.dwSize = sizeof(PROCESSENTRY32);
+
+ DWORD res = 0;
+ if (Process32First(th, &pe))
+ do {
+ if (pe.th32ProcessID == pid) {
+ res = pe.th32ParentProcessID;
+ break;
+ }
+ } while (Process32Next(th, &pe));
+
+ CloseHandle(th);
+
+ return res;
}
static void startSteam(QWidget *widget)
{
- QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", QSettings::NativeFormat);
+ QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
+ QSettings::NativeFormat);
QString exe = steamSettings.value("SteamExe", "").toString();
if (!exe.isEmpty()) {
exe = QString("\"%1\"").arg(exe);
- //See if username and password supplied. If so, pass them into steam.
+ // See if username and password supplied. If so, pass them into steam.
QStringList args;
QString username;
QString password;
@@ -134,8 +169,9 @@ static void startSteam(QWidget *widget) if (!QProcess::startDetached(exe, args)) {
reportError(QObject::tr("Failed to start \"%1\"").arg(exe));
} else {
- QMessageBox::information(widget, QObject::tr("Waiting"),
- QObject::tr("Please press OK once you're logged into steam."));
+ QMessageBox::information(
+ widget, QObject::tr("Waiting"),
+ QObject::tr("Please press OK once you're logged into steam."));
}
}
}
@@ -150,7 +186,6 @@ QStringList toStringList(InputIterator current, InputIterator end) return result;
}
-
OrganizerCore::OrganizerCore(const QSettings &initSettings)
: m_UserInterface(nullptr)
, m_PluginContainer(nullptr)
@@ -184,21 +219,33 @@ OrganizerCore::OrganizerCore(const QSettings &initSettings) 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_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(&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)));
+ connect(NexusInterface::instance()->getAccessManager(),
+ SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
+ connect(NexusInterface::instance()->getAccessManager(),
+ SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
- //This seems awfully imperative
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_DownloadManager, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), NexusInterface::instance(), SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ // This seems awfully imperative
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_Settings, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_DownloadManager,
+ SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ &m_PluginList, SLOT(managedGameChanged(MOBase::IPluginGame const *)));
+ connect(this, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)),
+ NexusInterface::instance(),
+ SLOT(managedGameChanged(MOBase::IPluginGame const *)));
- connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write);
+ connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter,
+ &DelayedFileWriterBase::write);
// make directory refresher run in a separate thread
m_RefresherThread.start();
@@ -221,7 +268,7 @@ OrganizerCore::~OrganizerCore() ModInfo::clear();
LogBuffer::cleanQuit();
m_ModList.setProfile(nullptr);
- NexusInterface::instance()->cleanup();
+ // NexusInterface::instance()->cleanup();
delete m_DirectoryStructure;
}
@@ -230,7 +277,8 @@ QString OrganizerCore::commitSettings(const QString &iniFile) {
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
+ // make a second attempt using qt functions but if that fails print the
+ // error from the first attempt
if (!renameFile(iniFile + ".new", iniFile)) {
return windowsErrorString(err);
}
@@ -245,7 +293,8 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName) m_UserInterface->storeSettings(settings);
}
if (m_CurrentProfile != nullptr) {
- settings.setValue("selected_profile", m_CurrentProfile->name().toUtf8().constData());
+ settings.setValue("selected_profile",
+ m_CurrentProfile->name().toUtf8().constData());
}
settings.setValue("ask_for_nexuspw", m_AskForNexusPW);
@@ -265,7 +314,6 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName) settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath());
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);
}
}
@@ -279,31 +327,41 @@ QSettings::Status OrganizerCore::storeSettings(const QString &fileName) void OrganizerCore::storeSettings()
{
- QString iniFile = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName());
+ QString iniFile = qApp->property("dataPath").toString() + "/"
+ + QString::fromStdWString(AppConfig::iniFileName());
if (QFileInfo(iniFile).exists()) {
if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) {
- QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"),
- tr("An error occured trying to update MO settings to %1: %2").arg(
- iniFile, windowsErrorString(::GetLastError())));
+ QMessageBox::critical(
+ qApp->activeWindow(), tr("Failed to write settings"),
+ tr("An error occured trying to update MO settings to %1: %2")
+ .arg(iniFile, windowsErrorString(::GetLastError())));
return;
}
}
- QSettings::Status result = storeSettings(iniFile + ".new");
+ QString writeTarget = iniFile + ".new";
+
+ QSettings::Status result = storeSettings(writeTarget);
if (result == QSettings::NoError) {
QString errMsg = commitSettings(iniFile);
if (!errMsg.isEmpty()) {
- qWarning("settings file not writable, may be locked by another application, trying direct write");
+ qWarning("settings file not writable, may be locked by another "
+ "application, trying direct write");
+ writeTarget = iniFile;
result = storeSettings(iniFile);
}
}
if (result != QSettings::NoError) {
- 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 to %1: %2").arg(iniFile + ".new", reason));
+ 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 to %1: %2")
+ .arg(writeTarget, reason));
}
}
@@ -315,7 +373,9 @@ bool OrganizerCore::testForSteam() bool success = false;
while (!success) {
processIDs.reset(new DWORD[currentSize]);
- if (!::EnumProcesses(processIDs.get(), currentSize * sizeof(DWORD), &bytesReturned)) {
+ if (!::EnumProcesses(processIDs.get(),
+ static_cast<DWORD>(currentSize) * sizeof(DWORD),
+ &bytesReturned)) {
qWarning("failed to determine if steam is running");
return true;
}
@@ -330,17 +390,25 @@ bool OrganizerCore::testForSteam() 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]);
+ HANDLE process = ::OpenProcess(
+ PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]);
if (process != nullptr) {
+
+ ON_BLOCK_EXIT([&]() {
+ if (process != INVALID_HANDLE_VALUE)
+ ::CloseHandle(process);
+ });
+
HMODULE module;
DWORD ignore;
// first module in a process is always the binary
- if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) {
+ 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)) {
+ if ((_tcsicmp(processName, TEXT("steam.exe")) == 0)
+ || (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) {
return true;
}
}
@@ -365,53 +433,64 @@ void OrganizerCore::updateExecutablesList(QSettings &settings) 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;
Executable::Flags flags;
- if (settings.value("custom", true).toBool()) flags |= Executable::CustomExecutable;
- if (settings.value("toolbar", false).toBool()) flags |= Executable::ShowInToolbar;
- if (settings.value("ownicon", false).toBool()) flags |= Executable::UseApplicationIcon;
+ if (settings.value("custom", true).toBool())
+ flags |= Executable::CustomExecutable;
+ if (settings.value("toolbar", false).toBool())
+ flags |= Executable::ShowInToolbar;
+ if (settings.value("ownicon", false).toBool())
+ flags |= Executable::UseApplicationIcon;
- m_ExecutablesList.addExecutable(settings.value("title").toString(),
- settings.value("binary").toString(),
- settings.value("arguments").toString(),
- settings.value("workingDirectory", "").toString(),
- closeMO,
- settings.value("steamAppID", "").toString(),
- flags);
+ m_ExecutablesList.addExecutable(
+ settings.value("title").toString(), settings.value("binary").toString(),
+ settings.value("arguments").toString(),
+ settings.value("workingDirectory", "").toString(),
+ settings.value("steamAppID", "").toString(), flags);
}
settings.endArray();
- // TODO this has nothing to do with executables list move to an appropriate function!
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), managedGame());
+ // TODO this has nothing to do with executables list move to an appropriate
+ // function!
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
+ m_Settings.displayForeign(), managedGame());
}
-void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *widget)
+void OrganizerCore::setUserInterface(IUserInterface *userInterface,
+ QWidget *widget)
{
storeSettings();
m_UserInterface = userInterface;
if (widget != nullptr) {
- connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int)));
- 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(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_ModList, SIGNAL(modorder_changed()), widget, SLOT(modorder_changed()));
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString)));
+ connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget,
+ SLOT(modlistChanged(QModelIndex, int)));
+ 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(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_ModList, SIGNAL(modorder_changed()), widget,
+ SLOT(modorder_changed()));
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget,
+ SLOT(showMessage(QString)));
}
m_InstallationManager.setParentWidget(widget);
m_Updater.setUserInterface(widget);
if (userInterface != nullptr) {
- // this currently wouldn't work reliably if the ui isn't initialized yet to display the result
+ // 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 {
@@ -422,21 +501,14 @@ void OrganizerCore::setUserInterface(IUserInterface *userInterface, QWidget *wid void OrganizerCore::connectPlugins(PluginContainer *container)
{
- m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions());
+ m_DownloadManager.setSupportedExtensions(
+ m_InstallationManager.getSupportedExtensions());
m_PluginContainer = container;
if (!m_GameName.isEmpty()) {
m_GamePlugin = m_PluginContainer->managedGame(m_GameName);
emit managedGameChanged(m_GamePlugin);
}
- //Do this the hard way
- for (const IPluginGame * const game : container->plugins<IPluginGame>()) {
- QString n = game->gameShortName();
- if (game->gameShortName() == "Skyrim") {
- m_Updater.setNexusDownload(game);
- break;
- }
- }
}
void OrganizerCore::disconnectPlugins()
@@ -448,13 +520,13 @@ void OrganizerCore::disconnectPlugins() m_PluginList.disconnectSlots();
m_Settings.clearPlugins();
- m_GamePlugin = nullptr;
+ m_GamePlugin = nullptr;
m_PluginContainer = nullptr;
}
-void OrganizerCore::setManagedGame(MOBase::IPluginGame const *game)
+void OrganizerCore::setManagedGame(MOBase::IPluginGame *game)
{
- m_GameName = game->gameName();
+ m_GameName = game->gameName();
m_GamePlugin = game;
qApp->setProperty("managed_game", QVariant::fromValue(m_GamePlugin));
emit managedGameChanged(m_GamePlugin);
@@ -467,10 +539,10 @@ Settings &OrganizerCore::settings() bool OrganizerCore::nexusLogin(bool retry)
{
- NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager();
+ NXMAccessManager *accessManager
+ = NexusInterface::instance()->getAccessManager();
- if ((accessManager->loginAttempted()
- || accessManager->loggedIn())
+ if ((accessManager->loginAttempted() || accessManager->loggedIn())
&& !retry) {
// previous attempt, maybe even successful
return false;
@@ -530,16 +602,21 @@ void OrganizerCore::downloadRequestedNXM(const QString &url) void OrganizerCore::externalMessage(const QString &message)
{
- if (message.left(6).toLower() == "nxm://") {
+ if (MOShortcut moshortcut{ message }) {
+ runShortcut(moshortcut);
+ }
+ else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
downloadRequestedNXM(message);
}
}
-void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName)
+void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID,
+ const QString &fileName)
{
try {
- if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) {
+ if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0,
+ new ModRepositoryFileInfo(modID))) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
}
} catch (const std::exception &e) {
@@ -565,21 +642,83 @@ InstallationManager *OrganizerCore::installationManager() return &m_InstallationManager;
}
+bool OrganizerCore::createDirectory(const QString &path) {
+ if (!QDir(path).exists() && !QDir().mkpath(path)) {
+ QMessageBox::critical(nullptr, QObject::tr("Error"),
+ QObject::tr("Failed to create \"%1\". Your user "
+ "account probably lacks permission.")
+ .arg(QDir::toNativeSeparators(path)));
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool OrganizerCore::bootstrap() {
+ return createDirectory(m_Settings.getProfileDirectory()) &&
+ createDirectory(m_Settings.getModDirectory()) &&
+ createDirectory(m_Settings.getDownloadDirectory()) &&
+ createDirectory(m_Settings.getOverwriteDirectory()) &&
+ createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics();
+}
+
void OrganizerCore::createDefaultProfile()
{
- QString profilesPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath());
- if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) {
+ QString profilesPath = settings().getProfileDirectory();
+ if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size()
+ == 0) {
Profile newProf("Default", managedGame(), false);
}
}
+void OrganizerCore::prepareVFS()
+{
+ m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
+}
+
+void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType) {
+ setGlobalCrashDumpsType(crashDumpsType);
+ m_USVFS.updateParams(logLevel, crashDumpsType);
+}
+
+bool OrganizerCore::cycleDiagnostics() {
+ if (int maxDumps = settings().crashDumpsMax())
+ removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed);
+ return true;
+}
+
+//static
+void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) {
+ m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType);
+}
+
+//static
+std::wstring OrganizerCore::crashDumpsPath() {
+ return (
+ qApp->property("dataPath").toString() + "/"
+ + QString::fromStdWString(AppConfig::dumpsDir())
+ ).toStdWString();
+}
+
void OrganizerCore::setCurrentProfile(const QString &profileName)
{
- if ((m_CurrentProfile != nullptr) &&
- (profileName == m_CurrentProfile->name())) {
+ if ((m_CurrentProfile != nullptr)
+ && (profileName == m_CurrentProfile->name())) {
return;
}
- QString profileDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()) + "/" + profileName;
+
+ QDir profileBaseDir(settings().getProfileDirectory());
+ QString profileDir = profileBaseDir.absoluteFilePath(profileName);
+
+ if (!QDir(profileDir).exists()) {
+ // selected profile doesn't exist. Ensure there is at least one profile,
+ // then pick any one
+ createDefaultProfile();
+
+ profileDir = profileBaseDir.absoluteFilePath(
+ profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0));
+ }
+
Profile *newProfile = new Profile(QDir(profileDir), managedGame());
delete m_CurrentProfile;
@@ -592,7 +731,8 @@ void OrganizerCore::setCurrentProfile(const QString &profileName) m_CurrentProfile->deactivateInvalidation();
}
- connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint)));
+ connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this,
+ SLOT(modStatusChanged(uint)));
refreshDirectoryStructure();
}
@@ -624,6 +764,16 @@ QString OrganizerCore::downloadsPath() const return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory());
}
+QString OrganizerCore::overwritePath() const
+{
+ return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory());
+}
+
+QString OrganizerCore::basePath() const
+{
+ return QDir::fromNativeSeparators(m_Settings.getBaseDirectory());
+}
+
MOBase::VersionInfo OrganizerCore::appVersion() const
{
return m_Updater.getVersion();
@@ -644,7 +794,10 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name) m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
- QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name);
+ QString targetDirectory
+ = QDir::fromNativeSeparators(m_Settings.getModDirectory())
+ .append("/")
+ .append(name);
QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat);
@@ -659,7 +812,8 @@ MOBase::IModInterface *OrganizerCore::createMod(GuessedValue<QString> &name) settingsFile.endArray();
}
- return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data();
+ return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure)
+ .data();
}
bool OrganizerCore::removeMod(MOBase::IModInterface *mod)
@@ -672,37 +826,44 @@ bool OrganizerCore::removeMod(MOBase::IModInterface *mod) }
}
-void OrganizerCore::modDataChanged(MOBase::IModInterface*)
+void OrganizerCore::modDataChanged(MOBase::IModInterface *)
{
refreshModList(false);
}
-QVariant OrganizerCore::pluginSetting(const QString &pluginName, const QString &key) const
+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)
+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
+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)
+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
{
- return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data";
+ return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())
+ + "/data";
}
-MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const QString &initModName)
+MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
+ const QString &initModName)
{
if (m_CurrentProfile == nullptr) {
return nullptr;
@@ -716,18 +877,21 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const m_CurrentProfile->writeModlistNow();
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow());
+ 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)
+ 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);
+ 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();
@@ -736,7 +900,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, const }
} else if (m_InstallationManager.wasCancelled()) {
QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."), QMessageBox::Ok);
+ tr("The mod was not installed completely."),
+ QMessageBox::Ok);
}
return nullptr;
}
@@ -745,8 +910,8 @@ void OrganizerCore::installDownload(int index) {
try {
QString fileName = m_DownloadManager.getFilePath(index);
- int modID = m_DownloadManager.getModID(index);
- int fileID = m_DownloadManager.getFileInfo(index)->fileID;
+ int modID = m_DownloadManager.getModID(index);
+ int fileID = m_DownloadManager.getFileInfo(index)->fileID;
GuessedValue<QString> modName;
// see if there already are mods with the specified mod id
@@ -754,7 +919,8 @@ void OrganizerCore::installDownload(int index) std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID);
for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) {
std::vector<ModInfo::EFlag> flags = (*iter)->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) {
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP)
+ == flags.end()) {
modName.update((*iter)->name(), GUESS_PRESET);
(*iter)->saveMeta();
}
@@ -766,7 +932,8 @@ void OrganizerCore::installDownload(int index) bool hasIniTweaks = false;
m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow());
+ MessageDialog::showMessage(tr("Installation successful"),
+ qApp->activeWindow());
refreshModList();
int modIndex = ModInfo::getIndex(modName);
@@ -774,12 +941,14 @@ void OrganizerCore::installDownload(int index) ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
modInfo->addInstalledFile(modID, fileID);
- if (hasIniTweaks
- && m_UserInterface != nullptr
+ 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);
+ 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);
@@ -790,8 +959,9 @@ void OrganizerCore::installDownload(int index) emit modInstalled(modName);
} else if (m_InstallationManager.wasCancelled()) {
- QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"),
- tr("The mod was not installed completely."), QMessageBox::Ok);
+ QMessageBox::information(
+ qApp->activeWindow(), tr("Installation cancelled"),
+ tr("The mod was not installed completely."), QMessageBox::Ok);
}
} catch (const std::exception &e) {
reportError(e.what());
@@ -803,7 +973,8 @@ QString OrganizerCore::resolvePath(const QString &fileName) const if (m_DirectoryStructure == nullptr) {
return QString();
}
- const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
+ const FileEntry::Ptr file
+ = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr);
if (file.get() != nullptr) {
return ToQString(file->getFullPath());
} else {
@@ -814,9 +985,10 @@ QString OrganizerCore::resolvePath(const QString &fileName) const QStringList OrganizerCore::listDirectories(const QString &directoryName) const
{
QStringList result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName));
+ DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(
+ ToWString(directoryName));
if (dir != nullptr) {
- std::vector<DirectoryEntry*>::iterator current, end;
+ std::vector<DirectoryEntry *>::iterator current, end;
dir->getSubDirectories(current, end);
for (; current != end; ++current) {
result.append(ToQString((*current)->getName()));
@@ -825,10 +997,13 @@ QStringList OrganizerCore::listDirectories(const QString &directoryName) const return result;
}
-QStringList OrganizerCore::findFiles(const QString &path, const std::function<bool (const QString &)> &filter) const
+QStringList OrganizerCore::findFiles(
+ const QString &path,
+ const std::function<bool(const QString &)> &filter) const
{
QStringList result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir
+ = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
@@ -845,12 +1020,15 @@ QStringList OrganizerCore::findFiles(const QString &path, const std::function<bo QStringList OrganizerCore::getFileOrigins(const QString &fileName) const
{
QStringList result;
- const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(QFileInfo(fileName).fileName()), nullptr);
+ const FileEntry::Ptr file = m_DirectoryStructure->searchFile(
+ ToWString(QFileInfo(fileName).fileName()), nullptr);
if (file.get() != nullptr) {
- result.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName()));
- foreach (int i, file->getAlternatives()) {
- result.append(ToQString(m_DirectoryStructure->getOriginByID(i).getName()));
+ result.append(ToQString(
+ m_DirectoryStructure->getOriginByID(file->getOrigin()).getName()));
+ foreach (auto i, file->getAlternatives()) {
+ result.append(
+ ToQString(m_DirectoryStructure->getOriginByID(i.first).getName()));
}
} else {
qDebug("%s not found", qPrintable(fileName));
@@ -858,20 +1036,27 @@ QStringList OrganizerCore::getFileOrigins(const QString &fileName) const return result;
}
-QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos(const QString &path, const std::function<bool (const MOBase::IOrganizer::FileInfo &)> &filter) const
+QList<MOBase::IOrganizer::FileInfo> OrganizerCore::findFileInfos(
+ const QString &path,
+ const std::function<bool(const MOBase::IOrganizer::FileInfo &)> &filter)
+ const
{
QList<IOrganizer::FileInfo> result;
- DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ DirectoryEntry *dir
+ = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
if (dir != nullptr) {
std::vector<FileEntry::Ptr> files = dir->getFiles();
foreach (FileEntry::Ptr file, files) {
IOrganizer::FileInfo info;
- info.filePath = ToQString(file->getFullPath());
+ info.filePath = ToQString(file->getFullPath());
bool fromArchive = false;
- info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName()));
+ 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()));
+ foreach (auto idx, file->getAlternatives()) {
+ info.origins.append(
+ ToQString(m_DirectoryStructure->getOriginByID(idx.first).getName()));
}
if (filter(info)) {
@@ -907,63 +1092,96 @@ QStringList OrganizerCore::modsSortedByProfilePriority() const return res;
}
-void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID)
+void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite)
{
- if (m_UserInterface != nullptr) {
- m_UserInterface->lock();
+ DWORD processExitCode = 0;
+ HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, &processExitCode);
+ if (processHandle != INVALID_HANDLE_VALUE) {
+ 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 (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
+ qDebug("removing loadorder.txt");
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ refreshDirectoryStructure();
+
+ refreshESPList();
+ savePluginList();
+
+ //These callbacks should not fiddle with directoy structure and ESPs.
+ m_FinishedRun(binary.absoluteFilePath(), processExitCode);
}
- ON_BLOCK_EXIT([&] () {
- if (m_UserInterface != nullptr) { m_UserInterface->unlock(); }
- });
+}
- HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID);
+HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary,
+ const QString &arguments,
+ const QString &profileName,
+ const QDir ¤tDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite,
+ LPDWORD exitCode)
+{
+ HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite);
if (processHandle != INVALID_HANDLE_VALUE) {
- if (closeAfterStart && (m_UserInterface != nullptr)) {
- m_UserInterface->closeWindow();
- } else {
+ std::unique_ptr<LockedDialog> dlg;
+ ILockedWaitingForProcess* uilock = nullptr;
- DWORD processExitCode;
- (void)waitForProcessCompletion(processHandle, &processExitCode);
+ if (m_UserInterface != nullptr) {
+ uilock = m_UserInterface->lock();
+ }
+ else {
+ // i.e. when running command line shortcuts there is no m_UserInterface
+ dlg.reset(new LockedDialog);
+ dlg->show();
+ dlg->setEnabled(true);
+ uilock = dlg.get();
+ }
- 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 (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- qDebug("removing loadorder.txt");
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
- refreshESPList();
- if (managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- // the load order should have been retrieved from file time, now save it to our own format
- savePluginList();
- }
+ ON_BLOCK_EXIT([&]() {
+ if (m_UserInterface != nullptr) {
+ m_UserInterface->unlock();
+ } });
- //These callbacks should not fiddle with directoy structure and ESPs.
- m_FinishedRun(binary.absoluteFilePath(), processExitCode);
- }
+ DWORD ignoreExitCode;
+ waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock);
+ cycleDiagnostics();
}
+
+ return processHandle;
}
-HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName,
- const QDir ¤tDirectory, const QString &steamAppID)
+
+HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary,
+ const QString &arguments,
+ const QString &profileName,
+ const QDir ¤tDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite)
{
prepareStart();
if (!binary.exists()) {
- reportError(tr("Executable \"%1\" not found").arg(binary.absoluteFilePath()));
+ reportError(
+ tr("Executable \"%1\" not found").arg(binary.absoluteFilePath()));
return INVALID_HANDLE_VALUE;
}
if (!steamAppID.isEmpty()) {
::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
} else {
- ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str());
+ ::SetEnvironmentVariableW(L"SteamAPPId",
+ ToWString(m_Settings.getSteamAppID()).c_str());
}
-
- //This could possibly be extracted somewhere else but it's probably for when
- //we have more than one provider of game registration.
- if (QFileInfo(managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")).exists()
+ // This could possibly be extracted somewhere else but it's probably for when
+ // we have more than one provider of game registration.
+ if ((QFileInfo(
+ managedGame()->gameDirectory().absoluteFilePath("steam_api.dll"))
+ .exists()
+ || QFileInfo(managedGame()->gameDirectory().absoluteFilePath(
+ "steam_api64.dll"))
+ .exists())
&& (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) {
if (!testForSteam()) {
QWidget *window = qApp->activeWindow();
@@ -987,22 +1205,84 @@ HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, const QString & // need to make sure all data is saved before we start the application
if (m_CurrentProfile != nullptr) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
}
// TODO: should also pass arguments
if (m_AboutToRun(binary.absoluteFilePath())) {
- return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true);
+ try {
+ m_USVFS.updateMapping(fileMapping(profileName, customOverwrite));
+ } catch (const std::exception &e) {
+ QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what());
+ return INVALID_HANDLE_VALUE;
+ }
+
+ QString modsPath = settings().getModDirectory();
+
+ // Check if this a request with either an executable or a working directory under our mods folder
+ // then will start the processs in a virtualized "environment" with the appropriate paths fixed:
+ // (i.e. mods\FNIS\path\exe => game\data\path\exe)
+ QString cwdPath = currentDirectory.absolutePath();
+ bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive);
+ QString binPath = binary.absoluteFilePath();
+ bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive);
+ if (virtualizedCwd || virtualizedBin) {
+ if (virtualizedCwd) {
+ int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1);
+ cwdPath = m_GamePlugin->dataDirectory().absolutePath() + cwdPath.mid(cwdOffset, -1);
+ }
+
+ if (virtualizedBin) {
+ int binOffset = binPath.indexOf('/', modsPath.length() + 1);
+ binPath = m_GamePlugin->dataDirectory().absolutePath() + binPath.mid(binOffset, -1);
+ }
+
+ QString cmdline
+ = QString("launch \"%1\" \"%2\" %3")
+ .arg(QDir::toNativeSeparators(cwdPath),
+ QDir::toNativeSeparators(binPath), arguments);
+
+ qDebug() << "Spawning proxyed process <" << cmdline << ">";
+
+ return startBinary(QFileInfo(QCoreApplication::applicationFilePath()),
+ cmdline, QCoreApplication::applicationDirPath(), true);
+ } else {
+ qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">";
+ return startBinary(binary, arguments, currentDirectory, true);
+ }
} else {
- qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath()));
+ 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)
+HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut)
+{
+ if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance())
+ throw std::runtime_error(
+ QString("Refusing to run executable from different instance %1:%2")
+ .arg(shortcut.instance(),shortcut.executable())
+ .toLocal8Bit().constData());
+
+ Executable& exe = m_ExecutablesList.find(shortcut.executable());
+
+ return spawnBinaryDirect(
+ exe.m_BinaryInfo, exe.m_Arguments,
+ m_CurrentProfile->name(),
+ exe.m_WorkingDirectory.length() != 0
+ ? exe.m_WorkingDirectory
+ : exe.m_BinaryInfo.absolutePath(),
+ exe.m_SteamAppID, "");
+}
+
+HANDLE OrganizerCore::startApplication(const QString &executable,
+ const QStringList &args,
+ const QString &cwd,
+ const QString &profile)
{
QFileInfo binary;
- QString arguments = args.join(" ");
+ QString arguments = args.join(" ");
QString currentDirectory = cwd;
QString profileName = profile;
if (profile.length() == 0) {
@@ -1013,13 +1293,15 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL }
}
QString steamAppID;
+ QString customOverwrite;
if (executable.contains('\\') || executable.contains('/')) {
// file path
binary = QFileInfo(executable);
if (binary.isRelative()) {
// relative path, should be relative to game directory
- binary = QFileInfo(managedGame()->gameDirectory().absoluteFilePath(executable));
+ binary = QFileInfo(
+ managedGame()->gameDirectory().absoluteFilePath(executable));
}
if (cwd.length() == 0) {
currentDirectory = binary.absolutePath();
@@ -1027,7 +1309,10 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL try {
const Executable &exe = m_ExecutablesList.findByBinary(binary);
steamAppID = exe.m_SteamAppID;
- } catch (const std::runtime_error&) {
+ customOverwrite
+ = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ .toString();
+ } catch (const std::runtime_error &) {
// nop
}
} else {
@@ -1035,6 +1320,9 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL try {
const Executable &exe = m_ExecutablesList.find(executable);
steamAppID = exe.m_SteamAppID;
+ customOverwrite
+ = m_CurrentProfile->setting("custom_overwrites", exe.m_Title)
+ .toString();
if (arguments == "") {
arguments = exe.m_Arguments;
}
@@ -1042,107 +1330,213 @@ HANDLE OrganizerCore::startApplication(const QString &executable, const QStringL if (cwd.length() == 0) {
currentDirectory = exe.m_WorkingDirectory;
}
- } catch (const std::runtime_error&) {
- qWarning("\"%s\" not set up as executable", executable.toUtf8().constData());
+ } 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);
+ return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite);
}
bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode)
{
+ ILockedWaitingForProcess* uilock = nullptr;
if (m_UserInterface != nullptr) {
- m_UserInterface->lock();
+ uilock = m_UserInterface->lock();
}
ON_BLOCK_EXIT([&] () {
if (m_UserInterface != nullptr) {
m_UserInterface->unlock();
} });
- return waitForProcessCompletion(handle, exitCode);
+ return waitForProcessCompletion(handle, exitCode, uilock);
}
-bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode)
+bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock)
{
- bool isJobHandle = true;
+ bool originalHandle = true;
+ bool newHandle = true;
+ bool uiunlocked = false;
- ULONG lastProcessID = ULONG_MAX;
- HANDLE processHandle = handle;
+ DWORD currentPID = 0;
+ QString processName;
+ auto waitForChildUntil = GetTickCount64();
+ if (handle != INVALID_HANDLE_VALUE) {
+ currentPID = GetProcessId(handle);
+ processName = QString::fromStdWString(getProcessName(handle));
+ }
- DWORD res;
- //Wait for a an event on the handle, a key press, mouse click or timeout
- while (res = ::MsgWaitForMultipleObjects(1, &handle, false, 500, QS_KEY | QS_MOUSE),
- (res != WAIT_FAILED && res != WAIT_OBJECT_0
- && (m_UserInterface == nullptr || !m_UserInterface->unlockClicked()))) {
- if (isJobHandle) {
- DWORD retLen;
- JOBOBJECT_BASIC_PROCESS_ID_LIST info;
- 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 (m_UserInterface != nullptr) {
- m_UserInterface->setProcessName(ToQString(getProcessName(lastProcessID)));
- }
- 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;
- }
- }
+ // Certain process names we wish to "hide" for aesthetic reason:
+ bool waitingOnHidden = false;
+ std::vector<QString> hiddenList;
+ hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
+ for (QString hide : hiddenList)
+ if (processName.contains(hide, Qt::CaseInsensitive))
+ waitingOnHidden = true;
+ // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes.
+ // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want
+ // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden"
+ // process. For this reason we use exponential backoff and also start with a delibrately low value to improve
+ // the responsiveness of the initial update
+ DWORD64 nextHiddenCheck = GetTickCount64();
+ DWORD64 nextHiddenCheckDelay = 50;
+
+ constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1;
+ DWORD res = WAIT_TIMEOUT;
+ while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT))
+ {
+ if (newHandle) {
+ processName += QString(" (%1)").arg(currentPID);
+ if (uilock)
+ uilock->setProcessName(processName);
+ qDebug() << "Waiting for"
+ << (originalHandle ? "spawned" : "usvfs")
+ << "process completion :" << processName.toUtf8().constData();
+ newHandle = false;
+ }
+
+ // Wait for a an event on the handle, a key press, mouse click or timeout
+ res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON);
+ if (res == WAIT_FAILED) {
+ qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError();
+ break;
}
// keep processing events so the app doesn't appear dead
+ QCoreApplication::sendPostedEvents();
QCoreApplication::processEvents();
- }
- if (exitCode != nullptr) {
- //This is actually wrong if the process we started finished before we
- //got the event and so we end up with a job handle.
- if (! ::GetExitCodeProcess(processHandle, exitCode))
+ if (uilock && uilock->unlockForced()) {
+ uiunlocked = true;
+ break;
+ }
+
+ if (res == WAIT_OBJECT_0) {
+ // process we were waiting on has completed
+ if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode))
+ qWarning() << "Failed getting exit code of complete process :" << GetLastError();
+ CloseHandle(handle);
+ handle = INVALID_HANDLE_VALUE;
+ originalHandle = false;
+ // if the previous process spawned a child process and immediately exits we may miss it if we check immediately
+ waitForChildUntil = GetTickCount64() + 800;
+ }
+
+ // search for another process to wait on if either:
+ // 1. we just completed waiting for a process and need to find/wait for an inject child
+ // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on
+ bool firstIteration = true;
+ while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil)
+ || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck))
{
- DWORD error = ::GetLastError();
- qDebug() << "Failed to get process exit code: Error " << error;
+ if (firstIteration)
+ firstIteration = false;
+ else {
+ QThread::msleep(200);
+ QCoreApplication::sendPostedEvents();
+ QCoreApplication::processEvents();
+ }
+
+ // search if there is another usvfs process active
+ handle = findAndOpenAUSVFSProcess(hiddenList, currentPID);
+ waitingOnHidden = false;
+ newHandle = handle != INVALID_HANDLE_VALUE;
+ if (newHandle) {
+ currentPID = GetProcessId(handle);
+ processName = QString::fromStdWString(getProcessName(handle));
+ for (QString hide : hiddenList)
+ if (processName.contains(hide, Qt::CaseInsensitive))
+ waitingOnHidden = true;
+ }
+ if (waitingOnHidden) {
+ nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay;
+ nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000);
+ }
+ else {
+ nextHiddenCheck = GetTickCount64();
+ nextHiddenCheckDelay = 200;
+ }
}
}
- ::CloseHandle(processHandle);
- if (handle != processHandle) {
+ if (res == WAIT_OBJECT_0)
+ qDebug() << "Waiting for process completion successfull";
+ else if (uiunlocked)
+ qDebug() << "Waiting for process completion aborted by UI";
+ else
+ qDebug() << "Waiting for process completion not successfull :" << res;
+
+ if (handle != INVALID_HANDLE_VALUE)
::CloseHandle(handle);
- }
return res == WAIT_OBJECT_0;
}
-bool OrganizerCore::onAboutToRun(const std::function<bool (const QString &)> &func)
+HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector<QString>& hiddenList, DWORD preferedParentPid) {
+ // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics
+ // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid)
+ constexpr size_t querySize = 100;
+ DWORD pids[querySize];
+ size_t found = querySize;
+ if (!::GetVFSProcessList(&found, pids)) {
+ qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!";
+ return INVALID_HANDLE_VALUE;
+ }
+
+ HANDLE best_match = INVALID_HANDLE_VALUE;
+ bool best_match_hidden = true;
+ for (size_t i = 0; i < found; ++i) {
+ if (pids[i] == GetCurrentProcessId())
+ continue; // obviously don't wait for MO process
+
+ HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]);
+ if (handle == INVALID_HANDLE_VALUE) {
+ qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError();
+ continue;
+ }
+
+ QString pname = QString::fromStdWString(getProcessName(handle));
+ bool phidden = false;
+ for (auto hide : hiddenList)
+ if (pname.contains(hide, Qt::CaseInsensitive))
+ phidden = true;
+
+ bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid;
+
+ if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) {
+ if (best_match != INVALID_HANDLE_VALUE)
+ CloseHandle(best_match);
+ best_match = handle;
+ best_match_hidden = phidden;
+ }
+ else
+ CloseHandle(handle);
+
+ if (!phidden && pprefered)
+ return best_match;
+ }
+
+ return best_match;
+}
+
+bool OrganizerCore::onAboutToRun(
+ const std::function<bool(const QString &)> &func)
{
auto conn = m_AboutToRun.connect(func);
return conn.connected();
}
-bool OrganizerCore::onFinishedRun(const std::function<void (const QString &, unsigned int)> &func)
+bool OrganizerCore::onFinishedRun(
+ const std::function<void(const QString &, unsigned int)> &func)
{
auto conn = m_FinishedRun.connect(func);
return conn.connected();
}
-bool OrganizerCore::onModInstalled(const std::function<void (const QString &)> &func)
+bool OrganizerCore::onModInstalled(
+ const std::function<void(const QString &)> &func)
{
auto conn = m_ModInstalled.connect(func);
return conn.connected();
@@ -1152,9 +1546,10 @@ void OrganizerCore::refreshModList(bool saveChanges) {
// don't lose changes!
if (saveChanges) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
}
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), managedGame());
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure,
+ m_Settings.displayForeign(), managedGame());
m_CurrentProfile->refreshModStatus();
@@ -1166,18 +1561,18 @@ void OrganizerCore::refreshModList(bool saveChanges) void OrganizerCore::refreshESPList()
{
if (m_DirectoryUpdate) {
- // don't mess up the esp list if we're currently updating the directory structure
- m_PostRefreshTasks.append([this] () { this->refreshESPList(); });
+ // don't mess up the esp list if we're currently updating the directory
+ // structure
+ m_PostRefreshTasks.append([this]() {
+ this->refreshESPList();
+ });
return;
}
- m_CurrentProfile->modlistWriter().write();
+ m_CurrentProfile->writeModlist();
// clear list
try {
- m_PluginList.refresh(m_CurrentProfile->name(),
- *m_DirectoryStructure,
- m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
+ m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure,
m_CurrentProfile->getLockedOrderFileName());
} catch (const std::exception &e) {
reportError(tr("Failed to refresh list of esps: %1").arg(e.what()));
@@ -1191,8 +1586,10 @@ void OrganizerCore::refreshBSAList() if (archives != nullptr) {
m_ArchivesInit = false;
- // default archives are the ones enabled outside MO. if the list can't be found (which might
- // happen if ini files are missing) use hard-coded defaults (preferrably the same the game would use)
+ // default archives are the ones enabled outside MO. if the list can't be
+ // found (which might
+ // happen if ini files are missing) use hard-coded defaults (preferrably the
+ // same the game would use)
m_DefaultArchives = archives->archives(m_CurrentProfile);
if (m_DefaultArchives.length() == 0) {
m_DefaultArchives = archives->vanillaArchives();
@@ -1200,12 +1597,12 @@ void OrganizerCore::refreshBSAList() m_ActiveArchives.clear();
- auto iter = enabledArchives();
+ 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_DefaultArchives, m_ActiveArchives);
}
@@ -1219,17 +1616,33 @@ 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
+ } // 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());
- for (const QString &esm : dir.entryList(QStringList() << "*.esm", QDir::Files)) {
+ for (const QString &esm :
+ dir.entryList(QStringList() << "*.esm", QDir::Files)) {
m_PluginList.enableESP(esm, active);
}
- int enabled = 0;
+ int enabled = 0;
+ for (const QString &esl :
+ dir.entryList(QStringList() << "*.esl", QDir::Files)) {
+ const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl));
+ if (file.get() == nullptr) {
+ qWarning("failed to activate %s", qPrintable(esl));
+ continue;
+ }
+
+ if (active != m_PluginList.isEnabled(esl)
+ && file->getAlternatives().empty()) {
+ m_PluginList.enableESP(esl, active);
+ ++enabled;
+ }
+ }
QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files);
for (const QString &esp : esps) {
const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp));
@@ -1245,49 +1658,53 @@ void OrganizerCore::updateModActiveState(int index, bool active) }
}
if (active && (enabled > 1)) {
- MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), qApp->activeWindow());
+ MessageDialog::showMessage(
+ tr("Multiple esps/esls activated, please check that they don't conflict."),
+ qApp->activeWindow());
}
m_PluginList.refreshLoadOrder();
// immediately save affected lists
m_PluginListsWriter.writeImmediately(false);
}
-void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo)
+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()
- );
+ 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
+ // 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
+ // 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->archivesWriter().write();
+ m_UserInterface->archivesWriter().writeImmediately(false);
}
+
std::vector<QString> archives = enabledArchives();
- m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(),
- std::set<QString>(archives.begin(), archives.end()));
+ m_DirectoryRefresher.setMods(
+ m_CurrentProfile->getActiveMods(),
+ std::set<QString>(archives.begin(), archives.end()));
// 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()
- );
+ 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<MOBase::IPluginModPage>()) {
+ for (IPluginModPage *modPage :
+ m_PluginContainer->plugins<MOBase::IPluginModPage>()) {
ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo();
if (modPage->handlesDownload(url, reply->url(), *fileInfo)) {
fileInfo->repository = modPage->name();
@@ -1299,7 +1716,7 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) // no mod found that could handle the download. Is it a nexus mod?
if (url.host() == "www.nexusmods.com") {
- int modID = 0;
+ int modID = 0;
int fileID = 0;
QRegExp modExp("mods/(\\d+)");
if (modExp.indexIn(url.toString()) != -1) {
@@ -1309,13 +1726,18 @@ void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) if (fileExp.indexIn(reply->url().toString()) != -1) {
fileID = fileExp.cap(1).toInt();
}
- m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID));
+ 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) {
+ 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());
}
}
@@ -1356,13 +1778,14 @@ std::vector<QString> OrganizerCore::enabledArchives() void OrganizerCore::refreshDirectoryStructure()
{
if (!m_DirectoryUpdate) {
- m_CurrentProfile->modlistWriter().writeImmediately(true);
+ m_CurrentProfile->writeModlistNow(true);
m_DirectoryUpdate = true;
- std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods();
+ std::vector<std::tuple<QString, QString, int>> activeModList
+ = m_CurrentProfile->getActiveMods();
auto archives = enabledArchives();
- m_DirectoryRefresher.setMods(activeModList,
- std::set<QString>(archives.begin(), archives.end()));
+ m_DirectoryRefresher.setMods(
+ activeModList, std::set<QString>(archives.begin(), archives.end()));
QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
}
@@ -1376,13 +1799,11 @@ void OrganizerCore::directory_refreshed() 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
+ // 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);
@@ -1391,12 +1812,19 @@ void OrganizerCore::directory_refreshed() for (auto task : m_PostRefreshTasks) {
task();
}
+ m_PostRefreshTasks.clear();
+
+ if (m_CurrentProfile != nullptr) {
+ refreshLists();
+ }
}
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(), managedGame());
+ // 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(), managedGame());
m_CurrentProfile->refreshModStatus();
refreshModList();
@@ -1412,7 +1840,8 @@ void OrganizerCore::modStatusChanged(unsigned int index) updateModActiveState(index, false);
refreshESPList();
if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
- FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
+ FilesOrigin &origin
+ = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
origin.enable(false);
}
if (m_UserInterface != nullptr) {
@@ -1425,14 +1854,16 @@ void OrganizerCore::modStatusChanged(unsigned int index) 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);
+ // 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) {
+ } catch (const std::exception &e) {
reportError(tr("failed to update mod list: %1").arg(e.what()));
}
}
@@ -1442,7 +1873,7 @@ void OrganizerCore::loginSuccessful(bool necessary) if (necessary) {
MessageDialog::showMessage(tr("login successful"), qApp->activeWindow());
}
- foreach (QString url, m_PendingDownloads) {
+ for (QString url : m_PendingDownloads) {
downloadRequestedNXM(url);
}
m_PendingDownloads.clear();
@@ -1450,8 +1881,8 @@ void OrganizerCore::loginSuccessful(bool necessary) task();
}
- m_PostLoginTasks.clear();
- NexusInterface::instance()->loginCompleted();
+ m_PostLoginTasks.clear();
+ NexusInterface::instance()->loginCompleted();
}
void OrganizerCore::loginSuccessfulUpdate(bool necessary)
@@ -1464,39 +1895,50 @@ void OrganizerCore::loginSuccessfulUpdate(bool necessary) void OrganizerCore::loginFailed(const QString &message)
{
- if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == QMessageBox::Yes) {
+ if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"),
+ tr("Login failed, try again?"))
+ == QMessageBox::Yes) {
if (nexusLogin(true)) {
return;
}
}
if (!m_PendingDownloads.isEmpty()) {
- MessageDialog::showMessage(tr("login failed: %1. Download will not be associated with an account").arg(message), qApp->activeWindow());
+ MessageDialog::showMessage(
+ tr("login failed: %1. Download will not be associated with an account")
+ .arg(message),
+ qApp->activeWindow());
for (QString url : m_PendingDownloads) {
downloadRequestedNXM(url);
}
m_PendingDownloads.clear();
} else {
- MessageDialog::showMessage(tr("login failed: %1").arg(message), qApp->activeWindow());
+ 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());
+ MessageDialog::showMessage(
+ tr("login failed: %1. You need to log-in with Nexus to update MO.")
+ .arg(message),
+ qApp->activeWindow());
}
void OrganizerCore::syncOverwrite()
{
- unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
+ unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
std::vector<ModInfo::EFlag> flags = mod->getFlags();
- return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
+ return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE)
+ != flags.end();
+ });
ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex);
- SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow());
+ SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure,
+ qApp->activeWindow());
if (syncDialog.exec() == QDialog::Accepted) {
syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory()));
modInfo->testValid();
@@ -1504,11 +1946,28 @@ void OrganizerCore::syncOverwrite() }
}
+QString OrganizerCore::oldMO1HookDll() const
+{
+ if (auto extender = managedGame()->feature<ScriptExtender>()) {
+ QString hookdll = QDir::toNativeSeparators(
+ managedGame()->dataDirectory().absoluteFilePath(extender->PluginPath() + "/hook.dll"));
+ if (QFile(hookdll).exists())
+ return hookdll;
+ }
+ return QString();
+}
+
std::vector<unsigned int> OrganizerCore::activeProblems() const
{
std::vector<unsigned int> problems;
- if (m_PluginList.enabledCount() > 255) {
- problems.push_back(PROBLEM_TOOMANYPLUGINS);
+ const auto& hookdll = oldMO1HookDll();
+ if (!hookdll.isEmpty()) {
+ // This warning will now be shown every time the problems are checked, which is a bit
+ // of a "log spam". But since this is a sevre error which will most likely make the
+ // game crash/freeze/etc. and is very hard to diagnose, this "log spam" will make it
+ // easier for the user to notice the warning.
+ qWarning("hook.dll found in game folder: %s", qPrintable(hookdll));
+ problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND);
}
return problems;
}
@@ -1516,8 +1975,8 @@ std::vector<unsigned int> OrganizerCore::activeProblems() const QString OrganizerCore::shortDescription(unsigned int key) const
{
switch (key) {
- case PROBLEM_TOOMANYPLUGINS: {
- return tr("Too many esps and esms enabled");
+ case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: {
+ return tr("MO1 \"Script Extender\" load mechanism has left hook.dll in your game folder");
} break;
default: {
return tr("Description missing");
@@ -1528,10 +1987,13 @@ QString OrganizerCore::shortDescription(unsigned int key) const 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: <a href=\"http://wiki.step-project.com/Guide:Merging_Plugins\">http://wiki.step-project.com/Guide:Merging_Plugins</a>");
- } break;
+ case PROBLEM_MO1SCRIPTEXTENDERWORKAROUND: {
+ return tr("<a href=\"%1\">hook.dll</a> has been found in your game folder (right click to copy the full path). "
+ "This is most likely a leftover of setting the ModOrganizer 1 load mechanism to \"Script Extender\", "
+ "in which case you must remove this file either by changing the load mechanism in ModOrganizer 1 or "
+ "manually removing the file, otherwise the game is likely to crash and burn.").arg(oldMO1HookDll());
+ break;
+ }
default: {
return tr("Description missing");
} break;
@@ -1570,43 +2032,110 @@ void OrganizerCore::savePluginList() {
if (m_DirectoryUpdate) {
// delay save till after directory update
- m_PostRefreshTasks.append([&] () { this->savePluginList(); });
+ m_PostRefreshTasks.append([this]() {
+ this->savePluginList();
+ });
return;
}
- m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
- m_CurrentProfile->getLockedOrderFileName(),
+ m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(),
m_CurrentProfile->getDeleterFileName(),
m_Settings.hideUncheckedPlugins());
m_PluginList.saveLoadOrder(*m_DirectoryStructure);
}
-void OrganizerCore::prepareStart() {
+void OrganizerCore::prepareStart()
+{
if (m_CurrentProfile == nullptr) {
return;
}
- m_CurrentProfile->modlistWriter().write();
+ m_CurrentProfile->writeModlist();
m_CurrentProfile->createTweakedIniFile();
saveCurrentLists();
m_Settings.setupLoadMechanism();
storeSettings();
}
-/*
-std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping()
+std::vector<Mapping> OrganizerCore::fileMapping(const QString &profileName,
+ const QString &customOverwrite)
{
- return fileMapping(managedGame()->dataDirectory().absolutePath(),
- directoryStructure(),
- directoryStructure());
+ // need to wait until directory structure
+ while (m_DirectoryUpdate) {
+ ::Sleep(100);
+ QCoreApplication::processEvents();
+ }
+
+ IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>();
+ Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName),
+ game);
+
+ MappingType result;
+
+ QString dataPath
+ = QDir::toNativeSeparators(game->dataDirectory().absolutePath());
+
+ bool overwriteActive = false;
+
+ for (auto mod : profile.getActiveMods()) {
+ if (std::get<0>(mod).compare("overwrite", Qt::CaseInsensitive) == 0) {
+ continue;
+ }
+
+ unsigned int modIndex = ModInfo::getIndex(std::get<0>(mod));
+ ModInfo::Ptr modPtr = ModInfo::getByIndex(modIndex);
+
+ bool createTarget = customOverwrite == std::get<0>(mod);
+
+ overwriteActive |= createTarget;
+
+ if (modPtr->isRegular()) {
+ result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)),
+ dataPath, true, createTarget});
+ }
+ }
+
+ if (!overwriteActive && !customOverwrite.isEmpty()) {
+ throw MyException(tr("The designated write target \"%1\" is not enabled.")
+ .arg(customOverwrite));
+ }
+
+ if (m_CurrentProfile->localSavesEnabled()) {
+ LocalSavegames *localSaves = game->feature<LocalSavegames>();
+ if (localSaves != nullptr) {
+ MappingType saveMap
+ = localSaves->mappings(currentProfile()->absolutePath() + "/saves");
+ result.reserve(result.size() + saveMap.size());
+ result.insert(result.end(), saveMap.begin(), saveMap.end());
+ } else {
+ qWarning("local save games not supported by this game plugin");
+ }
+ }
+
+ result.insert(result.end(), {
+ QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()),
+ dataPath,
+ true,
+ customOverwrite.isEmpty()
+ });
+
+ for (MOBase::IPluginFileMapper *mapper :
+ m_PluginContainer->plugins<MOBase::IPluginFileMapper>()) {
+ IPlugin *plugin = dynamic_cast<IPlugin *>(mapper);
+ if (plugin->isActive()) {
+ MappingType pluginMap = mapper->mappings();
+ result.reserve(result.size() + pluginMap.size());
+ result.insert(result.end(), pluginMap.begin(), pluginMap.end());
+ }
+ }
+
+ return result;
}
-std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping(
- const QString &dataPath,
- const DirectoryEntry *base,
- const DirectoryEntry *directoryEntry)
+std::vector<Mapping> OrganizerCore::fileMapping(
+ const QString &dataPath, const QString &relPath, const DirectoryEntry *base,
+ const DirectoryEntry *directoryEntry, int createDestination)
{
- std::vector<std::pair<QString, QString>> result;
+ std::vector<Mapping> result;
for (FileEntry::Ptr current : directoryEntry->getFiles()) {
bool isArchive = false;
@@ -1615,20 +2144,36 @@ std::vector<std::pair<QString, QString>> OrganizerCore::fileMapping( continue;
}
- QString fileName = ToQString(current->getRelativePath());
- QString source = ToQString(base->getOriginByID(origin).getPath()) + fileName;
- QString target = QDir::toNativeSeparators(dataPath) + fileName;
- result.push_back(std::make_pair(source, target));
+ QString originPath
+ = QString::fromStdWString(base->getOriginByID(origin).getPath());
+ QString fileName = QString::fromStdWString(current->getName());
+// QString fileName = ToQString(current->getName());
+ QString source = originPath + relPath + fileName;
+ QString target = dataPath + relPath + fileName;
+ if (source != target) {
+ result.push_back({source, target, false, false});
+ }
}
// recurse into subdirectories
- std::vector<DirectoryEntry*>::const_iterator current, end;
+ std::vector<DirectoryEntry *>::const_iterator current, end;
directoryEntry->getSubDirectories(current, end);
for (; current != end; ++current) {
- std::vector<std::pair<QString, QString>> subRes = fileMapping(dataPath, base, *current);
+ int origin = (*current)->anyOrigin();
+
+ QString originPath
+ = QString::fromStdWString(base->getOriginByID(origin).getPath());
+ QString dirName = QString::fromStdWString((*current)->getName());
+ QString source = originPath + relPath + dirName;
+ QString target = dataPath + relPath + dirName;
+
+ bool writeDestination
+ = (base == directoryEntry) && (origin == createDestination);
+
+ result.push_back({source, target, true, writeDestination});
+ std::vector<Mapping> subRes = fileMapping(
+ dataPath, relPath + dirName + "\\", base, *current, createDestination);
result.insert(result.end(), subRes.begin(), subRes.end());
}
return result;
}
-
-*/
diff --git a/src/organizercore.h b/src/organizercore.h index 28fc14ca..9e81f0c3 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -12,6 +12,9 @@ #include "installationmanager.h"
#include "downloadmanager.h"
#include "executableslist.h"
+#include "usvfsconnector.h"
+#include "moshortcut.h"
+#include <directoryentry.h>
#include <imoinfo.h>
#include <iplugindiagnose.h>
#include <versioninfo.h>
@@ -83,6 +86,7 @@ private: typedef boost::signals2::signal<void (const QString&)> SignalModInstalled;
public:
+ static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); }
OrganizerCore(const QSettings &initSettings);
@@ -92,7 +96,7 @@ public: void connectPlugins(PluginContainer *container);
void disconnectPlugins();
- void setManagedGame(const MOBase::IPluginGame *game);
+ void setManagedGame(MOBase::IPluginGame *game);
void updateExecutablesList(QSettings &settings);
@@ -134,21 +138,50 @@ public: void doAfterLogin(const std::function<void()> &function) { m_PostLoginTasks.append(function); }
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = "");
- 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(),
+ const QString &steamAppID = "",
+ const QString &customOverwrite = "");
+
+ HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments,
+ const QString &profileName,
+ const QDir ¤tDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite,
+ LPDWORD exitCode = nullptr);
+
+ HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments,
+ const QString &profileName,
+ const QDir ¤tDirectory,
+ const QString &steamAppID,
+ const QString &customOverwrite);
void loginSuccessfulUpdate(bool necessary);
void loginFailedUpdate(const QString &message);
+ static bool createAndMakeWritable(const QString &path);
+ bool bootstrap();
void createDefaultProfile();
MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; }
+ void prepareVFS();
+
+ void updateVFSParams(int logLevel, int crashDumpsType);
+
+ bool cycleDiagnostics();
+
+ static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; }
+ static void setGlobalCrashDumpsType(int crashDumpsType);
+ static std::wstring crashDumpsPath();
+
public:
MOBase::IModRepositoryBridge *createNexusBridge() const;
QString profileName() const;
QString profilePath() const;
QString downloadsPath() const;
+ QString overwritePath() const;
+ QString basePath() const;
MOBase::VersionInfo appVersion() const;
MOBase::IModInterface *getMod(const QString &name) const;
MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name);
@@ -168,16 +201,16 @@ public: DownloadManager *downloadManager();
PluginList *pluginList();
ModList *modList();
+ HANDLE runShortcut(const MOShortcut& shortcut);
HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile);
bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr);
+ HANDLE findAndOpenAUSVFSProcess(const std::vector<QString>& hiddenList, DWORD preferedParentPid);
bool onModInstalled(const std::function<void (const QString &)> &func);
bool onAboutToRun(const std::function<bool (const QString &)> &func);
bool onFinishedRun(const std::function<void (const QString &, unsigned int)> &func);
void refreshModList(bool saveChanges = true);
QStringList modsSortedByProfilePriority() const;
- //std::vector<std::pair<QString, QString> > fileMapping();
-
public: // IPluginDiagnose interface
virtual std::vector<unsigned int> activeProblems() const;
@@ -215,6 +248,8 @@ signals: void managedGameChanged(MOBase::IPluginGame const *gamePlugin);
+ void close();
+
private:
void storeSettings();
@@ -229,13 +264,23 @@ private: bool testForSteam();
- /*
- * std::vector<std::pair<QString, QString>> fileMapping(const QString &dataPath,
- const MOShared::DirectoryEntry *base,
- const MOShared::DirectoryEntry *directoryEntry);
-*/
+ bool createDirectory(const QString &path);
- bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode);
+ QString oldMO1HookDll() const;
+
+ /**
+ * @brief return a descriptor of the mappings real file->virtual file
+ */
+ std::vector<Mapping> fileMapping(const QString &profile,
+ const QString &customOverwrite);
+
+ std::vector<Mapping>
+ fileMapping(const QString &dataPath, const QString &relPath,
+ const MOShared::DirectoryEntry *base,
+ const MOShared::DirectoryEntry *directoryEntry,
+ int createDestination);
+
+ bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock);
private slots:
@@ -247,15 +292,14 @@ private slots: void loginFailed(const QString &message);
private:
-
- static const unsigned int PROBLEM_TOOMANYPLUGINS = 1;
+ static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1;
private:
IUserInterface *m_UserInterface;
PluginContainer *m_PluginContainer;
QString m_GameName;
- MOBase::IPluginGame const *m_GamePlugin;
+ MOBase::IPluginGame *m_GamePlugin;
Profile *m_CurrentProfile;
@@ -270,6 +314,7 @@ private: ModList m_ModList;
PluginList m_PluginList;
+
QList<std::function<void()>> m_PostLoginTasks;
QList<std::function<void()>> m_PostRefreshTasks;
@@ -291,7 +336,9 @@ private: bool m_ArchivesInit;
MOBase::DelayedFileWriter m_PluginListsWriter;
+ UsvfsConnector m_USVFS;
+ static CrashDumpsType m_globalCrashDumpsType;
};
#endif // ORGANIZERCORE_H
diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index b6dbd242..cf2e29e3 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -37,9 +37,15 @@ QString OrganizerProxy::downloadsPath() const QString OrganizerProxy::overwritePath() const
{
- return QDir::fromNativeSeparators(qApp->property("dataPath").toString())
+ /*return QDir::fromNativeSeparators(qApp->property("dataPath").toString())
+ "/"
- + ToQString(AppConfig::overwritePath());
+ + ToQString(AppConfig::overwritePath());*/
+ return m_Proxied->overwritePath();
+}
+
+QString OrganizerProxy::basePath() const
+{
+ return m_Proxied->basePath();
}
VersionInfo OrganizerProxy::appVersion() const
diff --git a/src/organizerproxy.h b/src/organizerproxy.h index 7ce8982e..7004db14 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -18,6 +18,7 @@ public: virtual QString profilePath() const;
virtual QString downloadsPath() const;
virtual QString overwritePath() const;
+ virtual QString basePath() const;
virtual MOBase::VersionInfo appVersion() const;
virtual MOBase::IModInterface *getMod(const QString &name) const;
virtual MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name);
@@ -59,5 +60,4 @@ private: };
-
#endif // ORGANIZERPROXY_H
diff --git a/src/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 6ca0de39..a6eb78fa 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -15,6 +15,12 @@ PersistentCookieJar::~PersistentCookieJar() { save();
}
+void PersistentCookieJar::clear() {
+ for (const QNetworkCookie &cookie : allCookies()) {
+ deleteCookie(cookie);
+ }
+}
+
void PersistentCookieJar::save() {
QTemporaryFile file;
if (!file.open()) {
@@ -26,7 +32,7 @@ void PersistentCookieJar::save() { QList<QNetworkCookie> cookies = allCookies();
data << static_cast<quint32>(cookies.size());
- foreach (const QNetworkCookie &cookie, allCookies()) {
+ for (const QNetworkCookie &cookie : allCookies()) {
data << cookie.toRawForm();
}
diff --git a/src/persistentcookiejar.h b/src/persistentcookiejar.h index 812b785c..0ff747ee 100644 --- a/src/persistentcookiejar.h +++ b/src/persistentcookiejar.h @@ -5,9 +5,15 @@ class PersistentCookieJar : public QNetworkCookieJar {
+
+ Q_OBJECT
+
public:
PersistentCookieJar(const QString &fileName, QObject *parent = 0);
virtual ~PersistentCookieJar();
+
+ void clear();
+
private:
void save();
diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 7a059cd1..c980c0a1 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -87,7 +87,7 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) m_Organizer->settings().registerPlugin(pluginObj);
}
- { // diagnosis plugins
+ { // diagnosis plugin
IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin);
if (diagnose != nullptr) {
bf::at_key<IPluginDiagnose>(m_Plugins).push_back(diagnose);
@@ -96,6 +96,12 @@ bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) );
}
}
+ { // file mapper plugin
+ IPluginFileMapper *mapper = qobject_cast<IPluginFileMapper*>(plugin);
+ if (mapper != nullptr) {
+ bf::at_key<IPluginFileMapper>(m_Plugins).push_back(mapper);
+ }
+ }
{ // mod page plugin
IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin);
if (verifyPlugin(modPage)) {
@@ -194,7 +200,7 @@ void PluginContainer::unloadPlugins() bf::for_each(m_Plugins, clearPlugins());
- foreach (const boost::signals2::connection &connection, m_DiagnosisConnections) {
+ for (const boost::signals2::connection &connection : m_DiagnosisConnections) {
connection.disconnect();
}
m_DiagnosisConnections.clear();
@@ -277,7 +283,7 @@ void PluginContainer::loadPlugins() m_PluginLoaders.push_back(pluginLoader.release());
} else {
m_FailedPlugins.push_back(pluginName);
- qWarning("plugin \"%s\" failed to load", qPrintable(pluginName));
+ qWarning("plugin \"%s\" failed to load (may be outdated)", qPrintable(pluginName));
}
}
}
@@ -286,6 +292,7 @@ void PluginContainer::loadPlugins() // remove the load check file on success
loadCheck.remove();
+ bf::at_key<IPluginDiagnose>(m_Plugins).push_back(m_Organizer);
bf::at_key<IPluginDiagnose>(m_Plugins).push_back(this);
m_Organizer->connectPlugins(this);
diff --git a/src/plugincontainer.h b/src/plugincontainer.h index 02978e3e..70392d0b 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -10,6 +10,7 @@ #include <iplugintool.h>
#include <ipluginproxy.h>
#include <iplugininstaller.h>
+#include <ipluginfilemapper.h>
#include <QtPlugin>
#include <QPluginLoader>
#include <QFile>
@@ -36,7 +37,8 @@ private: boost::fusion::pair<MOBase::IPluginModPage, std::vector<MOBase::IPluginModPage*>>,
boost::fusion::pair<MOBase::IPluginPreview, std::vector<MOBase::IPluginPreview*>>,
boost::fusion::pair<MOBase::IPluginTool, std::vector<MOBase::IPluginTool*>>,
- boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>
+ boost::fusion::pair<MOBase::IPluginProxy, std::vector<MOBase::IPluginProxy*>>,
+ boost::fusion::pair<MOBase::IPluginFileMapper, std::vector<MOBase::IPluginFileMapper*>>
> PluginMap;
static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 96757ec6..8bf438f1 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -18,16 +18,17 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */
#include "pluginlist.h"
-#include "inject.h"
#include "settings.h"
-#include "safewritefile.h"
#include "scopeguard.h"
#include "modinfo.h"
+#include "viewmarkingscrollbar.h"
#include <utility.h>
#include <iplugingame.h>
#include <espfile.h>
#include <report.h>
#include <windows_error.h>
+#include <safewritefile.h>
+#include <gameplugins.h>
#include <QtDebug>
#include <QMessageBox>
@@ -75,14 +76,6 @@ PluginList::PluginList(QObject *parent) : QAbstractItemModel(parent)
, m_FontMetrics(QFont())
{
- m_Utf8Codec = QTextCodec::codecForName("utf-8");
- m_LocalCodec = QTextCodec::codecForName("Windows-1252");
-
- if (m_LocalCodec == nullptr) {
- qCritical("required 8-bit string-encoding not supported.");
- m_LocalCodec = m_Utf8Codec;
- }
-
}
PluginList::~PluginList()
@@ -110,38 +103,69 @@ QString PluginList::getColumnToolTip(int 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.");
+ case COL_MODINDEX: return tr("The modindex determines the formids of objects originating from this mods.");
default: return tr("unknown");
}
}
+void PluginList::highlightPlugins(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile)
+{
+ for (auto &esp : m_ESPs) {
+ esp.m_ModSelected = false;
+ }
+ for (QModelIndex idx : selected.indexes()) {
+ ModInfo::Ptr selectedMod = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt());
+ if (!selectedMod.isNull() && profile.modEnabled(idx.data(Qt::UserRole + 1).toInt())) {
+ QDir dir(selectedMod->absolutePath());
+ QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl");
+ MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString());
+ if (plugins.size() > 0) {
+ for (auto plugin : plugins) {
+ MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString());
+ if (file->getOrigin() != origin.getID()) {
+ const std::vector<std::pair<int, std::wstring>> alternatives = file->getAlternatives();
+ if (std::find_if(alternatives.begin(), alternatives.end(), [&](const std::pair<int, std::wstring>& element) { return element.first == origin.getID(); }) == alternatives.end())
+ continue;
+ }
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(plugin.toLower());
+ if (iter != m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_ModSelected = true;
+ }
+ }
+ }
+ }
+ }
+ emit dataChanged(this->index(0, 0), this->index(m_ESPs.size() - 1, this->columnCount() - 1));
+}
void PluginList::refresh(const QString &profileName
, const DirectoryEntry &baseDirectory
- , const QString &pluginsFile
- , const QString &loadOrderFile
, const QString &lockedOrderFile)
{
ChangeBracket<PluginList> layoutChange(this);
- m_ESPsByName.clear();
- m_ESPsByPriority.clear();
- m_ESPs.clear();
-
QStringList primaryPlugins = m_GamePlugin->primaryPlugins();
m_CurrentProfile = profileName;
+ QStringList availablePlugins;
+
std::vector<FileEntry::Ptr> files = baseDirectory.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry::Ptr current = *iter;
+ for (FileEntry::Ptr current : files) {
if (current.get() == nullptr) {
continue;
}
QString filename = ToQString(current->getName());
+
+ availablePlugins.append(filename.toLower());
+
+ if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) {
+ continue;
+ }
+
QString extension = filename.right(3).toLower();
- if ((extension == "esp") || (extension == "esm")) {
+ if ((extension == "esp") || (extension == "esm") || (extension == "esl")) {
bool forceEnabled = Settings::instance().forceEnableCoreFiles() &&
std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end();
@@ -160,73 +184,78 @@ void PluginList::refresh(const QString &profileName }
m_ESPs.push_back(ESPInfo(filename, forceEnabled, originName, ToQString(current->getFullPath()), hasIni));
+ m_ESPs.rbegin()->m_Priority = -1;
} 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<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
- std::map<QString, int>::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;
- }
+ for (const auto &espName : m_ESPsByName) {
+ if (!availablePlugins.contains(espName.first)) {
+ m_ESPs[espName.second].m_Name = "";
}
+ }
- ++maxPriority;
+ m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(),
+ [](const ESPInfo &info) -> bool {
+ return info.m_Name.isEmpty();
+ }),
+ m_ESPs.end());
- // assign maximum priorities for plugins with unknown priority
- for (std::vector<ESPInfo>::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);
+ fixPriorities();
- for (size_t i = 0; i < m_ESPs.size(); ++i) {
- m_ESPs[i].m_Priority = i;
- }
- }
+ // functions in GamePlugins will use the IPluginList interface of this, so
+ // indices need to work. priority will be off however
+ updateIndices();
- 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<int>(m_ESPs.size()); ++i) {
- m_ESPs[i].m_Priority = i;
- }
+ GamePlugins *gamePlugins = m_GamePlugin->feature<GamePlugins>();
+ gamePlugins->readPluginLists(this);
- std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works
+ testMasters();
updateIndices();
- readEnabledFrom(pluginsFile);
-
readLockedOrderFrom(lockedOrderFile);
layoutChange.finish();
refreshLoadOrder();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
+ emit dataChanged(this->index(0, 0),
+ this->index(static_cast<int>(m_ESPs.size()), columnCount()));
m_Refreshed();
}
+void PluginList::fixPriorities()
+{
+ std::vector<std::pair<int, int>> espPrios;
+
+ for (int i = 0; i < m_ESPs.size(); ++i) {
+ int prio = m_ESPs[i].m_Priority;
+ if (prio == -1) {
+ prio = INT_MAX;
+ }
+ espPrios.push_back(std::make_pair(prio, i));
+ }
+
+ std::sort(espPrios.begin(), espPrios.end(),
+ [](const std::pair<int, int> &lhs, const std::pair<int, int> &rhs) {
+ return lhs.first < rhs.first;
+ });
+
+ for (int i = 0; i < espPrios.size(); ++i) {
+ m_ESPs[espPrios[i].second].m_Priority = i;
+ }
+}
void PluginList::enableESP(const QString &name, bool enable)
{
std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
if (iter != m_ESPsByName.end()) {
- m_ESPs[iter->second].m_Enabled = enable;
+ m_ESPs[iter->second].m_Enabled =
+ enable | m_ESPs[iter->second].m_ForceEnabled;
emit writePluginsList();
} else {
@@ -239,8 +268,8 @@ void PluginList::enableAll() {
if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- iter->m_Enabled = true;
+ for (ESPInfo &info : m_ESPs) {
+ info.m_Enabled = true;
}
emit writePluginsList();
}
@@ -251,9 +280,9 @@ void PluginList::disableAll() {
if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
- for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (!iter->m_ForceEnabled) {
- iter->m_Enabled = false;
+ for (ESPInfo &info : m_ESPs) {
+ if (!info.m_ForceEnabled) {
+ info.m_Enabled = false;
}
}
emit writePluginsList();
@@ -302,89 +331,6 @@ bool PluginList::isEnabled(int index) return m_ESPs.at(index).m_Enabled;
}
-bool PluginList::readLoadOrder(const QString &fileName)
-{
- std::set<QString> availableESPs;
- for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- availableESPs.insert(iter->m_Name.toLower());
- }
-
- m_ESPLoadOrder.clear();
-
- int priority = 0;
-
- QStringList primaryPlugins = m_GamePlugin->primaryPlugins();
- for (const QString &plugin : primaryPlugins) {
- if (availableESPs.find(plugin) != availableESPs.end()) {
- m_ESPLoadOrder[plugin] = priority++;
- }
- }
-
- QFile file(fileName);
- if (!file.open(QIODevice::ReadOnly)) {
- return false;
- }
- if (file.size() == 0) {
- // MO stores at least a header in the file. if it's completely empty the file is broken
- 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<ESPInfo>::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<QString, int>::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());
- emit writePluginsList();
- }
- }
- }
-
- file.close();
-
- testMasters();
-}
-
-
void PluginList::readLockedOrderFrom(const QString &fileName)
{
m_LockedOrder.clear();
@@ -401,7 +347,26 @@ void PluginList::readLockedOrderFrom(const QString &fileName) if ((line.size() > 0) && (line.at(0) != '#')) {
QList<QByteArray> fields = line.split('|');
if (fields.count() == 2) {
- m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt();
+ int priority = fields.at(1).trimmed().toInt();
+ QString name = QString::fromUtf8(fields.at(0));
+ // Avoid locking a force-enabled plugin
+ if (!m_ESPs[m_ESPsByName.at(name)].m_ForceEnabled) {
+ // Is this an open and unclaimed priority?
+ if (m_ESPs[m_ESPsByPriority.at(priority)].m_ForceEnabled ||
+ std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair<QString, int> &a) { return a.second == priority; }) != m_LockedOrder.end()) {
+ // Attempt to find a priority but step over force-enabled plugins and already-set locks
+ int calcPriority = priority;
+ do {
+ ++calcPriority;
+ } while (calcPriority < m_ESPsByPriority.size() || (m_ESPs[m_ESPsByPriority.at(calcPriority)].m_ForceEnabled &&
+ std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair<QString, int> &a) { return a.second == calcPriority; }) != m_LockedOrder.end()));
+ // If we have a match, we can reassign the priority...
+ if (calcPriority < m_ESPsByPriority.size())
+ m_LockedOrder[name] = calcPriority;
+ } else {
+ m_LockedOrder[name] = priority;
+ }
+ }
} else {
reportError(tr("The file containing locked plugin indices is broken"));
break;
@@ -411,53 +376,6 @@ void PluginList::readLockedOrderFrom(const QString &fileName) 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 (writtenCount == 0) {
- qWarning("plugin list would be empty, this is almost certainly wrong. Not saving.");
- } else {
- if (file.commitIfDifferent(m_LastSaveHash[fileName])) {
- qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData());
- }
- }
-}
-
-
void PluginList::writeLockedOrder(const QString &fileName) const
{
SafeWriteFile file(fileName);
@@ -472,14 +390,13 @@ void PluginList::writeLockedOrder(const QString &fileName) const }
-void PluginList::saveTo(const QString &pluginFileName
- , const QString &loadOrderFileName
- , const QString &lockedOrderFileName
+void PluginList::saveTo(const QString &lockedOrderFileName
, const QString& deleterFileName
, bool hideUnchecked) const
{
- writePlugins(pluginFileName, false);
- writePlugins(loadOrderFileName, true);
+ GamePlugins *gamePlugins = m_GamePlugin->feature<GamePlugins>();
+ gamePlugins->writePluginLists(this);
+
writeLockedOrder(lockedOrderFileName);
if (hideUnchecked) {
@@ -553,7 +470,7 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) int PluginList::enabledCount() const
{
int enabled = 0;
- foreach (auto info, m_ESPs) {
+ for (const auto &info : m_ESPs) {
if (info.m_Enabled) {
++enabled;
}
@@ -569,7 +486,10 @@ bool PluginList::isESPLocked(int index) const void PluginList::lockESPIndex(int index, bool lock)
{
if (lock) {
- m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder;
+ if (!m_ESPs.at(index).m_ForceEnabled)
+ m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder;
+ else
+ return;
} else {
auto iter = m_LockedOrder.find(getName(index).toLower());
if (iter != m_LockedOrder.end()) {
@@ -607,7 +527,7 @@ void PluginList::refreshLoadOrder() bool savePluginsList = false;
// 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);
+ auto nameIter = m_ESPsByName.find(iter->second.toLower());
if (nameIter != m_ESPsByName.end()) {
// locked esp exists
@@ -642,6 +562,17 @@ void PluginList::disconnectSlots() { m_PluginStateChanged.disconnect_all_slots();
}
+QStringList PluginList::pluginNames() const
+{
+ QStringList result;
+
+ for (const ESPInfo &info : m_ESPs) {
+ result.append(info.m_Name);
+ }
+
+ return result;
+}
+
IPluginList::PluginStates PluginList::state(const QString &name) const
{
auto iter = m_ESPsByName.find(name.toLower());
@@ -652,6 +583,38 @@ IPluginList::PluginStates PluginList::state(const QString &name) const }
}
+void PluginList::setState(const QString &name, PluginStates state) {
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter != m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) ||
+ m_ESPs[iter->second].m_ForceEnabled;
+ } else {
+ qWarning("plugin %s not found", qPrintable(name));
+ }
+}
+
+void PluginList::setLoadOrder(const QStringList &pluginList)
+{
+ for (ESPInfo &info : m_ESPs) {
+ info.m_Priority = -1;
+ }
+ int maxPriority = 0;
+ for (const QString &plugin : pluginList) {
+ auto iter = m_ESPsByName.find(plugin.toLower());
+ if (iter !=m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Priority = maxPriority++;
+ }
+ }
+
+ // use old priorities
+ for (ESPInfo &info : m_ESPs) {
+ if (info.m_Priority == -1) {
+ info.m_Priority = maxPriority++;
+ }
+ }
+ updateIndices();
+}
+
int PluginList::priority(const QString &name) const
{
auto iter = m_ESPsByName.find(name.toLower());
@@ -682,6 +645,26 @@ bool PluginList::isMaster(const QString &name) const }
}
+bool PluginList::isLight(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_IsLight;
+ }
+}
+
+bool PluginList::isLightFlagged(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_IsLightFlagged;
+ }
+}
+
QStringList PluginList::masters(const QString &name) const
{
auto iter = m_ESPsByName.find(name.toLower());
@@ -689,7 +672,7 @@ QStringList PluginList::masters(const QString &name) const return QStringList();
} else {
QStringList result;
- foreach (const QString &master, m_ESPs[iter->second].m_Masters) {
+ for (const QString &master : m_ESPs[iter->second].m_Masters) {
result.append(master);
}
return result;
@@ -731,10 +714,16 @@ 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) {
+ if (m_ESPs[i].m_Priority < 0) {
+ continue;
+ }
+ if (m_ESPs[i].m_Priority >= static_cast<int>(m_ESPs.size())) {
+ qCritical("invalid priority %d", m_ESPs[i].m_Priority);
+ continue;
+ }
m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
- m_ESPsByPriority[m_ESPs[i].m_Priority] = i;
+ m_ESPsByPriority.at(static_cast<size_t>(m_ESPs[i].m_Priority)) = i;
}
}
@@ -742,7 +731,7 @@ void PluginList::updateIndices() int PluginList::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid()) {
- return m_ESPs.size();
+ return static_cast<int>(m_ESPs.size());
} else {
return 0;
}
@@ -759,18 +748,18 @@ void PluginList::testMasters() // emit layoutAboutToBeChanged();
std::set<QString> enabledMasters;
- for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- if (iter->m_Enabled) {
- enabledMasters.insert(iter->m_Name.toLower());
+ for (const auto& iter: m_ESPs) {
+ 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);
+ for (auto& iter: m_ESPs) {
+ iter.m_MasterUnset.clear();
+ if (iter.m_Enabled) {
+ for (const auto& master: iter.m_Masters) {
+ if (enabledMasters.find(master.toLower()) == enabledMasters.end()) {
+ iter.m_MasterUnset.insert(master);
}
}
}
@@ -786,7 +775,7 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if ((role == Qt::DisplayRole)
|| (role == Qt::EditRole)) {
switch (modelIndex.column()) {
- case COL_NAME: {
+ case COL_NAME: {
return m_ESPs[index].m_Name;
} break;
case COL_PRIORITY: {
@@ -796,7 +785,21 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (m_ESPs[index].m_LoadOrder == -1) {
return QString();
} else {
- return QString("%1").arg(m_ESPs[index].m_LoadOrder, 2, 16, QChar('0')).toUpper();
+ int numESLs = 0;
+ std::vector<ESPInfo> sortESPs(m_ESPs);
+ std::sort(sortESPs.begin(), sortESPs.end());
+ for (auto sortedESP: sortESPs) {
+ if (sortedESP.m_LoadOrder == m_ESPs[index].m_LoadOrder)
+ break;
+ if ((sortedESP.m_IsLight || sortedESP.m_IsLightFlagged) && sortedESP.m_LoadOrder != -1)
+ ++numESLs;
+ }
+ if (m_ESPs[index].m_IsLight || m_ESPs[index].m_IsLightFlagged) {
+ int ESLpos = 254 + ((numESLs+1) / 4096);
+ return QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs)%4096).toUpper();
+ } else {
+ return QString("%1").arg(m_ESPs[index].m_LoadOrder - numESLs, 2, 16, QChar('0')).toUpper();
+ }
}
} break;
default: {
@@ -811,15 +814,22 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const }
} else if (role == Qt::ForegroundRole) {
if ((modelIndex.column() == COL_NAME) &&
- m_ESPs[index].m_ForceEnabled) {
+ m_ESPs[index].m_ForceEnabled) {
return QBrush(Qt::gray);
}
+ } else if (role == Qt::BackgroundRole
+ || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) {
+ if (m_ESPs[index].m_ModSelected) {
+ return QColor(0, 0, 255, 32);
+ } else {
+ return QVariant();
+ }
} 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) {
+ } else if (m_ESPs[index].m_IsLight || m_ESPs[index].m_IsLightFlagged) {
result.setItalic(true);
}
return result;
@@ -839,7 +849,9 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const }
}
if (m_ESPs[index].m_ForceEnabled) {
- toolTip += tr("This plugin can't be disabled (enforced by the game)");
+ QString text = tr("<b>Origin</b>: %1").arg(m_ESPs[index].m_OriginName);
+ text += tr("<br><b><i>This plugin can't be disabled (enforced by the game).</i></b>");
+ toolTip += text;
} else {
QString text = tr("<b>Origin</b>: %1").arg(m_ESPs[index].m_OriginName);
if (m_ESPs[index].m_Author.size() > 0) {
@@ -861,9 +873,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (m_ESPs[index].m_HasIni) {
text += "<br>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 += "<br>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;
}
@@ -886,9 +895,6 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (m_ESPs[index].m_HasIni) {
result.append(":/MO/gui/attachment");
}
- if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) {
- result.append(":/MO/gui/edit_clear");
- }
return result;
}
return QVariant();
@@ -903,7 +909,8 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int bool result = false;
if (role == Qt::CheckStateRole) {
- m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
+ m_ESPs[modIndex.row()].m_Enabled =
+ value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled;
emit dataChanged(modIndex, modIndex);
refreshLoadOrder();
@@ -927,7 +934,9 @@ bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int try {
m_PluginStateChanged(modName, newState);
testMasters();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
+ emit dataChanged(
+ this->index(0, 0),
+ this->index(static_cast<int>(m_ESPs.size()), columnCount()));
} catch (const std::exception &e) {
qCritical("failed to invoke state changed notification: %s", e.what());
} catch (...) {
@@ -983,16 +992,18 @@ void PluginList::setPluginPriority(int row, int &newPriority) {
int newPriorityTemp = newPriority;
- if (!m_ESPs[row].m_IsMaster) {
+ if (!m_ESPs[row].m_IsMaster && !m_ESPs[row].m_IsLight) {
// don't allow esps to be moved above esms
while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
- m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
+ (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster ||
+ m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight)) {
++newPriorityTemp;
}
} else {
// don't allow esms to be moved below esps
while ((newPriorityTemp > 0) &&
- !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
+ !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster &&
+ !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight) {
--newPriorityTemp;
}
// also don't allow "regular" esms to be moved above primary plugins
@@ -1003,8 +1014,10 @@ void PluginList::setPluginPriority(int row, int &newPriority) }
// enforce valid range
- if (newPriorityTemp < 0) newPriorityTemp = 0;
- else if (newPriorityTemp >= static_cast<int>(m_ESPsByPriority.size())) newPriorityTemp = m_ESPsByPriority.size() - 1;
+ if (newPriorityTemp < 0)
+ newPriorityTemp = 0;
+ else if (newPriorityTemp >= static_cast<int>(m_ESPsByPriority.size()))
+ newPriorityTemp = static_cast<int>(m_ESPsByPriority.size()) - 1;
try {
int oldPriority = m_ESPs.at(row).m_Priority;
@@ -1086,11 +1099,11 @@ bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, row = parent.row();
}
- int newPriority = 0;
+ int newPriority;
if ((row < 0) ||
(row >= static_cast<int>(m_ESPs.size()))) {
- newPriority = m_ESPs.size();
+ newPriority = static_cast<int>(m_ESPs.size());
} else {
newPriority = m_ESPs[row].m_Priority;
}
@@ -1192,12 +1205,15 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath,
bool hasIni)
: m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled),
- m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni)
+ m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_ModSelected(false)
{
try {
ESP::File file(ToWString(fullPath));
m_IsMaster = file.isMaster();
- m_IsDummy = file.isDummy();
+ auto extension = name.right(3).toLower();
+ m_IsLight = (extension == "esl");
+ m_IsLightFlagged = file.isLight();
+
m_Author = QString::fromLatin1(file.author().c_str());
m_Description = QString::fromLatin1(file.description().c_str());
std::set<std::string> masters = file.masters();
@@ -1205,13 +1221,14 @@ PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, m_Masters.insert(QString(iter->c_str()));
}
} catch (const std::exception &e) {
- qCritical("failed to parse esp file %s: %s", qPrintable(fullPath), e.what());
+ qCritical("failed to parse plugin file %s: %s", qPrintable(fullPath), e.what());
m_IsMaster = false;
- m_IsDummy = false;
+ m_IsLight = false;
+ m_IsLightFlagged = false;
}
}
-void PluginList::managedGameChanged(IPluginGame const *gamePlugin)
+void PluginList::managedGameChanged(const IPluginGame *gamePlugin)
{
m_GamePlugin = gamePlugin;
}
diff --git a/src/pluginlist.h b/src/pluginlist.h index 9fe6eeac..4762f79f 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <directoryentry.h>
#include <ipluginlist.h>
+#include "profile.h"
namespace MOBase { class IPluginGame; }
#include <QString>
@@ -110,15 +111,11 @@ public: *
* @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);
/**
@@ -165,25 +162,13 @@ public: 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
+ void saveTo(const QString &lockedOrderFileName
, const QString &deleterFileName
, bool hideUnchecked) const;
@@ -216,19 +201,26 @@ public: static QString getColumnName(int column);
static QString getColumnToolTip(int column);
+ void highlightPlugins(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile);
+
void refreshLoadOrder();
void disconnectSlots();
public:
+ virtual QStringList pluginNames() const override;
virtual PluginStates state(const QString &name) const;
+ virtual void setState(const QString &name, PluginStates state) override;
virtual int priority(const QString &name) const;
virtual int loadOrder(const QString &name) const;
+ virtual bool onRefreshed(const std::function<void()> &callback);
virtual bool isMaster(const QString &name) const;
+ virtual bool isLight(const QString &name) const;
+ virtual bool isLightFlagged(const QString &name) const;
virtual QStringList masters(const QString &name) const;
virtual QString origin(const QString &name) const;
- virtual bool onRefreshed(const std::function<void()> &callback);
+ virtual void setLoadOrder(const QStringList &pluginList) override;
virtual bool onPluginMoved(const std::function<void (const QString &, int, int)> &func);
virtual bool onPluginStateChanged(const std::function<void (const QString &, PluginStates)> &func) override;
@@ -288,12 +280,18 @@ private: FILETIME m_Time;
QString m_OriginName;
bool m_IsMaster;
- bool m_IsDummy;
+ bool m_IsLight;
+ bool m_IsLightFlagged;
+ bool m_ModSelected;
QString m_Author;
QString m_Description;
bool m_HasIni;
std::set<QString> m_Masters;
mutable std::set<QString> m_MasterUnset;
+ bool operator < (const ESPInfo& str) const
+ {
+ return (m_LoadOrder < str.m_LoadOrder);
+ }
};
struct AdditionalInfo {
@@ -309,16 +307,16 @@ 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<int> rows, int newPriority);
void testMasters();
+ void fixPriorities();
+
private:
std::vector<ESPInfo> m_ESPs;
@@ -327,10 +325,6 @@ private: std::map<QString, int> m_ESPsByName;
std::vector<int> 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<QString, int> m_ESPLoadOrder;
std::map<QString, int> m_LockedOrder;
std::map<QString, AdditionalInfo> m_AdditionalInfo; // maps esp names to boss information
@@ -338,16 +332,13 @@ private: QString m_CurrentProfile;
QFontMetrics m_FontMetrics;
- QTextCodec *m_Utf8Codec;
- QTextCodec *m_LocalCodec;
-
SignalRefreshed m_Refreshed;
SignalPluginMoved m_PluginMoved;
SignalPluginStateChanged m_PluginStateChanged;
QTemporaryFile m_TempFile;
- MOBase::IPluginGame const *m_GamePlugin;
+ const MOBase::IPluginGame *m_GamePlugin;
};
diff --git a/src/pluginlistview.cpp b/src/pluginlistview.cpp new file mode 100644 index 00000000..0fcf8183 --- /dev/null +++ b/src/pluginlistview.cpp @@ -0,0 +1,58 @@ +#include "pluginlistview.h" +#include <QUrl> +#include <QMimeData> +#include <QProxyStyle> + + +class PluginListViewStyle : public QProxyStyle { +public: + PluginListViewStyle(QStyle *style, int indentation); + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; +private: + int m_Indentation; +}; + +PluginListViewStyle::PluginListViewStyle(QStyle *style, int indentation) + : QProxyStyle(style), m_Indentation(indentation) +{ +} + +void PluginListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { + QStyleOption opt(*option); + opt.rect.setLeft(m_Indentation); + if (widget) { + opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok + } + QProxyStyle::drawPrimitive(element, &opt, painter, widget); + } + else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } +} + +PluginListView::PluginListView(QWidget *parent) + : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) +{ + setVerticalScrollBar(m_Scrollbar); +} + +void PluginListView::dragEnterEvent(QDragEnterEvent *event) +{ + emit dropModeUpdate(event->mimeData()->hasUrls()); + + QTreeView::dragEnterEvent(event); +} + +void PluginListView::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +} + +#pragma once diff --git a/src/pluginlistview.h b/src/pluginlistview.h new file mode 100644 index 00000000..bdd4ee61 --- /dev/null +++ b/src/pluginlistview.h @@ -0,0 +1,24 @@ +#ifndef PLUGINLISTVIEW_H +#define PLUGINLISTVIEW_H + +#include <QTreeView> +#include <QDragEnterEvent> +#include "viewmarkingscrollbar.h" + +class PluginListView : public QTreeView +{ + Q_OBJECT +public: + explicit PluginListView(QWidget *parent = 0); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void setModel(QAbstractItemModel *model); +signals: + void dropModeUpdate(bool dropOnRows); + + public slots: +private: + + ViewMarkingScrollBar *m_Scrollbar; +}; + +#endif // PLUGINLISTVIEW_H diff --git a/src/profile.cpp b/src/profile.cpp index 21fb0b7a..bbb5b814 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -20,12 +20,13 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "profile.h" #include "modinfo.h" -#include "safewritefile.h" +#include "settings.h" #include <utility.h> #include <error_report.h> #include "appconfig.h" #include <iplugingame.h> #include <report.h> +#include <safewritefile.h> #include <bsainvalidation.h> #include <dataarchives.h> @@ -38,6 +39,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QStringList> // for QStringList #include <QtDebug> // for qDebug, qWarning, etc #include <QtGlobal> // for qPrintable +#include <QBuffer> +#include <QDirIterator> #include <Windows.h> @@ -65,12 +68,14 @@ void Profile::touchFile(QString fileName) } Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDefaultSettings) - : m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) + : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(gamePlugin) { - QString profilesDir = qApp->property("dataPath").toString() + "/" + ToQString(AppConfig::profilesPath()); + QString profilesDir = Settings::instance().getProfileDirectory(); QDir profileBase(profilesDir); + m_Settings = new QSettings(profileBase.absoluteFilePath("settings.ini")); + QString fixedName = name; if (!fixDirectoryName(fixedName)) { throw MyException(tr("invalid profile name %1").arg(name)); @@ -108,39 +113,41 @@ Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDef Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) : m_Directory(directory) , m_GamePlugin(gamePlugin) - , m_ModListWriter(std::bind(&Profile::writeModlistNow, this)) + , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) { assert(gamePlugin != nullptr); + m_Settings = new QSettings(directory.absoluteFilePath("settings.ini"), + QSettings::IniFormat); + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { qWarning("missing modlist.txt in %s", qPrintable(directory.path())); touchFile(m_Directory.filePath("modlist.txt")); } - IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION - | IPluginGame::MODS + IPluginGame::ProfileSettings settings = IPluginGame::MODS | IPluginGame::SAVEGAMES; gamePlugin->initializeProfile(directory, settings); - 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_ModListWriter(std::bind(&Profile::writeModlistNow, this)) + , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) , m_GamePlugin(reference.m_GamePlugin) { + m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), + QSettings::IniFormat); refreshModStatus(); } Profile::~Profile() { + delete m_Settings; m_ModListWriter.writeImmediately(true); } @@ -149,7 +156,22 @@ bool Profile::exists() const return m_Directory.exists(); } -void Profile::writeModlistNow() +void Profile::writeModlist() +{ + m_ModListWriter.write(); +} + +void Profile::writeModlistNow(bool onlyIfPending) +{ + m_ModListWriter.writeImmediately(onlyIfPending); +} + +void Profile::cancelModlistWrite() +{ + m_ModListWriter.cancel(); +} + +void Profile::doWriteModlist() { if (!m_Directory.exists()) return; @@ -162,7 +184,7 @@ void Profile::writeModlistNow() return; } - for (int i = m_ModStatus.size() - 1; i >= 0; --i) { + for (int i = static_cast<int>(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) { @@ -216,27 +238,89 @@ void Profile::createTweakedIniFile() error = true; } - if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"0", 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))); +} + +// static +void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) +{ + QDir profilesDir(Settings::instance().getProfileDirectory()); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + QDirIterator profileIter(profilesDir); + while (profileIter.hasNext()) { + profileIter.next(); + QFile modList(profileIter.filePath() + "/modlist.txt"); + if (modList.exists()) + renameModInList(modList, oldName, newName); + else + qWarning("Profile has no modlist.txt : %s", qPrintable(profileIter.filePath())); + } +} + +// static +void Profile::renameModInList(QFile &modList, const QString &oldName, const QString &newName) +{ + if (!modList.open(QIODevice::ReadOnly)) { + reportError(tr("failed to open %1").arg(modList.fileName())); + return; + } + + QBuffer outBuffer; + outBuffer.open(QIODevice::WriteOnly); + + int renamed = 0; + while (!modList.atEnd()) { + QByteArray line = modList.readLine(); + + if (line.length() == 0) { + // ignore empty lines + qWarning("mod list contained invalid data: empty line"); + continue; } - if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str())) { - error = true; + 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; + ++renamed; + } + outBuffer.write(modName.toUtf8().constData()); + outBuffer.write("\r\n"); } + modList.close(); - if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + if (renamed) { + modList.open(QIODevice::WriteOnly); + modList.write(outBuffer.buffer()); + modList.close(); } - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); -} + if (renamed) + qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", + renamed, qPrintable(oldName), qPrintable(newName), qPrintable(modList.fileName())); +} void Profile::refreshModStatus() { + writeModlistNow(true); // if there are pending changes write them first + QFile file(getModlistFileName()); if (!file.open(QIODevice::ReadOnly)) { throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); @@ -310,7 +394,7 @@ void Profile::refreshModStatus() // 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); + ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<int>(i)); if (modInfo->alwaysEnabled()) { m_ModStatus[i].m_Enabled = true; } @@ -339,7 +423,7 @@ void Profile::refreshModStatus() if (topInsert < 0) { int offset = topInsert * -1; for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast<unsigned int>(i)); if (modInfo->getFixedPriority() == INT_MAX) { continue; } @@ -468,7 +552,9 @@ void Profile::setModPriority(unsigned int index, int &newPriority) return; } - int newPriorityTemp = (std::max)(0, (std::min<int>)(m_ModStatus.size() - 1, newPriority)); + int newPriorityTemp = + (std::max)(0, (std::min<int>)(static_cast<int>(m_ModStatus.size()) - 1, + newPriority)); // don't try to place below overwrite while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || @@ -497,7 +583,7 @@ void Profile::setModPriority(unsigned int index, int &newPriority) Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) { - QString profileDirectory = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()) + "/" + name; + QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; reference.copyFilesTo(profileDirectory); return new Profile(QDir(profileDirectory), gamePlugin); } @@ -592,7 +678,9 @@ bool Profile::invalidationActive(bool *supported) const } return false; } else { - *supported = false; + if (supported != nullptr) { + *supported = false; + } } return false; } @@ -633,11 +721,12 @@ bool Profile::enableLocalSaves(bool enable) 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); + 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) { @@ -651,6 +740,30 @@ bool Profile::enableLocalSaves(bool enable) return true; } +bool Profile::localSettingsEnabled() const +{ + return m_Directory.exists(getIniFileName()); +} + +bool Profile::enableLocalSettings(bool enable) +{ + // TODO: this currently assumes game settings are stored in an ini file. + // This shall become very interesting when a game stores its settings in the + // registry + QString backupFile = getIniFileName() + "_"; + if (enable) { + if (m_Directory.exists(backupFile)) { + shellRename(backupFile, getIniFileName()); + } else { + IPluginGame *game = qApp->property("managed_game").value<IPluginGame *>(); + game->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); + } + } else { + shellRename(getIniFileName(), backupFile); + } + + return true; +} QString Profile::getModlistFileName() const { @@ -705,8 +818,24 @@ QString Profile::savePath() const void Profile::rename(const QString &newName) { - QDir profileDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath())); + QDir profileDir(Settings::instance().getProfileDirectory()); profileDir.rename(name(), newName); m_Directory = profileDir.absoluteFilePath(newName); } +QVariant Profile::setting(const QString §ion, const QString &name, + const QVariant &fallback) +{ + return m_Settings->value(section + "/" + name, fallback); +} + +void Profile::storeSetting(const QString §ion, const QString &name, + const QVariant &value) +{ + m_Settings->setValue(section + "/" + name, value); +} + +void Profile::removeSetting(const QString §ion, const QString &name) +{ + m_Settings->remove(section + "/" + name); +}
\ No newline at end of file diff --git a/src/profile.h b/src/profile.h index 39e8ff5f..1fcad046 100644 --- a/src/profile.h +++ b/src/profile.h @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDir> #include <QObject> #include <QString> +#include <QSettings> #include <boost/shared_ptr.hpp> @@ -88,7 +89,14 @@ public: **/ static Profile *createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin); - MOBase::DelayedFileWriter &modlistWriter() { return m_ModListWriter; } + + static void renameModInAllProfiles(const QString& oldName, const QString& newName); + + void writeModlist(); + + void writeModlistNow(bool onlyIfPending=false); + + void cancelModlistWrite(); /** * @brief test if this profile uses archive invalidation @@ -112,20 +120,32 @@ public: /** * @return true if this profile uses local save games */ - bool localSavesEnabled() const; + virtual bool localSavesEnabled() const override; /** * @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 + * when disabling the user will be asked if he wants to remove the save games + * in the profile * @param enable if true, local saves are enabled, otherewise they are disabled */ bool enableLocalSaves(bool enable); /** + * @return true if this profile uses local ini files + */ + virtual bool localSettingsEnabled() const override; + + /** + * @brief enables or disables the use of local ini files for this profile + * disabling this does not delete existing ini files but the global ones will be used + * @param enable + */ + bool enableLocalSettings(bool enable); + + /** * @return name of the profile (this is identical to its directory name) **/ - QString name() const { return m_Directory.dirName(); } + virtual QString name() const override { return m_Directory.dirName(); } /** * @return the path of the plugins file in this profile @@ -174,7 +194,7 @@ public: /** * @return path to this profile **/ - QString absolutePath() const; + virtual QString absolutePath() const override; /** * @return path to this profile's save games @@ -213,7 +233,7 @@ public: * * @return number of mods for which the profile has status information **/ - unsigned int numMods() const { return m_ModStatus.size(); } + size_t numMods() const { return m_ModStatus.size(); } /** * @return the number of mods that can be enabled and where the priority can be modified @@ -267,6 +287,13 @@ public: void dumpModStatus() const; + QVariant setting(const QString §ion, const QString &name, + const QVariant &fallback = QVariant()); + + void storeSetting(const QString §ion, const QString &name, + const QVariant &value); + void removeSetting(const QString §ion, const QString &name); + signals: /** @@ -278,7 +305,8 @@ signals: public slots: - void writeModlistNow(); + // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead + void doWriteModlist(); private: @@ -307,11 +335,15 @@ private: void touchFile(QString fileName); void finishChangeStatus() const; + static void renameModInList(QFile &modList, const QString &oldName, const QString &newName); + private: QDir m_Directory; - MOBase::IPluginGame const * const m_GamePlugin; + QSettings *m_Settings; + + const MOBase::IPluginGame *m_GamePlugin; mutable QByteArray m_LastModlistHash; std::vector<ModStatus> m_ModStatus; diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 4d0b3389..04c1876d 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -28,8 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "report.h"
#include "transfersavesdialog.h"
#include "utility.h"
+#include "settings.h"
-#include <Qt>
#include <QDir>
#include <QDirIterator>
#include <QInputDialog>
@@ -56,9 +56,8 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c {
ui->setupUi(this);
- QDir profilesDir(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::profilesPath()));
+ QDir profilesDir(Settings::instance().getProfileDirectory());
profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
- m_ProfilesList = findChild<QListWidget*>("profilesList");
QDirIterator profileIter(profilesDir);
@@ -66,17 +65,15 @@ ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame c profileIter.next();
QListWidgetItem *item = addItem(profileIter.filePath());
if (profileName == profileIter.fileName()) {
- m_ProfilesList->setCurrentItem(item);
+ ui->profilesList->setCurrentItem(item);
}
}
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
-
BSAInvalidation *invalidation = game->feature<BSAInvalidation>();
if (invalidation == nullptr) {
- invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
- invalidationBox->setEnabled(false);
+ ui->invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
+ ui->invalidationBox->setEnabled(false);
}
}
@@ -89,13 +86,13 @@ 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;
+ if (ui->profilesList->count() == 0) {
+ QPoint pos = ui->profilesList->mapToGlobal(QPoint(0, 0));
+ pos.rx() += ui->profilesList->width() / 2;
+ pos.ry() += (ui->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);
+ "ATTENTION: Run the game at least once before creating a profile!"), ui->profilesList);
}
}
@@ -108,7 +105,7 @@ void ProfilesDialog::on_closeButton_clicked() QListWidgetItem *ProfilesDialog::addItem(const QString &name)
{
QDir profileDir(name);
- QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), ui->profilesList);
try {
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir, m_Game))));
m_FailState = false;
@@ -121,10 +118,9 @@ QListWidgetItem *ProfilesDialog::addItem(const QString &name) void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings)
{
try {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, m_Game, useDefaultSettings))));
- profilesList->addItem(newItem);
+ ui->profilesList->addItem(newItem);
m_FailState = false;
} catch (const std::exception&) {
m_FailState = true;
@@ -135,10 +131,9 @@ void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) void ProfilesDialog::createProfile(const QString &name, const Profile &reference)
{
try {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
- QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList);
newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game))));
- profilesList->addItem(newItem);
+ ui->profilesList->addItem(newItem);
m_FailState = false;
} catch (const std::exception&) {
m_FailState = true;
@@ -164,20 +159,23 @@ void ProfilesDialog::on_addProfileButton_clicked() 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);
+ 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<QListWidget*>("profilesList");
-
try {
- const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ const Profile::Ptr currentProfile = ui->profilesList->currentItem()
+ ->data(Qt::UserRole)
+ .value<Profile::Ptr>();
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"));
+ QMessageBox::warning(this, tr("Invalid name"),
+ tr("Invalid profile name"));
}
}
}
@@ -188,14 +186,11 @@ void ProfilesDialog::on_removeProfileButton_clicked() QMessageBox::Yes | QMessageBox::No);
if (confirmBox.exec() == QMessageBox::Yes) {
- QListWidget *profilesList = findChild<QListWidget*>("profilesList");
-
- Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
QString profilePath;
if (currentProfile.get() == nullptr) {
- profilePath = qApp->property("dataPath").toString()
- + "/" + QString::fromStdWString(AppConfig::profilesPath())
- + "/" + profilesList->currentItem()->text();
+ profilePath = Settings::instance().getProfileDirectory()
+ + "/" + ui->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) {
@@ -206,7 +201,7 @@ void ProfilesDialog::on_removeProfileButton_clicked() // we have to get rid of the it before deleting the directory
profilePath = currentProfile->absolutePath();
}
- QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow());
+ QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow());
if (item != nullptr) {
delete item;
}
@@ -271,53 +266,50 @@ void ProfilesDialog::on_invalidationBox_stateChanged(int state) void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem*)
{
- QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
- QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox");
- QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton");
- QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton");
- QPushButton *transferButton = findChild<QPushButton*>("transferButton");
- QPushButton *renameButton = findChild<QPushButton*>("renameButton");
-
if (current != nullptr) {
if (!current->data(Qt::UserRole).isValid()) return;
const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>();
try {
bool invalidationSupported = false;
- invalidationBox->blockSignals(true);
- invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported));
- invalidationBox->setEnabled(invalidationSupported);
- invalidationBox->blockSignals(false);
+ ui->invalidationBox->blockSignals(true);
+ ui->invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported));
+ ui->invalidationBox->setEnabled(invalidationSupported);
+ ui->invalidationBox->blockSignals(false);
bool localSaves = currentProfile->localSavesEnabled();
- transferButton->setEnabled(localSaves);
+ ui->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);
+ ui->localSavesBox->blockSignals(true);
+ ui->localSavesBox->setChecked(localSaves);
+ ui->localSavesBox->blockSignals(false);
- copyButton->setEnabled(true);
- removeButton->setEnabled(true);
- renameButton->setEnabled(true);
+ ui->copyProfileButton->setEnabled(true);
+ ui->removeProfileButton->setEnabled(true);
+ ui->renameButton->setEnabled(true);
+
+ ui->localIniFilesBox->blockSignals(true);
+ ui->localIniFilesBox->setChecked(currentProfile->localSettingsEnabled());
+ ui->localIniFilesBox->blockSignals(false);
} 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);
+ ui->copyProfileButton->setEnabled(false);
+ ui->removeProfileButton->setEnabled(false);
+ ui->renameButton->setEnabled(false);
+ ui->invalidationBox->setChecked(false);
}
} else {
- invalidationBox->setChecked(false);
- copyButton->setEnabled(false);
- removeButton->setEnabled(false);
- renameButton->setEnabled(false);
+ ui->invalidationBox->setChecked(false);
+ ui->copyProfileButton->setEnabled(false);
+ ui->removeProfileButton->setEnabled(false);
+ ui->renameButton->setEnabled(false);
}
}
void ProfilesDialog::on_localSavesBox_stateChanged(int state)
{
- Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
if (currentProfile->enableLocalSaves(state == Qt::Checked)) {
ui->transferButton->setEnabled(state == Qt::Checked);
@@ -329,7 +321,17 @@ void ProfilesDialog::on_localSavesBox_stateChanged(int state) void ProfilesDialog::on_transferButton_clicked()
{
- const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ const Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
TransferSavesDialog transferDialog(*currentProfile, m_Game, this);
transferDialog.exec();
}
+
+void ProfilesDialog::on_localIniFilesBox_stateChanged(int state)
+{
+ Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+
+ if (!currentProfile->enableLocalSettings(state == Qt::Checked)) {
+ // revert checkbox-state
+ ui->localIniFilesBox->setChecked(state != Qt::Checked);
+ }
+}
diff --git a/src/profilesdialog.h b/src/profilesdialog.h index 0e79b94b..cab25f6a 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -61,6 +61,9 @@ protected: virtual void showEvent(QShowEvent *event);
+private slots:
+ void on_localIniFilesBox_stateChanged(int state);
+
private:
QListWidgetItem *addItem(const QString &name);
diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui index 0c952877..9b4e7e62 100644 --- a/src/profilesdialog.ui +++ b/src/profilesdialog.ui @@ -6,8 +6,8 @@ <rect>
<x>0</x>
<y>0</y>
- <width>471</width>
- <height>318</height>
+ <width>482</width>
+ <height>332</height>
</rect>
</property>
<property name="windowTitle">
@@ -46,6 +46,16 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
+ <widget class="QCheckBox" name="localIniFilesBox">
+ <property name="text">
+ <string>Local Game Settings</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="invalidationBox">
<property name="toolTip">
<string>This ensures data files from mods are actually used. You want to enable this unless you use a different tool for Archive Invalidation.</string>
diff --git a/src/resources.qrc b/src/resources.qrc index 8434b367..618d3cfc 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -15,6 +15,7 @@ <file>resources/document-properties.png</file> <file>resources/go-up.png</file> <file>resources/go-down.png</file> + <file>resources/switch-instance-icon.png</file> <file alias="profiles">resources/contact-new.png</file> <file alias="settings">resources/preferences-system.png</file> <file alias="executable">resources/application-x-executable.png</file> @@ -26,7 +27,6 @@ <file alias="update_available">resources/software-update-available.png</file> <file alias="important">resources/emblem-important.png</file> <file alias="check">resources/check.png</file> - <file>mo_icon.ico</file> <file alias="warning">resources/dialog-warning.png</file> <file alias="emblem_backup">resources/symbol-backup.png</file> <file alias="icon_tools">resources/applications-accessories.png</file> @@ -68,6 +68,10 @@ <file alias="active">resources/status_active.png</file> <file alias="awaiting">resources/status_awaiting.png</file> <file alias="inactive">resources/status_inactive.png</file> + <file alias="app_icon">resources/mo_icon.png</file> + <file alias="package">resources/package.png</file> + <file alias="instance_switch">resources/switch-instance-icon.png</file> + <file alias="open_folder">resources/open-Folder-Icon.png</file> </qresource> <qresource prefix="/MO/gui/content"> <file alias="plugin">resources/contents/jigsaw-piece.png</file> @@ -81,6 +85,7 @@ <file alias="mesh">resources/contents/breastplate.png</file> <file alias="string">resources/contents/conversation.png</file> <file alias="bsa">resources/contents/locked-chest.png</file> + <file alias="menu">resources/contents/config.png</file> </qresource> <qresource prefix="/qt/etc"> <file>qt.conf</file> diff --git a/src/resources/contents/config.png b/src/resources/contents/config.png Binary files differnew file mode 100644 index 00000000..3896d885 --- /dev/null +++ b/src/resources/contents/config.png diff --git a/src/resources/mo_icon.png b/src/resources/mo_icon.png Binary files differnew file mode 100644 index 00000000..c926d722 --- /dev/null +++ b/src/resources/mo_icon.png diff --git a/src/resources/open-Folder-Icon.png b/src/resources/open-Folder-Icon.png Binary files differnew file mode 100644 index 00000000..345671f7 --- /dev/null +++ b/src/resources/open-Folder-Icon.png diff --git a/src/resources/package.png b/src/resources/package.png Binary files differnew file mode 100644 index 00000000..4b55b504 --- /dev/null +++ b/src/resources/package.png diff --git a/src/resources/switch-instance-icon.png b/src/resources/switch-instance-icon.png Binary files differnew file mode 100644 index 00000000..b992babe --- /dev/null +++ b/src/resources/switch-instance-icon.png diff --git a/src/safewritefile.cpp b/src/safewritefile.cpp deleted file mode 100644 index 007b3da9..00000000 --- a/src/safewritefile.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/*
-Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
-*/
-
-
-#include "safewritefile.h"
-#include <QStringList>
-#include <QCryptographicHash>
-
-
-using namespace MOBase;
-
-
-SafeWriteFile::SafeWriteFile(const QString &fileName)
-: m_FileName(fileName)
-{
- if (!m_TempFile.open()) {
- throw MyException(QObject::tr("failed to open temporary file"));
- }
-}
-
-
-QFile *SafeWriteFile::operator->() {
- Q_ASSERT(m_TempFile.isOpen());
- return &m_TempFile;
-}
-
-
-void SafeWriteFile::commit() {
- shellDeleteQuiet(m_FileName);
- m_TempFile.rename(m_FileName);
- m_TempFile.setAutoRemove(false);
- m_TempFile.close();
-}
-
-bool SafeWriteFile::commitIfDifferent(QByteArray &inHash) {
- QByteArray newHash = hash();
- if (newHash != inHash
- || !QFile::exists(m_FileName)) {
- commit();
- inHash = newHash;
- return true;
- } else {
- return false;
- }
-}
-
-QByteArray SafeWriteFile::hash()
-{
-
- qint64 pos = m_TempFile.pos();
- m_TempFile.seek(0);
- QByteArray data = m_TempFile.readAll();
- m_TempFile.seek(pos);
- return QCryptographicHash::hash(data, QCryptographicHash::Md5);
-}
diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index aae95f56..55728751 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -79,6 +79,11 @@ QString SelectionDialog::getChoiceString() }
}
+void SelectionDialog::disableCancel()
+{
+ ui->cancelButton->setEnabled(false);
+ ui->cancelButton->setHidden(true);
+}
void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button)
{
diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 43ba9767..3c4b25df 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -53,6 +53,8 @@ public: QVariant getChoiceData();
QString getChoiceString();
+ void disableCancel();
+
private slots:
void on_buttonBox_clicked(QAbstractButton *button);
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index bc8908bc..273e9b45 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -28,6 +28,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "downloadmanager.h"
#include "nexusinterface.h"
#include "nxmaccessmanager.h"
+#include "settings.h"
+#include "bbcode.h"
#include <versioninfo.h>
#include <report.h>
#include <util.h>
@@ -49,6 +51,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QUrl>
#include <QVariantList>
#include <QVariantMap>
+#include <QAbstractButton>
#include <Qt>
#include <QtDebug>
@@ -83,12 +86,10 @@ template <typename T> static T resolveFunction(QLibrary &lib, const char *name) SelfUpdater::SelfUpdater(NexusInterface *nexusInterface)
: m_Parent(nullptr)
, m_Interface(nexusInterface)
- , m_UpdateRequestID(-1)
, m_Reply(nullptr)
, m_Attempts(3)
- , m_NexusDownload(nullptr)
{
- QLibrary archiveLib("dlls\\archive.dll");
+ QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll");
if (!archiveLib.load()) {
throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
}
@@ -104,7 +105,8 @@ SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16,
version.dwFileVersionMS & 0xFFFF,
- version.dwFileVersionLS >> 16);
+ version.dwFileVersionLS >> 16,
+ version.dwFileVersionLS & 0xFFFF);
}
@@ -120,33 +122,77 @@ void SelfUpdater::setUserInterface(QWidget *widget) void SelfUpdater::testForUpdate()
{
- if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- emit updateAvailable();
- return;
+ // TODO: if prereleases are disabled we could just request the latest release
+ // directly
+ try {
+ m_GitHub.releases(GitHub::Repository("LePresidente", "modorganizer"),
+ [this](const QJsonArray &releases) {
+ QJsonObject newest;
+ for (const QJsonValue &releaseVal : releases) {
+ QJsonObject release = releaseVal.toObject();
+ if (!release["draft"].toBool() && (Settings::instance().usePrereleases()
+ || !release["prerelease"].toBool())) {
+ if (newest.empty() || (VersionInfo(release["tag_name"].toString())
+ > VersionInfo(newest["tag_name"].toString()))) {
+ newest = release;
+ }
+ }
+ }
+
+ if (!newest.empty()) {
+ VersionInfo newestVer(newest["tag_name"].toString());
+ if (newestVer > this->m_MOVersion) {
+ m_UpdateCandidate = newest;
+ qDebug("update available: %s -> %s",
+ qPrintable(this->m_MOVersion.displayString()),
+ qPrintable(newestVer.displayString()));
+ emit updateAvailable();
+ } else if (newestVer < this->m_MOVersion) {
+ // this could happen if the user switches from using prereleases to
+ // stable builds. Should we downgrade?
+ qDebug("this version is newer than the newest installed one: %s -> %s",
+ qPrintable(this->m_MOVersion.displayString()),
+ qPrintable(newestVer.displayString()));
+ }
+ }
+ });
}
- if (m_UpdateRequestID == -1 && m_NexusDownload != nullptr) {
- m_UpdateRequestID = m_Interface->requestDescription(
- m_NexusDownload->nexusModOrganizerID(), this, QVariant(),
- QString(), m_NexusDownload);
+ //Catch all is bad by design, should be improved
+ catch (...) {
+ qDebug("Unable to connect to github.com to check version");
}
}
void SelfUpdater::startUpdate()
{
- if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
- m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z");
- installUpdate();
- return;
- }
+ // the button can't be pressed if there isn't an update candidate
+ Q_ASSERT(!m_UpdateCandidate.empty());
+
+ QMessageBox query(QMessageBox::Question,
+ tr("New update available (%1)")
+ .arg(m_UpdateCandidate["tag_name"].toString()),
+ BBCode::convertToHTML(m_UpdateCandidate["body"].toString()),
+ QMessageBox::Yes | QMessageBox::Cancel, m_Parent);
+
+ query.button(QMessageBox::Yes)->setText(tr("Install"));
- 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(m_NexusDownload->nexusModOrganizerID(),
- this, m_NewestVersion, "",
- m_NexusDownload);
+ int res = query.exec();
+
+ if (query.result() == QMessageBox::Yes) {
+ bool found = false;
+ for (const QJsonValue &assetVal : m_UpdateCandidate["assets"].toArray()) {
+ QJsonObject asset = assetVal.toObject();
+ if (asset["content_type"].toString() == "application/x-msdownload") {
+ openOutputFile(asset["name"].toString());
+ download(asset["browser_download_url"].toString());
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ QMessageBox::warning(
+ m_Parent, tr("Download failed"),
+ tr("Failed to find correct download, please try again later."));
}
}
}
@@ -174,15 +220,21 @@ void SelfUpdater::closeProgress() }
}
-void SelfUpdater::download(const QString &downloadLink, const QString &fileName)
+void SelfUpdater::openOutputFile(const QString &fileName)
+{
+ QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName;
+ qDebug("downloading to %s", qPrintable(outputPath));
+ m_UpdateFile.setFileName(outputPath);
+ m_UpdateFile.open(QIODevice::WriteOnly);
+}
+
+void SelfUpdater::download(const QString &downloadLink)
{
QNetworkAccessManager *accessManager = m_Interface->getAccessManager();
QUrl dlUrl(downloadLink);
QNetworkRequest request(dlUrl);
m_Canceled = false;
m_Reply = accessManager->get(request);
- m_UpdateFile.setFileName(QDir::fromNativeSeparators(qApp->property("dataPath").toString()).append("/").append(fileName));
- m_UpdateFile.open(QIODevice::WriteOnly);
showProgress();
connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
@@ -220,6 +272,12 @@ void SelfUpdater::downloadFinished() int error = QNetworkReply::NoError;
if (m_Reply != nullptr) {
+ if (m_Reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 302) {
+ QUrl url = m_Reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ m_UpdateFile.reset();
+ download(url.toString());
+ return;
+ }
m_UpdateFile.write(m_Reply->readAll());
error = m_Reply->error();
@@ -265,237 +323,25 @@ void SelfUpdater::downloadCancel() void SelfUpdater::installUpdate()
{
- const QString mopath = QDir::fromNativeSeparators(qApp->property("dataPath").toString());
-
- QString backupPath = mopath + "/update_backup";
- QDir().mkdir(backupPath);
-
- // rename files that are currently open so we can unpack the update
- if (!m_ArchiveHandler->open(m_UpdateFile.fileName(), nullptr)) {
- throw MyException(tr("failed to open archive \"%1\": %2")
- .arg(m_UpdateFile.fileName())
- .arg(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())));
- }
+ const QString mopath
+ = QDir::fromNativeSeparators(qApp->property("dataPath").toString());
- // move all files contained in the archive out of the way,
- // otherwise we can't overwrite everything
- FileData* const *data;
- size_t size;
- m_ArchiveHandler->getFileList(data, size);
+ HINSTANCE res = ::ShellExecuteW(
+ nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), nullptr,
+ nullptr, SW_SHOW);
- for (size_t i = 0; i < size; ++i) {
- QString outputName = data[i]->getFileName();
- if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) {
- outputName = outputName.mid(13);
- data[i]->addOutputFileName(outputName);
- } else if (outputName != "ModOrganizer") {
- data[i]->addOutputFileName(outputName);
- }
- QFileInfo file(mopath + "/" + outputName);
- if (file.exists() && file.isFile()) {
- if (!shellMove(QStringList(mopath + "/" + outputName),
- QStringList(backupPath + "/" + 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_ArchiveHandler->extract(mopath, nullptr, nullptr,
- new MethodCallback<SelfUpdater, void, QString const &>(this, &SelfUpdater::report7ZipError))) {
- throw std::runtime_error("extracting failed");
+ if (res > (HINSTANCE)32) {
+ QCoreApplication::quit();
+ } else {
+ reportError(tr("Failed to start %1: %2")
+ .arg(m_UpdateFile.fileName())
+ .arg((int)res));
}
- m_ArchiveHandler->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 + "/ModOrganizer.exe")) {
- newProcess.startDetached(mopath + "/ModOrganizer.exe", QStringList("update"));
- } else {
- newProcess.startDetached(mopath + "/ModOrganiser.exe", QStringList("update"));
- }
- emit restart();
}
void SelfUpdater::report7ZipError(QString const &errorMessage)
{
QMessageBox::critical(m_Parent, tr("Error"), 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<QVariantList>()) {
- 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;
-
- for(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(m_NexusDownload->nexusModOrganizerID(),
- updateFileID, this, updateFileName, "",
- m_NexusDownload);
- } 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(m_NexusDownload->nexusModOrganizerID(),
- mainFileID, this, mainFileName, "",
- m_NexusDownload);
- }
- } else {
- qCritical("no file for update found");
- MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent);
- closeProgress();
- }
-}
-
-
-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<QString, int> 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);
- closeProgress();
- }
- }
-}
-
-/** Set the game check for updates */
-void SelfUpdater::setNexusDownload(MOBase::IPluginGame const *game)
-{
- m_NexusDownload = game;
-}
diff --git a/src/selfupdater.h b/src/selfupdater.h index 37021b42..4743f6a4 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -22,6 +22,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <versioninfo.h>
+#include <github.h>
class Archive;
class NexusInterface;
@@ -87,9 +88,6 @@ public: **/
MOBase::VersionInfo getVersion() const { return m_MOVersion; }
- /** Set the game check for updates */
- void setNexusDownload(MOBase::IPluginGame const *game);
-
public slots:
/**
@@ -97,11 +95,6 @@ public slots: **/
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:
/**
@@ -121,10 +114,10 @@ signals: private:
- void download(const QString &downloadLink, const QString &fileName);
+ void openOutputFile(const QString &fileName);
+ void download(const QString &downloadLink);
void installUpdate();
void report7ZipError(const QString &errorMessage);
- QString retrieveNews(const QString &description);
void showProgress();
void closeProgress();
@@ -140,8 +133,6 @@ 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 { nullptr };
@@ -150,7 +141,9 @@ private: Archive *m_ArchiveHandler;
- MOBase::IPluginGame const *m_NexusDownload;
+ GitHub m_GitHub;
+ QJsonObject m_UpdateCandidate;
+
};
diff --git a/src/settings.cpp b/src/settings.cpp index 03e13826..d1130e05 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -24,10 +24,12 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "settingsdialog.h" #include "versioninfo.h" #include "appconfig.h" +#include "organizercore.h" #include <utility.h> #include <iplugin.h> #include <iplugingame.h> #include <questionboxmemory.h> +#include <usvfsparameters.h> #include <QCheckBox> #include <QCoreApplication> @@ -38,12 +40,16 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QDirIterator> #include <QFileInfo> #include <QLineEdit> +#include <QSpinBox> #include <QListWidgetItem> #include <QLocale> #include <QMessageBox> +#include <QApplication> #include <QRegExp> +#include <QDir> #include <QStringList> #include <QVariantMap> +#include <QLabel> #include <Qt> // for Qt::UserRole, etc #include <QtDebug> // for qDebug, qWarning @@ -145,7 +151,7 @@ void Settings::registerPlugin(IPlugin *plugin) m_Plugins.push_back(plugin); m_PluginSettings.insert(plugin->name(), QMap<QString, QVariant>()); m_PluginDescriptions.insert(plugin->name(), QMap<QString, QVariant>()); - foreach (const PluginSetting &setting, plugin->settings()) { + for (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", @@ -199,11 +205,16 @@ QString Settings::getSteamAppID() const return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); } +bool Settings::usePrereleases() const +{ + return m_Settings.value("Settings/use_prereleases", false).toBool(); +} + void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) { m_Settings.beginGroup("Servers"); - foreach (const QString &serverKey, m_Settings.childKeys()) { + for (const QString &serverKey : m_Settings.childKeys()) { QVariantMap data = m_Settings.value(serverKey).toMap(); if (serverKey == serverName) { data["downloadCount"] = data["downloadCount"].toInt() + 1; @@ -221,7 +232,7 @@ std::map<QString, int> Settings::getPreferredServers() std::map<QString, int> result; m_Settings.beginGroup("Servers"); - foreach (const QString &serverKey, m_Settings.childKeys()) { + for (const QString &serverKey : m_Settings.childKeys()) { QVariantMap data = m_Settings.value(serverKey).toMap(); int preference = data["preferred"].toInt(); if (preference > 0) { @@ -233,29 +244,54 @@ std::map<QString, int> Settings::getPreferredServers() return result; } -QString Settings::getConfigurablePath(const QString &key, const QString &def) const +QString Settings::getConfigurablePath(const QString &key, + const QString &def, + bool resolve) const +{ + QString result = QDir::fromNativeSeparators( + m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def) + .toString()); + if (resolve) { + result.replace("%BASE_DIR%", getBaseDirectory()); + } + return result; +} + +QString Settings::getBaseDirectory() const { - return QDir::fromNativeSeparators(m_Settings.value(QString("settings/") + key, qApp->property("dataPath").toString() + "/" + def).toString()); + return QDir::fromNativeSeparators(m_Settings.value( + "settings/base_directory", qApp->property("dataPath").toString()).toString()); } -QString Settings::getDownloadDirectory() const +QString Settings::getDownloadDirectory(bool resolve) const { - return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath())); + return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve); } -QString Settings::getCacheDirectory() const +QString Settings::getCacheDirectory(bool resolve) const { - return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath())); + return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve); } -QString Settings::getModDirectory() const +QString Settings::getModDirectory(bool resolve) const { - return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath())); + return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); +} + +QString Settings::getProfileDirectory(bool resolve) const +{ + return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); +} + +QString Settings::getOverwriteDirectory(bool resolve) const +{ + return getConfigurablePath("overwrite_directory", + ToQString(AppConfig::overwritePath()), resolve); } QString Settings::getNMMVersion() const { - static const QString MIN_NMM_VERSION = "0.52.3"; + static const QString MIN_NMM_VERSION = "0.61.13"; QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { result = MIN_NMM_VERSION; @@ -302,9 +338,18 @@ bool Settings::offlineMode() const int Settings::logLevel() const { - return m_Settings.value("Settings/log_level", 0).toInt(); + return m_Settings.value("Settings/log_level", static_cast<int>(LogLevel::Info)).toInt(); } +int Settings::crashDumpsType() const +{ + return m_Settings.value("Settings/crash_dumps_type", static_cast<int>(CrashDumpsType::Mini)).toInt(); +} + +int Settings::crashDumpsMax() const +{ + return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); +} void Settings::setNexusLogin(QString username, QString password) { @@ -431,7 +476,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers) m_Settings.beginGroup("Servers"); QStringList oldServerKeys = m_Settings.childKeys(); - foreach (const ServerInfo &server, servers) { + for (const ServerInfo &server : servers) { if (!oldServerKeys.contains(server.name)) { // not yet known server QVariantMap newVal; @@ -453,7 +498,7 @@ void Settings::updateServers(const QList<ServerInfo> &servers) // clean up unavailable servers QDate now = QDate::currentDate(); - foreach (const QString &key, m_Settings.childKeys()) { + for (const QString &key : m_Settings.childKeys()) { QVariantMap val = m_Settings.value(key).toMap(); QDate lastSeen = val["lastSeen"].toDate(); if (lastSeen.daysTo(now) > 30) { @@ -477,7 +522,7 @@ void Settings::writePluginBlacklist() { m_Settings.beginWriteArray("pluginBlacklist"); int idx = 0; - foreach (const QString &plugin, m_PluginBlacklist) { + for (const QString &plugin : m_PluginBlacklist) { m_Settings.setArrayIndex(idx++); m_Settings.setValue("name", plugin); } @@ -523,12 +568,7 @@ void Settings::addLanguages(QComboBox *languageBox) 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()) { @@ -553,74 +593,95 @@ void Settings::query(QWidget *parent) std::vector<std::unique_ptr<SettingsTab>> tabs; tabs.push_back(std::unique_ptr<SettingsTab>(new GeneralTab(this, dialog))); + tabs.push_back(std::unique_ptr<SettingsTab>(new PathsTab(this, dialog))); + tabs.push_back(std::unique_ptr<SettingsTab>(new DiagnosticsTab(this, dialog))); tabs.push_back(std::unique_ptr<SettingsTab>(new NexusTab(this, dialog))); tabs.push_back(std::unique_ptr<SettingsTab>(new SteamTab(this, dialog))); tabs.push_back(std::unique_ptr<SettingsTab>(new PluginsTab(this, dialog))); tabs.push_back(std::unique_ptr<SettingsTab>(new WorkaroundsTab(this, dialog))); if (dialog.exec() == QDialog::Accepted) { + // remember settings before change + QMap<QString, QString> before; + m_Settings.beginGroup("Settings"); + for (auto k : m_Settings.allKeys()) + before[k] = m_Settings.value(k).toString(); + m_Settings.endGroup(); + // transfer modified settings to configuration file for (std::unique_ptr<SettingsTab> const &tab: tabs) { tab->update(); } + + // print "changed" settings + m_Settings.beginGroup("Settings"); + bool first_update = true; + for (auto k : m_Settings.allKeys()) + if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) + { + if (first_update) { + qDebug("Changed settings:"); + first_update = false; + } + qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + } + m_Settings.endGroup(); } } -Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) : - m_parent(m_parent), - m_Settings(m_parent->m_Settings), - m_dialog(m_dialog) -{} +Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->m_Settings) + , m_dialog(m_dialog) +{ +} Settings::SettingsTab::~SettingsTab() {} -Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_languageBox(m_dialog.findChild<QComboBox*>("languageBox")), - m_styleBox(m_dialog.findChild<QComboBox*>("styleBox")), - m_logLevelBox(m_dialog.findChild<QComboBox*>("logLevelBox")), - m_downloadDirEdit(m_dialog.findChild<QLineEdit*>("downloadDirEdit")), - m_modDirEdit(m_dialog.findChild<QLineEdit*>("modDirEdit")), - m_cacheDirEdit(m_dialog.findChild<QLineEdit*>("cacheDirEdit")), - m_compactBox(m_dialog.findChild<QCheckBox*>("compactBox")), - m_showMetaBox(m_dialog.findChild<QCheckBox*>("showMetaBox")) +Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_languageBox(m_dialog.findChild<QComboBox *>("languageBox")) + , m_styleBox(m_dialog.findChild<QComboBox *>("styleBox")) + , m_compactBox(m_dialog.findChild<QCheckBox *>("compactBox")) + , m_showMetaBox(m_dialog.findChild<QCheckBox *>("showMetaBox")) + , m_usePrereleaseBox(m_dialog.findChild<QCheckBox *>("usePrereleaseBox")) +{ + // FIXME I think 'addLanguages' lives in here not in parent + m_parent->addLanguages(m_languageBox); { - //FIXME I think 'addLanguages' lives in here not in parent - m_parent->addLanguages(m_languageBox); - { - QString languageCode = m_parent->language(); - int currentID = m_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 = m_languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - m_languageBox->setCurrentIndex(currentID); - } + QString languageCode = m_parent->language(); + int currentID = m_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 = m_languageBox->findData(languageCode.mid(0, 2)); } + if (currentID != -1) { + m_languageBox->setCurrentIndex(currentID); + } + } - //FIXME I think addStyles lives in here not in parent - m_parent->addStyles(m_styleBox); - { - int currentID = m_styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - m_styleBox->setCurrentIndex(currentID); - } + // FIXME I think addStyles lives in here not in parent + m_parent->addStyles(m_styleBox); + { + int currentID = m_styleBox->findData( + m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + m_styleBox->setCurrentIndex(currentID); } + } - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); - m_downloadDirEdit->setText(m_parent->getDownloadDirectory()); - m_modDirEdit->setText(m_parent->getModDirectory()); - m_cacheDirEdit->setText(m_parent->getCacheDirectory()); - m_compactBox->setChecked(m_parent->compactDownloads()); - m_showMetaBox->setChecked(m_parent->metaDownloads()); + m_compactBox->setChecked(m_parent->compactDownloads()); + m_showMetaBox->setChecked(m_parent->metaDownloads()); + m_usePrereleaseBox->setChecked(m_parent->usePrereleases()); } void Settings::GeneralTab::update() { - QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); + QString oldLanguage = m_parent->language(); QString newLanguage = m_languageBox->itemData(m_languageBox->currentIndex()).toString(); if (newLanguage != oldLanguage) { m_Settings.setValue("Settings/language", newLanguage); @@ -634,70 +695,131 @@ void Settings::GeneralTab::update() emit m_parent->styleChanged(newStyle); } - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); + m_Settings.setValue("Settings/use_prereleases", m_usePrereleaseBox->isChecked()); +} - { // advanced settings - if ((QDir::fromNativeSeparators(m_modDirEdit->text()) != QDir::fromNativeSeparators(m_parent->getModDirectory())) && - (QMessageBox::question(nullptr, 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)) { - m_modDirEdit->setText(m_parent->getModDirectory()); - } +Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) + : SettingsTab(parent, dialog) + , m_baseDirEdit(m_dialog.findChild<QLineEdit *>("baseDirEdit")) + , m_downloadDirEdit(m_dialog.findChild<QLineEdit *>("downloadDirEdit")) + , m_modDirEdit(m_dialog.findChild<QLineEdit *>("modDirEdit")) + , m_cacheDirEdit(m_dialog.findChild<QLineEdit *>("cacheDirEdit")) + , m_profilesDirEdit(m_dialog.findChild<QLineEdit *>("profilesDirEdit")) + , m_overwriteDirEdit(m_dialog.findChild<QLineEdit *>("overwriteDirEdit")) +{ + m_baseDirEdit->setText(m_parent->getBaseDirectory()); - if (!QDir(m_downloadDirEdit->text()).exists()) { - QDir().mkpath(m_downloadDirEdit->text()); - } - if (QFileInfo(m_downloadDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::downloadPath()))) { - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(m_downloadDirEdit->text())); - } else { - m_Settings.remove("Settings/download_directory"); - } + QString basePath = parent->getBaseDirectory(); + QDir baseDir(basePath); + for (const auto &dir : { + std::make_pair(m_downloadDirEdit, m_parent->getDownloadDirectory(false)), + std::make_pair(m_modDirEdit, m_parent->getModDirectory(false)), + std::make_pair(m_cacheDirEdit, m_parent->getCacheDirectory(false)), + std::make_pair(m_profilesDirEdit, m_parent->getProfileDirectory(false)), + std::make_pair(m_overwriteDirEdit, m_parent->getOverwriteDirectory(false)) + }) { + QString storePath = baseDir.relativeFilePath(dir.second); + storePath = dir.second; + dir.first->setText(storePath); + } +} - if (!QDir(m_modDirEdit->text()).exists()) { - QDir().mkpath(m_modDirEdit->text()); - } - if (QFileInfo(m_modDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::modsPath()))) { - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(m_modDirEdit->text())); - } else { - m_Settings.remove("Settings/mod_directory"); - } +void Settings::PathsTab::update() +{ + typedef std::tuple<QString, QString, std::wstring> Directory; + + QString basePath = m_parent->getBaseDirectory(); - if (!QDir(m_cacheDirEdit->text()).exists()) { - QDir().mkpath(m_cacheDirEdit->text()); + for (const Directory &dir :{ + Directory{m_downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, + Directory{m_cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, + Directory{m_modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, + Directory{m_overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, + Directory{m_profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()} + }) { + QString path, settingsKey; + std::wstring defaultName; + std::tie(path, settingsKey, defaultName) = dir; + + settingsKey = QString("Settings/%1").arg(settingsKey); + + QString realPath = path; + realPath.replace("%BASE_DIR%", m_baseDirEdit->text()); + + if (!QDir(realPath).exists()) { + if (!QDir().mkpath(realPath)) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), + tr("Failed to create \"%1\", you may not have the " + "necessary permission. path remains unchanged.") + .arg(realPath)); + } } - if (QFileInfo(m_cacheDirEdit->text()) != - QFileInfo(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::cachePath()))) { - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(m_cacheDirEdit->text())); + + if (QFileInfo(realPath) + != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { + m_Settings.setValue(settingsKey, path); } else { - m_Settings.remove("Settings/cache_directory"); + m_Settings.remove(settingsKey); } } - m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); + if (QFileInfo(m_baseDirEdit->text()) != + QFileInfo(qApp->property("dataPath").toString())) { + m_Settings.setValue("Settings/base_directory", m_baseDirEdit->text()); + } else { + m_Settings.remove("Settings/base_directory"); + } } -Settings::NexusTab::NexusTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_loginCheckBox(m_dialog.findChild<QCheckBox*>("loginCheckBox")), - m_usernameEdit(m_dialog.findChild<QLineEdit*>("usernameEdit")), - m_passwordEdit(m_dialog.findChild<QLineEdit*>("passwordEdit")), - m_offlineBox(m_dialog.findChild<QCheckBox*>("offlineBox")), - m_proxyBox(m_dialog.findChild<QCheckBox*>("proxyBox")), - m_knownServersList(m_dialog.findChild<QListWidget*>("knownServersList")), - m_preferredServersList(m_dialog.findChild<QListWidget*>("preferredServersList")) +Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_logLevelBox(m_dialog.findChild<QComboBox *>("logLevelBox")) + , m_dumpsTypeBox(m_dialog.findChild<QComboBox *>("dumpsTypeBox")) + , m_dumpsMaxEdit(m_dialog.findChild<QSpinBox *>("dumpsMaxEdit")) + , m_diagnosticsExplainedLabel(m_dialog.findChild<QLabel *>("diagnosticsExplainedLabel")) { - if (m_parent->automaticLoginEnabled()) { + m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); + m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); + QString logsPath = qApp->property("dataPath").toString() + + "/" + QString::fromStdWString(AppConfig::logPath()); + m_diagnosticsExplainedLabel->setText( + m_diagnosticsExplainedLabel->text() + .replace("LOGS_FULL_PATH", logsPath) + .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) + .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) + .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) + ); +} + +void Settings::DiagnosticsTab::update() +{ + m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); +} + +Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) + : Settings::SettingsTab(parent, dialog) + , m_loginCheckBox(dialog.findChild<QCheckBox *>("loginCheckBox")) + , m_usernameEdit(dialog.findChild<QLineEdit *>("usernameEdit")) + , m_passwordEdit(dialog.findChild<QLineEdit *>("passwordEdit")) + , m_offlineBox(dialog.findChild<QCheckBox *>("offlineBox")) + , m_proxyBox(dialog.findChild<QCheckBox *>("proxyBox")) + , m_knownServersList(dialog.findChild<QListWidget *>("knownServersList")) + , m_preferredServersList( + dialog.findChild<QListWidget *>("preferredServersList")) +{ + if (parent->automaticLoginEnabled()) { m_loginCheckBox->setChecked(true); m_usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); m_passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); } - m_offlineBox->setChecked(m_parent->offlineMode()); - m_proxyBox->setChecked(m_parent->useProxy()); + m_offlineBox->setChecked(parent->offlineMode()); + m_proxyBox->setChecked(parent->useProxy()); // display server preferences m_Settings.beginGroup("Servers"); @@ -757,11 +879,10 @@ void Settings::NexusTab::update() m_Settings.endGroup(); } - -Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_steamUserEdit(m_dialog.findChild<QLineEdit*>("steamUserEdit")), - m_steamPassEdit(m_dialog.findChild<QLineEdit*>("steamPassEdit")) +Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_steamUserEdit(m_dialog.findChild<QLineEdit *>("steamUserEdit")) + , m_steamPassEdit(m_dialog.findChild<QLineEdit *>("steamPassEdit")) { if (m_Settings.contains("Settings/steam_username")) { m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString()); @@ -777,10 +898,10 @@ void Settings::SteamTab::update() m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text()); } -Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_pluginsList(m_dialog.findChild<QListWidget*>("pluginsList")), - m_pluginBlacklistList(m_dialog.findChild<QListWidget*>("pluginBlacklist")) +Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_pluginsList(m_dialog.findChild<QListWidget *>("pluginsList")) + , m_pluginBlacklistList(m_dialog.findChild<QListWidget *>("pluginBlacklist")) { // display plugin settings for (IPlugin *plugin : m_parent->m_Plugins) { @@ -813,20 +934,21 @@ void Settings::PluginsTab::update() // store plugin blacklist m_parent->m_PluginBlacklist.clear(); - foreach (QListWidgetItem *item, m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + for (QListWidgetItem *item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { m_parent->m_PluginBlacklist.insert(item->text()); } m_parent->writePluginBlacklist(); } -Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog) : - Settings::SettingsTab(m_parent, m_dialog), - m_appIDEdit(m_dialog.findChild<QLineEdit*>("appIDEdit")), - m_mechanismBox(m_dialog.findChild<QComboBox*>("mechanismBox")), - m_nmmVersionEdit(m_dialog.findChild<QLineEdit*>("nmmVersionEdit")), - m_hideUncheckedBox(m_dialog.findChild<QCheckBox*>("hideUncheckedBox")), - m_forceEnableBox(m_dialog.findChild<QCheckBox*>("forceEnableBox")), - m_displayForeignBox(m_dialog.findChild<QCheckBox*>("displayForeignBox")) +Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, + SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_appIDEdit(m_dialog.findChild<QLineEdit *>("appIDEdit")) + , m_mechanismBox(m_dialog.findChild<QComboBox *>("mechanismBox")) + , m_nmmVersionEdit(m_dialog.findChild<QLineEdit *>("nmmVersionEdit")) + , m_hideUncheckedBox(m_dialog.findChild<QCheckBox *>("hideUncheckedBox")) + , m_forceEnableBox(m_dialog.findChild<QCheckBox *>("forceEnableBox")) + , m_displayForeignBox(m_dialog.findChild<QCheckBox *>("displayForeignBox")) { m_appIDEdit->setText(m_parent->getSteamAppID()); diff --git a/src/settings.h b/src/settings.h index 9f6adaa7..ae38223f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -38,8 +38,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. class QCheckBox; class QComboBox; class QLineEdit; +class QSpinBox; class QListWidget; class QWidget; +class QLabel; struct ServerInfo; @@ -120,9 +122,15 @@ public: QString getSteamAppID() const; /** + * retrieves the base directory under which the other directories usually + * reside + */ + QString getBaseDirectory() const; + + /** * retrieve the directory where downloads are stored (with native separators) **/ - QString getDownloadDirectory() const; + QString getDownloadDirectory(bool resolve = true) const; /** * retrieve a sorted list of preferred servers @@ -132,7 +140,7 @@ public: /** * retrieve the directory where mods are stored (with native separators) **/ - QString getModDirectory() const; + QString getModDirectory(bool resolve = true) const; /** * returns the version of nmm to impersonate when connecting to nexus @@ -142,7 +150,18 @@ public: /** * retrieve the directory where the web cache is stored (with native separators) **/ - QString getCacheDirectory() const; + QString getCacheDirectory(bool resolve = true) const; + + /** + * retrieve the directory where profiles stored (with native separators) + **/ + QString getProfileDirectory(bool resolve = true) const; + + /** + * retrieve the directory were new files are stored that can't be assigned + * to a mod (with native separators) + */ + QString getOverwriteDirectory(bool resolve = true) const; /** * @return true if the user has set up automatic login to nexus @@ -188,6 +207,16 @@ public: int logLevel() const; /** + * @return the configured crash dumps type + */ + int crashDumpsType() const; + + /** + * @return the configured crash dumps max + */ + int crashDumpsMax() const; + + /** * @brief set the nexus login information * * @param username username @@ -304,6 +333,8 @@ public: */ std::vector<MOBase::IPlugin*> plugins() const { return m_Plugins; } + bool usePrereleases() const; + /** * @brief register MO as the handler for nxm links * @param force set to true to enforce the registration dialog to show up, @@ -324,7 +355,7 @@ private: void addStyles(QComboBox *styleBox); void readPluginBlacklist(); void writePluginBlacklist(); - QString getConfigurablePath(const QString &key, const QString &def) const; + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; class SettingsTab { @@ -352,12 +383,39 @@ private: private: QComboBox *m_languageBox; QComboBox *m_styleBox; - QComboBox *m_logLevelBox; + QCheckBox *m_compactBox; + QCheckBox *m_showMetaBox; + QCheckBox *m_usePrereleaseBox; + }; + + class PathsTab : public SettingsTab + { + public: + PathsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + + private: + QLineEdit *m_baseDirEdit; QLineEdit *m_downloadDirEdit; QLineEdit *m_modDirEdit; QLineEdit *m_cacheDirEdit; - QCheckBox *m_compactBox; - QCheckBox *m_showMetaBox; + QLineEdit *m_profilesDirEdit; + QLineEdit *m_overwriteDirEdit; + }; + + class DiagnosticsTab : public SettingsTab + { + public: + DiagnosticsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + + private: + QComboBox *m_logLevelBox; + QComboBox *m_dumpsTypeBox; + QSpinBox *m_dumpsMaxEdit; + QLabel *m_diagnosticsExplainedLabel; }; /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 8bc1dbc6..fb9433a6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -25,6 +25,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "noeditdelegate.h" #include "iplugingame.h" #include "settings.h" +#include "instancemanager.h" +#include "nexusinterface.h" #include <QDirIterator> #include <QFileDialog> @@ -39,11 +41,13 @@ using namespace MOBase; SettingsDialog::SettingsDialog(QWidget *parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) + : TutorableDialog("SettingsDialog", parent) + , ui(new Ui::SettingsDialog) { ui->setupUi(this); - QShortcut *delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + QShortcut *delShortcut + = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); } @@ -54,13 +58,30 @@ SettingsDialog::~SettingsDialog() void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins) { - foreach (IPlugin *plugin, plugins) { + for (IPlugin *plugin : plugins) { ui->pluginsList->addItem(plugin->name()); } } void SettingsDialog::accept() { + QString newModPath = ui->modDirEdit->text(); + newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + if ((QDir::fromNativeSeparators(newModPath) != + QDir::fromNativeSeparators( + Settings::instance().getModDirectory(true))) && + (QMessageBox::question( + nullptr, 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)) { + return; + } + storeSettings(ui->pluginsList->currentItem()); TutorableDialog::accept(); } @@ -89,16 +110,29 @@ void SettingsDialog::on_categoriesBtn_clicked() void SettingsDialog::on_bsaDateBtn_clicked() { - IPluginGame const *game = qApp->property("managed_game").value<IPluginGame const *>(); + IPluginGame const *game + = qApp->property("managed_game").value<IPluginGame *>(); QDir dir = game->dataDirectory(); - Helper::backdateBSAs(qApp->property("dataPath").toString().toStdWString(), + Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); } +void SettingsDialog::on_browseBaseDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory( + this, tr("Select base directory"), ui->baseDirEdit->text()); + if (!temp.isEmpty()) { + ui->baseDirEdit->setText(temp); + } +} + void SettingsDialog::on_browseDownloadDirBtn_clicked() { - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); + QString searchPath = ui->downloadDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), searchPath); if (!temp.isEmpty()) { ui->downloadDirEdit->setText(temp); } @@ -106,7 +140,10 @@ void SettingsDialog::on_browseDownloadDirBtn_clicked() void SettingsDialog::on_browseModDirBtn_clicked() { - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); + QString searchPath = ui->modDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), searchPath); if (!temp.isEmpty()) { ui->modDirEdit->setText(temp); } @@ -114,12 +151,37 @@ void SettingsDialog::on_browseModDirBtn_clicked() void SettingsDialog::on_browseCacheDirBtn_clicked() { - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); + QString searchPath = ui->cacheDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), searchPath); if (!temp.isEmpty()) { ui->cacheDirEdit->setText(temp); } } +void SettingsDialog::on_browseProfilesDirBtn_clicked() +{ + QString searchPath = ui->profilesDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), searchPath); + if (!temp.isEmpty()) { + ui->profilesDirEdit->setText(temp); + } +} + +void SettingsDialog::on_browseOverwriteDirBtn_clicked() +{ + QString searchPath = ui->overwriteDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), searchPath); + if (!temp.isEmpty()) { + ui->overwriteDirEdit->setText(temp); + } +} + void SettingsDialog::on_resetDialogsButton_clicked() { if (QMessageBox::question(this, tr("Confirm?"), @@ -184,5 +246,12 @@ void SettingsDialog::deleteBlacklistItem() void SettingsDialog::on_associateButton_clicked() { - Settings::instance().registerAsNXMHandler(true); + Settings::instance().registerAsNXMHandler(false); +} + +void SettingsDialog::on_clearCacheButton_clicked() +{ + QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + NexusInterface::instance()->clearCache(); } + diff --git a/src/settingsdialog.h b/src/settingsdialog.h index e99fb3e3..e9d995d3 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -77,8 +77,19 @@ private slots: void on_associateButton_clicked();
+ void on_clearCacheButton_clicked();
+
+ void on_browseBaseDirBtn_clicked();
+
+ void on_browseOverwriteDirBtn_clicked();
+
+ void on_browseProfilesDirBtn_clicked();
+
private:
Ui::SettingsDialog *ui;
+
};
+
+
#endif // WORKAROUNDDIALOG_H
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index b88885dc..3c47d226 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -71,178 +71,251 @@ p, li { white-space: pre-wrap; } </layout>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_6">
- <item>
- <widget class="QLabel" name="label_12">
- <property name="text">
- <string>Log Level</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QComboBox" name="logLevelBox">
- <property name="toolTip">
- <string>Decides the amount of data printed to "ModOrganizer.log"</string>
- </property>
- <property name="whatsThis">
- <string>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.</string>
- </property>
- <item>
- <property name="text">
- <string>Debug</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Info</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Error</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
+ <widget class="QCheckBox" name="usePrereleaseBox">
+ <property name="toolTip">
+ <string>Update to non-stable releases.</string>
+ </property>
+ <property name="whatsThis">
+ <string>If this is enabled, the integrated update mechanism will notify of all releases, including pre-releases (alphas, betas).
+
+Please use this only if you're sufficiently tech-savvy to investigate issues, look for known problems in the issue tracker and create meaningful reports.
+
+If you use pre-releases, never contact me directly by e-mail or via private messages!</string>
+ </property>
+ <property name="text">
+ <string>Install Pre-releases (Betas)</string>
+ </property>
+ </widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
+ <widget class="QGroupBox" name="groupBox">
<property name="title">
- <string>Advanced</string>
- </property>
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
+ <string>User interface</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="1">
- <widget class="QLineEdit" name="downloadDirEdit">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <widget class="QCheckBox" name="compactBox">
<property name="toolTip">
- <string>Directory where downloads are stored.</string>
+ <string>If checked, the download interface will be more compact.</string>
</property>
- <property name="whatsThis">
- <string>Directory where downloads are stored.</string>
+ <property name="text">
+ <string>Compact Download Interface</string>
</property>
</widget>
</item>
- <item row="0" column="2">
- <widget class="QPushButton" name="browseDownloadDirBtn">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item>
+ <widget class="QCheckBox" name="showMetaBox">
+ <property name="toolTip">
+ <string>If checked, the download list will display meta information instead of file names.</string>
</property>
<property name="text">
- <string notr="true">...</string>
+ <string>Download Meta Information</string>
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_8">
- <property name="text">
- <string>Mod Directory</string>
+ <item>
+ <widget class="QPushButton" name="resetDialogsButton">
+ <property name="maximumSize">
+ <size>
+ <width>16777215</width>
+ <height>16777215</height>
+ </size>
</property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="modDirEdit">
<property name="toolTip">
- <string>Directory where mods are stored.</string>
+ <string>Reset stored information from dialogs.</string>
</property>
<property name="whatsThis">
- <string>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).</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QPushButton" name="browseModDirBtn">
- <property name="text">
- <string notr="true">...</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="label_7">
- <property name="text">
- <string>Download Directory</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_9">
- <property name="text">
- <string>Cache Directory</string>
+ <string>This will make all dialogs show up again where you checked the "Remember selection"-box.</string>
</property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLineEdit" name="cacheDirEdit"/>
- </item>
- <item row="3" column="2">
- <widget class="QPushButton" name="browseCacheDirBtn">
<property name="text">
- <string notr="true">...</string>
+ <string>Reset Dialogs</string>
</property>
</widget>
</item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string>User interface</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_11">
<item>
- <widget class="QCheckBox" name="compactBox">
- <property name="toolTip">
- <string>If checked, the download interface will be more compact.</string>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <property name="text">
- <string>Compact Download Interface</string>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
</property>
- </widget>
+ </spacer>
</item>
<item>
- <widget class="QCheckBox" name="showMetaBox">
+ <widget class="QPushButton" name="categoriesBtn">
<property name="toolTip">
- <string>If checked, the download list will display meta information instead of file names.</string>
+ <string>Modify the categories available to arrange your mods.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Modify the categories available to arrange your mods.</string>
</property>
<property name="text">
- <string>Download Meta Information</string>
+ <string>Configure Mod Categories</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pathsTab">
+ <attribute name="title">
+ <string>Paths</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
<item>
- <widget class="QPushButton" name="resetDialogsButton">
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="toolTip">
- <string>Reset stored information from dialogs.</string>
- </property>
- <property name="whatsThis">
- <string>This will make all dialogs show up again where you checked the "Remember selection"-box.</string>
- </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_25">
+ <property name="text">
+ <string>Base Directory</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="2">
+ <widget class="QPushButton" name="browseOverwriteDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLineEdit" name="overwriteDirEdit"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="baseDirEdit"/>
+ </item>
+ <item row="0" column="2">
+ <widget class="QPushButton" name="browseBaseDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_24">
+ <property name="text">
+ <string>Overwrite</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Caches</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QPushButton" name="browseModDirBtn">
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="cacheDirEdit"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Downloads</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QPushButton" name="browseCacheDirBtn">
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="modDirEdit">
+ <property name="toolTip">
+ <string>Directory where mods are stored.</string>
+ </property>
+ <property name="whatsThis">
+ <string>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).</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="browseDownloadDirBtn">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="downloadDirEdit">
+ <property name="toolTip">
+ <string>Directory where downloads are stored.</string>
+ </property>
+ <property name="whatsThis">
+ <string>Directory where downloads are stored.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Mods</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="2">
+ <widget class="QPushButton" name="browseProfilesDirBtn">
+ <property name="text">
+ <string>...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLineEdit" name="profilesDirEdit"/>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_22">
+ <property name="text">
+ <string>Profiles</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <spacer name="verticalSpacer_8">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_26">
<property name="text">
- <string>Reset Dialogs</string>
+ <string>Use %BASE_DIR% to refer to the Base Directory.</string>
</property>
</widget>
</item>
<item>
- <spacer name="verticalSpacer">
+ <spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -255,21 +328,15 @@ p, li { white-space: pre-wrap; } </spacer>
</item>
<item>
- <widget class="QPushButton" name="categoriesBtn">
- <property name="toolTip">
- <string>Modify the categories available to arrange your mods.</string>
- </property>
- <property name="whatsThis">
- <string>Modify the categories available to arrange your mods.</string>
- </property>
+ <widget class="QLabel" name="label_23">
<property name="text">
- <string>Configure Mod Categories</string>
+ <string>Important: All directories have to be writeable!</string>
</property>
</widget>
</item>
</layout>
</widget>
- <widget class="QWidget" name="nexusTab">
+ <widget class="QWidget" name="nexusTab">
<attribute name="title">
<string>Nexus</string>
</attribute>
@@ -338,6 +405,37 @@ p, li { white-space: pre-wrap; } </item>
</layout>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_11">
+ <item>
+ <widget class="QPushButton" name="clearCacheButton">
+ <property name="toolTip">
+ <string>Remove cache and cookies. Forces a new login.</string>
+ </property>
+ <property name="text">
+ <string>Clear Cache</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_5">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -614,7 +712,7 @@ p, li { white-space: pre-wrap; } <bool>false</bool>
</attribute>
<attribute name="headerDefaultSectionSize">
- <number>100</number>
+ <number>170</number>
</attribute>
<column>
<property name="text">
@@ -830,7 +928,7 @@ Uncheck this if you want to use Mod Organizer with total conversions (like Nehri <string>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.</string>
+If you disable this feature, MO will only display official DLCs this way. Please note that plugins (esps, esms, and esls) displayed in the right pane are completely unaffected by this feature.</string>
</property>
<property name="text">
<string>Display mods installed outside MO</string>
@@ -884,6 +982,188 @@ For the other games this is not a sufficient replacement for AI!</string> </item>
</layout>
</widget>
+ <widget class="QWidget" name="diagnosticsTab">
+ <attribute name="title">
+ <string>Diagnostics</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Log Level</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="logLevelBox">
+ <property name="toolTip">
+ <string>Decides the amount of data printed to "ModOrganizer.log"</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ 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.
+ </string>
+ </property>
+ <item>
+ <property name="text">
+ <string>Debug</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Info (recommended)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Warning</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Error</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer_9">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_12">
+ <item>
+ <widget class="QLabel" name="label_27">
+ <property name="text">
+ <string>Crash Dumps</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="dumpsTypeBox">
+ <property name="toolTip">
+ <string>Decides which type of crash dumps are collected when injected processes crash.</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ Decides which type of crash dumps are collected when injected processes crash.
+ "None" Disables the generation of crash dumps by MO.
+ "Mini" Default level which generates small dumps (only stack traces).
+ "Data" Much larger dumps with additional information which may be need (also data segments).
+ "Full" Even larger dumps with a full memory dump of the process.
+ </string>
+ </property>
+ <item>
+ <property name="text">
+ <string>None</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Mini (recommended)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Full</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_13">
+ <item>
+ <widget class="QLabel" name="label_28">
+ <property name="text">
+ <string>Max Dumps To Keep</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_6">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>60</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="dumpsMaxEdit">
+ <property name="toolTip">
+ <string>Maximum number of crash dumps to keep on disk. Use 0 for unlimited.</string>
+ </property>
+ <property name="whatsThis">
+ <string>
+ Maximum number of crash dumps to keep on disk. Use 0 for unlimited.
+ Set "Crash Dumps" above to None to disable crash dump collection.
+ </string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="diagnosticsExplainedLabel">
+ <property name="text">
+ <string>
+ Logs and crash dumps are stored under your current instance in the <a href="LOGS_FULL_PATH">LOGS_DIR</a>
+ and <a href="DUMPS_FULL_PATH">DUMPS_DIR</a> folders.
+ Sending logs and/or crash dumps to the developers can help investigate issues.
+ It is recommended to compress large log and dmp files before sending.
+ </string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip">
+ <string>Hint: right click link and copy link location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <spacer name="verticalSpacer_10">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Expanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>232</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+</widget>
</widget>
</item>
<item>
@@ -898,7 +1178,53 @@ For the other games this is not a sufficient replacement for AI!</string> </item>
</layout>
</widget>
- <resources/>
+ <tabstops>
+ <tabstop>languageBox</tabstop>
+ <tabstop>styleBox</tabstop>
+ <tabstop>logLevelBox</tabstop>
+ <tabstop>usePrereleaseBox</tabstop>
+ <tabstop>compactBox</tabstop>
+ <tabstop>showMetaBox</tabstop>
+ <tabstop>resetDialogsButton</tabstop>
+ <tabstop>categoriesBtn</tabstop>
+ <tabstop>baseDirEdit</tabstop>
+ <tabstop>browseBaseDirBtn</tabstop>
+ <tabstop>downloadDirEdit</tabstop>
+ <tabstop>browseDownloadDirBtn</tabstop>
+ <tabstop>modDirEdit</tabstop>
+ <tabstop>browseModDirBtn</tabstop>
+ <tabstop>cacheDirEdit</tabstop>
+ <tabstop>browseCacheDirBtn</tabstop>
+ <tabstop>profilesDirEdit</tabstop>
+ <tabstop>browseProfilesDirBtn</tabstop>
+ <tabstop>overwriteDirEdit</tabstop>
+ <tabstop>browseOverwriteDirBtn</tabstop>
+ <tabstop>loginCheckBox</tabstop>
+ <tabstop>usernameEdit</tabstop>
+ <tabstop>passwordEdit</tabstop>
+ <tabstop>clearCacheButton</tabstop>
+ <tabstop>offlineBox</tabstop>
+ <tabstop>proxyBox</tabstop>
+ <tabstop>associateButton</tabstop>
+ <tabstop>knownServersList</tabstop>
+ <tabstop>preferredServersList</tabstop>
+ <tabstop>steamUserEdit</tabstop>
+ <tabstop>steamPassEdit</tabstop>
+ <tabstop>pluginsList</tabstop>
+ <tabstop>pluginSettingsList</tabstop>
+ <tabstop>pluginBlacklist</tabstop>
+ <tabstop>appIDEdit</tabstop>
+ <tabstop>mechanismBox</tabstop>
+ <tabstop>nmmVersionEdit</tabstop>
+ <tabstop>hideUncheckedBox</tabstop>
+ <tabstop>forceEnableBox</tabstop>
+ <tabstop>displayForeignBox</tabstop>
+ <tabstop>bsaDateBtn</tabstop>
+ <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources>
+ <include location="resources.qrc"/>
+ </resources>
<connections>
<connection>
<sender>buttonBox</sender>
diff --git a/src/shared/appconfig.inc b/src/shared/appconfig.inc index e2dbfe20..e572a32b 100644 --- a/src/shared/appconfig.inc +++ b/src/shared/appconfig.inc @@ -8,13 +8,15 @@ APPPARAM(std::wstring, stylesheetsPath, L"stylesheets") APPPARAM(std::wstring, cachePath, L"webcache")
APPPARAM(std::wstring, tutorialsPath, L"tutorials")
APPPARAM(std::wstring, logPath, L"logs")
+APPPARAM(std::wstring, dumpsDir, L"crashDumps")
APPPARAM(std::wstring, profileTweakIni, L"profile_tweaks.ini")
APPPARAM(std::wstring, logFileName, L"ModOrganizer.log")
APPPARAM(std::wstring, iniFileName, L"ModOrganizer.ini")
APPPARAM(std::wstring, proxyDLLTarget, L"steam_api.dll")
APPPARAM(std::wstring, proxyDLLOrig, L"steam_api_orig.dll") // needs to be identical to the value used in proxydll-project
APPPARAM(std::wstring, proxyDLLSource, L"proxy.dll")
-APPPARAM(std::wstring, hookDLLName, L"hook.dll") // needs to be identical to the value used in proxydll-project
+APPPARAM(std::wstring, vfs32DLLName, L"usvfs_x86.dll")
+APPPARAM(std::wstring, vfs64DLLName, L"usvfs_x64.dll")
APPPARAM(const wchar_t*, localSavePlaceholder, L"__MOProfileSave__\\")
APPPARAM(std::wstring, firstStepsTutorial, L"tutorial_firststeps_main.js")
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 54b6efff..cebf270e 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -24,7 +24,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <bsatk.h>
#include <boost/bind.hpp>
#include <boost/scoped_array.hpp>
-#include <boost/foreach.hpp>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <sstream>
@@ -214,9 +213,9 @@ void FilesOrigin::setName(const std::wstring &name) std::vector<FileEntry::Ptr> FilesOrigin::getFiles() const
{
std::vector<FileEntry::Ptr> result;
- for (FileEntry::Index fileIdx : m_Files) {
- result.push_back(m_FileRegister.lock()->getFile(fileIdx));
- }
+ for (FileEntry::Index fileIdx : m_Files)
+ if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx))
+ result.push_back(p);
return result;
}
@@ -237,9 +236,10 @@ void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &arc m_FileTime = fileTime;
m_Archive = archive;
} else if ((m_Parent != nullptr)
- && (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority())) {
- if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) {
- m_Alternatives.push_back(m_Origin);
+ && (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority())
+ && (archive.size() == 0 || m_Archive.size() > 0 )) {
+ if (std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair<int, std::wstring> &i) -> bool { return i.first == m_Origin; }) == m_Alternatives.end()) {
+ m_Alternatives.push_back(std::pair<int, std::wstring>(m_Origin, m_Archive));
}
m_Origin = origin;
m_FileTime = fileTime;
@@ -250,20 +250,20 @@ void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &arc // already an origin
return;
}
- for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if (*iter == origin) {
+ for (std::vector<std::pair<int, std::wstring>>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if (iter->first == origin) {
// already an origin
return;
}
if ((m_Parent != nullptr)
- && (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority())) {
- m_Alternatives.insert(iter, origin);
+ && (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) {
+ m_Alternatives.insert(iter, std::pair<int, std::wstring>(origin, archive));
found = true;
break;
}
}
if (!found) {
- m_Alternatives.push_back(origin);
+ m_Alternatives.push_back(std::pair<int, std::wstring>(origin, archive));
}
}
}
@@ -273,14 +273,14 @@ bool FileEntry::removeOrigin(int origin) if (m_Origin == origin) {
if (!m_Alternatives.empty()) {
// find alternative with the highest priority
- std::vector<int>::iterator currentIter = m_Alternatives.begin();
- for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) &&
- (*iter != origin)) {
+ std::vector<std::pair<int, std::wstring>>::iterator currentIter = m_Alternatives.begin();
+ for (std::vector<std::pair<int, std::wstring>>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority()) &&
+ (iter->first != origin)) {
currentIter = iter;
}
}
- int currentID = *currentIter;
+ int currentID = currentIter->first;
m_Alternatives.erase(currentIter);
m_Origin = currentID;
@@ -304,23 +304,13 @@ bool FileEntry::removeOrigin(int origin) return true;
}
} else {
- std::vector<int>::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin);
- m_Alternatives.erase(newEnd, m_Alternatives.end());
+ std::vector<std::pair<int, std::wstring>>::iterator newEnd = std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair<int, std::wstring> &i) -> bool { return i.first == origin; });
+ if (newEnd != m_Alternatives.end())
+ m_Alternatives.erase(newEnd, m_Alternatives.end());
}
return false;
}
-
-// sorted by priority descending
-static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS)
-{
- int l = entry->getOriginByID(LHS).getPriority(); if (l < 0) l = INT_MAX;
- int r = entry->getOriginByID(RHS).getPriority(); if (r < 0) r = INT_MAX;
-
- return l < r;
-}
-
-
FileEntry::FileEntry()
: m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr))
{
@@ -340,10 +330,23 @@ FileEntry::~FileEntry() void FileEntry::sortOrigins()
{
- m_Alternatives.push_back(m_Origin);
- std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2));
- m_Origin = m_Alternatives[m_Alternatives.size() - 1];
- m_Alternatives.pop_back();
+ m_Alternatives.push_back(std::pair<int, std::wstring>(m_Origin, m_Archive));
+ std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair<int, std::wstring> &LHS, const std::pair<int, std::wstring> &RHS) -> bool {
+ if ((!LHS.second.size() && !RHS.second.size()) || (LHS.second.size() && RHS.second.size())) {
+ int l = m_Parent->getOriginByID(LHS.first).getPriority(); if (l < 0) l = INT_MAX;
+ int r = m_Parent->getOriginByID(RHS.first).getPriority(); if (r < 0) r = INT_MAX;
+
+ return l < r;
+ }
+
+ if (RHS.second.size()) return false;
+ return true;
+ });
+ if (!m_Alternatives.empty()) {
+ m_Origin = m_Alternatives.back().first;
+ m_Archive = m_Alternatives.back().second;
+ m_Alternatives.pop_back();
+ }
}
@@ -704,7 +707,7 @@ FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const return m_OriginConnection->getByName(name);
}
-
+/*
int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
{
const DirectoryEntry *directory = nullptr;
@@ -718,7 +721,7 @@ int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive) return -1;
}
}
-}
+}*/
std::vector<FileEntry::Ptr> DirectoryEntry::getFiles() const
{
@@ -887,9 +890,9 @@ void FileRegister::unregisterFile(FileEntry::Ptr file) // unregister from origin
int originID = file->getOrigin(ignore);
m_OriginConnection->getByID(originID).removeFile(file->getIndex());
- const std::vector<int> &alternatives = file->getAlternatives();
+ const std::vector<std::pair<int, std::wstring>> &alternatives = file->getAlternatives();
for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
- m_OriginConnection->getByID(*iter).removeFile(file->getIndex());
+ m_OriginConnection->getByID(iter->first).removeFile(file->getIndex());
}
// unregister from directory
@@ -950,12 +953,12 @@ void FileRegister::removeOriginMulti(std::set<FileEntry::Index> indices, int ori // 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<DirectoryEntry*> parents;
- BOOST_FOREACH (const FileEntry::Ptr &file, removedFiles) {
+ for (const FileEntry::Ptr &file : removedFiles) {
if (file->getParent() != nullptr) {
parents.insert(file->getParent());
}
}
- BOOST_FOREACH (DirectoryEntry *parent, parents) {
+ for (DirectoryEntry *parent : parents) {
parent->removeFiles(indices);
}
}
diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index 2d32450a..8dbedaf4 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -72,7 +72,7 @@ public: // gets the list of alternative origins (origins with lower priority than the primary one).
// if sortOrigins has been called, it is sorted by priority (ascending)
- const std::vector<int> &getAlternatives() const { return m_Alternatives; }
+ const std::vector<std::pair<int, std::wstring>> &getAlternatives() const { return m_Alternatives; }
const std::wstring &getName() const { return m_Name; }
int getOrigin() const { return m_Origin; }
@@ -96,9 +96,9 @@ private: Index m_Index;
std::wstring m_Name;
- int m_Origin;
+ int m_Origin = -1;
std::wstring m_Archive;
- std::vector<int> m_Alternatives;
+ std::vector<std::pair<int, std::wstring>> m_Alternatives;
DirectoryEntry *m_Parent;
mutable FILETIME m_FileTime;
@@ -207,7 +207,8 @@ public: DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID);
DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID,
- boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection);
+ boost::shared_ptr<FileRegister> fileRegister,
+ boost::shared_ptr<OriginConnection> originConnection);
~DirectoryEntry();
@@ -232,7 +233,9 @@ public: FilesOrigin &getOriginByID(int ID) const;
FilesOrigin &getOriginByName(const std::wstring &name) const;
- int getOrigin(const std::wstring &path, bool &archive);
+ int anyOrigin() const;
+
+ //int getOrigin(const std::wstring &path, bool &archive);
std::vector<FileEntry::Ptr> getFiles() const;
@@ -316,8 +319,6 @@ private: DirectoryEntry *getSubDirectoryRecursive(const std::wstring &path, bool create, int originID = -1);
- int anyOrigin() const;
-
void removeDirRecursive();
private:
diff --git a/src/shared/inject.cpp b/src/shared/inject.cpp deleted file mode 100644 index fcd4cfab..00000000 --- a/src/shared/inject.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/*
-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 <http://www.gnu.org/licenses/>.
-*/
-
-#include "inject.h"
-/*#if defined UNICODE && !defined _UNICODE
-#define _UNICODE 1
-#endif*/
-#include <wchar.h>
-#include <cstdio>
-#include <tchar.h>
-#include <cstdlib>
-#include <exception>
-#include <stdexcept>
-#include <string>
-#include "windows_error.h"
-#include "error_report.h"
-
-namespace MOShared {
-
-
-struct TParameters {
- char dllname[MAX_PATH];
- wchar_t profileName[101];
- char initstr[5];
- int logLevel;
-};
-
-
-typedef HMODULE (WINAPI *TLoadLibraryType)(LPCTSTR);
-typedef FARPROC (WINAPI *TGetProcAddressType)(HMODULE, LPCSTR);
-
-
-
-void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel)
-{
- // prepare parameters that are to be passed to the injected dll
- TParameters parameters;
- memset(¶meters, '\0', sizeof(TParameters));
- strncpy(parameters.dllname, dllname.c_str(), MAX_PATH - 1);
- wcsncpy(parameters.profileName, profileName.c_str(), 100);
- _snprintf(parameters.initstr, 5, "Init"); //this is the name of thie initialisation function we want to call in the target process
-
- HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll"));
- TLoadLibraryType loadLibraryFunc = nullptr;
- TGetProcAddressType getProcAddressFunc = nullptr;
- // ansi binaries
- if (k32mod != nullptr) {
- loadLibraryFunc = reinterpret_cast<TLoadLibraryType>(::GetProcAddress(k32mod, "LoadLibraryA"));
- getProcAddressFunc = reinterpret_cast<TGetProcAddressType>(::GetProcAddress(k32mod, "GetProcAddress"));
- if ((loadLibraryFunc == nullptr) || (getProcAddressFunc == nullptr)) {
- throw windows_error("failed to determine address for required functions");
- }
- } else {
- throw windows_error("kernel32.dll not loaded??");
- }
-
- // allocate memory in the target process and write the parameter-block there
- LPVOID remoteMem = ::VirtualAllocEx(processHandle, nullptr, sizeof(TParameters), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
- if (remoteMem == nullptr) {
- throw windows_error("failed to allocate memory in target process");
- }
- SIZE_T written;
- if (!::WriteProcessMemory(processHandle, remoteMem, ¶meters, sizeof(TParameters), &written) ||
- (written != sizeof(TParameters))) {
- throw windows_error("failed to write parameters to target process");
- }
-
- // now for the interesting part: write a stub into the target process that is run before any code of the original binary. This code will load
- // our injected dll into the process and run its Init-function which takes the profile name as its parameter
- // obviously this code is ultra-hacky
-
- // construct the stub in beautiful assembler with placeholders
- BYTE stubLocal[] = { 0x60, // PUSHAD
- 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOV EAX, imm32 (LoadLibrary)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (dllname)
- 0xFF, 0xD0, // CALL EAX (=LoadLibrary, leaves module handle of our dll in eax)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 ("Init")
- 0x50, // PUSH EAX
- 0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOVE EAX, imm32 (GetProcAddress)
- 0xFF, 0xD0, // CALL EAX (=GetProcAddress, leaves address of init function in eax)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (profile name)
- 0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (log level)
- 0xFF, 0xD0, // CALL EAX (=InitFunction)
- 0x58, // POP EAX (init function is defined cdecl)
- 0x58, // POP EAX (init function is defined cdecl)
- 0x61, // POPAD
- 0xE9, 0xBA, 0xAD, 0xF0, 0x0D // JMP near, relative (=original entry point)
- };
-
- // reserve memory for the stub
- PBYTE stubRemote = reinterpret_cast<PBYTE>(::VirtualAllocEx(processHandle, nullptr, sizeof(stubLocal), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
- if (stubRemote == nullptr) {
- throw windows_error("failed to allocate memory for stub");
- }
- TParameters *remoteParams = reinterpret_cast<TParameters*>(remoteMem);
-
- // dizzy yet? we still have to calculate the entry point as an address relative to our stub
- ULONG entryPoint = 0;
-
- // not implemented on 64 bit systems
-#ifdef _X86_
- CONTEXT threadContext;
- threadContext.ContextFlags = CONTEXT_CONTROL;
- if (::GetThreadContext(threadHandle, &threadContext) == 0) {
- throw windows_error("failed to access thread context. Please note that Mod Organizer does not support 64bit binaries!");
- } else {
- entryPoint = threadContext.Eip - (reinterpret_cast<ULONG>(stubRemote) + sizeof(stubLocal));
- }
- // now replace each baadf00d by the correct value. The pointers need to be the pointers in the REMOTE memory of course, that's why we copied them there
- *(PULONG)&stubLocal[2] = reinterpret_cast<ULONG>(*loadLibraryFunc);
- *(PULONG)&stubLocal[7] = reinterpret_cast<ULONG>(remoteParams->dllname);
- *(PULONG)&stubLocal[14] = reinterpret_cast<ULONG>(remoteParams->initstr);
- *(PULONG)&stubLocal[20] = reinterpret_cast<ULONG>(*getProcAddressFunc);
- *(PULONG)&stubLocal[27] = reinterpret_cast<ULONG>(remoteParams->profileName);
- *(PULONG)&stubLocal[32] = logLevel;
- *(PULONG)&stubLocal[42] = entryPoint;
- // almost there. copy stub to target process
- if (!::WriteProcessMemory(processHandle, stubRemote, reinterpret_cast<LPCVOID>(stubLocal), sizeof(stubLocal), &written) ||
- (written != sizeof(stubLocal))) {
- throw windows_error("failed to write stub to target process");
- }
-
- // finally, make the stub the new next thing for the thread to execute
- threadContext.Eip = (ULONG)stubRemote;
- if (::SetThreadContext(threadHandle, &threadContext) == 0) {
- throw windows_error("failed to overwrite thread context");
- }
-#endif
-}
-
-} // namespace MOShared
diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp index 73786c6b..1ff1fa9d 100644 --- a/src/shared/leaktrace.cpp +++ b/src/shared/leaktrace.cpp @@ -33,8 +33,9 @@ static struct __TraceData { "%d objects not freed, allocated at:\n%s",
iter->second.size(), iter->first.toString().c_str());
printf("Addresses: ");
- for (int i = 0; i < (std::min<int>)(5, iter->second.size()); ++i) {
- printf("%p, ", reinterpret_cast<void*>(iter->second[i]));
+ for (int i = 0;
+ i < (std::min<int>)(5, static_cast<int>(iter->second.size())); ++i) {
+ printf("%p, ", reinterpret_cast<void *>(iter->second[i]));
}
printf("\n");
}
diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 3692aae1..5491a9e6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -81,12 +81,16 @@ std::wstring ToWString(const std::string &source, bool utf8) if (!utf8) {
codepage = AreFileApisANSI() ? GetACP() : GetOEMCP();
}
- int sizeRequired = ::MultiByteToWideChar(codepage, 0, source.c_str(), source.length(), nullptr, 0);
+ int sizeRequired
+ = ::MultiByteToWideChar(codepage, 0, source.c_str(),
+ static_cast<int>(source.length()), nullptr, 0);
if (sizeRequired == 0) {
throw windows_error("failed to convert string to wide character");
}
result.resize(sizeRequired, L'\0');
- ::MultiByteToWideChar(codepage, 0, source.c_str(), source.length(), &result[0], sizeRequired);
+ ::MultiByteToWideChar(codepage, 0, source.c_str(),
+ static_cast<int>(source.length()), &result[0],
+ sizeRequired);
}
return result;
diff --git a/src/spawn.cpp b/src/spawn.cpp index 49e89a8b..ac8ccf30 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -22,7 +22,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "report.h"
#include "utility.h"
#include <report.h>
-#include <inject.h>
+#include <usvfs.h>
+#include <Shellapi.h>
#include <appconfig.h>
#include <windows_error.h>
@@ -39,7 +40,8 @@ using namespace MOShared; static const int BUFSIZE = 4096;
-static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended,
+static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory,
+ bool suspended, bool hooked,
HANDLE stdOut, HANDLE stdErr,
HANDLE& processHandle, HANDLE& threadHandle)
{
@@ -57,7 +59,7 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b si.dwFlags |= STARTF_USESTDHANDLES;
}
si.cb = sizeof(si);
- int length = wcslen(binary) + wcslen(arguments) + 4;
+ size_t length = wcslen(binary) + wcslen(arguments) + 4;
wchar_t *commandLine = nullptr;
if (arguments[0] != L'\0') {
commandLine = new wchar_t[length];
@@ -87,15 +89,28 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b }
PROCESS_INFORMATION pi;
- BOOL success = ::CreateProcess(nullptr,
- commandLine,
- nullptr, nullptr, // 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
- nullptr, // same environment as parent
- currentDirectory, // current directory
- &si, &pi // startup and process information
- );
+ BOOL success = FALSE;
+ if (hooked) {
+ success = ::CreateProcessHooked(nullptr,
+ commandLine,
+ nullptr, nullptr, // no special process or thread attributes
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
+ CREATE_BREAKAWAY_FROM_JOB,
+ nullptr, // same environment as parent
+ currentDirectory, // current directory
+ &si, &pi // startup and process information
+ );
+ } else {
+ success = ::CreateProcess(nullptr,
+ commandLine,
+ nullptr, nullptr, // no special process or thread attributes
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
+ CREATE_BREAKAWAY_FROM_JOB,
+ nullptr, // same environment as parent
+ currentDirectory, // current directory
+ &si, &pi // startup and process information
+ );
+ }
::SetEnvironmentVariable(TEXT("PATH"), oldPath.get());
@@ -113,33 +128,18 @@ static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, b HANDLE startBinary(const QFileInfo &binary,
const QString &arguments,
- const QString& profileName,
- int logLevel,
const QDir ¤tDirectory,
bool hooked,
HANDLE stdOut,
HANDLE stdErr)
{
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo;
-
- ::QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation, &jobInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), nullptr);
- jobInfo.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
-
- HANDLE jobObject = ::CreateJobObject(nullptr, nullptr);
-
- if (jobObject == nullptr) {
- qWarning("failed to create job object: %lu", ::GetLastError());
- } else {
- ::SetInformationJobObject(jobObject, JobObjectExtendedLimitInformation, &jobInfo, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
- }
-
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)) {
+ if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(),
+ true, hooked, stdOut, stdErr, processHandle, threadHandle)) {
reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
return INVALID_HANDLE_VALUE;
}
@@ -168,38 +168,6 @@ HANDLE startBinary(const QFileInfo &binary, }
}
- 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;
+ return processHandle;
}
diff --git a/src/spawn.h b/src/spawn.h index 63eda18d..c2d99bdb 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -60,7 +60,7 @@ private: * @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,
+HANDLE startBinary(const QFileInfo &binary, const QString &arguments,
const QDir ¤tDirectory, bool hooked,
HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE);
diff --git a/src/splash.png b/src/splash.png Binary files differindex 3eec8bed..d29a6711 100644 --- a/src/splash.png +++ b/src/splash.png diff --git a/src/stylesheets/Paper Dark by 6788-00.qss b/src/stylesheets/Paper Dark by 6788-00.qss new file mode 100644 index 00000000..ea36e675 --- /dev/null +++ b/src/stylesheets/Paper Dark by 6788-00.qss @@ -0,0 +1,507 @@ +/* v3.0 Paper Dark by 6788-00 */ +/* https://6788-00.tumblr.com/ */ + +/* Main Window */ + +QWidget { + background: #222222; + color: #D3D3D3; +} + +QWidget:disabled { + background: #222222; + color: #808080; +} + +QMainWindow::separator { + border: 0px; +} + +QAbstractItemView { + background: #141414; + alternate-background-color: #1A1A1A; + show-decoration-selected: 1; + selection-background-color: #007272; + selection-color: #FFFFFF; +} + +QAbstractItemView::item:hover { + background: #007E7E; + color: #FFFFFF; +} + +QAbstractItemView::item:selected { + background: #007272; + color: #FFFFFF; +} + +QAbstractScrollArea::corner { + background: #141414; + border: 2px solid #222222; + border-bottom-right-radius: 6px; + margin: 0px -2px -2px 0px; +} + +/* Toolbar */ + +QToolBar { + background: #222222; + border: 1px solid #222222; +} + +QToolBar::separator { + background: #222222; +} + +QToolButton { + background: #1A1A1A; + padding: 4px 6px; + border-radius: 6px; + margin: 4px 4px 0px 4px; +} + +QToolButton:hover { + background: #007E7E; +} + +QToolButton:pressed { + background: #007272; +} + +/* Left Pane & File Trees */ + +QTreeView { + border-radius: 6px; +} + +QTreeView::branch:hover { + background: #007E7E; + color: #FFFFFF; +} + +QTreeView::branch:selected { + background: #007272; + color: #FFFFFF; +} + +QTreeView::item:selected { + background: #007272; + color: #FFFFFF; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url(:/stylesheet/branch-closed.png); + border: 0px; +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url(:/stylesheet/branch-open.png); + border: 0px; +} + +QListView { + border-radius: 6px; +} + +QListView::item:hover { + background: #007E7E; + color: #FFFFFF; +} + +QListView::item:selected { + background: #007272; + color: #FFFFFF; +} + +QTextEdit { + background: #141414; + border-radius: 6px; +} + +QWebView { + background: #141414; + border-radius: 6px; +} + +/* Group Boxes */ + +QGroupBox { + padding: 24px 4px; + border: 2px solid #141414; + border-radius: 10px; +} + +QGroupBox::title { + subcontrol-origin: padding; + subcontrol-position: top left; + padding: 8px; +} + +/* Search Boxes */ + +QLineEdit { + background: #141414; + min-height: 14px; + padding: 2px; + border: 2px solid #141414; + border-radius: 6px; + margin-top: 3px; +} + +QLineEdit:hover { + border: 2px solid #007E7E; +} + +/* Most Dropdowns */ + +QComboBox { + background: #141414; + min-height: 20px; + padding-left: 5px; + border: 2px solid #141414; + border-radius: 6px; + margin: 3px 0px 1px 0px; +} + +QComboBox:hover { + border: 2px solid #007E7E; +} + +QComboBox:on { + background: #007272; + color: #FFFFFF; + border: 2px solid #007272; +} + +QComboBox::drop-down { + width: 20px; + subcontrol-origin: padding; + subcontrol-position: top right; + border: none; +} + +QComboBox QAbstractItemView { + border: 0px; +} + +QComboBox::down-arrow { + image: url(:/stylesheet/combobox-down.png); +} + +/* Most Buttons */ + +QPushButton { + background: #141414; + color: #FFFFFF; + min-height: 18px; + padding: 2px 12px; + border-radius: 6px; +} + +QPushButton:hover { + background: #007E7E; + color: #FFFFFF; +} + +QPushButton:pressed { + background: #007272; + color: #FFFFFF; +} + +QPushButton:checked { + background: #007272; + color: #FFFFFF; + margin: 4px; +} + +/* Scroll Bars */ + +/* Horizontal */ + +QScrollBar:horizontal { + background: #141414; + height: 20px; + border: 2px solid #222222; + margin: 0px 23px -2px 23px; +} + +QScrollBar::handle:horizontal { + background: #222222; + min-width: 32px; + border-radius: 6px; + margin: 2px; +} + +QScrollBar::add-line:horizontal { + background: #141414; + width: 23px; + subcontrol-position: right; + subcontrol-origin: margin; + border: 2px solid #222222; + margin: 0px -2px -2px 0px; +} + +QScrollBar::sub-line:horizontal { + background: #141414; + width: 23px; + subcontrol-position: left; + subcontrol-origin: margin; + border: 2px solid #222222; + border-bottom-left-radius: 6px; + margin: 0px 0px -2px -2px; +} + +/* Vertical */ + +QScrollBar:vertical { + background: #141414; + width: 20px; + border: 2px solid #222222; + margin: 23px -2px 23px 0px; +} + +QScrollBar::handle:vertical { + background: #222222; + min-height: 32px; + border-radius: 6px; + margin: 2px; +} + +QScrollBar::add-line:vertical { + background: #141414; + height: 23px; + subcontrol-position: bottom; + subcontrol-origin: margin; + border: 2px solid #222222; + border-bottom-right-radius: 6px; + margin: 0px -2px -2px 0px; +} + +QScrollBar::sub-line:vertical { + background: #141414; + height: 23px; + subcontrol-position: top; + subcontrol-origin: margin; + border: 2px solid #222222; + border-top-right-radius: 6px; + margin: -2px -2px 0px 0px; +} + +/* Combined */ + +QScrollBar::handle:horizontal:hover, +QScrollBar::handle:vertical:hover, +QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, +QScrollBar::sub-line:vertical:hover { + background: #007E7E; +} + +QScrollBar::handle:horizontal:pressed, +QScrollBar::handle:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, +QScrollBar::sub-line:vertical:pressed { + background: #007272; +} + +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal, +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical { + background: transparent; +} + +QScrollBar::up-arrow:vertical, +QScrollBar::right-arrow:horizontal, +QScrollBar::down-arrow:vertical, +QScrollBar::left-arrow:horizontal { + height: 1px; + width: 1px; + border: 1px solid #222222; +} + +/* Header Rows */ + +QHeaderView { + background: #222222; +} + +QHeaderView::section { + background: #141414; + color: #D3D3D3; + height: 22px; + padding: 0px 5px; + border: 0px; + border-bottom: 2px solid #222222; + border-right: 2px solid #222222; +} + +QHeaderView::section:first { + border-top-left-radius: 6px; +} + +QHeaderView::section:last { + border-right: 0px; + border-top-right-radius: 6px; +} + +QHeaderView::section:hover { + background: #007E7E; + color: #FFFFFF; +} + +QHeaderView::down-arrow { + padding-right: 4px; + height: 10px; + width: 10px; +} + +/* Context Menus, Toolbar Dropdowns, & Tooltips */ + +QMenu { + background: #141414; + selection-color: #FFFFFF; + border: 0px; +} + +QMenu::item { + background: #141414; + selection-background-color: #007E7E; + padding: 4px 20px; +} + +QMenu::item:selected { + background: #007E7E; + color: #FFFFFF; +} + +QMenu::item:disabled { + background: #FFFFFF; + color: #6C6C6C; +} + +QMenu::separator { + background: #222222; + height: 2px; +} + +QMenu::icon { + margin: 1px; +} + +QToolTip { + background: #222222; + color: #FFFFFF; + padding: 1px; + border: 0px; +} + +/* Progress Bars (Downloads) */ + +QProgressBar { + background: #141414; + text-align: center; + border: 0px; + border-radius: 6px; + margin: 0px 10px; +} + +QProgressBar::chunk { + background: #007E7E; +} + +/* Right Pane and Tab Bars */ + +QTabWidget::pane { + top: 1px; + padding: 2px 2px 10px 2px; + border: 2px solid #141414; + border-radius: 10px; +} + +QTabWidget::tab-bar { + alignment: center; +} + +QTabBar::tab { + background: #141414; + color: #141414; + padding: 4px 1em; + border: 1px solid #222222; + border-top: 0px; + border-bottom: 0px; +} + +QTabBar::tab:!selected { + background: #141414; + color: #D3D3D3; +} + +QTabBar::tab:disabled { + background: #222222; + color: #808080; +} + +QTabBar::tab:selected { + color: #007272; + +} + +QTabBar::tab:!selected:hover { + background: #007E7E; + color: #FFFFFF; +} + +QTabBar::tab:first { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} + +QTabBar::tab:last { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} + +QTabBar QToolButton { + background: #007E7E; + padding: 1px; + border-radius: 6px; + margin: 1px; +} + +QTabBar QToolButton:disabled { + background: transparent; +} + +/* Sliders (Configurator) */ + +/* QSlider::groove:horizontal { + background: #FFFFFF; + height: 1px; + border: 1px solid #FFFFFF; +} + +QSlider::handle:horizontal { + background: #007E7E; + width: 10px; + border: 2px solid #007E7E; + border-radius: 6px; + margin: -10px 0px; +} + +QSlider::handle:horizontal:hover { + background: #007272; + border: 2px solid #007272; +} */ + +/* Tables (Configure Mod Categories) */ + +QTableView { + gridline-color: #222222; + border: 0px; +}
\ No newline at end of file diff --git a/src/stylesheets/Paper Light by 6788-00.qss b/src/stylesheets/Paper Light by 6788-00.qss new file mode 100644 index 00000000..a75831f8 --- /dev/null +++ b/src/stylesheets/Paper Light by 6788-00.qss @@ -0,0 +1,507 @@ +/* v3.0 Paper Light by 6788-00 */ +/* https://6788-00.tumblr.com/ */ + +/* Main Window */ + +QWidget { + background: #EFEFEF; + color: #000000; +} + +QWidget:disabled { + background: #EFEFEF; + color: #6C6C6C; +} + +QMainWindow::separator { + border: 0px; +} + +QAbstractItemView { + background: #FFFFFF; + alternate-background-color: #F6F6F6; + show-decoration-selected: 1; + selection-background-color: #008484; + selection-color: #FFFFFF; +} + +QAbstractItemView::item:hover { + background: #008F8F; + color: #FFFFFF; +} + +QAbstractItemView::item:selected { + background: #008484; + color: #FFFFFF; +} + +QAbstractScrollArea::corner { + background: #FFFFFF; + border: 2px solid #EFEFEF; + border-bottom-right-radius: 6px; + margin: 0px -2px -2px 0px; +} + +/* Toolbar */ + +QToolBar { + background: #EFEFEF; + border: 1px solid #EFEFEF; +} + +QToolBar::separator { + background: #EFEFEF; +} + +QToolButton { + background: #FFFFFF; + padding: 4px 6px; + border-radius: 6px; + margin: 4px 4px 0px 4px; +} + +QToolButton:hover { + background: #008F8F; +} + +QToolButton:pressed { + background: #008484; +} + +/* Left Pane & File Trees */ + +QTreeView { + border-radius: 6px; +} + +QTreeView::branch:hover { + background: #008F8F; + color: #FFFFFF; +} + +QTreeView::branch:selected { + background: #008484; + color: #FFFFFF; +} + +QTreeView::item:selected { + background: #008484; + color: #FFFFFF; +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url(:/stylesheet/branch-closed.png); + border: 0px; +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url(:/stylesheet/branch-open.png); + border: 0px; +} + +QListView { + border-radius: 6px; +} + +QListView::item:hover { + background: #008F8F; + color: #FFFFFF; +} + +QListView::item:selected { + background: #008484; + color: #FFFFFF; +} + +QTextEdit { + background: #FFFFFF; + border-radius: 6px; +} + +QWebView { + background: #FFFFFF; + border-radius: 6px; +} + +/* Group Boxes */ + +QGroupBox { + padding: 24px 4px; + border: 2px solid #FFFFFF; + border-radius: 10px; +} + +QGroupBox::title { + subcontrol-origin: padding; + subcontrol-position: top left; + padding: 8px; +} + +/* Search Boxes */ + +QLineEdit { + background: #FFFFFF; + min-height: 14px; + padding: 2px; + border: 2px solid #FFFFFF; + border-radius: 6px; + margin-top: 3px; +} + +QLineEdit:hover { + border: 2px solid #008F8F; +} + +/* Most Dropdowns */ + +QComboBox { + background: #FFFFFF; + min-height: 20px; + padding-left: 5px; + border: 2px solid #FFFFFF; + border-radius: 6px; + margin: 3px 0px 1px 0px; +} + +QComboBox:hover { + border: 2px solid #008F8F; +} + +QComboBox:on { + background: #008484; + color: #FFFFFF; + border: 2px solid #008484; +} + +QComboBox::drop-down { + width: 20px; + subcontrol-origin: padding; + subcontrol-position: top right; + border: none; +} + +QComboBox QAbstractItemView { + border: 0px; +} + +QComboBox::down-arrow { + image: url(:/stylesheet/combobox-down.png); +} + +/* Most Buttons */ + +QPushButton { + background: #FFFFFF; + color: #000000; + min-height: 18px; + padding: 2px 12px; + border-radius: 6px; +} + +QPushButton:hover { + background: #008F8F; + color: #FFFFFF; +} + +QPushButton:pressed { + background: #008484; + color: #FFFFFF; +} + +QPushButton:checked { + background: #008484; + color: #FFFFFF; + margin: 4px; +} + +/* Scroll Bars */ + +/* Horizontal */ + +QScrollBar:horizontal { + background: #FFFFFF; + height: 20px; + border: 2px solid #EFEFEF; + margin: 0px 23px -2px 23px; +} + +QScrollBar::handle:horizontal { + background: #EFEFEF; + min-width: 32px; + border-radius: 6px; + margin: 2px; +} + +QScrollBar::add-line:horizontal { + background: #FFFFFF; + width: 23px; + subcontrol-position: right; + subcontrol-origin: margin; + border: 2px solid #EFEFEF; + margin: 0px -2px -2px 0px; +} + +QScrollBar::sub-line:horizontal { + background: #FFFFFF; + width: 23px; + subcontrol-position: left; + subcontrol-origin: margin; + border: 2px solid #EFEFEF; + border-bottom-left-radius: 6px; + margin: 0px 0px -2px -2px; +} + +/* Vertical */ + +QScrollBar:vertical { + background: #FFFFFF; + width: 20px; + border: 2px solid #EFEFEF; + margin: 23px -2px 23px 0px; +} + +QScrollBar::handle:vertical { + background: #EFEFEF; + min-height: 32px; + border-radius: 6px; + margin: 2px; +} + +QScrollBar::add-line:vertical { + background: #FFFFFF; + height: 23px; + subcontrol-position: bottom; + subcontrol-origin: margin; + border: 2px solid #EFEFEF; + border-bottom-right-radius: 6px; + margin: 0px -2px -2px 0px; +} + +QScrollBar::sub-line:vertical { + background: #FFFFFF; + height: 23px; + subcontrol-position: top; + subcontrol-origin: margin; + border: 2px solid #EFEFEF; + border-top-right-radius: 6px; + margin: -2px -2px 0px 0px; +} + +/* Combined */ + +QScrollBar::handle:horizontal:hover, +QScrollBar::handle:vertical:hover, +QScrollBar::add-line:horizontal:hover, +QScrollBar::sub-line:horizontal:hover, +QScrollBar::add-line:vertical:hover, +QScrollBar::sub-line:vertical:hover { + background: #008F8F; +} + +QScrollBar::handle:horizontal:pressed, +QScrollBar::handle:vertical:pressed, +QScrollBar::add-line:horizontal:pressed, +QScrollBar::sub-line:horizontal:pressed, +QScrollBar::add-line:vertical:pressed, +QScrollBar::sub-line:vertical:pressed { + background: #008484; +} + +QScrollBar::add-page:horizontal, +QScrollBar::sub-page:horizontal, +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical { + background: transparent; +} + +QScrollBar::up-arrow:vertical, +QScrollBar::right-arrow:horizontal, +QScrollBar::down-arrow:vertical, +QScrollBar::left-arrow:horizontal { + height: 1px; + width: 1px; + border: 1px solid #EFEFEF; +} + +/* Header Rows */ + +QHeaderView { + background: #EFEFEF; +} + +QHeaderView::section { + background: #FFFFFF; + color: #000000; + height: 22px; + padding: 0px 5px; + border: 0px; + border-bottom: 2px solid #EFEFEF; + border-right: 2px solid #EFEFEF; +} + +QHeaderView::section:first { + border-top-left-radius: 6px; +} + +QHeaderView::section:last { + border-right: 0px; + border-top-right-radius: 6px; +} + +QHeaderView::section:hover { + background: #008F8F; + color: #FFFFFF; +} + +QHeaderView::down-arrow { + padding-right: 4px; + height: 10px; + width: 10px; +} + +/* Context Menus, Toolbar Dropdowns, & Tooltips */ + +QMenu { + background: #FFFFFF; + selection-color: #FFFFFF; + border: 0px; +} + +QMenu::item { + background: #FFFFFF; + selection-background-color: #008F8F; + padding: 4px 20px; +} + +QMenu::item:selected { + background: #008F8F; + color: #FFFFFF; +} + +QMenu::item:disabled { + background: #FFFFFF; + color: #6C6C6C; +} + +QMenu::separator { + background: #EFEFEF; + height: 2px; +} + +QMenu::icon { + margin: 1px; +} + +QToolTip { + background: #FFFFFF; + color: #000000; + padding: 1px; + border: 0px; +} + +/* Progress Bars (Downloads) */ + +QProgressBar { + background: #FFFFFF; + text-align: center; + border: 0px; + border-radius: 6px; + margin: 0px 10px; +} + +QProgressBar::chunk { + background: #008F8F; +} + +/* Right Pane and Tab Bars */ + +QTabWidget::pane { + top: 1px; + padding: 2px 2px 10px 2px; + border: 2px solid #FFFFFF; + border-radius: 10px; +} + +QTabWidget::tab-bar { + alignment: center; +} + +QTabBar::tab { + background: #FFFFFF; + color: #000000; + padding: 4px 1em; + border: 1px solid #EFEFEF; + border-top: 0px; + border-bottom: 0px; +} + +QTabBar::tab:!selected { + background: #FFFFFF; + color: #000000; +} + +QTabBar::tab:disabled { + background: #EFEFEF; + color: #6C6C6C; +} + +QTabBar::tab:selected { + color: #008484; + +} + +QTabBar::tab:!selected:hover { + background: #008484; + color: #FFFFFF; +} + +QTabBar::tab:first { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} + +QTabBar::tab:last { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} + +QTabBar QToolButton { + background: #008F8F; + padding: 1px; + border-radius: 6px; + margin: 1px; +} + +QTabBar QToolButton:disabled { + background: transparent; +} + +/* Sliders (Configurator) */ + +/* QSlider::groove:horizontal { + background: #FFFFFF; + height: 1px; + border: 1px solid #FFFFFF; +} + +QSlider::handle:horizontal { + background: #008F8F; + width: 10px; + border: 2px solid #008F8F; + border-radius: 6px; + margin: -10px 0px; +} + +QSlider::handle:horizontal:hover { + background: #008484; + border: 2px solid #008484; +} */ + +/* Tables (Configure Mod Categories) */ + +QTableView { + gridline-color: #EFEFEF; + border: 0px; +}
\ No newline at end of file diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index aeed0a55..f03e29c5 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -98,9 +98,9 @@ void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *director bool ignore;
int origin = entry->getOrigin(ignore);
addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin);
- const std::vector<int> &alternatives = entry->getAlternatives();
- for (std::vector<int>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
- addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter);
+ const std::vector<std::pair<int, std::wstring>> &alternatives = entry->getAlternatives();
+ for (std::vector<std::pair<int, std::wstring>>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
+ addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(iter->first).getName()), iter->first);
}
combo->setCurrentIndex(combo->count() - 1);
} else {
diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index cb3d0d61..b8dda056 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -23,6 +23,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "iplugingame.h"
#include "isavegame.h"
#include "savegameinfo.h"
+#include <utility.h>
#include <QtDebug>
#include <QDateTime>
@@ -159,6 +160,7 @@ bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QStr return res == QMessageBox::Yes;
}
+
#define MOVE_SAVES "Move all save games of character \"%1\""
#define COPY_SAVES "Copy all save games of character \"%1\""
@@ -168,13 +170,13 @@ bool TransferSavesDialog::testOverwrite(OverwriteMode &overwriteMode, const QStr void TransferSavesDialog::on_moveToLocalBtn_clicked()
{
QString character = ui->globalCharacterList->currentItem()->text();
- if (transferCharacters(character,
- MOVE_SAVES TO_PROFILE,
- m_GlobalSaves[character],
- m_Profile.savePath(),
- QFile::rename,
- "Failed to move %s to %s"))
- {
+ if (transferCharacters(
+ character, MOVE_SAVES TO_PROFILE, m_GlobalSaves[character],
+ m_Profile.savePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellMove(source, destination, this);
+ },
+ "Failed to move %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -185,12 +187,13 @@ void TransferSavesDialog::on_moveToLocalBtn_clicked() void TransferSavesDialog::on_copyToLocalBtn_clicked()
{
QString character = ui->globalCharacterList->currentItem()->text();
- if (transferCharacters(character,
- COPY_SAVES TO_PROFILE,
- m_GlobalSaves[character],
- m_Profile.savePath(),
- QFile::copy,
- "Failed to copy %s to %s")) {
+ if (transferCharacters(
+ character, COPY_SAVES TO_PROFILE, m_GlobalSaves[character],
+ m_Profile.savePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellCopy(source, destination, this);
+ },
+ "Failed to copy %s to %s")) {
refreshLocalSaves();
refreshLocalCharacters();
}
@@ -199,12 +202,13 @@ void TransferSavesDialog::on_copyToLocalBtn_clicked() void TransferSavesDialog::on_moveToGlobalBtn_clicked()
{
QString character = ui->localCharacterList->currentItem()->text();
- if (transferCharacters(character,
- MOVE_SAVES TO_GLOBAL,
- m_LocalSaves[character],
- m_GamePlugin->savesDirectory().absolutePath(),
- QFile::rename,
- "Failed to move %s to %s")) {
+ if (transferCharacters(
+ character, MOVE_SAVES TO_GLOBAL, m_LocalSaves[character],
+ m_GamePlugin->savesDirectory().absolutePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellMove(source, destination, this);
+ },
+ "Failed to move %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
refreshLocalSaves();
@@ -215,12 +219,13 @@ void TransferSavesDialog::on_moveToGlobalBtn_clicked() void TransferSavesDialog::on_copyToGlobalBtn_clicked()
{
QString character = ui->localCharacterList->currentItem()->text();
- if (transferCharacters(character,
- COPY_SAVES TO_GLOBAL,
- m_LocalSaves[character],
- m_GamePlugin->savesDirectory().absolutePath(),
- QFile::copy,
- "Failed to copy %s to %s")) {
+ if (transferCharacters(
+ character, COPY_SAVES TO_GLOBAL, m_LocalSaves[character],
+ m_GamePlugin->savesDirectory().absolutePath(),
+ [this](const QString &source, const QString &destination) -> bool {
+ return shellCopy(source, destination, this);
+ },
+ "Failed to copy %s to %s")) {
refreshGlobalSaves();
refreshGlobalCharacters();
}
@@ -295,12 +300,11 @@ void TransferSavesDialog::refreshCharacters(const SaveCollection &saveCollection }
}
-bool TransferSavesDialog::transferCharacters(QString const &character,
- char const *message,
- SaveList &saves,
- QString const &dest,
- bool (method)(QString const &, QString const &),
- char const *errmsg)
+bool TransferSavesDialog::transferCharacters(
+ QString const &character, char const *message, SaveList &saves,
+ QString const &dest,
+ const std::function<bool(const QString &, const QString &)> &method,
+ char const *errmsg)
{
if (QMessageBox::question(this, tr("Confirm"),
tr(message).arg(character),
@@ -325,11 +329,31 @@ bool TransferSavesDialog::transferCharacters(QString const &character, QFile::remove(destinationFile);
}
- if (! method(sourceFile.absoluteFilePath(), destinationFile)) {
+ if (!method(sourceFile.absoluteFilePath(), destinationFile)) {
qCritical(errmsg,
sourceFile.absoluteFilePath().toUtf8().constData(),
destinationFile.toUtf8().constData());
}
+
+ QFileInfo sourceFileSE(sourceFile.absolutePath() + "/" + sourceFile.completeBaseName() + "." + m_GamePlugin->savegameSEExtension());
+ if (sourceFileSE.exists()) {
+ QString destinationFileSE(destination.absoluteFilePath(sourceFileSE.fileName()));
+
+ //If the file is already there, let them skip (or not).
+ if (QFile::exists(destinationFileSE)) {
+ if (!testOverwrite(overwriteMode, destinationFileSE)) {
+ continue;
+ }
+ //OK, they want to remove it.
+ QFile::remove(destinationFileSE);
+ }
+
+ if (!method(sourceFileSE.absoluteFilePath(), destinationFileSE)) {
+ qCritical(errmsg,
+ sourceFileSE.absoluteFilePath().toUtf8().constData(),
+ destinationFileSE.toUtf8().constData());
+ }
+ }
}
}
return true;
diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index d3f079e3..dee41c72 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -37,14 +37,15 @@ namespace Ui { class TransferSavesDialog; } namespace MOBase { class IPluginGame; }
namespace MOBase { class ISaveGame; }
-class TransferSavesDialog : public MOBase::TutorableDialog
-{
+class TransferSavesDialog : public MOBase::TutorableDialog {
Q_OBJECT
-
+
public:
- explicit TransferSavesDialog(const Profile &profile, MOBase::IPluginGame const *gamePlugin, QWidget *parent = 0);
+ explicit TransferSavesDialog(const Profile &profile,
+ MOBase::IPluginGame const *gamePlugin,
+ QWidget *parent = 0);
~TransferSavesDialog();
-
+
private slots:
void on_moveToLocalBtn_clicked();
@@ -62,23 +63,17 @@ private slots: void on_copyToGlobalBtn_clicked();
private:
-
- enum OverwriteMode {
- OVERWRITE_ASK,
- OVERWRITE_YES,
- OVERWRITE_NO
- };
+ enum OverwriteMode { OVERWRITE_ASK, OVERWRITE_YES, OVERWRITE_NO };
private:
-
void refreshGlobalCharacters();
void refreshLocalCharacters();
void refreshGlobalSaves();
void refreshLocalSaves();
- bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
+ bool testOverwrite(OverwriteMode &overwriteMode,
+ const QString &destinationFile);
private:
-
Ui::TransferSavesDialog *ui;
Profile m_Profile;
@@ -93,18 +88,14 @@ private: void refreshSaves(SaveCollection &saveCollection, const QString &savedir);
void refreshCharacters(SaveCollection const &saveCollection,
- QListWidget *charList,
- QPushButton *copy,
+ QListWidget *charList, QPushButton *copy,
QPushButton *move);
- bool transferCharacters(QString const &character,
- char const *message,
- SaveList &saves,
- QString const &dest,
- bool (method)(QString const &, QString const &),
- char const *errmsg
- );
-
+ bool transferCharacters(
+ QString const &character, char const *message, SaveList &saves,
+ QString const &dest,
+ const std::function<bool(const QString &, const QString &)> &method,
+ char const *errmsg);
};
#endif // TRANSFERSAVESDIALOG_H
diff --git a/src/tutorials/Highlight.qml b/src/tutorials/Highlight.qml index 55474c91..b6fb7676 100644 --- a/src/tutorials/Highlight.qml +++ b/src/tutorials/Highlight.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
Rectangle {
radius: 10
diff --git a/src/tutorials/Tooltip.qml b/src/tutorials/Tooltip.qml index 6424d9ab..cca71efa 100644 --- a/src/tutorials/Tooltip.qml +++ b/src/tutorials/Tooltip.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
Rectangle {
diff --git a/src/tutorials/TooltipArea.qml b/src/tutorials/TooltipArea.qml index 48a50f76..8d404beb 100644 --- a/src/tutorials/TooltipArea.qml +++ b/src/tutorials/TooltipArea.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
Rectangle {
radius: 2
@@ -17,7 +17,7 @@ Rectangle { anchors.fill: parent
hoverEnabled: true
- onMousePositionChanged: {
+ onPositionChanged: {
if (parent.parent.width - (parent.x + mouseX) < tooltip.width + 50) {
tooltip.x = parent.x + mouseX - 15 - tooltip.width
} else {
diff --git a/src/tutorials/TutorialDescription.qml b/src/tutorials/TutorialDescription.qml index 498573d2..4079b70d 100644 --- a/src/tutorials/TutorialDescription.qml +++ b/src/tutorials/TutorialDescription.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
// rectangle for description texts
Rectangle {
diff --git a/src/tutorials/TutorialOverlay.qml b/src/tutorials/TutorialOverlay.qml index 15fe9603..47e5065d 100644 --- a/src/tutorials/TutorialOverlay.qml +++ b/src/tutorials/TutorialOverlay.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
import "tutorials.js" as Logic
Rectangle {
diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js index 3a7160c6..a60a5c8d 100644 --- a/src/tutorials/tutorial_conflictresolution_main.js +++ b/src/tutorials/tutorial_conflictresolution_main.js @@ -44,19 +44,19 @@ function getTutorialSteps() { },
function() {
unhighlight()
- tutorial.text = qsTr("<img src=\":/MO/gui/emblem_conflict_overwrite\" /> indicates that the mod overwrites files that are also available in another mod.")
+ tutorial.text = qsTr("<img src=\"qrc:///MO/gui/emblem_conflict_overwrite\" /> indicates that the mod overwrites files that are also available in another mod.")
waitForClick()
},
function() {
- tutorial.text = qsTr("<img src=\":/MO/gui/emblem_conflict_overwritten\" /> indicates that the mod is <b>partially</b> overwritten by another.")
+ tutorial.text = qsTr("<img src=\"qrc:///MO/gui/emblem_conflict_overwritten\" /> indicates that the mod is <b>partially</b> overwritten by another.")
waitForClick()
},
function() {
- tutorial.text = qsTr("<img src=\":/MO/gui/emblem_conflict_mixed\" /> indicates that both of the above is true.")
+ tutorial.text = qsTr("<img src=\"qrc:///MO/gui/emblem_conflict_mixed\" /> indicates that both of the above is true.")
waitForClick()
},
function() {
- tutorial.text = qsTr("<img src=\":/MO/gui/emblem_conflict_redundant\" /> indicates that the mod is completely overwrtten by another. You could as well disable it.");
+ tutorial.text = qsTr("<img src=\"qrc:///MO/gui/emblem_conflict_redundant\" /> indicates that the mod is completely overwrtten by another. You could as well disable it.");
waitForClick()
},
function() {
@@ -65,7 +65,7 @@ function getTutorialSteps() { },
function() {
tutorial.text = qsTr("Option A: Switch to the \"Data\"-tab if necessary")
- if (!tutorialControl.waitForTabOpen("tabWidget", 2)) {
+ if (!tutorialControl.waitForTabOpen("tabWidget", 1)) {
highlightItem("tabWidget", false)
waitForClick()
} else {
@@ -93,7 +93,7 @@ function getTutorialSteps() { waitForClick()
},
function() {
- tutorial.text = qsTr("I told you in the \"First Steps\" tutorial how the esp/esm plugins contain changes to the game world "
+ tutorial.text = qsTr("I told you in the \"First Steps\" tutorial how the esp/esm/esl plugins contain changes to the game world "
+"like modifications to the terrain or existing NPCs. Each change like this is stored in a record, hence the "
+"name \"record conflict\". For example when two mods try to change the same location, only one change can become active.")
waitForClick()
@@ -105,7 +105,7 @@ function getTutorialSteps() { waitForClick()
},
function() {
- tutorial.text = qsTr("Please open the \"ESPs\"-tab...")
+ tutorial.text = qsTr("Please open the \"Plugins\"-tab...")
highlightItem("tabWidget", true)
if (!tutorialControl.waitForTabOpen("tabWidget", 0)) {
nextStep()
@@ -120,17 +120,17 @@ function getTutorialSteps() { function() {
unhighlight()
tutorial.text = qsTr("Unlike with file conflicts, MO does not provide help on finding conflicts. The good news is, there "
- +"already is a perfect tool for that called BOSS. BOSS is available on the Nexus and integrates "
- +"neatly with MO. Basically, if you don't have BOSS yet, install it once this tutorial is over.")
+ +"already is a perfect tool for that called LOOT. LOOT is available on the Nexus and integrates "
+ +"neatly with MO. Basically, if you don't have LOOT yet, install it once this tutorial is over.")
waitForClick()
},
function() {
- tutorial.text = qsTr("After you installed BOSS in the default location (follow its instructions), start MO again and BOSS should automatically appear as an Executable...")
+ tutorial.text = qsTr("After you installed LOOT in the default location (follow its instructions), start MO again and LOOT should automatically appear as an Executable...")
highlightItem("startGroup", false)
waitForClick()
},
function() {
- tutorial.text = qsTr("When you run BOSS, it will automatically re-organize plugins for best compatibility (overwriting your manual changes). "
+ tutorial.text = qsTr("When you run LOOT, it will automatically re-organize plugins for best compatibility (overwriting your manual changes). "
+"It will also open a report in your browser that warns about incompatibilities. You should read the report, at least "
+"for new mods.")
waitForClick()
diff --git a/src/tutorials/tutorial_firststeps_main.js b/src/tutorials/tutorial_firststeps_main.js index 2bc270d7..c47b5c0d 100644 --- a/src/tutorials/tutorial_firststeps_main.js +++ b/src/tutorials/tutorial_firststeps_main.js @@ -145,42 +145,6 @@ function getTutorialSteps() },
function() {
- tutorial.text = qsTr("Another special type of files are BSAs. These are bundles of game resources. "
- + "Please open the \"Archives\"-tab.")
- if (tutorialControl.waitForTabOpen("tabWidget", 1)) {
- highlightItem("tabWidget", true)
- } else {
- waitForClick()
- }
- },
-
- function() {
- tutorial.text = qsTr("These archives can be a real headache because the way bsas interact "
- + "with non-bundled resources is complicated. The game can even crash if required "
- + "archives are not loaded or ordered incorrectly.")
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("MO applies some \"magic\" to make all BSAs that are checked in this list load in "
- + "the correct order interleaved with the non-bundled resources.")
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("You can disable this magic to make MO behave more like other tools. In this case "
- + "their load order follows that of the corresponding plugin (.esp).")
- highlightItem("managedArchiveLabel", false)
- waitForClick()
- },
-
- function() {
- tutorial.text = qsTr("Many BSAs will appear grayed out and enabled. These mods are loaded by the game engine "
- + "automatically so they can't be disabled here.")
- waitForClick()
- },
-
- function() {
tutorial.text = qsTr("Now you know how to download, install and enable mods.\n"
+ "It's important you always start the game from inside MO, otherwise "
+ "the mods you installed here won't work.")
diff --git a/src/tutorials/tutorial_firststeps_settings.js b/src/tutorials/tutorial_firststeps_settings.js index dd5c6e9c..1dd77d2e 100644 --- a/src/tutorials/tutorial_firststeps_settings.js +++ b/src/tutorials/tutorial_firststeps_settings.js @@ -4,7 +4,7 @@ function getTutorialSteps() function() {
highlightItem("tabWidget", true)
tutorial.text = qsTr("You can use your regular browser to download from Nexus.\nPlease open the \"Nexus\"-tab")
- tutorialControl.waitForTabOpen("tabWidget", 1)
+ tutorialControl.waitForTabOpen("tabWidget", 2)
},
function() {
diff --git a/src/tutorials/tutorial_primer_main.js b/src/tutorials/tutorial_primer_main.js index 12df2c86..5d5597c1 100644 --- a/src/tutorials/tutorial_primer_main.js +++ b/src/tutorials/tutorial_primer_main.js @@ -4,29 +4,40 @@ var tooltips = []
function tooltipWidget(widgetName, explanation, maxheight, clickable) {
- var rect = tutorialControl.getRect(widgetName)
- var component = Qt.createComponent("TooltipArea.qml")
- if (component.status === Component.Error) {
- console.log("b" + component.errorString())
- }
- if (typeof clickable === 'undefined') {
- clickable = false
- }
- if ((typeof maxheight === 'undefined') || maxheight === 0) {
- maxheight = rect.height
- }
+ var component = Qt.createComponent("TooltipArea.qml")
+ if (component.status === Component.Ready)
+ finishCreation(component, widgetName, explanation, maxheight, clickable);
+ else
+ component.statusChanged.connect(function() {
+ finishCreation(component, widgetName, explanation, maxheight, clickable);
+ });
+}
- var obj = component.createObject(tutToplevel,
- { "x" : rect.x,
- "y" : rect.y,
- "width" : rect.width,
- "height" : maxheight
- })
- obj.tooltipText = explanation
- obj.clickable = clickable
- obj.visible = true
+function finishCreation(component, widgetName, explanation, maxheight, clickable) {
+ if (component.status === Component.Ready) {
+ var rect = tutorialControl.getRect(widgetName)
+ if (typeof clickable === 'undefined') {
+ clickable = false
+ }
+ if ((typeof maxheight === 'undefined') || maxheight === 0) {
+ maxheight = rect.height
+ }
- tooltips.push(obj)
+ var obj = component.createObject(tutToplevel,
+ {
+ "x": rect.x,
+ "y": rect.y,
+ "width": rect.width,
+ "height": maxheight
+ })
+ obj.tooltipText = explanation
+ obj.clickable = clickable
+ obj.visible = true
+
+ tooltips.push(obj)
+ } else if (component.status === Component.Error) {
+ console.log("Error loading component: " + component.errorString())
+ }
}
function tooltipAction(actionName, explanation, maxheight, clickable) {
@@ -75,20 +86,20 @@ function setupTooptips() { switch (manager.findControl("tabWidget").currentIndex) {
case 0:
- tooltipWidget("espList", qsTr("Plugins (esp/esm files) of the mods in the current profile. They need to be checked to be loaded."))
+ tooltipWidget("espList", qsTr("Plugins (esp/esm/esl files) of the mods in the current profile. They need to be checked to be loaded."))
tooltipWidget("bossButton", qsTr("Automatically sort plugins using the bundled LOOT application."))
tooltipWidget("espFilterEdit", qsTr("Quickly filter plugin list as you type."))
break
+ // case 1:
+ // tooltipWidget("bsaList", qsTr("All the asset archives (.bsa files) for all active mods."))
+ // break
case 1:
- tooltipWidget("bsaList", qsTr("All the asset archives (.bsa files) for all active mods."))
- break
- case 2:
tooltipWidget("dataTree", qsTr("The directory tree and all files that the program will see."))
break
- case 3:
+ case 2:
tooltipWidget("savegameList", qsTr("Save game browser. Shows all the saves for the current profile and whether or not the current mod-load status is correct."))
break
- case 4:
+ case 3:
tooltipWidget("downloadView", qsTr("Shows the mods that have been downloaded and if they’ve been installed."))
break
}
diff --git a/src/tutorials/tutorials_installdialog.qml b/src/tutorials/tutorials_installdialog.qml index 33f0a660..9cc21097 100644 --- a/src/tutorials/tutorials_installdialog.qml +++ b/src/tutorials/tutorials_installdialog.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
TutorialOverlay {
id: tutorial
diff --git a/src/tutorials/tutorials_mainwindow.qml b/src/tutorials/tutorials_mainwindow.qml index 0012b686..0d660a29 100644 --- a/src/tutorials/tutorials_mainwindow.qml +++ b/src/tutorials/tutorials_mainwindow.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
TutorialOverlay {
id: tutorial
diff --git a/src/tutorials/tutorials_modinfodialog.qml b/src/tutorials/tutorials_modinfodialog.qml index e9a56cb3..74f6adfa 100644 --- a/src/tutorials/tutorials_modinfodialog.qml +++ b/src/tutorials/tutorials_modinfodialog.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
TutorialOverlay {
id: tutorial
diff --git a/src/tutorials/tutorials_nexusdialog.qml b/src/tutorials/tutorials_nexusdialog.qml index 6a7af6d6..9fb9aafd 100644 --- a/src/tutorials/tutorials_nexusdialog.qml +++ b/src/tutorials/tutorials_nexusdialog.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
TutorialOverlay {
id: tutorial
diff --git a/src/tutorials/tutorials_settingsdialog.qml b/src/tutorials/tutorials_settingsdialog.qml index e9a56cb3..74f6adfa 100644 --- a/src/tutorials/tutorials_settingsdialog.qml +++ b/src/tutorials/tutorials_settingsdialog.qml @@ -1,4 +1,4 @@ -import QtQuick 1.1
+import QtQuick 2.7
TutorialOverlay {
id: tutorial
diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp new file mode 100644 index 00000000..ffbdf3aa --- /dev/null +++ b/src/usvfsconnector.cpp @@ -0,0 +1,200 @@ +/* +Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. +*/ + +#include "usvfsconnector.h" +#include "settings.h" +#include "organizercore.h" +#include "shared/util.h" +#include <memory> +#include <sstream> +#include <iomanip> +#include <usvfs.h> +#include <QTemporaryFile> +#include <QProgressDialog> +#include <QDateTime> +#include <QCoreApplication> +#include <qstandardpaths.h> + +static const char SHMID[] = "mod_organizer_instance"; + + +std::string to_hex(void *bufferIn, size_t bufferSize) +{ + unsigned char *buffer = static_cast<unsigned char *>(bufferIn); + std::ostringstream temp; + temp << std::hex; + for (size_t i = 0; i < bufferSize; ++i) { + temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; + if ((i % 16) == 15) { + temp << "\n"; + } else { + temp << " "; + } + } + return temp.str(); +} + + +LogWorker::LogWorker() + : m_Buffer(1024, '\0') + , m_QuitRequested(false) + , m_LogFile(qApp->property("dataPath").toString() + + QString("/logs/usvfs-%1.log") + .arg(QDateTime::currentDateTimeUtc().toString( + "yyyy-MM-dd_hh-mm-ss"))) +{ + m_LogFile.open(QIODevice::WriteOnly); + qDebug("usvfs log messages are written to %s", + qPrintable(m_LogFile.fileName())); +} + +LogWorker::~LogWorker() +{ +} + +void LogWorker::process() +{ + int noLogCycles = 0; + while (!m_QuitRequested) { + if (GetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) { + m_LogFile.write(m_Buffer.c_str()); + m_LogFile.write("\n"); + m_LogFile.flush(); + noLogCycles = 0; + } else { + QThread::msleep(std::min(40, noLogCycles) * 5); + ++noLogCycles; + } + } + emit finished(); +} + +void LogWorker::exit() +{ + m_QuitRequested = true; +} + +LogLevel logLevel(int level) +{ + switch (level) { + case LogLevel::Info: + return LogLevel::Info; + case LogLevel::Warning: + return LogLevel::Warning; + case LogLevel::Error: + return LogLevel::Error; + default: + return LogLevel::Debug; + } +} + +CrashDumpsType crashDumpsType(int type) +{ + switch (type) { + case CrashDumpsType::Mini: + return CrashDumpsType::Mini; + case CrashDumpsType::Data: + return CrashDumpsType::Data; + case CrashDumpsType::Full: + return CrashDumpsType::Full; + default: + return CrashDumpsType::None; + } +} + +UsvfsConnector::UsvfsConnector() +{ + USVFSParameters params; + LogLevel level = logLevel(Settings::instance().logLevel()); + CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); + + std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); + USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); + InitLogging(false); + + qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath); + + CreateVFS(¶ms); + + BlacklistExecutable(L"TSVNCache.exe"); + + m_LogWorker.moveToThread(&m_WorkerThread); + + connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process())); + connect(&m_LogWorker, SIGNAL(finished()), &m_WorkerThread, SLOT(quit())); + + m_WorkerThread.start(QThread::LowestPriority); +} + +UsvfsConnector::~UsvfsConnector() +{ + DisconnectVFS(); + m_LogWorker.exit(); + m_WorkerThread.quit(); + m_WorkerThread.wait(); +} + + +void UsvfsConnector::updateMapping(const MappingType &mapping) +{ + QProgressDialog progress; + progress.setLabelText(tr("Preparing vfs")); + progress.setMaximum(static_cast<int>(mapping.size())); + progress.show(); + int value = 0; + int files = 0; + int dirs = 0; + + qDebug("Updating VFS mappings..."); + + ClearVirtualMappings(); + + for (auto map : mapping) { + progress.setValue(value++); + if (value % 10 == 0) { + QCoreApplication::processEvents(); + } + + if (map.isDirectory) { + VirtualLinkDirectoryStatic(map.source.toStdWString().c_str(), + map.destination.toStdWString().c_str(), + (map.createTarget ? LINKFLAG_CREATETARGET : 0) + | LINKFLAG_RECURSIVE + ); + ++dirs; + } else { + VirtualLinkFile(map.source.toStdWString().c_str(), + map.destination.toStdWString().c_str(), 0); + ++files; + } + } + + qDebug("VFS mappings updated <linked %d dirs, %d files>", dirs, files); + /* + size_t dumpSize = 0; + CreateVFSDump(nullptr, &dumpSize); + std::unique_ptr<char[]> buffer(new char[dumpSize]); + CreateVFSDump(buffer.get(), &dumpSize); + qDebug(buffer.get()); + */ +} + +void UsvfsConnector::updateParams(int logLevel, int crashDumpsType) { + USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); +} diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h new file mode 100644 index 00000000..0935bac1 --- /dev/null +++ b/src/usvfsconnector.h @@ -0,0 +1,82 @@ +/* +Copyright (C) 2015 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 <http://www.gnu.org/licenses/>. +*/ + + +#ifndef USVFSCONNECTOR_H +#define USVFSCONNECTOR_H + + +#include <filemapping.h> +#include <QString> +#include <QThread> +#include <QFile> +#include <QDebug> +#include <usvfsparameters.h> + + +class LogWorker : public QThread { + + Q_OBJECT + +public: + + LogWorker(); + ~LogWorker(); + +public slots: + + void process(); + void exit(); + +signals: + + void outputLog(const QString &message); + void finished(); + +private: + + std::string m_Buffer; + bool m_QuitRequested; + QFile m_LogFile; + +}; + + +class UsvfsConnector : public QObject { + + Q_OBJECT + +public: + + UsvfsConnector(); + ~UsvfsConnector(); + + void updateMapping(const MappingType &mapping); + void updateParams(int logLevel, int crashDumpsType); + +private: + + LogWorker m_LogWorker; + QThread m_WorkerThread; + +}; + +CrashDumpsType crashDumpsType(int type); + +#endif // USVFSCONNECTOR_H diff --git a/src/version.rc b/src/version.rc index 50615bdc..a71a462a 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h"
-#define VER_FILEVERSION 1,3,11,0
-#define VER_FILEVERSION_STR "1,3,11,0\0"
+#define VER_FILEVERSION 2,1,1
+#define VER_FILEVERSION_STR "2.1.1\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp new file mode 100644 index 00000000..565d0a36 --- /dev/null +++ b/src/waitingonclosedialog.cpp @@ -0,0 +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 <http://www.gnu.org/licenses/>. +*/ + +#include "waitingonclosedialog.h" +#include "ui_waitingonclosedialog.h" + +#include <QPoint> +#include <QResizeEvent> +#include <QWidget> +#include <Qt> // for Qt::FramelessWindowHint, etc + +WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) + : LockedDialogBase(parent,true) + , ui(new Ui::WaitingOnCloseDialog) +{ + ui->setupUi(this); + + // Supposedly the Qt::CustomizeWindowHint should use a customized window + // allowing us to select if there is a close button. In practice this doesn't + // seem to work. We will ignore pressing the close button if unlockByButton == true + Qt::WindowFlags flags = + this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; + if (m_allowClose) + flags |= Qt::WindowCloseButtonHint; + this->setWindowFlags(flags); +} + +WaitingOnCloseDialog::~WaitingOnCloseDialog() +{ + delete ui; +} + + +void WaitingOnCloseDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + +void WaitingOnCloseDialog::on_closeButton_clicked() +{ + unlock(); +} + +void WaitingOnCloseDialog::on_cancelButton_clicked() +{ + cancel(); + unlock(); +} + +void WaitingOnCloseDialog::unlock() { + LockedDialogBase::unlock(); + ui->label->setText("unlocking may take a few seconds"); + ui->closeButton->setEnabled(false); + ui->cancelButton->setEnabled(false); +} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h new file mode 100644 index 00000000..6650c390 --- /dev/null +++ b/src/waitingonclosedialog.h @@ -0,0 +1,56 @@ +/* +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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "lockeddialogbase.h" + +namespace Ui { + class WaitingOnCloseDialog; +} + +/** + * Similar to the LockedDialog but used for waiting on running process during + * a process close request which requries a slightly different dialog. + **/ +class WaitingOnCloseDialog : public LockedDialogBase +{ + Q_OBJECT + +public: + explicit WaitingOnCloseDialog(QWidget *parent = 0); + ~WaitingOnCloseDialog(); + + bool canceled() const { return m_Canceled; } + + void setProcessName(const QString &name) override; + +protected: + + void unlock() override; + +private slots: + + void on_closeButton_clicked(); + void on_cancelButton_clicked(); + +private: + + Ui::WaitingOnCloseDialog *ui; +}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui new file mode 100644 index 00000000..9c7818e0 --- /dev/null +++ b/src/waitingonclosedialog.ui @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>WaitingOnCloseDialog</class> + <widget class="QDialog" name="WaitingOnCloseDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>317</width> + <height>151</height> + </rect> + </property> + <property name="windowTitle"> + <string>Waiting for virtualized processes</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0"> + <item> + <widget class="QLabel" name="label"> + <property name="toolTip"> + <string>This dialog should disappear automatically if the application/game is done.</string> + </property> + <property name="text"> + <string>Virtualized processes are still running, it is prefered to keep MO running until they are finished.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer1"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="processLabel"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="styleSheet"> + <string notr="true">color: grey;</string> + </property> + <property name="text"> + <string/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <height>10</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0,0"> + <item> + <widget class="QPushButton" name="closeButton"> + <property name="text"> + <string>Close Now</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="cancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <height>10</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> |
