summaryrefslogtreecommitdiff
path: root/src/pluginlist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/pluginlist.cpp')
-rw-r--r--src/pluginlist.cpp291
1 files changed, 224 insertions, 67 deletions
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp
index f73805fd..1f4ed9b1 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -20,8 +20,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include "pluginlist.h"
#include "report.h"
#include "inject.h"
-#include <utility.h>
#include "settings.h"
+#include "safewritefile.h"
+#include "scopeguard.h"
+#include <utility.h>
#include <gameinfo.h>
#include <espfile.h>
#include <windows_error.h>
@@ -39,7 +41,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
#include <QKeyEvent>
#include <QSortFilterProxyModel>
-#include <tchar.h>
#include <ctime>
#include <algorithm>
#include <stdexcept>
@@ -74,8 +75,9 @@ bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
}
PluginList::PluginList(QObject *parent)
- : QAbstractTableModel(parent),
- m_FontMetrics(QFont()), m_SaveTimer(this)
+ : QAbstractItemModel(parent)
+ , m_FontMetrics(QFont())
+ , m_SaveTimer(this)
{
m_SaveTimer.setSingleShot(true);
connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer()));
@@ -90,6 +92,10 @@ PluginList::PluginList(QObject *parent)
}
+PluginList::~PluginList()
+{
+}
+
QString PluginList::getColumnName(int column)
{
@@ -97,6 +103,7 @@ QString PluginList::getColumnName(int column)
case COL_NAME: return tr("Name");
case COL_PRIORITY: return tr("Priority");
case COL_MODINDEX: return tr("Mod Index");
+ case COL_FLAGS: return tr("Flags");
default: return tr("unknown");
}
}
@@ -118,7 +125,8 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD
const QString &pluginsFile, const QString &loadOrderFile,
const QString &lockedOrderFile)
{
- emit layoutAboutToBeChanged();
+ ChangeBracket<PluginList> layoutChange(this);
+
m_ESPsByName.clear();
m_ESPsByPriority.clear();
m_ESPs.clear();
@@ -140,8 +148,16 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD
std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(filename.toLower())) != primaryPlugins.end();
bool archive = false;
- FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive));
- m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath())));
+ try {
+ FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive));
+
+ QString iniPath = QFileInfo(filename).baseName() + ".ini";
+ bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != NULL;
+
+ m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni));
+ } catch (const std::exception &e) {
+ reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what()));
+ }
}
}
@@ -191,10 +207,12 @@ void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseD
readLockedOrderFrom(lockedOrderFile);
+ layoutChange.finish();
+
refreshLoadOrder();
+ emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
- emit layoutChanged();
- emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), 1));
+ m_Refreshed();
}
@@ -362,21 +380,19 @@ void PluginList::readLockedOrderFrom(const QString &fileName)
}
+
void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const
{
- QFile file(fileName);
- if (!file.open(QIODevice::WriteOnly)) {
- throw MyException(tr("failed to open output file: %1").arg(fileName));
- }
+ SafeWriteFile file(fileName);
QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec;
- file.resize(0);
+ file->resize(0);
- file.write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n"));
+ file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n"));
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) && !m_ESPs[priority].m_Removed) {
@@ -385,35 +401,34 @@ void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) cons
invalidFileNames = true;
qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData());
} else {
- file.write(textCodec->fromUnicode(m_ESPs[priority].m_Name));
+ file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name));
}
- file.write("\r\n");
+ file->write("\r\n");
+ ++writtenCount;
}
}
- file.close();
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."));
}
+ file.commit();
+
qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData());
}
void PluginList::writeLockedOrder(const QString &fileName) const
{
- QFile file(fileName);
- if (!file.open(QIODevice::WriteOnly)) {
- throw MyException(tr("failed to open output file: %1").arg(fileName));
- }
+ SafeWriteFile file(fileName);
- file.resize(0);
- file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8());
+ file->resize(0);
+ file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8());
for (auto iter = m_LockedOrder.begin(); iter != m_LockedOrder.end(); ++iter) {
- file.write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8());
+ file->write(QString("%1|%2\r\n").arg(iter->first).arg(iter->second).toUtf8());
}
- file.close();
+ file.commit();
}
@@ -490,6 +505,17 @@ bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
return true;
}
+int PluginList::enabledCount() const
+{
+ int enabled = 0;
+ foreach (auto info, m_ESPs) {
+ if (info.m_Enabled) {
+ ++enabled;
+ }
+ }
+ return enabled;
+}
+
bool PluginList::isESPLocked(int index) const
{
return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end();
@@ -526,6 +552,7 @@ void PluginList::syncLoadOrder()
void PluginList::refreshLoadOrder()
{
+ ChangeBracket<PluginList> layoutChange(this);
syncLoadOrder();
// set priorities according to locked load order
std::map<int, QString> lockedLoadOrder;
@@ -542,9 +569,7 @@ void PluginList::refreshLoadOrder()
// find the location to insert at
while ((targetPrio < static_cast<int>(m_ESPs.size() - 1)) &&
(m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) {
- if (QString::compare(m_ESPs[m_ESPsByPriority[targetPrio]].m_Name, iter->second) != 0) {
- ++targetPrio;
- }
+ ++targetPrio;
}
if (static_cast<size_t>(targetPrio) >= m_ESPs.size()) {
@@ -564,6 +589,67 @@ void PluginList::refreshLoadOrder()
}
+void PluginList::lootSort()
+{
+
+}
+
+IPluginList::PluginState PluginList::state(const QString &name) const
+{
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter == m_ESPsByName.end()) {
+ return IPluginList::STATE_MISSING;
+ } else {
+ return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE;
+ }
+}
+
+int PluginList::priority(const QString &name) const
+{
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter == m_ESPsByName.end()) {
+ return -1;
+ } else {
+ return m_ESPs[iter->second].m_Priority;
+ }
+}
+
+int PluginList::loadOrder(const QString &name) const
+{
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter == m_ESPsByName.end()) {
+ return -1;
+ } else {
+ return m_ESPs[iter->second].m_LoadOrder;
+ }
+}
+
+bool PluginList::isMaster(const QString &name) const
+{
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter == m_ESPsByName.end()) {
+ return false;
+ } else {
+ return m_ESPs[iter->second].m_IsMaster;
+ }
+}
+
+QString PluginList::origin(const QString &name) const
+{
+ auto iter = m_ESPsByName.find(name.toLower());
+ if (iter == m_ESPsByName.end()) {
+ return false;
+ } else {
+ return m_ESPs[iter->second].m_OriginName;
+ }
+}
+
+bool PluginList::onRefreshed(const std::function<void ()> &callback)
+{
+ auto conn = m_Refreshed.connect(callback);
+ return conn.connected();
+}
+
void PluginList::updateIndices()
{
m_ESPsByName.clear();
@@ -588,12 +674,14 @@ int PluginList::rowCount(const QModelIndex &parent) const
int PluginList::columnCount(const QModelIndex &) const
{
- return 3;
+ return COL_LASTCOLUMN + 1;
}
void PluginList::testMasters()
{
+// emit layoutAboutToBeChanged();
+
std::set<QString> enabledMasters;
for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
if (iter->m_Enabled) {
@@ -602,18 +690,18 @@ void PluginList::testMasters()
}
for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
- iter->m_MasterUnset = false;
+ 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 = true;
- break;
+ iter->m_MasterUnset.insert(*master);
}
}
}
}
- emit layoutChanged();
+#pragma message("emitting this seems to cause a crash!")
+// emit layoutChanged();
}
@@ -640,21 +728,24 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
return QVariant();
} break;
}
- } else if ((role == Qt::DecorationRole) && (modelIndex.column() == 0)) {
- if (m_ESPs[index].m_MasterUnset) {
- return QIcon(":/MO/gui/warning");
- } else if (m_LockedOrder.find(m_ESPs[index].m_Name.toLower()) != m_LockedOrder.end()) {
- return QIcon(":/MO/gui/locked");
- } else {
+ } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) {
+ if (m_ESPs[index].m_ForceEnabled) {
return QVariant();
+ } else {
+ return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked;
+ }
+ } else if (role == Qt::ForegroundRole) {
+ if ((modelIndex.column() == COL_NAME) &&
+ m_ESPs[index].m_ForceEnabled) {
+ return QBrush(Qt::gray);
}
- } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) {
- return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked;
} else if (role == Qt::FontRole) {
QFont result;
if (m_ESPs[index].m_IsMaster) {
result.setItalic(true);
result.setWeight(QFont::Bold);
+ } else if (m_ESPs[index].m_IsDummy) {
+ result.setItalic(true);
}
return result;
} else if (role == Qt::TextAlignmentRole) {
@@ -664,25 +755,74 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const
return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
}
} else if (role == Qt::ToolTipRole) {
+ QString name = m_ESPs[index].m_Name.toLower();
+ auto bossInfoIter = m_BossInfo.find(name);
+ QString toolTip;
+ if (bossInfoIter != m_BossInfo.end()) {
+ if (!bossInfoIter->second.m_BOSSMessages.isEmpty()) {
+ toolTip += bossInfoIter->second.m_BOSSMessages.join("<br>") + "<br><hr>";
+ }
+ if (bossInfoIter->second.m_BOSSUnrecognized) {
+ toolTip += "Not recognized by BOSS<br><ht>";
+ }
+ }
if (m_ESPs[index].m_ForceEnabled) {
- return tr("This plugin can't be disabled (enforced by the game)");
+ toolTip += tr("This plugin can't be disabled (enforced by the game)");
} else {
QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName);
- if (m_ESPs[index].m_MasterUnset) {
- text += "\nDepends on the following masters: " + SetJoin(m_ESPs[index].m_Masters, ", ");
+ if (m_ESPs[index].m_MasterUnset.size() > 0) {
+ text += "<br>" + tr("Missing Masters") + ": <b>" + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + "</b>";
+ }
+ std::set<QString> enabledMasters;
+ std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(),
+ m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(),
+ std::inserter(enabledMasters, enabledMasters.end()));
+ text += "<br>" + tr("Enabled Masters") + ": " + 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.";
+ } 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. With MO you don't need this: "
+ "If you enable the archive with the same name in the \"Archive\" tab you can disable this plugin.";
}
- return text;
+ toolTip += text;
}
- } else {
- return QVariant();
+ return toolTip;
+ } else if (role == Qt::UserRole + 1) {
+ QVariantList result;
+ QString nameLower = m_ESPs[index].m_Name.toLower();
+ if (m_ESPs[index].m_MasterUnset.size() > 0) {
+ result.append(QIcon(":/MO/gui/warning"));
+ }
+ if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) {
+ result.append(QIcon(":/MO/gui/locked"));
+ }
+ auto bossInfoIter = m_BossInfo.find(nameLower);
+ if (bossInfoIter != m_BossInfo.end()) {
+ if (!bossInfoIter->second.m_BOSSMessages.isEmpty()) {
+ result.append(QIcon(":/MO/gui/information"));
+ }
+ if (bossInfoIter->second.m_BOSSUnrecognized) {
+ result.append(QIcon(":/MO/gui/help"));
+ }
+ }
+ if (m_ESPs[index].m_HasIni) {
+ result.append(QIcon(":/MO/gui/attachment"));
+ }
+ if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) {
+ result.append(QIcon(":/MO/gui/edit_clear"));
+ }
+ return result;
}
+ return QVariant();
}
-bool PluginList::setData(const QModelIndex &index, const QVariant &value, int role)
+bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole) {
- m_ESPs[index.row()].m_Enabled = value.toInt() == Qt::Checked;
+ m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
+ emit dataChanged(modIndex, modIndex);
refreshLoadOrder();
startSaveTime();
@@ -716,13 +856,12 @@ QVariant PluginList::headerData(int section, Qt::Orientation orientation,
Qt::ItemFlags PluginList::flags(const QModelIndex &modelIndex) const
{
int index = modelIndex.row();
- Qt::ItemFlags result = QAbstractTableModel::flags(modelIndex);
+ Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex);
if (modelIndex.isValid()) {
- if ((m_ESPs[index].m_ForceEnabled)) {
- result &= ~Qt::ItemIsEnabled;
+ if (!m_ESPs[index].m_ForceEnabled) {
+ result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
}
- result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
} else {
result |= Qt::ItemIsDropEnabled;
}
@@ -738,18 +877,18 @@ void PluginList::setPluginPriority(int row, int &newPriority)
if (!m_ESPs[row].m_IsMaster) {
// don't allow esps to be moved above esms
while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
- m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_IsMaster) {
+ m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
++newPriorityTemp;
}
} else {
// don't allow esms to be moved below esps
while ((newPriorityTemp > 0) &&
- !m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_IsMaster) {
+ !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
--newPriorityTemp;
}
// also don't allow "regular" esms to be moved above primary plugins
while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
- (m_ESPs[m_ESPsByPriority[newPriorityTemp]].m_ForceEnabled)) {
+ (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_ForceEnabled)) {
++newPriorityTemp;
}
}
@@ -765,14 +904,17 @@ void PluginList::setPluginPriority(int row, int &newPriority)
for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) {
--m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority;
}
+ emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount()));
} else {
for (int i = newPriorityTemp; i < oldPriority; ++i) {
++m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority;
}
+ emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount()));
++newPriority;
}
m_ESPs.at(row).m_Priority = newPriorityTemp;
+ emit dataChanged(index(row, 0), index(row, columnCount()));
} catch (const std::out_of_range&) {
reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name));
}
@@ -783,7 +925,7 @@ void PluginList::setPluginPriority(int row, int &newPriority)
void PluginList::changePluginPriority(std::vector<int> rows, int newPriority)
{
- emit layoutAboutToBeChanged();
+ ChangeBracket<PluginList> layoutChange(this);
// sort rows to insert by their old priority (ascending) and insert them move them in that order
const std::vector<ESPInfo> &esp = m_ESPs;
std::sort(rows.begin(), rows.end(),
@@ -804,11 +946,11 @@ void PluginList::changePluginPriority(std::vector<int> rows, int newPriority)
for (std::vector<int>::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) {
setPluginPriority(*iter, newPriority);
}
+
+ layoutChange.finish();
refreshLoadOrder();
startSaveTime();
-
- emit layoutChanged();
}
@@ -859,6 +1001,19 @@ bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action,
return false;
}
+QModelIndex PluginList::index(int row, int column, const QModelIndex&) const
+{
+ if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) {
+ return QModelIndex();
+ }
+ return createIndex(row, column, row);
+}
+
+QModelIndex PluginList::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
bool PluginList::eventFilter(QObject *obj, QEvent *event)
{
@@ -870,7 +1025,6 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event)
}
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
-
// ctrl+up and ctrl+down -> increase or decrease priority of selected plugins
if ((keyEvent->modifiers() == Qt::ControlModifier) &&
((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) {
@@ -894,7 +1048,6 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event)
int newPriority = m_ESPs[idx.row()].m_Priority + diff;
if ((newPriority >= 0) && (newPriority < rowCount())) {
setPluginPriority(idx.row(), newPriority);
- emit dataChanged(this->index(idx.row(), 0), this->index(idx.row(), this->columnCount() - 1));
}
}
refreshLoadOrder();
@@ -926,19 +1079,23 @@ bool PluginList::eventFilter(QObject *obj, QEvent *event)
}
-PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath)
- : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), m_Removed(false), m_Priority(0),
- m_LoadOrder(-1), m_Time(time), m_OriginName(originName)
+PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time,
+ const QString &originName, const QString &fullPath,
+ bool hasIni)
+ : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), m_Removed(false),
+ m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni)
{
try {
ESP::File file(ToWString(fullPath));
m_IsMaster = file.isMaster();
+ m_IsDummy = file.isDummy();
std::set<std::string> masters = file.masters();
for (auto iter = masters.begin(); iter != masters.end(); ++iter) {
m_Masters.insert(QString(iter->c_str()));
}
} catch (const std::exception &e) {
- reportError(tr("failed to parse esp file %1: %2").arg(fullPath).arg(e.what()));
+ qCritical("failed to parse esp file %s: %s", qPrintable(fullPath), e.what());
m_IsMaster = false;
+ m_IsDummy = false;
}
}