├── .gitignore ├── OpenMW-MM.pro ├── OpenMWConfigInterface.cpp ├── OpenMWConfigInterface.h ├── SettingsInterface.cpp ├── SettingsInterface.h ├── TreeModItem.cpp ├── TreeModItem.h ├── TreeModModel.cpp ├── TreeModModel.h ├── WinMain.cpp ├── WinMain.h ├── WinMain.ui ├── main.cpp └── readme.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files built by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | #Ignore files built by qmake and make 31 | *.o 32 | moc_* 33 | ui_* 34 | Makefile 35 | .qmake.stash 36 | *.swp 37 | *.app 38 | -------------------------------------------------------------------------------- /OpenMW-MM.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2017-02-14T15:20:26 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = OpenMW-MM 12 | TEMPLATE = app 13 | 14 | 15 | SOURCES += main.cpp\ 16 | WinMain.cpp \ 17 | TreeModModel.cpp \ 18 | TreeModItem.cpp \ 19 | SettingsInterface.cpp \ 20 | OpenMWConfigInterface.cpp 21 | 22 | HEADERS += WinMain.h \ 23 | TreeModModel.h \ 24 | TreeModItem.h \ 25 | SettingsInterface.h \ 26 | OpenMWConfigInterface.h 27 | 28 | FORMS += WinMain.ui 29 | -------------------------------------------------------------------------------- /OpenMWConfigInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "OpenMWConfigInterface.h" 2 | 3 | OpenMWConfigInterface::OpenMWConfigInterface(const QString& configFilePath) 4 | { 5 | setConfigPath(configFilePath); 6 | load(); 7 | } 8 | 9 | OpenMWConfigInterface::~OpenMWConfigInterface() 10 | { 11 | save(); 12 | } 13 | 14 | void OpenMWConfigInterface::insert(const QString& key, const QVariant& value) 15 | { 16 | getByKey(key).push_back(value); 17 | } 18 | 19 | QVector& OpenMWConfigInterface::getByKey(const QString &key) 20 | { 21 | if (!data.contains(key)) 22 | { 23 | data[key] = QVector(); 24 | } 25 | 26 | return data[key]; 27 | } 28 | 29 | void OpenMWConfigInterface::save() 30 | { 31 | QFile cfgFile(cfgPath); 32 | if (!cfgFile.open(QIODevice::WriteOnly | QIODevice::Text)) 33 | { 34 | qWarning("Couldn't open OpenMW config file for writing."); 35 | return; 36 | } 37 | 38 | foreach(const QString& key, data.keys()) 39 | { 40 | foreach(const QVariant& value, data[key]) 41 | { 42 | QString line = key + "=" + value.toString() + '\n'; 43 | cfgFile.write(line.toUtf8()); 44 | } 45 | } 46 | } 47 | 48 | void OpenMWConfigInterface::load() 49 | { 50 | // Clear current map. 51 | data.clear(); 52 | 53 | // Load json from settings file. 54 | QFile cfgFile(cfgPath); 55 | if (!cfgFile.open(QIODevice::ReadOnly | QIODevice::Text)) 56 | { 57 | qWarning("Couldn't open OpenMW config file for reading."); 58 | return; 59 | } 60 | 61 | QString line; 62 | line = cfgFile.readLine(); 63 | while (!line.isEmpty()) 64 | { 65 | int splitIndex = line.indexOf('='); 66 | insert(line.left(splitIndex).trimmed(), line.right(line.length()-splitIndex-1).trimmed()); 67 | line = cfgFile.readLine(); 68 | } 69 | } 70 | 71 | void OpenMWConfigInterface::setConfigPath(const QString& path) 72 | { 73 | cfgPath = path; 74 | } 75 | -------------------------------------------------------------------------------- /OpenMWConfigInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENMWCONFIGINTERFACE_H 2 | #define OPENMWCONFIGINTERFACE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class OpenMWConfigInterface 12 | { 13 | public: 14 | OpenMWConfigInterface(const QString& configFilePath); 15 | ~OpenMWConfigInterface(); 16 | 17 | void insert(const QString& key, const QVariant& value); 18 | QVector& getByKey(const QString& key); 19 | 20 | void save(); 21 | void load(); 22 | 23 | void setConfigPath(const QString& path); 24 | 25 | private: 26 | QString cfgPath; 27 | QMap> data; 28 | }; 29 | 30 | #endif // OPENMWCONFIGINTERFACE_H 31 | -------------------------------------------------------------------------------- /SettingsInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "SettingsInterface.h" 2 | 3 | SettingsInterface::SettingsInterface(const QString& jsonFilePath) 4 | { 5 | jsonPath = jsonFilePath; 6 | 7 | // Load json from settings file. 8 | QFile jsonFile(jsonPath); 9 | if (!jsonFile.open(QIODevice::ReadOnly | QIODevice::Text)) 10 | { 11 | qWarning("Couldn't open settings file for reading."); 12 | return; 13 | } 14 | 15 | json = QJsonDocument::fromJson(jsonFile.readAll()); 16 | } 17 | 18 | SettingsInterface::~SettingsInterface() 19 | { 20 | save(); 21 | } 22 | 23 | void SettingsInterface::save() 24 | { 25 | QFile jsonFile(jsonPath); 26 | if (!jsonFile.open(QIODevice::WriteOnly)) 27 | { 28 | qWarning("Couldn't open settings file for writing."); 29 | return; 30 | } 31 | 32 | jsonFile.write(json.toJson()); 33 | } 34 | 35 | 36 | QVariant SettingsInterface::getSetting(const QString& key) 37 | { 38 | return (QVariant) json.object()["settings"].toObject()[key]; 39 | } 40 | 41 | void SettingsInterface::setSetting(const QString& key, const QString& value) 42 | { 43 | QJsonObject rootObject = json.object(); 44 | 45 | QJsonObject settingsObject = rootObject["settings"].toObject(); 46 | settingsObject[key] = value; 47 | rootObject["settings"] = settingsObject; 48 | 49 | json = QJsonDocument(rootObject); 50 | } 51 | 52 | const QJsonDocument& SettingsInterface::getJsonDoc() 53 | { 54 | return json; 55 | } 56 | 57 | void SettingsInterface::setModJson(TreeModItem* rootItem) 58 | { 59 | QJsonObject rootObject = json.object(); 60 | rootObject["mods"] = rootItem->toJsonObject(); 61 | json = QJsonDocument(rootObject); 62 | } 63 | -------------------------------------------------------------------------------- /SettingsInterface.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGSINTERFACE_H 2 | #define SETTINGSINTERFACE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "TreeModItem.h" 10 | 11 | class SettingsInterface 12 | { 13 | public: 14 | SettingsInterface(const QString& jsonFilePath); 15 | ~SettingsInterface(); 16 | 17 | void save(); 18 | 19 | QVariant getSetting(const QString& key); 20 | void setSetting(const QString& key, const QString& value); 21 | 22 | const QJsonDocument& getJsonDoc(); 23 | void setModJson(TreeModItem* rootItem); 24 | 25 | private: 26 | QString jsonPath; 27 | QJsonDocument json; 28 | }; 29 | 30 | #endif // SETTINGSINTERFACE_H 31 | -------------------------------------------------------------------------------- /TreeModItem.cpp: -------------------------------------------------------------------------------- 1 | #include "TreeModItem.h" 2 | 3 | #include 4 | 5 | TreeModItem::TreeModItem(const QVector &data, TreeModItem *parent) 6 | { 7 | parentItem = parent; 8 | itemData = data; 9 | } 10 | 11 | TreeModItem::~TreeModItem() 12 | { 13 | qDeleteAll(childItems); 14 | } 15 | 16 | TreeModItem *TreeModItem::child(int number) 17 | { 18 | return childItems.value(number); 19 | } 20 | 21 | int TreeModItem::childCount() const 22 | { 23 | return childItems.count(); 24 | } 25 | 26 | int TreeModItem::childNumber() const 27 | { 28 | if (parentItem) 29 | return parentItem->childItems.indexOf(const_cast(this)); 30 | 31 | return 0; 32 | } 33 | 34 | int TreeModItem::columnCount() const 35 | { 36 | return itemData.count(); 37 | } 38 | 39 | QVariant TreeModItem::data(int column) const 40 | { 41 | return itemData.value(column); 42 | } 43 | 44 | bool TreeModItem::insertChildren(int position, int count, int columns) 45 | { 46 | if (position < 0 || position > childItems.size()) 47 | return false; 48 | 49 | for (int row = 0; row < count; ++row) { 50 | QVector data(columns); 51 | TreeModItem *item = new TreeModItem(data, this); 52 | childItems.insert(position, item); 53 | } 54 | 55 | return true; 56 | } 57 | 58 | bool TreeModItem::insertColumns(int position, int columns) 59 | { 60 | if (position < 0 || position > itemData.size()) 61 | return false; 62 | 63 | for (int column = 0; column < columns; ++column) 64 | itemData.insert(position, QVariant()); 65 | 66 | foreach (TreeModItem *child, childItems) 67 | child->insertColumns(position, columns); 68 | 69 | return true; 70 | } 71 | 72 | TreeModItem *TreeModItem::parent() 73 | { 74 | return parentItem; 75 | } 76 | 77 | bool TreeModItem::removeChildren(int position, int count) 78 | { 79 | if (position < 0 || position + count > childItems.size()) 80 | return false; 81 | 82 | for (int row = 0; row < count; ++row) 83 | delete childItems.takeAt(position); 84 | 85 | return true; 86 | } 87 | 88 | bool TreeModItem::removeColumns(int position, int columns) 89 | { 90 | if (position < 0 || position + columns > itemData.size()) 91 | return false; 92 | 93 | for (int column = 0; column < columns; ++column) 94 | itemData.remove(position); 95 | 96 | foreach (TreeModItem *child, childItems) 97 | child->removeColumns(position, columns); 98 | 99 | return true; 100 | } 101 | 102 | bool TreeModItem::setData(int column, const QVariant &value) 103 | { 104 | if (column < 0 || column >= itemData.size()) 105 | return false; 106 | 107 | // if (column == TreeModItem::COLUMN_ENABLED) 108 | // { 109 | // itemData[column] = value.toBool() ? Qt::Checked : Qt::Unchecked; 110 | // return true; 111 | // } 112 | 113 | itemData[column] = value; 114 | return true; 115 | } 116 | 117 | QJsonArray TreeModItem::getChildrenAsJsonArray() 118 | { 119 | QJsonArray array; 120 | 121 | foreach (TreeModItem *child, childItems) 122 | { 123 | array.insert(child->childNumber(), child->toJsonObject()); 124 | } 125 | 126 | return array; 127 | } 128 | 129 | QJsonValue TreeModItem::toJsonObject() 130 | { 131 | if (parentItem == 0) 132 | { 133 | return getChildrenAsJsonArray(); 134 | } 135 | 136 | QJsonObject obj; 137 | obj["name"] = data(1).toString(); 138 | obj["folder"] = data(2).toString(); 139 | obj["enabled"] = data(3).toBool(); 140 | if (childCount() > 0) 141 | obj["mods"] = getChildrenAsJsonArray(); 142 | return obj; 143 | } 144 | 145 | void TreeModItem::serialize(QDataStream& stream) 146 | { 147 | // Store base data. 148 | for (int column = 0; column < TreeModItem::COLUMN_COUNT; column++) 149 | { 150 | Qt::ItemDataRole role = Qt::DisplayRole; 151 | if (column == TreeModItem::COLUMN_ENABLED) 152 | role = Qt::CheckStateRole; 153 | 154 | stream << this->data(column); 155 | } 156 | 157 | // Store children. 158 | stream << QVariant(this->childCount()); 159 | for (int child = 0; child < this->childCount(); child++) 160 | { 161 | this->child(child)->serialize(stream); 162 | } 163 | } 164 | 165 | void TreeModItem::serialize(QVector& dataVect) 166 | { 167 | if (data(COLUMN_ENABLED).toBool()) 168 | { 169 | if (parentItem) 170 | dataVect.push_back(data(COLUMN_FOLDER)); 171 | foreach (TreeModItem* child, childItems) 172 | child->serialize(dataVect); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /TreeModItem.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEMODITEM_H 2 | #define TREEMODITEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class TreeModItem 11 | { 12 | public: 13 | explicit TreeModItem(const QVector &data, TreeModItem *parent = 0); 14 | ~TreeModItem(); 15 | 16 | TreeModItem *child(int number); 17 | int childCount() const; 18 | int columnCount() const; 19 | QVariant data(int column) const; 20 | bool insertChildren(int position, int count, int columns); 21 | bool insertColumns(int position, int columns); 22 | TreeModItem *parent(); 23 | bool removeChildren(int position, int count); 24 | bool removeColumns(int position, int columns); 25 | int childNumber() const; 26 | bool setData(int column, const QVariant &value); 27 | 28 | QJsonArray getChildrenAsJsonArray(); 29 | QJsonValue toJsonObject(); 30 | void serialize(QDataStream& stream); 31 | void serialize(QVector& dataVect); 32 | 33 | enum Columns { 34 | COLUMN_INDEX, 35 | COLUMN_NAME, 36 | COLUMN_FOLDER, 37 | COLUMN_ENABLED, 38 | COLUMN_COUNT 39 | }; 40 | 41 | private: 42 | // Parents & Children 43 | QList childItems; 44 | TreeModItem *parentItem; 45 | 46 | // Data 47 | QVector itemData; 48 | }; 49 | 50 | #endif // TREEMODITEM_H 51 | -------------------------------------------------------------------------------- /TreeModModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TreeModModel.h" 2 | 3 | #include 4 | 5 | TreeModModel::TreeModModel(SettingsInterface* settingsInterface, OpenMWConfigInterface* configInterface, QObject *parent) 6 | : QAbstractItemModel(parent) 7 | { 8 | settings = settingsInterface; 9 | config = configInterface; 10 | 11 | QVector rootData; 12 | rootData << tr("Index") << tr("Mod") << tr("Folder") << tr("Enabled"); 13 | 14 | rootItem = new TreeModItem(rootData); 15 | 16 | loadDataFromJson(); 17 | } 18 | 19 | TreeModModel::~TreeModModel() 20 | { 21 | saveDataToJson(); 22 | saveDataToConfig(); 23 | delete rootItem; 24 | } 25 | 26 | int TreeModModel::columnCount(const QModelIndex & /* parent */) const 27 | { 28 | return rootItem->columnCount(); 29 | } 30 | 31 | QVariant TreeModModel::data(const QModelIndex &index, int role) const 32 | { 33 | if (!index.isValid()) 34 | return QVariant(); 35 | 36 | if (role == Qt::TextColorRole) 37 | { 38 | foreach (const QModelIndex& conflict, currentConflicts) 39 | { 40 | if (conflict == index.sibling(index.row(),0)) 41 | return QVariant(QColor(255, 0, 0)); 42 | } 43 | 44 | } 45 | 46 | if (role == Qt::DisplayRole || role == Qt::EditRole) 47 | { 48 | if (index.column() != TreeModItem::COLUMN_ENABLED) 49 | { 50 | return getItem(index)->data(index.column()); 51 | } 52 | } 53 | else if (role == Qt::CheckStateRole && index.column() == TreeModItem::COLUMN_ENABLED) 54 | { 55 | return getItem(index)->data(index.column()).toBool() ? Qt::Checked : Qt::Unchecked; 56 | } 57 | 58 | return QVariant(); 59 | } 60 | 61 | Qt::ItemFlags TreeModModel::flags(const QModelIndex &index) const 62 | { 63 | if (!index.isValid()) 64 | return Qt::ItemIsDropEnabled; 65 | 66 | QFlags flag = QAbstractItemModel::flags(index); 67 | flag.setFlag(Qt::ItemIsSelectable, true); 68 | flag.setFlag(Qt::ItemIsEditable, true); 69 | if (index.column() == TreeModItem::COLUMN_INDEX) 70 | { 71 | flag.setFlag(Qt::ItemIsEditable, false); 72 | flag.setFlag(Qt::ItemIsDragEnabled, true); 73 | } 74 | else if (index.column() == TreeModItem::COLUMN_ENABLED) 75 | { 76 | flag.setFlag(Qt::ItemIsEditable, false); 77 | flag.setFlag(Qt::ItemIsUserCheckable, true); 78 | } 79 | 80 | return flag; 81 | } 82 | 83 | TreeModItem *TreeModModel::getItem(const QModelIndex &index) const 84 | { 85 | if (index.isValid()) { 86 | TreeModItem *item = static_cast(index.internalPointer()); 87 | if (item) 88 | return item; 89 | } 90 | return rootItem; 91 | } 92 | 93 | QVariant TreeModModel::headerData(int section, Qt::Orientation orientation, int role) const 94 | { 95 | if (orientation == Qt::Horizontal && role == Qt::DisplayRole) 96 | return rootItem->data(section); 97 | 98 | return QVariant(); 99 | } 100 | 101 | QModelIndex TreeModModel::index(int row, int column, const QModelIndex &parent) const 102 | { 103 | if (!hasIndex(row, column, parent)) 104 | return QModelIndex(); 105 | 106 | TreeModItem *parentItem = getItem(parent); 107 | TreeModItem *childItem = parentItem->child(row); 108 | if (childItem) 109 | return createIndex(row, column, childItem); 110 | else 111 | return QModelIndex(); 112 | } 113 | 114 | bool TreeModModel::insertColumns(int position, int columns, const QModelIndex &parent) 115 | { 116 | bool success; 117 | 118 | beginInsertColumns(parent, position, position + columns - 1); 119 | success = rootItem->insertColumns(position, columns); 120 | endInsertColumns(); 121 | 122 | return success; 123 | } 124 | 125 | bool TreeModModel::insertRows(int position, int rows, const QModelIndex &parent) 126 | { 127 | TreeModItem *parentItem = getItem(parent); 128 | bool success; 129 | 130 | beginInsertRows(parent, position, position + rows - 1); 131 | success = parentItem->insertChildren(position, rows, rootItem->columnCount()); 132 | endInsertRows(); 133 | 134 | // Redo indexing 135 | recalculateIndexes(parentItem, position); 136 | 137 | return success; 138 | } 139 | 140 | QModelIndex TreeModModel::parent(const QModelIndex &index) const 141 | { 142 | if (!index.isValid()) 143 | return QModelIndex(); 144 | 145 | TreeModItem *childItem = getItem(index); 146 | TreeModItem *parentItem = childItem->parent(); 147 | 148 | if (parentItem == rootItem) 149 | return QModelIndex(); 150 | 151 | return createIndex(parentItem->childNumber(), 0, parentItem); 152 | } 153 | 154 | QModelIndex TreeModModel::getIndexForFolder(const QString& folder) 155 | { 156 | QModelIndexList matches = this->match(this->index(0, TreeModItem::COLUMN_FOLDER), Qt::DisplayRole, QVariant::fromValue(folder), 1, Qt::MatchCaseSensitive | Qt::MatchRecursive); 157 | if (matches.count() > 0) 158 | return matches.first(); 159 | return QModelIndex(); 160 | } 161 | 162 | bool TreeModModel::removeColumns(int position, int columns, const QModelIndex &parent) 163 | { 164 | bool success; 165 | 166 | beginRemoveColumns(parent, position, position + columns - 1); 167 | success = rootItem->removeColumns(position, columns); 168 | endRemoveColumns(); 169 | 170 | if (rootItem->columnCount() == 0) 171 | removeRows(0, rowCount()); 172 | 173 | return success; 174 | } 175 | 176 | bool TreeModModel::removeRows(int position, int rows, const QModelIndex &parent) 177 | { 178 | TreeModItem* parentItem = getItem(parent); 179 | bool success = true; 180 | 181 | for (int r = 0; r < rows; r++) 182 | { 183 | QString folder = parent.child(position+r, TreeModItem::COLUMN_FOLDER).data().toString(); 184 | foreach(const QString& key, folderConflicts.keys()) 185 | { 186 | if (folderConflicts[key].contains(folder)) 187 | folderConflicts.remove(folder); 188 | if (folderConflicts[key].isEmpty()) 189 | folderConflicts.remove(key); 190 | } 191 | } 192 | // if (index.column() == TreeModItem::COLUMN_FOLDER) 193 | // { 194 | // QString oldFolder = parent.child(row) 195 | // QString newFolder = value.toString(); 196 | 197 | // // Remove all references to the old folder. 198 | // foreach(const QString& key, folderConflicts) 199 | // { 200 | // if (folderConflicts[key].contains(oldFolder)) 201 | // folderConflicts.remove(oldFolder); 202 | // if (folderConflicts[key].isEmpty()) 203 | // folderConflicts.remove(key); 204 | // } 205 | // } 206 | 207 | beginRemoveRows(parent, position, position + rows - 1); 208 | success = parentItem->removeChildren(position, rows); 209 | endRemoveRows(); 210 | 211 | // Redo indexing 212 | recalculateIndexes(parentItem, position); 213 | 214 | // Clear conflicts; another selection is going to come right after. 215 | currentConflicts.clear(); 216 | 217 | return success; 218 | } 219 | 220 | int TreeModModel::rowCount(const QModelIndex &parent) const 221 | { 222 | return getItem(parent)->childCount(); 223 | } 224 | 225 | bool TreeModModel::setData(const QModelIndex &index, const QVariant &value, int role) 226 | { 227 | if (role == Qt::CheckStateRole && index.column() == TreeModItem::COLUMN_ENABLED) 228 | { 229 | return getItem(index)->setData(index.column(), value.toBool() ? Qt::Checked : Qt::Unchecked); 230 | } 231 | 232 | if (role != Qt::EditRole) 233 | return false; 234 | 235 | if (index.column() == TreeModItem::COLUMN_FOLDER) 236 | { 237 | QString oldFolder = index.data().toString(); 238 | QString newFolder = value.toString(); 239 | 240 | // Remove all references to the old folder. 241 | foreach(const QString& key, folderConflicts.keys()) 242 | { 243 | if (folderConflicts[key].contains(oldFolder)) 244 | folderConflicts.remove(oldFolder); 245 | if (folderConflicts[key].isEmpty()) 246 | folderConflicts.remove(key); 247 | } 248 | 249 | // Go through all files and add them to the conflict map. 250 | if (QDir(newFolder).exists()) 251 | { 252 | QDirIterator it(newFolder, QStringList(), QDir::Files, QDirIterator::Subdirectories); 253 | while (it.hasNext()) 254 | { 255 | QString foundPath = it.next(); 256 | QString relativePath = foundPath.right(foundPath.length() - newFolder.length() - 1); 257 | folderConflicts[relativePath].push_back(newFolder); 258 | } 259 | } 260 | } 261 | 262 | bool result = false; 263 | if (index.column() == TreeModItem::COLUMN_INDEX) 264 | result = getItem(index)->setData(index.column(), index.row()); 265 | else 266 | result = getItem(index)->setData(index.column(), value); 267 | 268 | if (result) 269 | emit dataChanged(index, index); 270 | 271 | return result; 272 | } 273 | 274 | bool TreeModModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) 275 | { 276 | if (role != Qt::EditRole || orientation != Qt::Horizontal) 277 | return false; 278 | 279 | bool result = rootItem->setData(section, value); 280 | if (result) 281 | emit headerDataChanged(orientation, section, section); 282 | 283 | return result; 284 | } 285 | 286 | void TreeModModel::addMods(const QJsonArray& modsArray, const QModelIndex& parent) 287 | { 288 | foreach( QJsonValue mod, modsArray ) 289 | { 290 | QJsonObject modTable = mod.toObject(); 291 | 292 | QVector data; 293 | data << 0 << modTable["name"].toString() << modTable["folder"].toString() << modTable["enabled"].toBool(); 294 | 295 | int newRow = this->rowCount(parent); 296 | this->insertRow(newRow, parent); 297 | for (int column = 0; column < data.size(); column++) 298 | { 299 | this->setData(this->index(newRow, column, parent), data[column]); 300 | } 301 | 302 | QJsonValue subModsV = modTable["mods"]; 303 | if (!subModsV.isUndefined()) 304 | { 305 | QJsonArray subModsArray = subModsV.toArray(); 306 | addMods(subModsArray, this->index(newRow, 0, parent)); 307 | } 308 | } 309 | } 310 | 311 | void TreeModModel::loadDataFromJson() 312 | { 313 | QJsonArray modsArray = settings->getJsonDoc().object()["mods"].toArray(); 314 | addMods(modsArray, QModelIndex()); 315 | } 316 | 317 | void TreeModModel::saveDataToJson() 318 | { 319 | settings->setModJson(rootItem); 320 | } 321 | 322 | void TreeModModel::saveDataToConfig() 323 | { 324 | QVector& dataVect = config->getByKey("data"); 325 | dataVect.clear(); 326 | rootItem->serialize(dataVect); 327 | } 328 | 329 | void TreeModModel::recalculateIndexes(TreeModItem* parent, int startAt) 330 | { 331 | for (int i = startAt; i < parent->childCount(); i++) 332 | { 333 | parent->child(i)->setData(TreeModItem::COLUMN_INDEX, i); 334 | } 335 | } 336 | 337 | QStringList TreeModModel::mimeTypes() const 338 | { 339 | QStringList types; 340 | types << "application/openmwmm.text.data"; 341 | return types; 342 | } 343 | 344 | QMimeData* TreeModModel::mimeData(const QModelIndexList &indexes) const 345 | { 346 | QMimeData *mimeData = new QMimeData(); 347 | QByteArray encodedData; 348 | 349 | QDataStream stream(&encodedData, QIODevice::WriteOnly); 350 | stream.setVersion(QDataStream::Qt_5_7); 351 | 352 | // Serialize item. 353 | stream << indexes.length(); 354 | foreach (const QModelIndex &index, indexes) { 355 | if (index.isValid()) { 356 | this->getItem(index)->serialize(stream); 357 | } 358 | } 359 | 360 | mimeData->setData("application/openmwmm.text.data", encodedData); 361 | return mimeData; 362 | } 363 | 364 | void TreeModModel::importFromDataStream(QDataStream& stream, const QModelIndex& parent, int row) 365 | { 366 | if (!this->insertRow(row, parent)) 367 | return; 368 | 369 | for (int column = 0; column < this->columnCount(); column++) 370 | { 371 | QVariant columnData; 372 | stream >> columnData; 373 | QModelIndex child = this->index(row, column, parent); 374 | if (column == TreeModItem::COLUMN_INDEX) 375 | this->setData(child, row); 376 | else if (column == TreeModItem::COLUMN_ENABLED) 377 | this->setData(child, columnData, Qt::CheckStateRole); 378 | else 379 | this->setData(child, columnData); 380 | } 381 | 382 | // Handle children. 383 | QVariant childrenCount; 384 | stream >> childrenCount; 385 | for (int child = 0; child < childrenCount.toInt(); child++) 386 | importFromDataStream(stream, this->index(row, 0, parent), child); 387 | } 388 | 389 | bool TreeModModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) 390 | { 391 | if (!canDropMimeData(data, action, row, column, parent)) 392 | return false; 393 | 394 | if (action == Qt::IgnoreAction) 395 | return true; 396 | 397 | if (data->hasFormat("application/openmwmm.text.data")) 398 | { 399 | QByteArray encodedData = data->data("application/openmwmm.text.data"); 400 | QDataStream stream(&encodedData, QIODevice::ReadOnly); 401 | 402 | int sourceIndexCount; 403 | stream >> sourceIndexCount; 404 | for (int i = 0; i < sourceIndexCount; i++) 405 | importFromDataStream(stream, parent, row+i); 406 | 407 | return true; 408 | } 409 | 410 | return false; 411 | } 412 | 413 | bool TreeModModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const 414 | { 415 | Q_UNUSED(action); 416 | Q_UNUSED(row); 417 | Q_UNUSED(column); 418 | Q_UNUSED(parent); 419 | 420 | if (data->hasFormat("application/openmwmm.text.data")) 421 | return true; 422 | 423 | return false; 424 | } 425 | 426 | Qt::DropActions TreeModModel::supportedDragActions() const 427 | { 428 | return supportedDropActions(); 429 | } 430 | 431 | Qt::DropActions TreeModModel::supportedDropActions() const 432 | { 433 | return Qt::MoveAction; 434 | } 435 | 436 | //! TODO: Optimize this. 437 | void TreeModModel::updateConflictSelection(const QItemSelection& selected, const QItemSelection& deselected) 438 | { 439 | Q_UNUSED(deselected); 440 | 441 | if (selected == currentSelection) 442 | return; 443 | currentSelection = selected; 444 | 445 | // Clear conflicts. 446 | QModelIndexList selectionCopy = currentConflicts; 447 | currentConflicts.clear(); 448 | foreach(const QModelIndex& index, selectionCopy) 449 | this->dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), TreeModItem::COLUMN_COUNT), QVector() << Qt::TextColorRole); 450 | 451 | // We don't care what happens if the selection is empty. 452 | if (selected.empty()) 453 | return; 454 | 455 | QString baseFolder; 456 | QModelIndex thisIndex; 457 | foreach(const QModelIndex& index, selected.indexes()) 458 | { 459 | if (index.column() == TreeModItem::COLUMN_FOLDER) 460 | { 461 | thisIndex = index; 462 | baseFolder = index.data().toString(); 463 | break; 464 | } 465 | } 466 | 467 | if (baseFolder.isEmpty()) 468 | { 469 | qDebug("Warning: Could not locate folder for selection."); 470 | return; 471 | } 472 | 473 | QDirIterator it(baseFolder, QStringList(), QDir::Files, QDirIterator::Subdirectories); 474 | while (it.hasNext()) 475 | { 476 | QString foundPath = it.next(); 477 | QString relativePath = foundPath.right(foundPath.length() - baseFolder.length() - 1); 478 | if (folderConflicts.contains(relativePath) && folderConflicts[relativePath].size() > 1) 479 | { 480 | foreach (const QString& conflictingFolder, folderConflicts[relativePath]) 481 | { 482 | if (conflictingFolder == baseFolder) 483 | continue; 484 | 485 | QModelIndex conflictingIndex = getIndexForFolder(conflictingFolder); 486 | if (!conflictingIndex.isValid()) 487 | { 488 | qDebug() << "Warning: Could not find index for conflict for '" + conflictingFolder + "'"; 489 | continue; 490 | } 491 | 492 | while (conflictingIndex.isValid()) 493 | { 494 | currentConflicts.push_back(conflictingIndex.sibling(conflictingIndex.row(), 0)); 495 | this->dataChanged(conflictingIndex.sibling(conflictingIndex.row(), 0), conflictingIndex.sibling(conflictingIndex.row(), TreeModItem::COLUMN_COUNT), QVector() << Qt::TextColorRole); 496 | QModelIndex p = conflictingIndex.parent(); 497 | conflictingIndex = p.sibling(p.row(), conflictingIndex.column()); 498 | } 499 | } 500 | } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /TreeModModel.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEMODMODEL_H 2 | #define TREEMODMODEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "OpenMWConfigInterface.h" 10 | #include "SettingsInterface.h" 11 | #include "TreeModItem.h" 12 | 13 | class TreeModModel : public QAbstractItemModel 14 | { 15 | Q_OBJECT 16 | public: 17 | TreeModModel(SettingsInterface* settingsInterface, OpenMWConfigInterface* configInterface, QObject *parent = 0); 18 | ~TreeModModel(); 19 | 20 | // Basic data 21 | QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; 22 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 23 | 24 | // Row/column/index 25 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 26 | QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; 27 | QModelIndex getIndexForFolder(const QString& folder); 28 | 29 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 30 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 31 | 32 | // Data manipulation 33 | Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; 34 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; 35 | bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; 36 | 37 | bool insertColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; 38 | bool removeColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; 39 | bool insertRows(int position, int rows, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; 40 | bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) Q_DECL_OVERRIDE; 41 | void importFromDataStream(QDataStream& stream, const QModelIndex& parent, int row); 42 | 43 | // Drag/drop 44 | QStringList mimeTypes() const Q_DECL_OVERRIDE; 45 | QMimeData* mimeData(const QModelIndexList &indexes) const Q_DECL_OVERRIDE; 46 | bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) Q_DECL_OVERRIDE; 47 | bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const Q_DECL_OVERRIDE; 48 | Qt::DropActions supportedDragActions() const Q_DECL_OVERRIDE; 49 | Qt::DropActions supportedDropActions() const Q_DECL_OVERRIDE; 50 | 51 | public slots: 52 | void updateConflictSelection(const QItemSelection& selected, const QItemSelection& deselected); 53 | 54 | private: 55 | void addMods(const QJsonArray& modsArray, const QModelIndex& parent); 56 | void loadDataFromJson(); 57 | void saveDataToJson(); 58 | void saveDataToConfig(); 59 | 60 | void recalculateIndexes(TreeModItem* parent, int startAt = 0); 61 | 62 | TreeModItem *getItem(const QModelIndex &index) const; 63 | TreeModItem *rootItem; 64 | 65 | SettingsInterface* settings; 66 | OpenMWConfigInterface* config; 67 | 68 | QItemSelection currentSelection; 69 | QModelIndexList currentConflicts; 70 | QMap folderConflicts; 71 | }; 72 | 73 | #endif // TREEMODMODEL_H 74 | -------------------------------------------------------------------------------- /WinMain.cpp: -------------------------------------------------------------------------------- 1 | #include "WinMain.h" 2 | #include "ui_WinMain.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | WinMain::WinMain(QWidget *parent) : 11 | QMainWindow(parent), 12 | ui(new Ui::WinMain) 13 | { 14 | ui->setupUi(this); 15 | 16 | // Get OpenMW config folder. 17 | QString configFolder; 18 | #if defined(Q_OS_MACOS) 19 | configFolder = QStandardPaths::locate(QStandardPaths::ConfigLocation, "openmw", QStandardPaths::LocateDirectory); 20 | #elif defined(Q_OS_WIN) 21 | configFolder = QStandardPaths::locate(QStandardPaths::DocumentsLocation, "My Games/OpenMW", QStandardPaths::LocateDirectory); 22 | #elif defined(Q_OS_LINUX) 23 | // Default flatpak location 24 | configFolder = QStandardPaths::locate(QStandardPaths::HomeLocation, ".var/app/org.openmw.OpenMW/config/openmw/", QStandardPaths::LocateDirectory); 25 | #endif 26 | 27 | // If not found automatically, ask where it is... 28 | if(configFolder.isEmpty()) 29 | { 30 | QMessageBox mbox; 31 | mbox.setText("Couldn't locate your OpenMW config folder. Please locate the directory containing the files:\n\n - openmw.cfg\n - mods.json"); 32 | mbox.setModal(true); 33 | mbox.setStandardButtons(QMessageBox::Open); 34 | mbox.setButtonText(0, "Locate..."); 35 | mbox.setIcon(QMessageBox::Icon::Question); 36 | mbox.exec(); 37 | 38 | configFolder = locateConfigFolder(); 39 | } 40 | 41 | // Load configs. 42 | settings = new SettingsInterface(configFolder + "/mods.json"); 43 | openMWConfig = new OpenMWConfigInterface(configFolder + "/openmw.cfg"); 44 | 45 | // Set up mod view. 46 | TreeModModel* model = new TreeModModel(settings, openMWConfig); 47 | ui->tvMain->setModel(model); 48 | ui->tvMain->header()->setContextMenuPolicy(Qt::CustomContextMenu); 49 | ui->tvMain->header()->setSectionsMovable(false); 50 | connect(ui->tvMain->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), 51 | model, SLOT(updateConflictSelection(const QItemSelection&, const QItemSelection&))); 52 | 53 | // Resize columns to fit. 54 | for (int column = 0; column < ui->tvMain->header()->count(); column++) 55 | ui->tvMain->resizeColumnToContents(column); 56 | 57 | // Complicated signals/slots 58 | connect(ui->tvMain, SIGNAL(customContextMenuRequested(QPoint)), 59 | this, SLOT(actContextMenuDataTree(QPoint))); 60 | connect(ui->tvMain->header(), SIGNAL(customContextMenuRequested(QPoint)), 61 | this, SLOT(actContextMenuDataTreeHeader(QPoint))); 62 | } 63 | 64 | WinMain::~WinMain() 65 | { 66 | QAbstractItemModel* model = ui->tvMain->model(); 67 | delete ui; 68 | delete model; 69 | delete settings; 70 | delete openMWConfig; 71 | } 72 | 73 | void WinMain::actAddData() 74 | { 75 | QFileInfo result = QFileDialog::getExistingDirectory(this,tr("Open Directory"), settings->getSetting("defaultModFolder").toString(), QFileDialog::ShowDirsOnly); 76 | if (!result.exists()) 77 | return; 78 | 79 | QModelIndex index = ui->tvMain->selectionModel()->currentIndex(); 80 | addNewData(ui->tvMain->model(), index.parent(), index.row()+1, result); 81 | } 82 | 83 | void WinMain::actAddChildData() 84 | { 85 | QFileInfo result = QFileDialog::getExistingDirectory(this,tr("Open Directory"), settings->getSetting("defaultModFolder").toString(), QFileDialog::ShowDirsOnly); 86 | if (!result.exists()) 87 | return; 88 | 89 | QModelIndex index = ui->tvMain->selectionModel()->currentIndex(); 90 | addNewData(ui->tvMain->model(), index, 0, result); 91 | } 92 | 93 | void WinMain::actDeleteData() 94 | { 95 | QModelIndex index = ui->tvMain->selectionModel()->currentIndex(); 96 | QAbstractItemModel* model = ui->tvMain->model(); 97 | model->removeRow(index.row(), index.parent()); 98 | } 99 | 100 | void WinMain::actContextMenuDataTree(const QPoint& pos) 101 | { 102 | QModelIndex index = ui->tvMain->selectionModel()->currentIndex(); 103 | 104 | QMenu menu(this); 105 | 106 | menu.addAction(ui->actionAddData); 107 | menu.addAction(ui->actionDeleteData); 108 | 109 | menu.addSeparator(); 110 | QAction* actOpenFolder = menu.addAction("Open folder", this, SLOT(actContextMenuDataTreeOpenFolder())); 111 | QDir folder = index.sibling(index.row(), TreeModItem::COLUMN_FOLDER).data().toString(); 112 | if (!folder.exists()) 113 | actOpenFolder->setDisabled(true); 114 | 115 | menu.exec(ui->tvMain->viewport()->mapToGlobal(pos)); 116 | } 117 | 118 | void WinMain::actContextMenuDataTreeOpenFolder() 119 | { 120 | QModelIndex index = ui->tvMain->selectionModel()->currentIndex(); 121 | QDir folder = index.sibling(index.row(), TreeModItem::COLUMN_FOLDER).data().toString(); 122 | if (!folder.exists()) 123 | { 124 | qDebug() << "Warning: Folder '" << folder.absolutePath() << "' does not exist."; 125 | return; 126 | } 127 | 128 | QDesktopServices::openUrl(QUrl::fromLocalFile(folder.absolutePath())); 129 | } 130 | 131 | void WinMain::actContextMenuDataTreeHeader(const QPoint& pos) 132 | { 133 | QHeaderView* header = ui->tvMain->header(); 134 | QMenu menu(this); 135 | 136 | //QMenu* menuHeaders = menu.addMenu("Headers"); 137 | for (int column = 0; column < header->count(); column++) 138 | { 139 | QAction* action = menu.addAction(ui->tvMain->model()->headerData(column, Qt::Horizontal).toString()); 140 | action->setData(column); 141 | action->setCheckable(true); 142 | action->setChecked(!ui->tvMain->header()->isSectionHidden(column)); 143 | } 144 | connect(&menu, SIGNAL(triggered(QAction*)), 145 | this, SLOT(actContextMenuDataTreeHeaderTriggered(QAction*))); 146 | 147 | menu.exec(ui->tvMain->header()->viewport()->mapToGlobal(pos)); 148 | } 149 | 150 | void WinMain::actContextMenuDataTreeHeaderTriggered(QAction* action) 151 | { 152 | int column = action->data().toInt(); 153 | ui->tvMain->header()->setSectionHidden(column, !ui->tvMain->header()->isSectionHidden(column)); 154 | } 155 | 156 | void WinMain::dragEnterEvent(QDragEnterEvent* event) 157 | { 158 | auto data = event->mimeData()->data("text/uri-list"); 159 | QTextStream stream(&data); 160 | QString uri = stream.readLine(); 161 | while (!uri.isEmpty()) 162 | { 163 | QFileInfo file = QUrl(uri).toLocalFile(); 164 | if (file.isDir() && file.exists()) 165 | event->acceptProposedAction(); 166 | 167 | uri = stream.readLine(); 168 | } 169 | } 170 | 171 | void WinMain::dragMoveEvent(QDragMoveEvent* event) 172 | { 173 | if (event->mimeData()->hasFormat("text/uri-list") 174 | && event->answerRect().intersects(ui->tvMain->geometry())) 175 | { 176 | event->acceptProposedAction(); 177 | } 178 | } 179 | 180 | void WinMain::dropEvent(QDropEvent* event) 181 | { 182 | auto data = event->mimeData()->data("text/uri-list"); 183 | QTextStream stream(&data); 184 | QString uri = stream.readLine(); 185 | while (!uri.isEmpty()) 186 | { 187 | QFileInfo file = QUrl(uri).toLocalFile(); 188 | if (file.isDir() && file.exists()) 189 | { 190 | addNewData(ui->tvMain->model(), ui->tvMain->rootIndex(), ui->tvMain->model()->rowCount(), file); 191 | event->acceptProposedAction(); 192 | } 193 | 194 | uri = stream.readLine(); 195 | } 196 | } 197 | 198 | QString WinMain::locateConfigFolder() 199 | { 200 | QString configFolder = QFileDialog::getExistingDirectory(this, "Locate OpenMW config folder...", QString(), 0); 201 | 202 | // Cancelled... 203 | if(configFolder.isEmpty()) 204 | { 205 | exit(0); 206 | } 207 | 208 | QDir configDir(configFolder); 209 | 210 | bool foundConfig = QFile(configDir.filePath("openmw.cfg")).exists(); 211 | bool foundJson = QFile(configDir.filePath("mods.json")).exists(); 212 | 213 | if(foundConfig && foundJson) 214 | { 215 | return configFolder; 216 | } 217 | 218 | QMessageBox mbox; 219 | mbox.setText("Directory must contain the following files:\n\n - openmw.cfg\n - mods.json"); 220 | mbox.setModal(true); 221 | mbox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Open); 222 | mbox.setDefaultButton(QMessageBox::Open); 223 | mbox.setIcon(QMessageBox::Icon::Warning); 224 | int result = mbox.exec(); 225 | 226 | if(result == QMessageBox::Cancel) 227 | { 228 | exit(0); 229 | } 230 | 231 | return locateConfigFolder(); 232 | } 233 | 234 | 235 | void WinMain::addNewData(QAbstractItemModel* model, const QModelIndex& parent, int position, const QFileInfo& target) 236 | { 237 | // Get path as string. 238 | QString path = target.absoluteFilePath(); 239 | 240 | //! Make sure we aren't adding a duplicate. 241 | 242 | // Add to model. 243 | if (!model->insertRow(position, parent)) 244 | return; 245 | for (int column = 0; column < model->columnCount(parent); ++column) { 246 | QModelIndex child = model->index(position, column, parent); 247 | if ( column == TreeModItem::COLUMN_INDEX ) 248 | model->setData(child, position, Qt::EditRole); 249 | else if ( column == TreeModItem::COLUMN_NAME ) 250 | model->setData(child, target.baseName(), Qt::EditRole); 251 | else if ( column == TreeModItem::COLUMN_FOLDER ) 252 | model->setData(child, target.absoluteFilePath(), Qt::EditRole); 253 | else if ( column == TreeModItem::COLUMN_ENABLED ) 254 | model->setData(child, true, Qt::EditRole); 255 | else 256 | model->setData(child, QVariant("[No data]"), Qt::EditRole); 257 | } 258 | 259 | // Look for valid sub-elements. 260 | QDirIterator it(target.absoluteFilePath(), QDir::AllDirs); 261 | while (it.hasNext()) 262 | { 263 | QFileInfo subFolder = it.next(); 264 | bool converted = false; 265 | QVariant firstToken = subFolder.baseName().split(" ").at(0); 266 | int firstTokenAsInt = firstToken.toString().toInt(&converted); 267 | if (converted) 268 | { 269 | QModelIndex newParent = model->index(position, 0, parent); 270 | if (!model->insertRow(model->rowCount(newParent), newParent)) 271 | return; 272 | for (int column = 0; column < model->columnCount(parent); ++column) { 273 | QModelIndex child = model->index(model->rowCount(newParent)-1, column, newParent); 274 | if ( column == TreeModItem::COLUMN_INDEX ) 275 | model->setData(child, firstTokenAsInt, Qt::EditRole); 276 | else if ( column == TreeModItem::COLUMN_NAME ) 277 | model->setData(child, subFolder.baseName().right(subFolder.baseName().length() - firstToken.toString().length() - 1), Qt::EditRole); 278 | else if ( column == TreeModItem::COLUMN_FOLDER ) 279 | model->setData(child, subFolder.absoluteFilePath(), Qt::EditRole); 280 | else if ( column == TreeModItem::COLUMN_ENABLED ) 281 | model->setData(child, true, Qt::EditRole); 282 | else 283 | model->setData(child, QVariant("[No data]"), Qt::EditRole); 284 | } 285 | } 286 | } 287 | 288 | ui->tvMain->expand(ui->tvMain->model()->index(position, 0, parent)); 289 | } 290 | -------------------------------------------------------------------------------- /WinMain.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMAIN_H 2 | #define WINMAIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "OpenMWConfigInterface.h" 16 | #include "SettingsInterface.h" 17 | #include "TreeModModel.h" 18 | #include "TreeModItem.h" 19 | 20 | namespace Ui { 21 | class WinMain; 22 | } 23 | 24 | class WinMain : public QMainWindow 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | explicit WinMain(QWidget *parent = 0); 30 | ~WinMain(); 31 | 32 | public slots: 33 | void actAddData(); 34 | void actAddChildData(); 35 | void actDeleteData(); 36 | void actContextMenuDataTree(const QPoint& pos); 37 | void actContextMenuDataTreeOpenFolder(); 38 | void actContextMenuDataTreeHeader(const QPoint& pos); 39 | 40 | void actContextMenuDataTreeHeaderTriggered(QAction* action); 41 | 42 | protected: 43 | void dragEnterEvent(QDragEnterEvent* event) Q_DECL_OVERRIDE; 44 | void dragMoveEvent(QDragMoveEvent* event) Q_DECL_OVERRIDE; 45 | void dropEvent(QDropEvent* event) Q_DECL_OVERRIDE; 46 | 47 | private: 48 | /** Open a file-chooser to locate config folder manually. */ 49 | QString locateConfigFolder(); 50 | void addNewData(QAbstractItemModel* model, const QModelIndex& parent, int position, const QFileInfo& target); 51 | 52 | Ui::WinMain *ui; 53 | 54 | SettingsInterface* settings; 55 | OpenMWConfigInterface* openMWConfig; 56 | }; 57 | 58 | #endif // WINMAIN_H 59 | -------------------------------------------------------------------------------- /WinMain.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinMain 4 | 5 | 6 | 7 | 0 8 | 0 9 | 678 10 | 513 11 | 12 | 13 | 14 | true 15 | 16 | 17 | OpenMW Mod Manager 18 | 19 | 20 | 21 | 22 | 23 | 24 | Qt::CustomContextMenu 25 | 26 | 27 | true 28 | 29 | 30 | true 31 | 32 | 33 | QAbstractItemView::InternalMove 34 | 35 | 36 | Qt::MoveAction 37 | 38 | 39 | true 40 | 41 | 42 | QAbstractItemView::SingleSelection 43 | 44 | 45 | 15 46 | 47 | 48 | true 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 0 62 | 0 63 | 678 64 | 21 65 | 66 | 67 | 68 | 69 | File 70 | 71 | 72 | 73 | 74 | 75 | Content 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Add Data 86 | 87 | 88 | 89 | 90 | Delete Data 91 | 92 | 93 | 94 | 95 | Add Child Data 96 | 97 | 98 | Insert a child data point 99 | 100 | 101 | 102 | 103 | Quit 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | actionAddData 112 | triggered() 113 | WinMain 114 | actAddData() 115 | 116 | 117 | -1 118 | -1 119 | 120 | 121 | 338 122 | 256 123 | 124 | 125 | 126 | 127 | actionDeleteData 128 | triggered() 129 | WinMain 130 | actDeleteData() 131 | 132 | 133 | -1 134 | -1 135 | 136 | 137 | 338 138 | 256 139 | 140 | 141 | 142 | 143 | actionAddChildData 144 | triggered() 145 | WinMain 146 | actAddChildData() 147 | 148 | 149 | -1 150 | -1 151 | 152 | 153 | 338 154 | 256 155 | 156 | 157 | 158 | 159 | actionQuit 160 | triggered() 161 | WinMain 162 | close() 163 | 164 | 165 | -1 166 | -1 167 | 168 | 169 | 338 170 | 256 171 | 172 | 173 | 174 | 175 | 176 | actAddData() 177 | actDeleteData() 178 | actAddChildData() 179 | actContextMenuDataTree() 180 | 181 | 182 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "WinMain.h" 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | WinMain w; 8 | w.show(); 9 | 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | THIS PROJECT IS NOT MAINTAINED. Use Mod Organizer 2 and the OpenMW export plugin for it instead. 2 | 3 | # OpenMW Mod Manager 4 | 5 | This project aims to provide a GUI-driven tool for managing and detecting conflicts with mods for the open source re-implementation of *The Elder Scrolls III: Morrowind*, [OpenMW](https://openmw.org). 6 | 7 | ![screenshot](https://i.imgur.com/rpcfj9e.png) 8 | 9 | Key features include: 10 | 11 | * A simple user-interface that displays data repositories scattered across various locations. 12 | * The ability to disable/enable data repositories with a quick click. 13 | * The ability to quickly add data repositories through the native file system. 14 | * Recognition of mod sub-components for complicated data. 15 | * Conflict detection, to show how the order of data repositories matters. 16 | 17 | Planned features include: 18 | 19 | * Detailed conflict reporting. 20 | * Enabling/disabling content without using the OpenMW launcher. 21 | * Enabling/disabling BSAs without using the OpenMW launcher. 22 | * Updating conflict detection to compare to BSAs. 23 | * Interfaces to other tools. 24 | 25 | This tool was inspired by [Wrye Mash](http://www.uesp.net/wiki/Tes3Mod:Wrye_Mash) and [Mod Organizer](https://github.com/TanninOne/modorganizer). 26 | 27 | ## Source 28 | 29 | This tool suffers from major spaghetti code, as it was written as an introduction to Qt. I'm sorry. 30 | 31 | To compile run `qmake` followed by `make`. 32 | --------------------------------------------------------------------------------