diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 3c4d36de77..a0ef97ff9c 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -94,20 +94,28 @@ bool Launcher::DataFilesPage::loadSettings() if (!currentProfile.isEmpty()) addProfile(currentProfile, true); - QStringList files = mLauncherSettings.values(QString("Profiles/") + currentProfile + QString("/content"), Qt::MatchExactly); + mSelector->setProfileContent(filesInProfile(currentProfile, pathIterator)); + + return true; +} + +QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, PathIterator& pathIterator) +{ + QStringList files = mLauncherSettings.values(QString("Profiles/") + profileName + QString("/content"), Qt::MatchExactly); QStringList filepaths; - foreach (const QString &file, files) + // mLauncherSettings.values() returns the files in reverse load order + QListIterator i(files); + i.toBack(); + while (i.hasPrevious()) { - QString filepath = pathIterator.findFirstPath (file); + QString filepath = pathIterator.findFirstPath(i.previous()); if (!filepath.isEmpty()) filepaths << filepath; } - mSelector->setProfileContent (filepaths); - - return true; + return filepaths; } void Launcher::DataFilesPage::saveSettings(const QString &profile) diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 15fa00308d..c2fc224614 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -134,6 +134,8 @@ namespace Launcher } }; + + QStringList filesInProfile(const QString& profileName, PathIterator& pathIterator); }; } #endif diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a7b7056c5c..a3e79758f5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -133,6 +133,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel + model/loadordererror view/combobox view/contentselector ) add_component_qt_dir (config diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 41fcf92bc8..6c5614f13a 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "components/esm/esmreader.hpp" @@ -176,6 +177,16 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int switch (role) { + case Qt::ForegroundRole: + { + if (isLoadOrderError(file)) + { + QBrush redBackground(Qt::red, Qt::SolidPattern); + return redBackground; + } + break; + } + case Qt::EditRole: case Qt::DisplayRole: { @@ -205,7 +216,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int if (column != 0) return QVariant(); - return file->toolTip(); + return toolTip(file); } case Qt::CheckStateRole: @@ -290,7 +301,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const { setCheckState(file->filePath(), success); emit dataChanged(index, index); - + checkForLoadOrderErrors(); } else return success; @@ -340,6 +351,8 @@ bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, cons } endRemoveRows(); + // at this point we know that drag and drop has finished. + checkForLoadOrderErrors(); return true; } @@ -531,11 +544,95 @@ bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const return (flags(index) & Qt::ItemIsEnabled); } -void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &fileList, bool isChecked) +bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const { - foreach (const QString &file, fileList) + return mPluginsWithLoadOrderError.contains(file->filePath()); +} + +void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList, bool isChecked) +{ + mPluginsWithLoadOrderError.clear(); + int previousPosition = -1; + foreach (const QString &filepath, fileList) { - setCheckState (file, isChecked); + if (setCheckState(filepath, isChecked)) + { + // as necessary, move plug-ins in visible list to match sequence of supplied filelist + const EsmFile* file = item(filepath); + int filePosition = indexFromItem(file).row(); + if (filePosition < previousPosition) + { + mFiles.move(filePosition, previousPosition); + emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex())); + } + else + { + previousPosition = filePosition; + } + } + } + checkForLoadOrderErrors(); +} + +void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() +{ + for (int row = 0; row < mFiles.count(); ++row) + { + EsmFile* file = item(row); + bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; + if (isRowInError) + { + mPluginsWithLoadOrderError.insert(file->filePath()); + } + else + { + mPluginsWithLoadOrderError.remove(file->filePath()); + } + } +} + +QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const +{ + QList errors = QList(); + foreach(QString dependentfileName, file->gameFiles()) + { + const EsmFile* dependentFile = item(dependentfileName); + + if (!dependentFile) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_MissingDependency, dependentfileName)); + } + if (!isChecked(dependentFile->filePath())) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); + } + if (row < indexFromItem(dependentFile).row()) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_LoadOrder, dependentfileName)); + } + } + return errors; +} + +QString ContentSelectorModel::ContentModel::toolTip(const EsmFile *file) const +{ + if (isLoadOrderError(file)) + { + QString text(""); + int index = indexFromItem(item(file->filePath())).row(); + foreach(const LoadOrderError& error, checkForLoadOrderErrors(file, index)) + { + text += "

"; + text += error.toolTip(); + text += "

"; + } + text += ("
"); + text += file->toolTip(); + return text; + } + else + { + return file->toolTip(); } } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 80b5094890..153800dcaa 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -3,6 +3,9 @@ #include #include +#include + +#include "loadordererror.hpp" namespace ContentSelectorModel { @@ -48,7 +51,7 @@ namespace ContentSelectorModel bool isEnabled (QModelIndex index) const; bool isChecked(const QString &filepath) const; bool setCheckState(const QString &filepath, bool isChecked); - void setCheckStates (const QStringList &fileList, bool isChecked); + void setContentList(const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); @@ -62,8 +65,21 @@ namespace ContentSelectorModel void sortFiles(); + /// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues + void checkForLoadOrderErrors(); + + /// Checks a specific plug-in for load order errors + /// \return all errors found for specific plug-in + QList checkForLoadOrderErrors(const EsmFile *file, int row) const; + + /// \return true if plug-in has a Load Order error + bool isLoadOrderError(const EsmFile *file) const; + + QString toolTip(const EsmFile *file) const; + ContentFileList mFiles; QHash mCheckStates; + QSet mPluginsWithLoadOrderError; QTextCodec *mCodec; QString mEncoding; diff --git a/components/contentselector/model/loadordererror.cpp b/components/contentselector/model/loadordererror.cpp new file mode 100644 index 0000000000..aa69f330e3 --- /dev/null +++ b/components/contentselector/model/loadordererror.cpp @@ -0,0 +1,15 @@ +#include "loadordererror.hpp" +#include + +QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder] = +{ + QString("Unable to find dependent file: %1"), + QString("Dependent file needs to be active: %1"), + QString("This file needs to load after %1") +}; + +QString ContentSelectorModel::LoadOrderError::toolTip() const +{ + assert(mErrorCode); + return sErrorToolTips[mErrorCode - 1].arg(mFileName); +} diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp new file mode 100644 index 0000000000..2b840cf69b --- /dev/null +++ b/components/contentselector/model/loadordererror.hpp @@ -0,0 +1,37 @@ +#ifndef LOADORDERERROR_HPP +#define LOADORDERERROR_HPP + +#include + +namespace ContentSelectorModel +{ + /// \brief Details of a suspected Load Order problem a plug-in will have. This is basically a POD. + class LoadOrderError + { + public: + enum ErrorCode + { + ErrorCode_None = 0, + ErrorCode_MissingDependency = 1, + ErrorCode_InactiveDependency = 2, + ErrorCode_LoadOrder = 3 + }; + + inline LoadOrderError() : mErrorCode(ErrorCode_None) {}; + inline LoadOrderError(ErrorCode errorCode, QString fileName) + { + mErrorCode = errorCode; + mFileName = fileName; + } + inline ErrorCode errorCode() const { return mErrorCode; } + inline QString fileName() const { return mFileName; } + QString toolTip() const; + + private: + ErrorCode mErrorCode; + QString mFileName; + static QString sErrorToolTips[ErrorCode_LoadOrder]; + }; +} + +#endif // LOADORDERERROR_HPP diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 643d93a9dd..770d45c862 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -75,7 +75,7 @@ void ContentSelectorView::ContentSelector::setProfileContent(const QStringList & } } - setCheckStates (fileList); + setContentList(fileList); } void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) @@ -103,14 +103,14 @@ void ContentSelectorView::ContentSelector::clearCheckStates() mContentModel->uncheckAll(); } -void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) +void ContentSelectorView::ContentSelector::setContentList(const QStringList &list) { if (list.isEmpty()) { slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); } else - mContentModel->setCheckStates (list, true); + mContentModel->setContentList(list, true); } ContentSelectorModel::ContentFileList diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index a25eb20ae3..a4da38727f 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -32,7 +32,7 @@ namespace ContentSelectorView void setProfileContent (const QStringList &fileList); void clearCheckStates(); - void setCheckStates (const QStringList &list); + void setContentList(const QStringList &list); ContentSelectorModel::ContentFileList selectedFiles() const;