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