mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 20:56:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			724 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			724 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "contentmodel.hpp"
 | |
| #include "esmfile.hpp"
 | |
| 
 | |
| #include <stdexcept>
 | |
| 
 | |
| #include <QDir>
 | |
| #include <QTextCodec>
 | |
| #include <QDebug>
 | |
| 
 | |
| #include "components/esm/esmreader.hpp"
 | |
| 
 | |
| ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) :
 | |
|     QAbstractTableModel(parent),
 | |
|     mWarningIcon(warningIcon),
 | |
|     mMimeType ("application/omwcontent"),
 | |
|     mMimeTypes (QStringList() << mMimeType),
 | |
|     mColumnCount (1),
 | |
|     mDropActions (Qt::MoveAction)
 | |
| {
 | |
|     setEncoding ("win1252");
 | |
|     uncheckAll();
 | |
| }
 | |
| 
 | |
| ContentSelectorModel::ContentModel::~ContentModel()
 | |
| {
 | |
|     qDeleteAll(mFiles);
 | |
|     mFiles.clear();
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding)
 | |
| {
 | |
|     mEncoding = encoding;
 | |
| }
 | |
| 
 | |
| int ContentSelectorModel::ContentModel::columnCount(const QModelIndex &parent) const
 | |
| {
 | |
|     if (parent.isValid())
 | |
|         return 0;
 | |
| 
 | |
|     return mColumnCount;
 | |
| }
 | |
| 
 | |
| int ContentSelectorModel::ContentModel::rowCount(const QModelIndex &parent) const
 | |
| {
 | |
|     if(parent.isValid())
 | |
|         return 0;
 | |
| 
 | |
|     return mFiles.size();
 | |
| }
 | |
| 
 | |
| const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row) const
 | |
| {
 | |
|     if (row >= 0 && row < mFiles.size())
 | |
|         return mFiles.at(row);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(int row)
 | |
| {
 | |
|     if (row >= 0 && row < mFiles.count())
 | |
|         return mFiles.at(row);
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(const QString &name) const
 | |
| {
 | |
|     EsmFile::FileProperty fp = EsmFile::FileProperty_FileName;
 | |
| 
 | |
|     if (name.contains ('/'))
 | |
|         fp = EsmFile::FileProperty_FilePath;
 | |
| 
 | |
|     foreach (const EsmFile *file, mFiles)
 | |
|     {
 | |
|         if (name.compare(file->fileProperty (fp).toString(), Qt::CaseInsensitive) == 0)
 | |
|             return file;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *item) const
 | |
| {
 | |
|     //workaround: non-const pointer cast for calls from outside contentmodel/contentselector
 | |
|     EsmFile *non_const_file_ptr = const_cast<EsmFile *>(item);
 | |
| 
 | |
|     if (item)
 | |
|         return index(mFiles.indexOf(non_const_file_ptr),0);
 | |
| 
 | |
|     return QModelIndex();
 | |
| }
 | |
| 
 | |
| Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|         return Qt::ItemIsDropEnabled;
 | |
| 
 | |
|     const EsmFile *file = item(index.row());
 | |
| 
 | |
|     if (!file)
 | |
|         return Qt::NoItemFlags;
 | |
| 
 | |
|     //game files can always be checked
 | |
|     if (file->isGameFile())
 | |
|         return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
 | |
| 
 | |
|     Qt::ItemFlags returnFlags;
 | |
| 
 | |
|     // addon can be checked if its gamefile is
 | |
|     // ... special case, addon with no dependency can be used with any gamefile.
 | |
|     bool gamefileChecked = (file->gameFiles().count() == 0);
 | |
|     foreach (const QString &fileName, file->gameFiles())
 | |
|     {
 | |
|         bool depFound = false;
 | |
|         foreach (EsmFile *dependency, mFiles)
 | |
|         {
 | |
|             //compare filenames only.  Multiple instances
 | |
|             //of the filename (with different paths) is not relevant here.
 | |
|             depFound = (dependency->fileName().compare(fileName, Qt::CaseInsensitive) == 0);
 | |
| 
 | |
|             if (!depFound)
 | |
|                 continue;
 | |
| 
 | |
|             if (!gamefileChecked)
 | |
|             {
 | |
|                 if (isChecked (dependency->filePath()))
 | |
|                     gamefileChecked = (dependency->isGameFile());
 | |
|             }
 | |
| 
 | |
|             // force it to iterate all files in cases where the current
 | |
|             // dependency is a game file to ensure that a later duplicate
 | |
|             // game file is / is not checked.
 | |
|             // (i.e., break only if it's not a gamefile or the game file has been checked previously)
 | |
|             if (gamefileChecked || !(dependency->isGameFile()))
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (gamefileChecked)
 | |
|     {
 | |
|         returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
 | |
|     }
 | |
| 
 | |
|     return returnFlags;
 | |
| }
 | |
| 
 | |
| QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int role) const
 | |
| {
 | |
|     if (!index.isValid())
 | |
|         return QVariant();
 | |
| 
 | |
|     if (index.row() >= mFiles.size())
 | |
|         return QVariant();
 | |
| 
 | |
|     const EsmFile *file = item(index.row());
 | |
| 
 | |
|     if (!file)
 | |
|         return QVariant();
 | |
| 
 | |
|     const int column = index.column();
 | |
| 
 | |
|     switch (role)
 | |
|     {
 | |
|     case Qt::DecorationRole:
 | |
|     {
 | |
|         return isLoadOrderError(file) ? mWarningIcon : QVariant();
 | |
|     }
 | |
| 
 | |
|     case Qt::EditRole:
 | |
|     case Qt::DisplayRole:
 | |
|     {
 | |
|         if (column >=0 && column <=EsmFile::FileProperty_GameFile)
 | |
|             return file->fileProperty(static_cast<const EsmFile::FileProperty>(column));
 | |
| 
 | |
|         return QVariant();
 | |
|     }
 | |
| 
 | |
|     case Qt::TextAlignmentRole:
 | |
|     {
 | |
|         switch (column)
 | |
|         {
 | |
|         case 0:
 | |
|         case 1:
 | |
|             return Qt::AlignLeft + Qt::AlignVCenter;
 | |
|         case 2:
 | |
|         case 3:
 | |
|             return Qt::AlignRight + Qt::AlignVCenter;
 | |
|         default:
 | |
|             return Qt::AlignLeft + Qt::AlignVCenter;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     case Qt::ToolTipRole:
 | |
|     {
 | |
|         if (column != 0)
 | |
|             return QVariant();
 | |
| 
 | |
|         return toolTip(file);
 | |
|     }
 | |
| 
 | |
|     case Qt::CheckStateRole:
 | |
|     {
 | |
|         if (file->isGameFile())
 | |
|             return QVariant();
 | |
| 
 | |
|         return mCheckStates[file->filePath()];
 | |
|     }
 | |
| 
 | |
|     case Qt::UserRole:
 | |
|     {
 | |
|         if (file->isGameFile())
 | |
|             return ContentType_GameFile;
 | |
|         else
 | |
|             if (flags(index))
 | |
|                 return ContentType_Addon;
 | |
| 
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     case Qt::UserRole + 1:
 | |
|         return isChecked(file->filePath());
 | |
|     }
 | |
|     return QVariant();
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const QVariant &value, int role)
 | |
| {
 | |
|     if(!index.isValid())
 | |
|         return false;
 | |
| 
 | |
|     EsmFile *file = item(index.row());
 | |
|     QString fileName = file->fileName();
 | |
|     bool success = false;
 | |
| 
 | |
|     switch(role)
 | |
|     {
 | |
|         case Qt::EditRole:
 | |
|         {
 | |
|             QStringList list = value.toStringList();
 | |
| 
 | |
|             for (int i = 0; i < EsmFile::FileProperty_GameFile; i++)
 | |
|                 file->setFileProperty(static_cast<EsmFile::FileProperty>(i), list.at(i));
 | |
| 
 | |
|             for (int i = EsmFile::FileProperty_GameFile; i < list.size(); i++)
 | |
|                 file->setFileProperty (EsmFile::FileProperty_GameFile, list.at(i));
 | |
| 
 | |
|             emit dataChanged(index, index);
 | |
| 
 | |
|             success = true;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|         case Qt::UserRole+1:
 | |
|         {
 | |
|             success = (flags (index) & Qt::ItemIsEnabled);
 | |
| 
 | |
|             if (success)
 | |
|             {
 | |
|                 success = setCheckState(file->filePath(), value.toBool());
 | |
|                 emit dataChanged(index, index);
 | |
|             }
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|         case Qt::CheckStateRole:
 | |
|         {
 | |
|             int checkValue = value.toInt();
 | |
|             bool setState = false;
 | |
|             if ((checkValue==Qt::Checked) && !isChecked(file->filePath()))
 | |
|             {
 | |
|                 setState = true;
 | |
|                 success = true;
 | |
|             }
 | |
|             else if ((checkValue == Qt::Checked) && isChecked (file->filePath()))
 | |
|                 setState = true;
 | |
|             else if (checkValue == Qt::Unchecked)
 | |
|                 setState = true;
 | |
| 
 | |
|             if (setState)
 | |
|             {
 | |
|                 setCheckState(file->filePath(), success);
 | |
|                 emit dataChanged(index, index);
 | |
|                 checkForLoadOrderErrors();
 | |
|             }
 | |
|             else
 | |
|                 return success;
 | |
| 
 | |
| 
 | |
|             foreach (EsmFile *file, mFiles)
 | |
|             {
 | |
|                 if (file->gameFiles().contains(fileName, Qt::CaseInsensitive))
 | |
|                 {
 | |
|                     QModelIndex idx = indexFromItem(file);
 | |
|                     emit dataChanged(idx, idx);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             success =  true;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     return success;
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex &parent)
 | |
| {
 | |
|     if (parent.isValid())
 | |
|         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)
 | |
| {
 | |
|     if (parent.isValid())
 | |
|         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
 | |
| {
 | |
|     return mDropActions;
 | |
| }
 | |
| 
 | |
| QStringList ContentSelectorModel::ContentModel::mimeTypes() const
 | |
| {
 | |
|     return mMimeTypes;
 | |
| }
 | |
| 
 | |
| QMimeData *ContentSelectorModel::ContentModel::mimeData(const QModelIndexList &indexes) const
 | |
| {
 | |
|     QByteArray encodedData;
 | |
| 
 | |
|     foreach (const QModelIndex &index, indexes)
 | |
|     {
 | |
|         if (!index.isValid())
 | |
|             continue;
 | |
| 
 | |
|         encodedData.append(item(index.row())->encodedData());
 | |
|     }
 | |
| 
 | |
|     QMimeData *mimeData = new QMimeData();
 | |
|     mimeData->setData(mMimeType, encodedData);
 | |
| 
 | |
|     return mimeData;
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
 | |
| {
 | |
|     if (action == Qt::IgnoreAction)
 | |
|         return true;
 | |
| 
 | |
|     if (column > 0)
 | |
|         return false;
 | |
| 
 | |
|     if (!data->hasFormat(mMimeType))
 | |
|         return false;
 | |
| 
 | |
|     int beginRow = rowCount();
 | |
| 
 | |
|     if (row != -1)
 | |
|         beginRow = row;
 | |
| 
 | |
|     else if (parent.isValid())
 | |
|         beginRow = parent.row();
 | |
| 
 | |
|     QByteArray encodedData = data->data(mMimeType);
 | |
|     QDataStream stream(&encodedData, QIODevice::ReadOnly);
 | |
| 
 | |
|     while (!stream.atEnd())
 | |
|     {
 | |
| 
 | |
|         QString value;
 | |
|         QStringList values;
 | |
|         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);
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::addFile(EsmFile *file)
 | |
| {
 | |
|     beginInsertRows(QModelIndex(), mFiles.count(), mFiles.count());
 | |
|         mFiles.append(file);
 | |
|     endInsertRows();
 | |
| 
 | |
|     QModelIndex idx = index (mFiles.size() - 2, 0, QModelIndex());
 | |
| 
 | |
|     emit dataChanged (idx, idx);
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::addFiles(const QString &path)
 | |
| {
 | |
|     QDir dir(path);
 | |
|     QStringList filters;
 | |
|     filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon";
 | |
|     dir.setNameFilters(filters);
 | |
| 
 | |
|     foreach (const QString &path, dir.entryList())
 | |
|     {
 | |
|         QFileInfo info(dir.absoluteFilePath(path));
 | |
| 
 | |
|         if (item(info.absoluteFilePath()) != 0)
 | |
|             continue;
 | |
| 
 | |
|         try {
 | |
|             ESM::ESMReader fileReader;
 | |
|             ToUTF8::Utf8Encoder encoder =
 | |
|             ToUTF8::calculateEncoding(mEncoding.toStdString());
 | |
|             fileReader.setEncoder(&encoder);
 | |
|             fileReader.open(std::string(dir.absoluteFilePath(path).toUtf8().constData()));
 | |
| 
 | |
|             EsmFile *file = new EsmFile(path);
 | |
| 
 | |
|             foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles())
 | |
|                 file->addGameFile(QString::fromUtf8(item.name.c_str()));
 | |
| 
 | |
|             file->setAuthor     (QString::fromUtf8(fileReader.getAuthor().c_str()));
 | |
|             file->setDate       (info.lastModified());
 | |
|             file->setFormat     (fileReader.getFormat());
 | |
|             file->setFilePath       (info.absoluteFilePath());
 | |
|             file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str()));
 | |
| 
 | |
|             // HACK
 | |
|             // Load order constraint of Bloodmoon.esm needing Tribunal.esm is missing
 | |
|             // from the file supplied by Bethesda, so we have to add it ourselves
 | |
|             if (file->fileName().compare("Bloodmoon.esm", Qt::CaseInsensitive) == 0)
 | |
|             {
 | |
|                 file->addGameFile(QString::fromUtf8("Tribunal.esm"));
 | |
|             }
 | |
| 
 | |
|             // Put the file in the table
 | |
|             addFile(file);
 | |
| 
 | |
|         } catch(std::runtime_error &e) {
 | |
|             // An error occurred while reading the .esp
 | |
|             qWarning() << "Error reading addon file: " << e.what();
 | |
|             continue;
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     sortFiles();
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::clearFiles()
 | |
| {
 | |
|     beginRemoveRows(QModelIndex(), 0, mFiles.count()-1);
 | |
|     mFiles.clear();
 | |
|     endRemoveRows();
 | |
| }
 | |
| 
 | |
| QStringList ContentSelectorModel::ContentModel::gameFiles() const
 | |
| {
 | |
|     QStringList gameFiles;
 | |
|     foreach(const ContentSelectorModel::EsmFile *file, mFiles)
 | |
|     {
 | |
|         if (file->isGameFile())
 | |
|         {
 | |
|             gameFiles.append(file->fileName());
 | |
|         }
 | |
|     }
 | |
|     return gameFiles;
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::sortFiles()
 | |
| {
 | |
|     //first, sort the model such that all dependencies are ordered upstream (gamefile) first.
 | |
|     bool movedFiles = true;
 | |
|     int fileCount = mFiles.size();
 | |
| 
 | |
|     //Dependency sort
 | |
|     //iterate until no sorting of files occurs
 | |
|     while (movedFiles)
 | |
|     {
 | |
|         movedFiles = false;
 | |
|         //iterate each file, obtaining a reference to it's gamefiles list
 | |
|         for (int i = 0; i < fileCount; i++)
 | |
|         {
 | |
|             QModelIndex idx1 = index (i, 0, QModelIndex());
 | |
|             const QStringList &gamefiles = mFiles.at(i)->gameFiles();
 | |
|             //iterate each file after the current file, verifying that none of it's
 | |
|             //dependencies appear.
 | |
|             for (int j = i + 1; j < fileCount; j++)
 | |
|             {
 | |
|                 if (gamefiles.contains(mFiles.at(j)->fileName(), Qt::CaseInsensitive))
 | |
|                 {
 | |
|                         mFiles.move(j, i);
 | |
| 
 | |
|                         QModelIndex idx2 = index (j, 0, QModelIndex());
 | |
| 
 | |
|                         emit dataChanged (idx1, idx2);
 | |
| 
 | |
|                         movedFiles = true;
 | |
|                 }
 | |
|             }
 | |
|             if (movedFiles)
 | |
|                 break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const
 | |
| {
 | |
|     if (mCheckStates.contains(filepath))
 | |
|         return (mCheckStates[filepath] == Qt::Checked);
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const
 | |
| {
 | |
|     return (flags(index) & Qt::ItemIsEnabled);
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const
 | |
| {
 | |
|     return mPluginsWithLoadOrderError.contains(file->filePath());
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList)
 | |
| {
 | |
|     mPluginsWithLoadOrderError.clear();
 | |
|     int previousPosition = -1;
 | |
|     foreach (const QString &filepath, fileList)
 | |
|     {
 | |
|         if (setCheckState(filepath, true))
 | |
|         {
 | |
|             // 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::LoadOrderError> ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const
 | |
| {
 | |
|     QList<LoadOrderError> errors = QList<LoadOrderError>();
 | |
|     foreach(const QString &dependentfileName, file->gameFiles())
 | |
|     {
 | |
|         const EsmFile* dependentFile = item(dependentfileName);
 | |
| 
 | |
|         if (!dependentFile)
 | |
|         {
 | |
|             errors.append(LoadOrderError(LoadOrderError::ErrorCode_MissingDependency, dependentfileName));
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             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("<b>");
 | |
|         int index = indexFromItem(item(file->filePath())).row();
 | |
|         foreach(const LoadOrderError& error, checkForLoadOrderErrors(file, index))
 | |
|         {
 | |
|             text += "<p>";
 | |
|             text += error.toolTip();
 | |
|             text += "</p>";
 | |
|         }
 | |
|         text += ("</b>");
 | |
|         text += file->toolTip();
 | |
|         return text;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         return file->toolTip();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::refreshModel()
 | |
| {
 | |
|     emit dataChanged (index(0,0), index(rowCount()-1,0));
 | |
| }
 | |
| 
 | |
| bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, bool checkState)
 | |
| {
 | |
|     if (filepath.isEmpty())
 | |
|         return false;
 | |
| 
 | |
|     const EsmFile *file = item(filepath);
 | |
| 
 | |
|     if (!file)
 | |
|         return false;
 | |
| 
 | |
|     Qt::CheckState state = Qt::Unchecked;
 | |
| 
 | |
|     if (checkState)
 | |
|         state = Qt::Checked;
 | |
| 
 | |
|     mCheckStates[filepath] = state;
 | |
|     emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath)));
 | |
| 
 | |
|     if (file->isGameFile())
 | |
|         refreshModel();
 | |
| 
 | |
|     //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well.
 | |
|     if (state == Qt::Checked)
 | |
|     {
 | |
|         foreach (QString upstreamName, file->gameFiles())
 | |
|         {
 | |
|             const EsmFile *upstreamFile = item(upstreamName);
 | |
| 
 | |
|             if (!upstreamFile)
 | |
|                 continue;
 | |
| 
 | |
|             if (!isChecked(upstreamFile->filePath()))
 | |
|                 mCheckStates[upstreamFile->filePath()] = Qt::Checked;
 | |
| 
 | |
|             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.
 | |
|     if (state == Qt::Unchecked)
 | |
|     {
 | |
|         foreach (const EsmFile *downstreamFile, mFiles)
 | |
|         {
 | |
|             QFileInfo fileInfo(filepath);
 | |
|             QString filename = fileInfo.fileName();
 | |
| 
 | |
|             if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive))
 | |
|             {
 | |
|                 if (mCheckStates.contains(downstreamFile->filePath()))
 | |
|                     mCheckStates[downstreamFile->filePath()] = Qt::Unchecked;
 | |
| 
 | |
|                 emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checkedItems() const
 | |
| {
 | |
|     ContentFileList list;
 | |
| 
 | |
|     // TODO:
 | |
|     // First search for game files and next addons,
 | |
|     // so we get more or less correct game files vs addons order.
 | |
|     foreach (EsmFile *file, mFiles)
 | |
|         if (isChecked(file->filePath()))
 | |
|             list << file;
 | |
| 
 | |
|     return list;
 | |
| }
 | |
| 
 | |
| void ContentSelectorModel::ContentModel::uncheckAll()
 | |
| {
 | |
|     emit layoutAboutToBeChanged();
 | |
|     mCheckStates.clear();
 | |
|     emit layoutChanged();
 | |
| }
 |