summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt22
-rw-r--r--src/aboutdialog.cpp42
-rw-r--r--src/aboutdialog.h15
-rw-r--r--src/aboutdialog.ui108
-rw-r--r--src/categories.cpp2
-rw-r--r--src/categories.h2
-rw-r--r--src/downloadmanager.cpp79
-rw-r--r--src/downloadmanager.h5
-rw-r--r--src/instancemanager.cpp4
-rw-r--r--src/main.cpp76
-rw-r--r--src/mainwindow.cpp175
-rw-r--r--src/mainwindow.h5
-rw-r--r--src/mainwindow.ui61
-rw-r--r--src/modinfo.cpp6
-rw-r--r--src/modinfo.h1
-rw-r--r--src/modinfodialog.cpp186
-rw-r--r--src/modinfodialog.h12
-rw-r--r--src/modinfodialog.ui14
-rw-r--r--src/modinforegular.cpp2
-rw-r--r--src/modlist.cpp5
-rw-r--r--src/moshortcut.cpp3
-rw-r--r--src/moshortcut.h3
-rw-r--r--src/nexusinterface.cpp2
-rw-r--r--src/organizercore.cpp23
-rw-r--r--src/organizercore.h2
-rw-r--r--src/pluginlist.cpp20
-rw-r--r--src/pluginlist.h3
-rw-r--r--src/queryoverwritedialog.cpp2
-rw-r--r--src/resources.qrc5
-rw-r--r--src/resources/archive-conflict-loser.pngbin0 -> 886 bytes
-rw-r--r--src/resources/archive-conflict-mixed.pngbin0 -> 969 bytes
-rw-r--r--src/resources/archive-conflict-neutral.pngbin0 -> 846 bytes
-rw-r--r--src/resources/archive-conflict-winner.pngbin0 -> 939 bytes
-rw-r--r--src/resources/check.pngbin455 -> 395 bytes
-rw-r--r--src/resources/contents/breastplate.pngbin1419 -> 788 bytes
-rw-r--r--src/resources/contents/checkbox-tree.pngbin658 -> 532 bytes
-rw-r--r--src/resources/contents/config.pngbin552 -> 480 bytes
-rw-r--r--src/resources/contents/conversation.pngbin1506 -> 832 bytes
-rw-r--r--src/resources/contents/double-quaver.pngbin1009 -> 640 bytes
-rw-r--r--src/resources/contents/empty-chessboard.pngbin706 -> 537 bytes
-rw-r--r--src/resources/contents/hand-of-god.pngbin1101 -> 687 bytes
-rw-r--r--src/resources/contents/jigsaw-piece.pngbin952 -> 678 bytes
-rw-r--r--src/resources/contents/locked-chest.pngbin1440 -> 801 bytes
-rw-r--r--src/resources/contents/lyre.pngbin1388 -> 783 bytes
-rw-r--r--src/resources/contents/tinker.pngbin1038 -> 742 bytes
-rw-r--r--src/resources/contents/usable.pngbin1345 -> 834 bytes
-rw-r--r--src/resources/document-save.pngbin1150 -> 1117 bytes
-rw-r--r--src/resources/multiply-red.pngbin0 -> 666 bytes
-rw-r--r--src/resources/open-Folder-Icon.pngbin17646 -> 1434 bytes
-rw-r--r--src/resources/switch-instance-icon.pngbin57516 -> 4333 bytes
-rw-r--r--src/selfupdater.cpp2
-rw-r--r--src/transfersavesdialog.cpp30
-rw-r--r--src/tutorials/tutorial_conflictresolution_main.js2
-rw-r--r--src/version.rc14
54 files changed, 712 insertions, 221 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 60b36683..fef640b3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,4 @@
-CMAKE_MINIMUM_REQUIRED (VERSION 2.8)
+CMAKE_MINIMUM_REQUIRED (VERSION 2.8.11)
CMAKE_POLICY(SET CMP0020 NEW)
#CMAKE_POLICY(SET CMP0043 NEW)
@@ -249,10 +249,13 @@ FIND_PACKAGE(Qt5Quick REQUIRED)
FIND_PACKAGE(Qt5Network REQUIRED)
FIND_PACKAGE(Qt5WinExtras REQUIRED)
FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED)
+FIND_PACKAGE(Qt5Script REQUIRED)
+FIND_PACKAGE(Qt5Qml REQUIRED)
FIND_PACKAGE(Qt5LinguistTools)
QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS})
QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS})
QT5_CREATE_TRANSLATION(organizer_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/organizer_en.ts)
+ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm})
INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES})
@@ -288,11 +291,13 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src
${project_path}/../usvfs/include
${project_path}/game_gamebryo/src
${project_path}/game_features/src
- ${project_path}/githubpp/src)
+ ${project_path}/githubpp/src
+ ${LZ4_ROOT}/include)
INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS})
LINK_DIRECTORIES(${lib_path}
- ${ZLIB_ROOT}/lib)
+ ${ZLIB_ROOT}/lib
+ ${LZ4_ROOT}/dll)
EXECUTE_PROCESS(
COMMAND git log -1 --format=%h
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
@@ -311,11 +316,12 @@ ENDIF()
ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm})
TARGET_LINK_LIBRARIES(ModOrganizer
Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick
+ Qt5::Script Qt5::Qml Qt5::QuickWidgets Qt5::Network
${Boost_LIBRARIES}
zlibstatic
uibase esptk bsatk githubpp
${usvfs_name}
- Dbghelp advapi32 Version Shlwapi)
+ Dbghelp advapi32 Version Shlwapi liblz4)
IF (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "")
SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO
${OPTIMIZE_COMPILE_FLAGS})
@@ -324,9 +330,6 @@ SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES LINK_FLAGS_RELWITHDEBINFO
"/LARGEADDRESSAWARE ${OPTIMIZE_LINK_FLAGS}")
-QT5_USE_MODULES(ModOrganizer Widgets Script Qml QuickWidgets Quick Network WebEngineWidgets)
-
-
###############
## Installation
@@ -368,6 +371,11 @@ INSTALL(
file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/qtplugins)"
)
+# qdds.dll needs installing manually as Qt no longer ships with it by default.
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../qdds.dll DESTINATION bin/dlls/imageformats)
+
+install(FILES ${organizer_translations_qm} DESTINATION bin/translations)
+
INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/stylesheets
${CMAKE_CURRENT_SOURCE_DIR}/tutorials
DESTINATION bin)
diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp
index ed57a217..a350036f 100644
--- a/src/aboutdialog.cpp
+++ b/src/aboutdialog.cpp
@@ -29,7 +29,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QTextBrowser>
#include <QVariant>
#include <Qt>
-
+#include <QFontDatabase>
AboutDialog::AboutDialog(const QString &version, QWidget *parent)
: QDialog(parent)
@@ -37,24 +37,44 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent)
{
ui->setupUi(this);
- m_LicenseFiles[LICENSE_LGPL3] = "lgpl-3.0.txt";
- m_LicenseFiles[LICENSE_GPL3] = "gpl-3.0.txt";
- m_LicenseFiles[LICENSE_BSD3] = "bsd3.txt";
+ m_LicenseFiles[LICENSE_LGPL3] = "LGPL-v3.0.txt";
+ m_LicenseFiles[LICENSE_LGPL21] = "GNU-LGPL-v2.1.txt";
+ m_LicenseFiles[LICENSE_GPL3] = "GPL-v3.0.txt";
+ m_LicenseFiles[LICENSE_GPL2] = "GPL-v2.0.txt";
m_LicenseFiles[LICENSE_BOOST] = "boost.txt";
- m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt";
+ m_LicenseFiles[LICENSE_7ZIP] = "7zip.txt";
+ m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt";
m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt";
- m_LicenseFiles[LICENSE_APACHE2] = "apache-license-2.0.txt";
+ m_LicenseFiles[LICENSE_PYTHON] = "python.txt";
+ m_LicenseFiles[LICENSE_SSL] = "openssl.txt";
+ m_LicenseFiles[LICENSE_CPPTOML] = "cpptoml.txt";
+ m_LicenseFiles[LICENSE_UDIS] = "udis86.txt";
+ m_LicenseFiles[LICENSE_SPDLOG] = "spdlog.txt";
+ m_LicenseFiles[LICENSE_FMT] = "fmt.txt";
+ m_LicenseFiles[LICENSE_SIP] = "sip.txt";
+ m_LicenseFiles[LICENSE_CASTLE] = "Castle.txt";
+ m_LicenseFiles[LICENSE_ANTLR] = "AntlrBuildTask.txt";
+ m_LicenseFiles[LICENSE_DXTEX] = "DXTex.txt";
addLicense("Qt", LICENSE_LGPL3);
addLicense("Qt Json", LICENSE_GPL3);
addLicense("Boost Library", LICENSE_BOOST);
- addLicense("7-zip", LICENSE_LGPL3);
- addLicense("ZLib", LICENSE_ZLIB);
+ addLicense("7-zip", LICENSE_7ZIP);
+ addLicense("ZLib", LICENSE_NONE);
addLicense("Tango Icon Theme", LICENSE_NONE);
addLicense("RRZE Icon Set", LICENSE_CCBY3);
addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3);
- addLicense("Castle Core", LICENSE_APACHE2);
+ addLicense("Castle Core", LICENSE_CASTLE);
+ addLicense("ANTLR", LICENSE_ANTLR);
addLicense("LOOT", LICENSE_GPL3);
+ addLicense("Python", LICENSE_PYTHON);
+ addLicense("OpenSSL", LICENSE_SSL);
+ addLicense("cpptoml", LICENSE_CPPTOML);
+ addLicense("Udis86", LICENSE_UDIS);
+ addLicense("spdlog", LICENSE_SPDLOG);
+ addLicense("{fmt}", LICENSE_FMT);
+ addLicense("SIP", LICENSE_SIP);
+ addLicense("DXTex Headers", LICENSE_DXTEX);
ui->nameLabel->setText(QString("<span style=\"font-size:12pt; font-weight:600;\">%1 %2</span>").arg(ui->nameLabel->text()).arg(version));
#if defined(HGID)
@@ -64,6 +84,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent)
#else
ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown");
#endif
+
+ ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
@@ -85,7 +107,7 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL
{
auto iter = m_LicenseFiles.find(current->data(Qt::UserRole).toInt());
if (iter != m_LicenseFiles.end()) {
- QString filePath = qApp->applicationDirPath() + "/license/" + iter->second;
+ QString filePath = qApp->applicationDirPath() + "/licenses/" + iter->second;
QString text = MOBase::readFileText(filePath);
ui->licenseText->setText(text);
} else {
diff --git a/src/aboutdialog.h b/src/aboutdialog.h
index 103a0d2f..3ae3237b 100644
--- a/src/aboutdialog.h
+++ b/src/aboutdialog.h
@@ -45,12 +45,23 @@ private:
enum Licenses {
LICENSE_NONE,
LICENSE_LGPL3,
+ LICENSE_LGPL21,
LICENSE_GPL3,
- LICENSE_BSD3,
+ LICENSE_GPL2,
LICENSE_BOOST,
LICENSE_CCBY3,
+ LICENSE_PYTHON,
+ LICENSE_SSL,
+ LICENSE_CPPTOML,
+ LICENSE_7ZIP,
LICENSE_ZLIB,
- LICENSE_APACHE2
+ LICENSE_UDIS,
+ LICENSE_SPDLOG,
+ LICENSE_FMT,
+ LICENSE_SIP,
+ LICENSE_CASTLE,
+ LICENSE_ANTLR,
+ LICENSE_DXTEX
};
private:
diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui
index 50a9de5f..69e52708 100644
--- a/src/aboutdialog.ui
+++ b/src/aboutdialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>604</width>
- <height>369</height>
+ <width>1010</width>
+ <height>477</height>
</rect>
</property>
<property name="windowTitle">
@@ -120,14 +120,14 @@
<item>
<widget class="QLabel" name="label_3">
<property name="text">
- <string notr="true">Copyright 2011-2016 Sebastian Herbord</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright 2011-2016 Sebastian Herbord&lt;/p&gt;&lt;p&gt;Copyright 2016-2018 Mod Organizer 2 contributors&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.&lt;/p&gt;&lt;p&gt;See the GNU General Public License for more details.&lt;/p&gt;&lt;p&gt;Source code can be found at &lt;a href=&quot;https://github.com/TanninOne/modorganizer&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;GitHub&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.&lt;/p&gt;&lt;p&gt;See the GNU General Public License for more details.&lt;/p&gt;&lt;p&gt;Source code can be found at &lt;a href=&quot;https://github.com/Modorganizer2/modorganizer&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;GitHub&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
@@ -154,7 +154,7 @@
<string>Thanks</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
+ <item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Current Maintainers</string>
@@ -167,12 +167,17 @@
</property>
<item>
<property name="text">
- <string notr="true">LePresidente</string>
+ <string notr="true">LePresidente (Project Lead)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">Silarn</string>
+ <string>AL12</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Diana</string>
</property>
</item>
<item>
@@ -182,7 +187,7 @@
</item>
<item>
<property name="text">
- <string notr="true">Diana</string>
+ <string notr="true">Silarn</string>
</property>
</item>
</widget>
@@ -203,17 +208,22 @@
</property>
<item>
<property name="text">
- <string notr="true">pndrev (German)</string>
+ <string notr="true">Scythe1912 (Chinese)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">DaWul (Spanish)</string>
+ <string notr="true">yc0620shen (Chinese)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">Fiama (Spanish)</string>
+ <string notr="true">miraclefreak (Czech)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Cyb3r (Dutch)</string>
</property>
</item>
<item>
@@ -223,6 +233,11 @@
</item>
<item>
<property name="text">
+ <string>fruttyx (French)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string notr="true">Jlkawaii (French)</string>
</property>
</item>
@@ -233,22 +248,22 @@
</item>
<item>
<property name="text">
- <string notr="true">Scythe1912 (Chinese)</string>
+ <string>Yoplala (French)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">yc0620shen (Chinese)</string>
+ <string>Faron (German)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">miraclefreak (Czech)</string>
+ <string notr="true">pndrev (German)</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">tokcdk (Russian)</string>
+ <string>Mordan (Greek)</string>
</property>
</item>
<item>
@@ -261,7 +276,37 @@
</item>
<item>
<property name="text">
- <string>... more (Can't track)</string>
+ <string>Yoosk (Polish)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Brgodfx (Portuguese)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">tokcdk (Russian)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">DaWul (Spanish)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Fiama (Spanish)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Jax (Swedish)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>...and all other Transifex contributors!</string>
</property>
<property name="toolTip">
<string/>
@@ -275,7 +320,7 @@
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
- <string>Other Supporter</string>
+ <string>Other Supporters &amp;&amp; Contributors</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
@@ -283,9 +328,9 @@
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
- <item>
+ <item>
<property name="text">
- <string notr="true">Al12 (Discord) </string>
+ <string notr="true">AnyOldName3</string>
</property>
</item>
<item>
@@ -310,6 +355,11 @@
</item>
<item>
<property name="text">
+ <string notr="true">Brixified</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string notr="true">GamerPoet</string>
</property>
</item>
@@ -325,27 +375,37 @@
</item>
<item>
<property name="text">
- <string notr="true">Uhuru</string>
+ <string notr="true">ogrotten</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">Wolverine2710</string>
+ <string notr="true">Schilduin</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">z929669</string>
+ <string notr="true">SuperSandro2000</string>
</property>
</item>
<item>
<property name="text">
- <string notr="true">thosrtanner</string>
+ <string notr="true">Uhuru</string>
</property>
</item>
<item>
<property name="text">
- <string>ogrotten</string>
+ <string notr="true">Wolverine2710</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">z929669</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">thosrtanner</string>
</property>
</item>
</widget>
diff --git a/src/categories.cpp b/src/categories.cpp
index d8cd49fb..4d89eff9 100644
--- a/src/categories.cpp
+++ b/src/categories.cpp
@@ -167,7 +167,7 @@ void CategoryFactory::saveCategories()
}
-unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter)
+unsigned int CategoryFactory::countCategories(std::function<bool (const Category &category)> filter)
{
unsigned int result = 0;
for (const Category &cat : m_Categories) {
diff --git a/src/categories.h b/src/categories.h
index 66299c30..474a1440 100644
--- a/src/categories.h
+++ b/src/categories.h
@@ -99,7 +99,7 @@ public:
* @param filter the filter to test
* @return number of matching categories
*/
- unsigned int countCategories(std::tr1::function<bool (const Category &category)> filter);
+ unsigned int countCategories(std::function<bool (const Category &category)> filter);
/**
* @brief get the id of the parent category
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index a4fde093..569a7d31 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -460,7 +460,29 @@ void DownloadManager::addNXMDownload(const QString &url)
return;
}
+ for (auto pair : m_PendingDownloads) {
+ if (pair.first == nxmInfo.modId() && pair.second == nxmInfo.fileId()) {
+ qDebug("download requested is already started (mod id: %s, file id: %s)", qPrintable(QString(nxmInfo.modId())), qPrintable(QString(nxmInfo.fileId())));
+ QMessageBox::information(nullptr, tr("Already Started"), tr("A download for this mod file has already been queued."), QMessageBox::Ok);
+ return;
+ }
+ }
+
+ for (DownloadInfo *download : m_ActiveDownloads) {
+ if (download->m_FileInfo->modID == nxmInfo.modId() && download->m_FileInfo->fileID == nxmInfo.fileId()) {
+ if (download->m_State == STATE_DOWNLOADING || download->m_State == STATE_PAUSED || download->m_State == STATE_STARTED) {
+ qDebug("download requested is already started (mod: %s, file: %s)", qPrintable(download->m_FileInfo->modName),
+ qPrintable(download->m_FileInfo->fileName));
+
+ QMessageBox::information(nullptr, tr("Already Started"), tr("There is already a download started for this file (%2).")
+ .arg(download->m_FileInfo->modName).arg(download->m_FileInfo->name), QMessageBox::Ok);
+ return;
+ }
+ }
+ }
+
emit aboutToUpdate();
+
m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId()));
emit update(-1);
@@ -558,36 +580,20 @@ void DownloadManager::removeDownload(int index, bool deleteFile)
emit aboutToUpdate();
if (index < 0) {
- if (index == -1) {
- DownloadState minState = STATE_READY;
+ DownloadState minState = index == -1 ? STATE_READY : STATE_INSTALLED;
index = 0;
for (QVector<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);
+ QCoreApplication::processEvents();
}
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("remove: invalid download index %1").arg(index));
@@ -896,6 +902,26 @@ void DownloadManager::markInstalled(int index)
setState(m_ActiveDownloads.at(index), STATE_INSTALLED);
}
+void DownloadManager::markInstalled(QString fileName)
+{
+ int index = indexByName(fileName);
+ if (index >= 0) {
+ markInstalled(index);
+ } else {
+ DownloadInfo *info = getDownloadInfo(fileName);
+ if (info != nullptr) {
+ QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat);
+ metaFile.setValue("installed", true);
+ metaFile.setValue("uninstalled", false);
+ }
+ delete info;
+ }
+}
+
+DownloadManager::DownloadInfo* DownloadManager::getDownloadInfo(QString fileName)
+{
+ return DownloadInfo::createFromMeta(fileName, true);
+}
void DownloadManager::markUninstalled(int index)
{
@@ -911,6 +937,23 @@ void DownloadManager::markUninstalled(int index)
}
+void DownloadManager::markUninstalled(QString fileName)
+{
+ int index = indexByName(fileName);
+ if (index >= 0) {
+ markUninstalled(index);
+ } else {
+ QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName;
+ DownloadInfo *info = getDownloadInfo(filePath);
+ if (info != nullptr) {
+ QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat);
+ metaFile.setValue("uninstalled", true);
+ }
+ delete info;
+ }
+}
+
+
QString DownloadManager::getDownloadFileName(const QString &baseName) const
{
QString fullPath = m_OutputDirectory + "/" + baseName;
diff --git a/src/downloadmanager.h b/src/downloadmanager.h
index e63a0113..a6d3b20c 100644
--- a/src/downloadmanager.h
+++ b/src/downloadmanager.h
@@ -307,6 +307,8 @@ public:
*/
void markInstalled(int index);
+ void markInstalled(QString download);
+
/**
* @brief mark a download as uninstalled
*
@@ -314,6 +316,8 @@ public:
*/
void markUninstalled(int index);
+ void markUninstalled(QString download);
+
/**
* @brief refreshes the list of downloads
*/
@@ -431,6 +435,7 @@ private slots:
private:
void createMetaFile(DownloadInfo *info);
+ DownloadManager::DownloadInfo* getDownloadInfo(QString fileName);
public:
diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp
index d12ec119..43ae152b 100644
--- a/src/instancemanager.cpp
+++ b/src/instancemanager.cpp
@@ -105,7 +105,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const
nullptr);
for (const QString &instance : instanceList)
{
- selection.addChoice(instance, "", instance);
+ selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance);
}
if (selection.exec() == QDialog::Rejected) {
@@ -120,7 +120,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const
if (!deleteLocalInstance(choice))
{
QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"),
- QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok);
+ QObject::tr("Could not delete Instance \"%1\". \nIf the folder was still in use, restart MO and try again.").arg(choice), QMessageBox::Ok);
}
}
}
diff --git a/src/main.cpp b/src/main.cpp
index 935b7404..fc332fe7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -402,6 +402,8 @@ void setupPath()
qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(
QCoreApplication::applicationDirPath())));
+ QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths());
+
boost::scoped_array<TCHAR> oldPath(new TCHAR[BUFSIZE]);
DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE);
if (offset > BUFSIZE) {
@@ -409,12 +411,10 @@ void setupPath()
::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset);
}
- std::wstring newPath(oldPath.get());
+ std::wstring newPath(ToWString(QDir::toNativeSeparators(
+ QCoreApplication::applicationDirPath())) + L"\\dlls");
newPath += L";";
- newPath += ToWString(QDir::toNativeSeparators(
- QCoreApplication::applicationDirPath()))
- .c_str();
- newPath += L"\\dlls";
+ newPath += oldPath.get();
::SetEnvironmentVariableW(L"PATH", newPath.c_str());
}
@@ -561,36 +561,42 @@ int runApplication(MOApplication &application, SingleInstance &instance,
// if we have a command line parameter, it is either a nxm link or
// a binary to start
- if (arguments.size() > 1) {
- if (MOShortcut shortcut{ arguments.at(1) }) {
- try {
- organizer.runShortcut(shortcut);
- return 0;
- } catch (const std::exception &e) {
- reportError(
- QObject::tr("failed to start shortcut: %1").arg(e.what()));
- return 1;
- }
- } else if (OrganizerCore::isNxmLink(arguments.at(1))) {
- qDebug("starting download from command line: %s",
- qPrintable(arguments.at(1)));
- organizer.externalMessage(arguments.at(1));
- } else {
- QString exeName = arguments.at(1);
- qDebug("starting %s from command line", qPrintable(exeName));
- arguments.removeFirst(); // remove application name (ModOrganizer.exe)
- arguments.removeFirst(); // remove binary name
- // pass the remaining parameters to the binary
- try {
- organizer.startApplication(exeName, arguments, QString(), QString());
- return 0;
- } catch (const std::exception &e) {
- reportError(
- QObject::tr("failed to start application: %1").arg(e.what()));
- return 1;
- }
- }
- }
+ if (arguments.size() > 1) {
+ if (MOShortcut shortcut{ arguments.at(1) }) {
+ if (shortcut.hasExecutable()) {
+ try {
+ organizer.runShortcut(shortcut);
+ return 0;
+ }
+ catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start shortcut: %1").arg(e.what()));
+ return 1;
+ }
+ }
+ }
+ else if (OrganizerCore::isNxmLink(arguments.at(1))) {
+ qDebug("starting download from command line: %s",
+ qPrintable(arguments.at(1)));
+ organizer.externalMessage(arguments.at(1));
+ }
+ else {
+ QString exeName = arguments.at(1);
+ qDebug("starting %s from command line", qPrintable(exeName));
+ arguments.removeFirst(); // remove application name (ModOrganizer.exe)
+ arguments.removeFirst(); // remove binary name
+ // pass the remaining parameters to the binary
+ try {
+ organizer.startApplication(exeName, arguments, QString(), QString());
+ return 0;
+ }
+ catch (const std::exception &e) {
+ reportError(
+ QObject::tr("failed to start application: %1").arg(e.what()));
+ return 1;
+ }
+ }
+ }
QPixmap pixmap(splashPath);
QSplashScreen splash(pixmap);
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index fe93b546..abcb5312 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -89,6 +89,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QAbstractProxyModel>
#include <QAction>
#include <QApplication>
+#include <QButtonGroup>
#include <QBuffer>
#include <QCheckBox>
#include <QClipboard>
@@ -153,8 +154,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QDebug>
#include <QtGlobal>
-#include <QtConcurrent/QtConcurrentRun>
-
#ifndef Q_MOC_RUN
#include <boost/thread.hpp>
#include <boost/algorithm/string.hpp>
@@ -901,6 +900,8 @@ void MainWindow::cleanup()
QWebEngineProfile::defaultProfile()->clearAllVisitedLinks();
m_IntegratedBrowser.close();
+ m_SaveMetaTimer.stop();
+ m_MetaSave.waitForFinished();
}
@@ -1568,9 +1569,13 @@ void MainWindow::checkBSAList()
void MainWindow::saveModMetas()
{
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
- modInfo->saveMeta();
+ if (m_MetaSave.isFinished()) {
+ m_MetaSave = QtConcurrent::run([this]() {
+ for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ modInfo->saveMeta();
+ }
+ });
}
}
@@ -1886,7 +1891,7 @@ void MainWindow::wikiTriggered()
void MainWindow::issueTriggered()
{
- ::ShellExecuteW(nullptr, L"open", L"http://github.com/LePresidente/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL);
+ ::ShellExecuteW(nullptr, L"open", L"http://github.com/Modorganizer2/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL);
}
void MainWindow::tutorialTriggered()
@@ -2307,10 +2312,7 @@ void MainWindow::removeMod_clicked()
void MainWindow::modRemoved(const QString &fileName)
{
if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) {
- int index = m_OrganizerCore.downloadManager()->indexByName(fileName);
- if (index >= 0) {
- m_OrganizerCore.downloadManager()->markUninstalled(index);
- }
+ m_OrganizerCore.downloadManager()->markUninstalled(fileName);
}
}
@@ -2450,7 +2452,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index,
}
} else {
modInfo->saveMeta();
- ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this);
+ ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer,this);
connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString)));
connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString)));
connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection);
@@ -3035,6 +3037,12 @@ void MainWindow::openInstanceFolder()
::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
}
+void MainWindow::openLogsFolder()
+{
+ QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath());
+ ::ShellExecuteW(nullptr, L"explore", ToWString(logsPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+}
+
void MainWindow::openInstallFolder()
{
::ShellExecuteW(nullptr, L"explore", ToWString(qApp->applicationDirPath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL);
@@ -3073,6 +3081,10 @@ void MainWindow::exportModListCSV()
QGridLayout *grid = new QGridLayout;
selection.setWindowTitle(tr("Export to csv"));
+ QLabel *csvDescription = new QLabel();
+ csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead."));
+ grid->addWidget(csvDescription);
+
QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:"));
QRadioButton *all = new QRadioButton(tr("All installed mods"));
QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile"));
@@ -3195,7 +3207,7 @@ void MainWindow::exportModListCSV()
if (nexus_ID->isChecked())
builder.setRowField("#Nexus_ID", info->getNexusID());
if (mod_Nexus_URL->isChecked())
- builder.setRowField("#Mod_Nexus_URL", info->getURL());
+ builder.setRowField("#Mod_Nexus_URL",(info->getNexusID()>0)? NexusInterface::instance()->getModURL(info->getNexusID()) : "");
if (mod_Version->isChecked())
builder.setRowField("#Mod_Version", info->getVersion().canonicalString());
if (install_Date->isChecked())
@@ -3227,10 +3239,15 @@ static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu)
}
QMenu *MainWindow::openFolderMenu()
-{
+{
QMenu *FolderMenu = new QMenu(this);
+ FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder()));
+
+ FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder()));
+
+ FolderMenu->addSeparator();
FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder()));
@@ -3238,13 +3255,19 @@ QMenu *MainWindow::openFolderMenu()
FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder()));
- FolderMenu->addAction(tr("Open MO Install folder"), this, SLOT(openInstallFolder()));
-
FolderMenu->addSeparator();
- FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder()));
+ FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder()));
+
+ FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder()));
+
+
+
+
+
+
+
- FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder()));
return FolderMenu;
@@ -3304,15 +3327,18 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
} else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) {
// nop, nothing to do with this mod
} else {
- QMenu *addRemoveCategoriesMenu = new QMenu(tr("Add/Remove Categories"));
+ QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories"));
populateMenuCategories(addRemoveCategoriesMenu, 0);
connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler()));
addMenuAsPushButton(menu, addRemoveCategoriesMenu);
+ //Removed as it was redundant, just making the categories look more complicated.
+ /*
QMenu *replaceCategoriesMenu = new QMenu(tr("Replace Categories"));
populateMenuCategories(replaceCategoriesMenu, 0);
connect(replaceCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(replaceCategories_MenuHandler()));
addMenuAsPushButton(menu, replaceCategoriesMenu);
+ */
QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category"));
connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
@@ -3332,8 +3358,8 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
menu->addSeparator();
menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked()));
- menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked()));
menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked()));
+ menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked()));
menu->addSeparator();
@@ -3909,10 +3935,24 @@ void MainWindow::previewDataFile()
// what we have is an absolute path to the file in its actual location (for the primary origin)
// what we want is the path relative to the virtual data directory
- // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory
- int offset = m_OrganizerCore.settings().getModDirectory().size() + 1;
- offset = fileName.indexOf("/", offset);
- fileName = fileName.mid(offset + 1);
+ // we need to look in the virtual directory for the file to make sure the info is up to date.
+
+ // check if the file comes from the actual data folder instead of a mod
+ QDir gameDirectory = m_OrganizerCore.managedGame()->dataDirectory().absolutePath();
+ QString relativePath = gameDirectory.relativeFilePath(fileName);
+ QDir dirRelativePath = gameDirectory.relativeFilePath(fileName);
+ // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case
+ if (!dirRelativePath.isAbsolute() && !relativePath.startsWith("..")) {
+ fileName = relativePath;
+ }
+ else {
+ // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory
+ int offset = m_OrganizerCore.settings().getModDirectory().size() + 1;
+ offset = fileName.indexOf("/", offset);
+ fileName = fileName.mid(offset + 1);
+ }
+
+
const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), nullptr);
@@ -4250,27 +4290,40 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std:
void MainWindow::extractBSATriggered()
{
QTreeWidgetItem *item = m_ContextItem;
+ QString origin;
QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA"));
+ QStringList archives = {};
if (!targetFolder.isEmpty()) {
- BSA::Archive archive;
- QString originPath = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath()));
- QString archivePath = QString("%1\\%2").arg(originPath).arg(item->text(0));
-
- BSA::EErrorCode result = archive.read(archivePath.toLocal8Bit().constData(), true);
- if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) {
- reportError(tr("failed to read %1: %2").arg(archivePath).arg(result));
- return;
+ if (!item->parent()) {
+ for (int i = 0; i < item->childCount(); ++i) {
+ archives.append(item->child(i)->text(0));
+ }
+ origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(0))).getPath()));
+ } else {
+ origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath()));
+ archives = QStringList({ item->text(0) });
}
- QProgressDialog progress(this);
- progress.setMaximum(100);
- progress.setValue(0);
- progress.show();
- archive.extractAll(QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(),
- boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2));
- if (result == BSA::ERROR_INVALIDHASHES) {
- reportError(tr("This archive contains invalid hashes. Some files may be broken."));
+ for (auto archiveName : archives) {
+ BSA::Archive archive;
+ QString archivePath = QString("%1\\%2").arg(origin).arg(archiveName);
+ BSA::EErrorCode result = archive.read(archivePath.toLocal8Bit().constData(), true);
+ if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) {
+ reportError(tr("failed to read %1: %2").arg(archivePath).arg(result));
+ return;
+ }
+
+ QProgressDialog progress(this);
+ progress.setMaximum(100);
+ progress.setValue(0);
+ progress.show();
+ archive.extractAll(QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(),
+ boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2));
+ if (result == BSA::ERROR_INVALIDHASHES) {
+ reportError(tr("This archive contains invalid hashes. Some files may be broken."));
+ }
+ archive.close();
}
}
}
@@ -4307,6 +4360,18 @@ void MainWindow::displayColumnSelection(const QPoint &pos)
}
}
+void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
+{
+ m_ContextItem = ui->bsaList->itemAt(pos);
+
+// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos));
+
+ QMenu menu;
+ menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered()));
+
+ menu.exec(ui->bsaList->mapToGlobal(pos));
+}
+
void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int)
{
m_ArchiveListWriter.write();
@@ -4576,8 +4641,8 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe
std::vector<std::string> lines;
boost::split(lines, lootOut, boost::is_any_of("\r\n"));
- std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\.");
- std::tr1::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\.");
+ std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\.");
+ std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\.");
for (const std::string &line : lines) {
if (line.length() > 0) {
@@ -4589,12 +4654,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe
qWarning("%s", line.c_str());
errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n");
} else {
- std::tr1::smatch match;
- if (std::tr1::regex_match(line, match, exRequires)) {
+ std::smatch match;
+ if (std::regex_match(line, match, exRequires)) {
std::string modName(match[1].first, match[1].second);
std::string dependency(match[2].first, match[2].second);
m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str()));
- } else if (std::tr1::regex_match(line, match, exIncompatible)) {
+ } else if (std::regex_match(line, match, exIncompatible)) {
std::string modName(match[1].first, match[1].second);
std::string dependency(match[2].first, match[2].second);
m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str()));
@@ -4628,12 +4693,12 @@ void MainWindow::on_bossButton_clicked()
dialog.show();
QString outPath = QDir::temp().absoluteFilePath("lootreport.json");
-
+
QStringList parameters;
parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName()
- << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath())
- << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath())
- << "--out" << QString("\"%1\"").arg(outPath);
+ << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath())
+ << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath())
+ << "--out" << QString("\"%1\"").arg(outPath);
if (m_DidUpdateMasterList) {
parameters << "--skipUpdateMasterlist";
@@ -4753,7 +4818,7 @@ void MainWindow::on_bossButton_clicked()
if (success) {
m_DidUpdateMasterList = true;
- /*if (reportURL.length() > 0) {
+ if (reportURL.length() > 0) {
m_IntegratedBrowser.setWindowTitle("LOOT Report");
QString report(reportURL.c_str());
QStringList temp = report.split("?");
@@ -4762,15 +4827,8 @@ void MainWindow::on_bossButton_clicked()
url.setQuery(temp.at(1).toUtf8());
}
m_IntegratedBrowser.openUrl(url);
- }*/
-
- // if the game specifies load order by file time, our own load order file needs to be removed because it's outdated.
- // refreshESPList will then use the file time as the load order.
- if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) {
- qDebug("removing loadorder.txt");
- QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName());
}
- m_OrganizerCore.refreshESPList();
+ m_OrganizerCore.refreshESPList(false);
m_OrganizerCore.savePluginList();
}
}
@@ -4847,7 +4905,7 @@ void MainWindow::on_restoreButton_clicked()
QMessageBox::critical(this, tr("Restore failed"),
tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError())));
}
- m_OrganizerCore.refreshESPList();
+ m_OrganizerCore.refreshESPList(true);
}
}
@@ -5045,4 +5103,3 @@ void MainWindow::on_clearFiltersButton_clicked()
{
deselectFilters();
}
-
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 2c2e2723..ef6eb262 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -63,6 +63,7 @@ namespace MOShared { class DirectoryEntry; }
#include <QHeaderView>
#include <QVariant>
#include <Qt>
+#include <QtConcurrent/QtConcurrentRun>
class QAction;
class QAbstractItemModel;
@@ -336,6 +337,8 @@ private:
QTimer m_SaveMetaTimer;
QTimer m_UpdateProblemsTimer;
+ QFuture<void> m_MetaSave;
+
QTime m_StartTime;
//SaveGameInfoWidget *m_CurrentSaveView;
MOBase::ISaveGameInfoWidget *m_CurrentSaveView;
@@ -487,6 +490,7 @@ private slots:
void disableVisibleMods();
void exportModListCSV();
void openInstanceFolder();
+ void openLogsFolder();
void openInstallFolder();
void openDownloadsFolder();
void openProfileFolder();
@@ -552,6 +556,7 @@ private slots: // ui slots
void on_actionUpdate_triggered();
void on_actionEndorseMO_triggered();
+ void on_bsaList_customContextMenuRequested(const QPoint &pos);
void on_clearFiltersButton_clicked();
void on_btnRefreshData_clicked();
void on_categoriesList_customContextMenuRequested(const QPoint &pos);
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index 31522c03..e0aa6f36 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -194,7 +194,7 @@
</widget>
</item>
<item>
- <widget class="QComboBox" name="profileBox">
+ <widget class="QComboBox" name="profileBox">
<property name="toolTip">
<string>Pick a module collection</string>
</property>
@@ -253,7 +253,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QPushButton" name="openFolderMenu">
<property name="toolTip">
- <string>Quickly open in Explorer relevant Folders...</string>
+ <string>Show Open Folders menu...</string>
</property>
<property name="text">
<string/>
@@ -463,7 +463,7 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,1,0">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,0,1,1,1">
<item>
<widget class="QPushButton" name="displayCategoriesBtn">
<property name="maximumSize">
@@ -507,15 +507,25 @@ p, li { white-space: pre-wrap; }
<italic>true</italic>
</font>
</property>
- <property name="text">
- <string/>
- </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>
<item alignment="Qt::AlignLeft">
<widget class="QPushButton" name="clearFiltersButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -523,9 +533,15 @@ p, li { white-space: pre-wrap; }
<property name="minimumSize">
<size>
<width>0</width>
- <height>25</height>
+ <height>22</height>
</size>
</property>
+ <property name="baseSize">
+ <size>
+ <width>95</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="visible">
<bool>false</bool>
</property>
@@ -551,7 +567,26 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <widget class="QComboBox" name="groupCombo">
+ <spacer name="horizontalSpacer_4">
+ <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="QComboBox" name="groupCombo">
+ <property name="baseSize">
+ <size>
+ <width>220</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
@@ -573,7 +608,13 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <widget class="MOBase::LineEditClear" name="modFilterEdit">
+ <widget class="MOBase::LineEditClear" name="modFilterEdit">
+ <property name="baseSize">
+ <size>
+ <width>220</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="placeholderText">
<string>Namefilter</string>
</property>
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index c14eedb7..d80c636b 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -55,7 +55,7 @@ QMutex ModInfo::s_Mutex(QMutex::Recursive);
QString ModInfo::s_HiddenExt(".mohidden");
-static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS)
+bool ModInfo::ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS)
{
return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0;
}
@@ -98,7 +98,7 @@ QString ModInfo::getContentTypeName(int contentType)
case CONTENT_INTERFACE: return tr("UI Changes");
case CONTENT_SOUND: return tr("Sound Effects");
case CONTENT_SCRIPT: return tr("Scripts");
- case CONTENT_SKSE: return tr("SKSE Plugins");
+ case CONTENT_SKSE: return tr("Script Extender");
case CONTENT_SKYPROC: return tr("SkyProc Tools");
case CONTENT_MCM: return tr("MCM Data");
default: throw MyException(tr("invalid content type %1").arg(contentType));
@@ -239,7 +239,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory,
createFromOverwrite();
- std::sort(s_Collection.begin(), s_Collection.end(), ByName);
+ std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName);
updateIndices();
}
diff --git a/src/modinfo.h b/src/modinfo.h
index d00c04e7..1ceb8e1f 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -599,6 +599,7 @@ protected:
ModInfo();
static void updateIndices();
+ static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS);
private:
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index ccd2a122..6a53d915 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -20,6 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfodialog.h"
#include "ui_modinfodialog.h"
#include "descriptionpage.h"
+#include "mainwindow.h"
#include "iplugingame.h"
#include "nexusinterface.h"
@@ -30,6 +31,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "questionboxmemory.h"
#include "settings.h"
#include "categories.h"
+#include "organizercore.h"
+#include "pluginlistsortproxy.h"
+#include "previewgenerator.h"
+#include "previewdialog.h"
#include <QDir>
#include <QDirIterator>
@@ -40,6 +45,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QFileSystemModel>
#include <QInputDialog>
#include <QPointer>
+#include <QFileDialog>
#include <Shlwapi.h>
@@ -66,11 +72,12 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS
}
-ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent)
+ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent)
: TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo),
m_ThumbnailMapper(this), m_RequestStarted(false),
m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr),
- m_Directory(directory), m_Origin(nullptr)
+ m_Directory(directory), m_Origin(nullptr),
+ m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer)
{
ui->setupUi(this);
this->setWindowTitle(modInfo->name());
@@ -293,7 +300,9 @@ void ModInfoDialog::refreshLists()
QStringList fields(relativeName);
fields.append(ToQString(realOrigin.getName()));
QTreeWidgetItem *item = new QTreeWidgetItem(fields);
+ item->setData(0, Qt::UserRole, fileName);
item->setData(1, Qt::UserRole, ToQString(realOrigin.getName()));
+ item->setData(1, Qt::UserRole + 2, archive);
ui->overwrittenTree->addTopLevelItem(item);
++numOverwritten;
}
@@ -1197,6 +1206,151 @@ void ModInfoDialog::unhideConflictFile()
}
}
+int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments)
+{
+ QString extension = targetInfo.suffix();
+ if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) ||
+ (extension.compare("com", Qt::CaseInsensitive) == 0) ||
+ (extension.compare("bat", Qt::CaseInsensitive) == 0)) {
+ binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe");
+ arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
+ return 1;
+ }
+ else if (extension.compare("exe", Qt::CaseInsensitive) == 0) {
+ binaryInfo = targetInfo;
+ return 1;
+ }
+ else if (extension.compare("jar", Qt::CaseInsensitive) == 0) {
+ // types that need to be injected into
+ std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath());
+ QString binaryPath;
+
+ { // try to find java automatically
+ WCHAR buffer[MAX_PATH];
+ if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) {
+ DWORD binaryType = 0UL;
+ if (!::GetBinaryTypeW(buffer, &binaryType)) {
+ qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError());
+ }
+ else if (binaryType == SCS_32BIT_BINARY) {
+ binaryPath = ToQString(buffer);
+ }
+ }
+ }
+ if (binaryPath.isEmpty() && (extension == "jar")) {
+ // second attempt: look to the registry
+ QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat);
+ if (javaReg.contains("CurrentVersion")) {
+ QString currentVersion = javaReg.value("CurrentVersion").toString();
+ binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe");
+ }
+ }
+ if (binaryPath.isEmpty()) {
+ binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)");
+ }
+ if (binaryPath.isEmpty()) {
+ return 0;
+ }
+ binaryInfo = QFileInfo(binaryPath);
+ if (extension == "jar") {
+ arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
+ }
+ else {
+ arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
+ }
+ return 1;
+ }
+ else {
+ return 2;
+ }
+}
+
+void ModInfoDialog::openDataFile()
+{
+ if (m_ConflictsContextItem != nullptr) {
+ QFileInfo targetInfo(m_ConflictsContextItem->data(0, Qt::UserRole).toString());
+ QFileInfo binaryInfo;
+ QString arguments;
+ switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) {
+ case 1: {
+ m_OrganizerCore->spawnBinaryDirect(
+ binaryInfo, arguments, m_OrganizerCore->currentProfile()->name(),
+ targetInfo.absolutePath(), "", "");
+ } break;
+ case 2: {
+ ::ShellExecuteW(nullptr, L"open",
+ ToWString(targetInfo.absoluteFilePath()).c_str(),
+ nullptr, nullptr, SW_SHOWNORMAL);
+ } break;
+ default: {
+ // nop
+ } break;
+ }
+ }
+}
+
+void ModInfoDialog::previewDataFile()
+{
+ QString fileName = QDir::fromNativeSeparators(m_ConflictsContextItem->data(0, Qt::UserRole).toString());
+
+ // what we have is an absolute path to the file in its actual location (for the primary origin)
+ // what we want is the path relative to the virtual data directory
+
+ // we need to look in the virtual directory for the file to make sure the info is up to date.
+
+ // check if the file comes from the actual data folder instead of a mod
+ QDir gameDirectory = m_OrganizerCore->managedGame()->dataDirectory().absolutePath();
+ QString relativePath = gameDirectory.relativeFilePath(fileName);
+ QDir direRelativePath = gameDirectory.relativeFilePath(fileName);
+ // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case
+ if (!direRelativePath.isAbsolute() && !relativePath.startsWith("..")) {
+ fileName = relativePath;
+ }
+ else {
+ // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory
+ int offset = m_OrganizerCore->settings().getModDirectory().size() + 1;
+ offset = fileName.indexOf("/", offset);
+ fileName = fileName.mid(offset + 1);
+ }
+
+
+
+ const FileEntry::Ptr file = m_OrganizerCore->directoryStructure()->searchFile(ToWString(fileName), nullptr);
+
+ if (file.get() == nullptr) {
+ reportError(tr("file not found: %1").arg(fileName));
+ return;
+ }
+
+ // set up preview dialog
+ PreviewDialog preview(fileName);
+ auto addFunc = [&](int originId) {
+ FilesOrigin &origin = m_OrganizerCore->directoryStructure()->getOriginByID(originId);
+ QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName;
+ if (QFile::exists(filePath)) {
+ // it's very possible the file doesn't exist, because it's inside an archive. we don't support that
+ QWidget *wid = m_PluginContainer->previewGenerator().genPreview(filePath);
+ if (wid == nullptr) {
+ reportError(tr("failed to generate preview for %1").arg(filePath));
+ }
+ else {
+ preview.addVariant(ToQString(origin.getName()), wid);
+ }
+ }
+ };
+
+ addFunc(file->getOrigin());
+ for (auto alt : file->getAlternatives()) {
+ addFunc(alt.first);
+ }
+ if (preview.numVariants() > 0) {
+ preview.exec();
+ }
+ else {
+ QMessageBox::information(this, tr("Sorry"), tr("Sorry, can't preview anything. This function currently does not support extracting from bsas."));
+ }
+}
+
void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos)
{
@@ -1211,11 +1365,39 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po
} else {
menu.addAction(tr("Hide"), this, SLOT(hideConflictFile()));
}
+
+ menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile()));
+
+ QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString();
+ if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) {
+ menu.addAction(tr("Preview"), this, SLOT(previewDataFile()));
+ }
+
menu.exec(ui->overwriteTree->mapToGlobal(pos));
}
}
}
+void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos)
+{
+ m_ConflictsContextItem = ui->overwrittenTree->itemAt(pos.x(), pos.y());
+
+ if (m_ConflictsContextItem != nullptr) {
+ if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) {
+ QMenu menu;
+
+ menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile()));
+
+ QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString();
+ if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) {
+ menu.addAction(tr("Preview"), this, SLOT(previewDataFile()));
+ }
+
+ menu.exec(ui->overwrittenTree->mapToGlobal(pos));
+ }
+ }
+}
+
void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int)
{
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 312ff99e..9eb17be5 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -23,6 +23,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "modinfo.h"
#include "tutorabledialog.h"
+#include "plugincontainer.h"
+#include "organizercore.h"
#include <QDialog>
#include <QSignalMapper>
@@ -76,7 +78,7 @@ public:
* @param modInfo info structure about the mod to display
* @param parent parend widget
**/
- explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, QWidget *parent = 0);
+ explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer,QWidget *parent = 0);
~ModInfoDialog();
/**
@@ -155,6 +157,10 @@ private slots:
void hideConflictFile();
void unhideConflictFile();
+ int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
+ void previewDataFile();
+ void openDataFile();
+
void thumbnailClicked(const QString &fileName);
void linkClicked(const QUrl &url);
@@ -185,6 +191,7 @@ private slots:
void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column);
void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column);
void on_overwriteTree_customContextMenuRequested(const QPoint &pos);
+ void on_overwrittenTree_customContextMenuRequested(const QPoint &pos);
void on_fileTree_customContextMenuRequested(const QPoint &pos);
void on_refreshButton_clicked();
@@ -208,6 +215,9 @@ private:
QSignalMapper m_ThumbnailMapper;
QString m_RootPath;
+ OrganizerCore *m_OrganizerCore;
+ PluginContainer *m_PluginContainer;
+
QFileSystemModel *m_FileSystemModel;
QTreeView *m_FileTree;
QModelIndexList m_FileSelection;
diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui
index 25822fd0..05415445 100644
--- a/src/modinfodialog.ui
+++ b/src/modinfodialog.ui
@@ -110,13 +110,19 @@
</item>
<item>
<widget class="QLabel" name="label_5">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="text">
- <string>Ini Tweaks</string>
+ <string>Ini Tweaks *This feature is non-functional*</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="iniTweaksList">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="maximumSize">
<size>
<width>228</width>
@@ -483,6 +489,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e
</item>
<item>
<widget class="QTreeWidget" name="overwrittenTree">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
<property name="textElideMode">
<enum>Qt::ElideLeft</enum>
</property>
@@ -492,6 +501,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e
<property name="animated">
<bool>true</bool>
</property>
+ <property name="columnCount">
+ <number>2</number>
+ </property>
<attribute name="headerDefaultSectionSize">
<number>365</number>
</attribute>
diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp
index 5486bb2c..4572f5bf 100644
--- a/src/modinforegular.cpp
+++ b/src/modinforegular.cpp
@@ -299,7 +299,7 @@ bool ModInfoRegular::setName(const QString &name)
s_ModsByName[m_Name] = index;
- std::sort(s_Collection.begin(), s_Collection.end(), ByName);
+ std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName);
updateIndices();
} else { // otherwise mod isn't registered yet?
m_Name = name;
diff --git a/src/modlist.cpp b/src/modlist.cpp
index 4002a424..7b2ad1f9 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -424,6 +424,11 @@ bool ModList::renameMod(int index, const QString &newName)
return false;
}
+ if (ModList::allMods().contains(newName,Qt::CaseInsensitive)) {
+ MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr);
+ return false;
+ }
+
ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
QString oldName = modInfo->name();
if (newName != oldName) {
diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp
index c8c2ef6f..aad7380e 100644
--- a/src/moshortcut.cpp
+++ b/src/moshortcut.cpp
@@ -24,6 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
MOShortcut::MOShortcut(const QString& link)
: m_valid(link.startsWith("moshortcut://"))
, m_hasInstance(false)
+ , m_hasExecutable(false)
{
if (m_valid) {
int start = (int)strlen("moshortcut://");
@@ -35,5 +36,7 @@ MOShortcut::MOShortcut(const QString& link)
}
else
m_executable = link.mid(start);
+ if(!(m_executable==""))
+ m_hasExecutable=true;
}
}
diff --git a/src/moshortcut.h b/src/moshortcut.h
index 7b7574b9..2ce54910 100644
--- a/src/moshortcut.h
+++ b/src/moshortcut.h
@@ -33,6 +33,8 @@ public:
operator bool() const { return m_valid; }
bool hasInstance() const { return m_hasInstance; }
+
+ bool hasExecutable() const { return m_hasExecutable; }
const QString& instance() const { return m_instance; }
@@ -43,4 +45,5 @@ private:
QString m_executable;
bool m_valid;
bool m_hasInstance;
+ bool m_hasExecutable;
};
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index eba02a6f..97186e8b 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -500,7 +500,7 @@ void NexusInterface::requestFinished(std::list<NXMRequestInfo>::iterator iter)
iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString();
iter->m_Reroute = true;
m_RequestQueue.enqueue(*iter);
- nextRequest();
+ //nextRequest();
return;
}
QByteArray data = reply->readAll();
diff --git a/src/organizercore.cpp b/src/organizercore.cpp
index b84488da..999c7732 100644
--- a/src/organizercore.cpp
+++ b/src/organizercore.cpp
@@ -602,8 +602,9 @@ void OrganizerCore::downloadRequestedNXM(const QString &url)
void OrganizerCore::externalMessage(const QString &message)
{
- if (MOShortcut moshortcut{ message }) {
- runShortcut(moshortcut);
+ if (MOShortcut moshortcut{ message } ) {
+ if(moshortcut.hasExecutable())
+ runShortcut(moshortcut);
}
else if (isNxmLink(message)) {
MessageDialog::showMessage(tr("Download started"), qApp->activeWindow());
@@ -894,6 +895,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName,
ModInfoDialog::TAB_INIFILES);
}
m_ModInstalled(modName);
+ m_DownloadManager.markInstalled(fileName);
+ emit modInstalled(modName);
return modInfo.data();
} else {
reportError(tr("mod \"%1\" not found").arg(modName));
@@ -1106,7 +1109,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &argument
}
refreshDirectoryStructure();
- refreshESPList();
+ refreshESPList(true);
savePluginList();
//These callbacks should not fiddle with directoy structure and ESPs.
@@ -1558,13 +1561,13 @@ void OrganizerCore::refreshModList(bool saveChanges)
refreshDirectoryStructure();
}
-void OrganizerCore::refreshESPList()
+void OrganizerCore::refreshESPList(bool force)
{
if (m_DirectoryUpdate) {
// don't mess up the esp list if we're currently updating the directory
// structure
- m_PostRefreshTasks.append([this]() {
- this->refreshESPList();
+ m_PostRefreshTasks.append([=]() {
+ this->refreshESPList(force);
});
return;
}
@@ -1573,7 +1576,7 @@ void OrganizerCore::refreshESPList()
// clear list
try {
m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure,
- m_CurrentProfile->getLockedOrderFileName());
+ m_CurrentProfile->getLockedOrderFileName(), force);
} catch (const std::exception &e) {
reportError(tr("Failed to refresh list of esps: %1").arg(e.what()));
}
@@ -1614,7 +1617,7 @@ void OrganizerCore::refreshBSAList()
void OrganizerCore::refreshLists()
{
if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) {
- refreshESPList();
+ refreshESPList(true);
refreshBSAList();
} // no point in refreshing lists if no files have been added to the directory
// tree
@@ -1677,7 +1680,7 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index,
modInfo->stealFiles());
DirectoryRefresher::cleanStructure(m_DirectoryStructure);
// need to refresh plugin list now so we can activate esps
- refreshESPList();
+ refreshESPList(true);
// activate all esps of the specified mod so the bsas get activated along with
// it
updateModActiveState(index, true);
@@ -1838,7 +1841,7 @@ void OrganizerCore::modStatusChanged(unsigned int index)
updateModInDirectoryStructure(index, modInfo);
} else {
updateModActiveState(index, false);
- refreshESPList();
+ refreshESPList(true);
if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
FilesOrigin &origin
= m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
diff --git a/src/organizercore.h b/src/organizercore.h
index 9e81f0c3..54d9dd6d 100644
--- a/src/organizercore.h
+++ b/src/organizercore.h
@@ -130,7 +130,7 @@ public:
void prepareStart();
- void refreshESPList();
+ void refreshESPList(bool force = false);
void refreshBSAList();
void refreshDirectoryStructure();
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index 8bf438f1..bd863f52 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -140,8 +140,15 @@ void PluginList::highlightPlugins(const QItemSelection &selected, const MOShared
void PluginList::refresh(const QString &profileName
, const DirectoryEntry &baseDirectory
- , const QString &lockedOrderFile)
+ , const QString &lockedOrderFile
+ , bool force)
{
+ if (force) {
+ m_ESPs.clear();
+ m_ESPsByName.clear();
+ m_ESPsByPriority.clear();
+ }
+
ChangeBracket<PluginList> layoutChange(this);
QStringList primaryPlugins = m_GamePlugin->primaryPlugins();
@@ -871,8 +878,12 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
text += "<br><b>" + tr("Enabled Masters") + "</b>: " + SetJoin(enabledMasters, ", ");
}
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.";
+ text += "<br>" + tr("There is an ini file connected to this esp. "
+ "Its settings will be added to your game settings, overwriting in case of conflicts.");
+ }
+ if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) {
+ text += "<br><br>" + tr("This ESP is flagged as an ESL. "
+ "It will adhere to the ESP load order but the records will be loaded in ESL space.");
}
toolTip += text;
}
@@ -895,6 +906,9 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
if (m_ESPs[index].m_HasIni) {
result.append(":/MO/gui/attachment");
}
+ if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) {
+ result.append(":/MO/gui/awaiting");
+ }
return result;
}
return QVariant();
diff --git a/src/pluginlist.h b/src/pluginlist.h
index 4762f79f..f9173810 100644
--- a/src/pluginlist.h
+++ b/src/pluginlist.h
@@ -116,7 +116,8 @@ public:
**/
void refresh(const QString &profileName
, const MOShared::DirectoryEntry &baseDirectory
- , const QString &lockedOrderFile);
+ , const QString &lockedOrderFile
+ , bool refresh);
/**
* @brief enable a plugin based on its name
diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp
index 932b2637..eb67719d 100644
--- a/src/queryoverwritedialog.cpp
+++ b/src/queryoverwritedialog.cpp
@@ -20,6 +20,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "queryoverwritedialog.h"
#include "ui_queryoverwritedialog.h"
+#include <QStyle>
+
QueryOverwriteDialog::QueryOverwriteDialog(QWidget *parent, Backup b)
: QDialog(parent), ui(new Ui::QueryOverwriteDialog),
m_Action(ACT_NONE)
diff --git a/src/resources.qrc b/src/resources.qrc
index 618d3cfc..14c8e533 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -72,6 +72,11 @@
<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>
+ <file alias="multiply_red">resources/multiply-red.png</file>
+ <file alias="archive_conflict_loser">resources/archive-conflict-loser.png</file>
+ <file alias="archive_conflict_mixed">resources/archive-conflict-mixed.png</file>
+ <file alias="archive_conflict_neutral">resources/archive-conflict-neutral.png</file>
+ <file alias="archive_conflict_winner">resources/archive-conflict-winner.png</file>
</qresource>
<qresource prefix="/MO/gui/content">
<file alias="plugin">resources/contents/jigsaw-piece.png</file>
diff --git a/src/resources/archive-conflict-loser.png b/src/resources/archive-conflict-loser.png
new file mode 100644
index 00000000..561e8be8
--- /dev/null
+++ b/src/resources/archive-conflict-loser.png
Binary files differ
diff --git a/src/resources/archive-conflict-mixed.png b/src/resources/archive-conflict-mixed.png
new file mode 100644
index 00000000..df20532c
--- /dev/null
+++ b/src/resources/archive-conflict-mixed.png
Binary files differ
diff --git a/src/resources/archive-conflict-neutral.png b/src/resources/archive-conflict-neutral.png
new file mode 100644
index 00000000..5bedd9c4
--- /dev/null
+++ b/src/resources/archive-conflict-neutral.png
Binary files differ
diff --git a/src/resources/archive-conflict-winner.png b/src/resources/archive-conflict-winner.png
new file mode 100644
index 00000000..5cc568c8
--- /dev/null
+++ b/src/resources/archive-conflict-winner.png
Binary files differ
diff --git a/src/resources/check.png b/src/resources/check.png
index 0f294a02..5df44755 100644
--- a/src/resources/check.png
+++ b/src/resources/check.png
Binary files differ
diff --git a/src/resources/contents/breastplate.png b/src/resources/contents/breastplate.png
index d3d39407..f8c787e2 100644
--- a/src/resources/contents/breastplate.png
+++ b/src/resources/contents/breastplate.png
Binary files differ
diff --git a/src/resources/contents/checkbox-tree.png b/src/resources/contents/checkbox-tree.png
index a44dd5fd..f4a63237 100644
--- a/src/resources/contents/checkbox-tree.png
+++ b/src/resources/contents/checkbox-tree.png
Binary files differ
diff --git a/src/resources/contents/config.png b/src/resources/contents/config.png
index 3896d885..3aaa2e5c 100644
--- a/src/resources/contents/config.png
+++ b/src/resources/contents/config.png
Binary files differ
diff --git a/src/resources/contents/conversation.png b/src/resources/contents/conversation.png
index 11030fff..76286e33 100644
--- a/src/resources/contents/conversation.png
+++ b/src/resources/contents/conversation.png
Binary files differ
diff --git a/src/resources/contents/double-quaver.png b/src/resources/contents/double-quaver.png
index 7102f472..21dcf4c1 100644
--- a/src/resources/contents/double-quaver.png
+++ b/src/resources/contents/double-quaver.png
Binary files differ
diff --git a/src/resources/contents/empty-chessboard.png b/src/resources/contents/empty-chessboard.png
index b5983010..fccb3aa5 100644
--- a/src/resources/contents/empty-chessboard.png
+++ b/src/resources/contents/empty-chessboard.png
Binary files differ
diff --git a/src/resources/contents/hand-of-god.png b/src/resources/contents/hand-of-god.png
index e3a05335..7cbd0102 100644
--- a/src/resources/contents/hand-of-god.png
+++ b/src/resources/contents/hand-of-god.png
Binary files differ
diff --git a/src/resources/contents/jigsaw-piece.png b/src/resources/contents/jigsaw-piece.png
index 0b26d56b..4949be40 100644
--- a/src/resources/contents/jigsaw-piece.png
+++ b/src/resources/contents/jigsaw-piece.png
Binary files differ
diff --git a/src/resources/contents/locked-chest.png b/src/resources/contents/locked-chest.png
index d70d448e..b49e1165 100644
--- a/src/resources/contents/locked-chest.png
+++ b/src/resources/contents/locked-chest.png
Binary files differ
diff --git a/src/resources/contents/lyre.png b/src/resources/contents/lyre.png
index 041dbb01..3adff24e 100644
--- a/src/resources/contents/lyre.png
+++ b/src/resources/contents/lyre.png
Binary files differ
diff --git a/src/resources/contents/tinker.png b/src/resources/contents/tinker.png
index 9709cee8..dceb1367 100644
--- a/src/resources/contents/tinker.png
+++ b/src/resources/contents/tinker.png
Binary files differ
diff --git a/src/resources/contents/usable.png b/src/resources/contents/usable.png
index 2bc93a7d..c705997a 100644
--- a/src/resources/contents/usable.png
+++ b/src/resources/contents/usable.png
Binary files differ
diff --git a/src/resources/document-save.png b/src/resources/document-save.png
index a94e0eab..18e1f974 100644
--- a/src/resources/document-save.png
+++ b/src/resources/document-save.png
Binary files differ
diff --git a/src/resources/multiply-red.png b/src/resources/multiply-red.png
new file mode 100644
index 00000000..f35adc74
--- /dev/null
+++ b/src/resources/multiply-red.png
Binary files differ
diff --git a/src/resources/open-Folder-Icon.png b/src/resources/open-Folder-Icon.png
index 345671f7..6218ed39 100644
--- a/src/resources/open-Folder-Icon.png
+++ b/src/resources/open-Folder-Icon.png
Binary files differ
diff --git a/src/resources/switch-instance-icon.png b/src/resources/switch-instance-icon.png
index b992babe..cd509d3d 100644
--- a/src/resources/switch-instance-icon.png
+++ b/src/resources/switch-instance-icon.png
Binary files differ
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp
index 273e9b45..8f962ba4 100644
--- a/src/selfupdater.cpp
+++ b/src/selfupdater.cpp
@@ -125,7 +125,7 @@ void SelfUpdater::testForUpdate()
// TODO: if prereleases are disabled we could just request the latest release
// directly
try {
- m_GitHub.releases(GitHub::Repository("LePresidente", "modorganizer"),
+ m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"),
[this](const QJsonArray &releases) {
QJsonObject newest;
for (const QJsonValue &releaseVal : releases) {
diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp
index b8dda056..61dab6e5 100644
--- a/src/transfersavesdialog.cpp
+++ b/src/transfersavesdialog.cpp
@@ -74,6 +74,11 @@ public:
return { m_File };
}
+ virtual bool hasScriptExtenderFile() const override
+ {
+ return false;
+ }
+
private:
QString m_File;
};
@@ -95,6 +100,11 @@ public:
{
return nullptr;
}
+
+ virtual bool hasScriptExtenderSave(QString const &file) const override
+ {
+ return false;
+ }
};
} //end anonymous namespace
@@ -334,26 +344,6 @@ bool TransferSavesDialog::transferCharacters(
sourceFile.absoluteFilePath().toUtf8().constData(),
destinationFile.toUtf8().constData());
}
-
- QFileInfo sourceFileSE(sourceFile.absolutePath() + "/" + sourceFile.completeBaseName() + "." + m_GamePlugin->savegameSEExtension());
- if (sourceFileSE.exists()) {
- QString destinationFileSE(destination.absoluteFilePath(sourceFileSE.fileName()));
-
- //If the file is already there, let them skip (or not).
- if (QFile::exists(destinationFileSE)) {
- if (!testOverwrite(overwriteMode, destinationFileSE)) {
- continue;
- }
- //OK, they want to remove it.
- QFile::remove(destinationFileSE);
- }
-
- if (!method(sourceFileSE.absoluteFilePath(), destinationFileSE)) {
- qCritical(errmsg,
- sourceFileSE.absoluteFilePath().toUtf8().constData(),
- destinationFileSE.toUtf8().constData());
- }
- }
}
}
return true;
diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js
index a60a5c8d..3c782af2 100644
--- a/src/tutorials/tutorial_conflictresolution_main.js
+++ b/src/tutorials/tutorial_conflictresolution_main.js
@@ -65,7 +65,7 @@ function getTutorialSteps() {
},
function() {
tutorial.text = qsTr("Option A: Switch to the \"Data\"-tab if necessary")
- if (!tutorialControl.waitForTabOpen("tabWidget", 1)) {
+ if (!tutorialControl.waitForTabOpen("tabWidget", 2)) {
highlightItem("tabWidget", false)
waitForClick()
} else {
diff --git a/src/version.rc b/src/version.rc
index a71a462a..142065bf 100644
--- a/src/version.rc
+++ b/src/version.rc
@@ -1,7 +1,7 @@
#include "Winver.h"
-#define VER_FILEVERSION 2,1,1
-#define VER_FILEVERSION_STR "2.1.1\0"
+#define VER_FILEVERSION 2,1,2
+#define VER_FILEVERSION_STR "2.1.2\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
@@ -10,17 +10,19 @@ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS (0)
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
-FILESUBTYPE VFT2_UNKNOWN
+FILESUBTYPE (0)
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "FileVersion", VER_FILEVERSION_STR
- VALUE "CompanyName", "Tannin\0"
- VALUE "FileDescription", "Mod Organizer UI\0"
+ VALUE "CompanyName", "Mod Organizer 2 Team\0"
+ VALUE "FileDescription", "Main Mod Oranizer 2 UI\0"
VALUE "OriginalFilename", "ModOrganizer.exe\0"
- VALUE "ProductName", "Mod Organizer\0"
+ VALUE "InternalName", "ModOrganizer\0"
+ VALUE "LegalCopyright", "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2018 Mod Organizer 2 contributors\0"
+ VALUE "ProductName", "Mod Organizer 2\0"
VALUE "ProductVersion", VER_FILEVERSION_STR
END
END