1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-28 15:09:43 +00:00

Merge branch 'pickme' into 'master'

Override launcher file info with higher priority info

Closes #7733 and #7103

See merge request OpenMW/openmw!3743
This commit is contained in:
Alexei Kotov 2024-01-09 21:42:45 +00:00
commit 78d8cf86c3
8 changed files with 78 additions and 126 deletions

View file

@ -64,6 +64,7 @@
Bug #7084: Resurrecting an actor doesn't take into account base record changes Bug #7084: Resurrecting an actor doesn't take into account base record changes
Bug #7088: Deleting last save game of last character doesn't clear character name/details Bug #7088: Deleting last save game of last character doesn't clear character name/details
Bug #7092: BSA archives from higher priority directories don't take priority Bug #7092: BSA archives from higher priority directories don't take priority
Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries
Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7122: Teleportation to underwater should cancel active water walking effect
Bug #7131: MyGUI log spam when post processing HUD is open Bug #7131: MyGUI log spam when post processing HUD is open
Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7134: Saves with an invalid last generated RefNum can be loaded
@ -121,6 +122,7 @@
Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7712: Casting doesn't support spells and enchantments with no effects
Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7723: Assaulting vampires and werewolves shouldn't be a crime
Bug #7724: Guards don't help vs werewolves Bug #7724: Guards don't help vs werewolves
Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name
Bug #7742: Governing attribute training limit should use the modified attribute Bug #7742: Governing attribute training limit should use the modified attribute
Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7758: Water walking is not taken into account to compute path cost on the water
Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7761: Rain and ambient loop sounds are mutually exclusive

View file

@ -125,27 +125,6 @@ namespace Launcher
{ {
return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024);
} }
std::optional<QString> findFirstPath(const QStringList& directories, const QString& fileName)
{
for (const QString& directoryPath : directories)
{
const QString filePath = QDir(directoryPath).absoluteFilePath(fileName);
if (QFile::exists(filePath))
return filePath;
}
return std::nullopt;
}
QStringList findAllFilePaths(const QStringList& directories, const QStringList& fileNames)
{
QStringList result;
result.reserve(fileNames.size());
for (const QString& fileName : fileNames)
if (const auto filepath = findFirstPath(directories, fileName))
result.append(*filepath);
return result;
}
} }
} }
@ -366,8 +345,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
row++; row++;
} }
mSelector->setProfileContent( mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName));
findAllFilePaths(directories, mLauncherSettings.getContentListFiles(contentModelName)));
} }
void Launcher::DataFilesPage::saveSettings(const QString& profile) void Launcher::DataFilesPage::saveSettings(const QString& profile)

View file

@ -2,12 +2,15 @@
#include "esmfile.hpp" #include "esmfile.hpp"
#include <fstream> #include <fstream>
#include <memory>
#include <stdexcept> #include <stdexcept>
#include <unordered_set> #include <unordered_set>
#include <QDataStream>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QFont> #include <QFont>
#include <QIODevice>
#include <components/esm/format.hpp> #include <components/esm/format.hpp>
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
@ -127,7 +130,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index
continue; continue;
noGameFiles = false; noGameFiles = false;
if (isChecked(depFile->filePath())) if (mCheckedFiles.contains(depFile))
{ {
gamefileChecked = true; gamefileChecked = true;
break; break;
@ -214,7 +217,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int
if (file == mGameFile) if (file == mGameFile)
return QVariant(); return QVariant();
return mCheckStates[file->filePath()]; return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked;
} }
case Qt::UserRole: case Qt::UserRole:
@ -228,7 +231,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int
} }
case Qt::UserRole + 1: case Qt::UserRole + 1:
return isChecked(file->filePath()); return mCheckedFiles.contains(file);
} }
return QVariant(); return QVariant();
} }
@ -276,12 +279,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
{ {
int checkValue = value.toInt(); int checkValue = value.toInt();
bool setState = false; bool setState = false;
if ((checkValue == Qt::Checked) && !isChecked(file->filePath())) if (checkValue == Qt::Checked && !mCheckedFiles.contains(file))
{ {
setState = true; setState = true;
success = true; success = true;
} }
else if ((checkValue == Qt::Checked) && isChecked(file->filePath())) else if (checkValue == Qt::Checked && mCheckedFiles.contains(file))
setState = true; setState = true;
else if (checkValue == Qt::Unchecked) else if (checkValue == Qt::Unchecked)
setState = true; setState = true;
@ -314,34 +317,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const
bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex& parent) bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex& parent)
{ {
if (parent.isValid()) return false;
return false;
beginInsertRows(parent, position, position + rows - 1);
{
for (int row = 0; row < rows; ++row)
mFiles.insert(position, new EsmFile);
}
endInsertRows();
return true;
} }
bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex& parent) bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex& parent)
{ {
if (parent.isValid()) return false;
return false;
beginRemoveRows(parent, position, position + rows - 1);
{
for (int row = 0; row < rows; ++row)
delete mFiles.takeAt(position);
}
endRemoveRows();
// at this point we know that drag and drop has finished.
checkForLoadOrderErrors();
return true;
} }
Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const
@ -357,13 +338,14 @@ QStringList ContentSelectorModel::ContentModel::mimeTypes() const
QMimeData* ContentSelectorModel::ContentModel::mimeData(const QModelIndexList& indexes) const QMimeData* ContentSelectorModel::ContentModel::mimeData(const QModelIndexList& indexes) const
{ {
QByteArray encodedData; QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex& index : indexes) for (const QModelIndex& index : indexes)
{ {
if (!index.isValid()) if (!index.isValid())
continue; continue;
encodedData.append(item(index.row())->encodedData()); stream << index.row();
} }
QMimeData* mimeData = new QMimeData(); QMimeData* mimeData = new QMimeData();
@ -395,26 +377,31 @@ bool ContentSelectorModel::ContentModel::dropMimeData(
QByteArray encodedData = data->data(mMimeType); QByteArray encodedData = data->data(mMimeType);
QDataStream stream(&encodedData, QIODevice::ReadOnly); QDataStream stream(&encodedData, QIODevice::ReadOnly);
std::vector<EsmFile*> toMove;
while (!stream.atEnd()) while (!stream.atEnd())
{ {
int sourceRow;
QString value; stream >> sourceRow;
QStringList values; toMove.emplace_back(mFiles.at(sourceRow));
QStringList gamefiles;
for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i)
{
stream >> value;
values << value;
}
stream >> gamefiles;
insertRows(beginRow, 1);
QModelIndex idx = index(beginRow++, 0, QModelIndex());
setData(idx, QStringList() << values << gamefiles, Qt::EditRole);
} }
int minRow = mFiles.size();
int maxRow = 0;
for (EsmFile* file : toMove)
{
int from = mFiles.indexOf(file);
int to = beginRow;
if (from < beginRow)
to--;
else if (from > beginRow)
beginRow++;
minRow = std::min(minRow, std::min(to, from));
maxRow = std::max(maxRow, std::max(to, from));
mFiles.move(from, to);
}
dataChanged(index(minRow, 0), index(maxRow, 0));
// at this point we know that drag and drop has finished.
checkForLoadOrderErrors();
return true; return true;
} }
@ -447,26 +434,37 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
{ {
QFileInfo info(dir.absoluteFilePath(path2)); QFileInfo info(dir.absoluteFilePath(path2));
if (item(info.fileName()))
continue;
// Enabled by default in system openmw.cfg; shouldn't be shown in content list. // Enabled by default in system openmw.cfg; shouldn't be shown in content list.
if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0)
continue; continue;
EsmFile* file = const_cast<EsmFile*>(item(info.fileName()));
bool add = file == nullptr;
std::unique_ptr<EsmFile> newFile;
if (add)
{
newFile = std::make_unique<EsmFile>(path2);
file = newFile.get();
}
else
{
// We've found the same file in a higher priority dir, update our existing entry
file->setFileName(path2);
file->setGameFiles({});
}
if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive))
{ {
EsmFile* file = new EsmFile(path2);
file->setDate(info.lastModified()); file->setDate(info.lastModified());
file->setFilePath(info.absoluteFilePath()); file->setFilePath(info.absoluteFilePath());
addFile(file); if (add)
addFile(newFile.release());
setNew(file->fileName(), newfiles); setNew(file->fileName(), newfiles);
continue; continue;
} }
try try
{ {
EsmFile* file = new EsmFile(path2);
file->setDate(info.lastModified()); file->setDate(info.lastModified());
file->setFilePath(info.absoluteFilePath()); file->setFilePath(info.absoluteFilePath());
std::filesystem::path filepath = Files::pathFromQString(info.absoluteFilePath()); std::filesystem::path filepath = Files::pathFromQString(info.absoluteFilePath());
@ -522,14 +520,14 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
} }
// Put the file in the table // Put the file in the table
addFile(file); if (add)
addFile(newFile.release());
setNew(file->fileName(), newfiles); setNew(file->fileName(), newfiles);
} }
catch (std::runtime_error& e) catch (std::runtime_error& e)
{ {
// An error occurred while reading the .esp // An error occurred while reading the .esp
qWarning() << "Error reading addon file: " << e.what(); qWarning() << "Error reading addon file: " << e.what();
continue;
} }
} }
} }
@ -554,6 +552,7 @@ void ContentSelectorModel::ContentModel::clearFiles()
if (filesCount > 0) if (filesCount > 0)
{ {
beginRemoveRows(QModelIndex(), 0, filesCount - 1); beginRemoveRows(QModelIndex(), 0, filesCount - 1);
qDeleteAll(mFiles);
mFiles.clear(); mFiles.clear();
endRemoveRows(); endRemoveRows();
} }
@ -616,14 +615,6 @@ void ContentSelectorModel::ContentModel::sortFiles()
emit layoutChanged(); emit layoutChanged();
} }
bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const
{
const auto it = mCheckStates.find(filepath);
if (it == mCheckStates.end())
return false;
return it.value() == Qt::Checked;
}
bool ContentSelectorModel::ContentModel::isEnabled(const QModelIndex& index) const bool ContentSelectorModel::ContentModel::isEnabled(const QModelIndex& index) const
{ {
return (flags(index) & Qt::ItemIsEnabled); return (flags(index) & Qt::ItemIsEnabled);
@ -684,7 +675,7 @@ void ContentSelectorModel::ContentModel::checkForLoadOrderErrors()
{ {
for (int row = 0; row < mFiles.count(); ++row) for (int row = 0; row < mFiles.count(); ++row)
{ {
EsmFile* file = item(row); EsmFile* file = mFiles.at(row);
bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0;
if (isRowInError) if (isRowInError)
{ {
@ -711,7 +702,7 @@ QList<ContentSelectorModel::LoadOrderError> ContentSelectorModel::ContentModel::
} }
else else
{ {
if (!isChecked(dependentFile->filePath())) if (!mCheckedFiles.contains(dependentFile))
{ {
errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName));
} }
@ -761,19 +752,18 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
if (!file) if (!file)
return false; return false;
Qt::CheckState state = Qt::Unchecked;
if (checkState) if (checkState)
state = Qt::Checked; mCheckedFiles.insert(file);
else
mCheckedFiles.erase(file);
mCheckStates[filepath] = state;
emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath)));
if (file->isGameFile()) if (file->isGameFile())
refreshModel(); refreshModel();
// if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. // if we're checking an item, ensure all "upstream" files (dependencies) are checked as well.
if (state == Qt::Checked) if (checkState)
{ {
for (const QString& upstreamName : file->gameFiles()) for (const QString& upstreamName : file->gameFiles())
{ {
@ -782,14 +772,13 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
if (!upstreamFile) if (!upstreamFile)
continue; continue;
if (!isChecked(upstreamFile->filePath())) mCheckedFiles.insert(upstreamFile);
mCheckStates[upstreamFile->filePath()] = Qt::Checked;
emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile));
} }
} }
// otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. // otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked.
if (state == Qt::Unchecked) else
{ {
for (const EsmFile* downstreamFile : mFiles) for (const EsmFile* downstreamFile : mFiles)
{ {
@ -798,8 +787,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath,
if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive)) if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive))
{ {
if (mCheckStates.contains(downstreamFile->filePath())) mCheckedFiles.erase(downstreamFile);
mCheckStates[downstreamFile->filePath()] = Qt::Unchecked;
emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile));
} }
@ -817,7 +805,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
// First search for game files and next addons, // First search for game files and next addons,
// so we get more or less correct game files vs addons order. // so we get more or less correct game files vs addons order.
for (EsmFile* file : mFiles) for (EsmFile* file : mFiles)
if (isChecked(file->filePath())) if (mCheckedFiles.contains(file))
list << file; list << file;
return list; return list;
@ -826,6 +814,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke
void ContentSelectorModel::ContentModel::uncheckAll() void ContentSelectorModel::ContentModel::uncheckAll()
{ {
emit layoutAboutToBeChanged(); emit layoutAboutToBeChanged();
mCheckStates.clear(); mCheckedFiles.clear();
emit layoutChanged(); emit layoutChanged();
} }

View file

@ -7,6 +7,8 @@
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
#include <set>
namespace ContentSelectorModel namespace ContentSelectorModel
{ {
class EsmFile; class EsmFile;
@ -57,7 +59,6 @@ namespace ContentSelectorModel
void setCurrentGameFile(const EsmFile* file); void setCurrentGameFile(const EsmFile* file);
bool isEnabled(const QModelIndex& index) const; bool isEnabled(const QModelIndex& index) const;
bool isChecked(const QString& filepath) const;
bool setCheckState(const QString& filepath, bool isChecked); bool setCheckState(const QString& filepath, bool isChecked);
bool isNew(const QString& filepath) const; bool isNew(const QString& filepath) const;
void setNew(const QString& filepath, bool isChecked); void setNew(const QString& filepath, bool isChecked);
@ -85,7 +86,7 @@ namespace ContentSelectorModel
const EsmFile* mGameFile; const EsmFile* mGameFile;
ContentFileList mFiles; ContentFileList mFiles;
QStringList mArchives; QStringList mArchives;
QHash<QString, Qt::CheckState> mCheckStates; std::set<const EsmFile*> mCheckedFiles;
QHash<QString, bool> mNewFiles; QHash<QString, bool> mNewFiles;
QSet<QString> mPluginsWithLoadOrderError; QSet<QString> mPluginsWithLoadOrderError;
QString mEncoding; QString mEncoding;

View file

@ -1,10 +1,5 @@
#include "esmfile.hpp" #include "esmfile.hpp"
#include <QDataStream>
#include <QIODevice>
int ContentSelectorModel::EsmFile::sPropertyCount = 7;
ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent)
: ModelItem(parent) : ModelItem(parent)
, mFileName(fileName) , mFileName(fileName)
@ -46,17 +41,6 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description)
mDescription = description; mDescription = description;
} }
QByteArray ContentSelectorModel::EsmFile::encodedData() const
{
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
stream << mFileName << mAuthor << mVersion << mModified.toString(Qt::ISODate) << mPath << mDescription
<< mGameFiles;
return encodedData;
}
bool ContentSelectorModel::EsmFile::isGameFile() const bool ContentSelectorModel::EsmFile::isGameFile() const
{ {
return (mGameFiles.size() == 0) return (mGameFiles.size() == 0)

View file

@ -30,15 +30,11 @@ namespace ContentSelectorModel
}; };
EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr);
// EsmFile(const EsmFile &);
~EsmFile() {}
void setFileProperty(const FileProperty prop, const QString& value); void setFileProperty(const FileProperty prop, const QString& value);
void setFileName(const QString& fileName); void setFileName(const QString& fileName);
void setAuthor(const QString& author); void setAuthor(const QString& author);
void setSize(const int size);
void setDate(const QDateTime& modified); void setDate(const QDateTime& modified);
void setFormat(const QString& format); void setFormat(const QString& format);
void setFilePath(const QString& path); void setFilePath(const QString& path);
@ -68,10 +64,6 @@ namespace ContentSelectorModel
} }
bool isGameFile() const; bool isGameFile() const;
QByteArray encodedData() const;
public:
static int sPropertyCount;
private: private:
QString mTooltipTemlate = tr( QString mTooltipTemlate = tr(

View file

@ -108,6 +108,7 @@ void ContentSelectorView::ContentSelector::buildAddonView()
connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated);
connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this,
&ContentSelector::signalAddonDataChanged); &ContentSelector::signalAddonDataChanged);
connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::slotRowsMoved);
buildContextMenu(); buildContextMenu();
} }
@ -331,3 +332,8 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt
{ {
ui->addonView->setDragEnabled(newText.isEmpty()); ui->addonView->setDragEnabled(newText.isEmpty());
} }
void ContentSelectorView::ContentSelector::slotRowsMoved()
{
ui->addonView->selectionModel()->clearSelection();
}

View file

@ -85,6 +85,7 @@ namespace ContentSelectorView
void slotUncheckMultiSelectedItems(); void slotUncheckMultiSelectedItems();
void slotCopySelectedItemsPaths(); void slotCopySelectedItemsPaths();
void slotSearchFilterTextChanged(const QString& newText); void slotSearchFilterTextChanged(const QString& newText);
void slotRowsMoved();
}; };
} }