diff options
Diffstat (limited to 'src')
32 files changed, 1706 insertions, 2603 deletions
diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 8b6db27a..01c9c146 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -13,7 +13,7 @@ SUBDIRS = bsatk \ proxydll
hookdll.depends = shared
-organizer.depends = shared, uibase
+organizer.depends = shared, uibase, plugins
CONFIG(debug, debug|release) {
DESTDIR = outputd
diff --git a/src/baincomplexinstallerdialog.cpp b/src/baincomplexinstallerdialog.cpp deleted file mode 100644 index f1440ff3..00000000 --- a/src/baincomplexinstallerdialog.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "baincomplexinstallerdialog.h" -#include "textviewer.h" -#include "ui_baincomplexinstallerdialog.h" - -#include <QDir> - - -using namespace MOBase; - - -BainComplexInstallerDialog::BainComplexInstallerDialog(DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent) - : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), m_Manual(false) -{ - ui->setupUi(this); - - ui->nameEdit->setText(modName); - - for (DirectoryTree::const_node_iterator iter = tree->nodesBegin(); iter != tree->nodesEnd(); ++iter) { - const QString &dirName = (*iter)->getData().name; - if ((dirName.compare("fomod", Qt::CaseInsensitive) == 0) || - (dirName.startsWith("--"))) { - continue; - } - - QListWidgetItem *item = new QListWidgetItem(ui->optionsList); - item->setText((*iter)->getData().name); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(item->text().mid(0, 2) == "00" ? Qt::Checked : Qt::Unchecked); - item->setData(Qt::UserRole, qVariantFromValue((void*)(*iter))); - ui->optionsList->addItem(item); - } - - ui->packageBtn->setEnabled(hasPackageTXT); -} - - -BainComplexInstallerDialog::~BainComplexInstallerDialog() -{ - delete ui; -} - - -QString BainComplexInstallerDialog::getName() const -{ - return ui->nameEdit->text(); -} - - -void BainComplexInstallerDialog::moveTreeUp(DirectoryTree *target, DirectoryTree::Node *child) -{ - for (DirectoryTree::const_node_iterator iter = child->nodesBegin(); - iter != child->nodesEnd();) { - target->addNode(*iter, true); - iter = child->detach(iter); - } - - for (DirectoryTree::const_leaf_reverse_iterator iter = child->leafsRBegin(); - iter != child->leafsREnd(); ++iter) { - target->addLeaf(*iter); - } -} - - -DirectoryTree *BainComplexInstallerDialog::updateTree(DirectoryTree *tree) -{ - DirectoryTree *newTree = new DirectoryTree; - - for (DirectoryTree::const_node_reverse_iterator iter = tree->nodesRBegin(); - iter != tree->nodesREnd();) { - QList<QListWidgetItem*> items = ui->optionsList->findItems((*iter)->getData().name, Qt::MatchFixedString); - if ((items.count() == 1) && (items.at(0)->checkState() == Qt::Checked)) { - moveTreeUp(newTree, *iter); - } - iter = tree->erase(iter); - } - - return newTree; -} - - -void BainComplexInstallerDialog::on_okBtn_clicked() -{ - this->accept(); -} - - -void BainComplexInstallerDialog::on_cancelBtn_clicked() -{ - this->reject(); -} - - -void BainComplexInstallerDialog::on_manualBtn_clicked() -{ - m_Manual = true; - this->reject(); -} - -void BainComplexInstallerDialog::on_packageBtn_clicked() -{ - TextViewer viewer("package.txt", this); - viewer.setDescription(""); - viewer.addFile(QDir::tempPath().append("/package.txt"), false); - viewer.exec(); -} diff --git a/src/baincomplexinstallerdialog.h b/src/baincomplexinstallerdialog.h deleted file mode 100644 index 136a4e54..00000000 --- a/src/baincomplexinstallerdialog.h +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef BAINCOMPLEXINSTALLERDIALOG_H -#define BAINCOMPLEXINSTALLERDIALOG_H - - -#include "mytree.h" -#include "installdialog.h" -#include "tutorabledialog.h" - - -namespace Ui { -class BainComplexInstallerDialog; -} - - -/** - * @brief Dialog to choose from options offered by a (complex) bain package - **/ -class BainComplexInstallerDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - /** - * @brief Constructor - * - * @param tree the directory tree of the archive. The caller is resonsible to verify this actually qualifies as a bain installer - * @param modName proposed name for the mod. The dialog allows the user to change this - * @param hasPackageTXT set to true if a package.txt is available for this archive. The file has to be unpacked to QDir::tempPath before displaying the dialog! - * @param parent parent widget - **/ - explicit BainComplexInstallerDialog(MOBase::DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent); - ~BainComplexInstallerDialog(); - - /** - * @return bool true if the user requested the manual dialog - **/ - bool manualRequested() const { return m_Manual; } - - /** - * @return QString the (user-modified) name to be used for the mod - **/ - QString getName() const; - - /** - * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree. - * - * @note This call is destructive on the input tree! - * - * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?) - * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer! - **/ - MOBase::DirectoryTree *updateTree(MOBase::DirectoryTree *tree); - -private slots: - - void on_manualBtn_clicked(); - - void on_okBtn_clicked(); - - void on_cancelBtn_clicked(); - - void on_packageBtn_clicked(); - -private: - - void moveTreeUp(MOBase::DirectoryTree *target, MOBase::DirectoryTree::Node *child); - -private: - - Ui::BainComplexInstallerDialog *ui; - - bool m_Manual; - -}; - -#endif // BAINCOMPLEXINSTALLERDIALOG_H diff --git a/src/categories.cpp b/src/categories.cpp index f8bc3530..62ba3ca5 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QFile> #include <QDir> #include <QList> +#include <QCoreApplication> using namespace MOBase; @@ -34,11 +35,17 @@ using namespace MOShared; CategoryFactory* CategoryFactory::s_Instance = NULL; +QString CategoryFactory::categoriesFilePath() +{ + return QCoreApplication::applicationDirPath() + "/categories.dat"; +} + + CategoryFactory::CategoryFactory() { reset(); - QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat")); + QFile categoryFile(categoriesFilePath()); if (!categoryFile.open(QIODevice::ReadOnly)) { loadDefaultCategories(); @@ -120,7 +127,7 @@ void CategoryFactory::setParents() void CategoryFactory::saveCategories() { - QFile categoryFile(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/categories.dat")); + QFile categoryFile(categoriesFilePath()); if (!categoryFile.open(QIODevice::WriteOnly)) { reportError(QObject::tr("Failed to save custom categories")); @@ -144,6 +151,18 @@ void CategoryFactory::saveCategories() } +unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter) +{ + unsigned int result = 0; + for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if (filter(*iter)) { + ++result; + } + } + return result; +} + + void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) { int index = m_Categories.size(); diff --git a/src/categories.h b/src/categories.h index e6978259..8ec573b2 100644 --- a/src/categories.h +++ b/src/categories.h @@ -17,164 +17,177 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef CATEGORIES_H
-#define CATEGORIES_H
-
-
-#include <QString>
-#include <vector>
-#include <map>
-
-
-/**
- * @brief Manage the available mod categories
- * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories,
- * optimized to where the request comes from. Therefore be very careful which of the two you have available
- **/
-class CategoryFactory {
-
- friend class CategoriesDialog;
-
-public:
-
- static const int CATEGORY_NONE = 0;
-
- static const int CATEGORY_SPECIAL_FIRST = 10000;
- static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST;
- static const int CATEGORY_SPECIAL_UNCHECKED = 10001;
- static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002;
- static const int CATEGORY_SPECIAL_NOCATEGORY = 10003;
- static const int CATEGORY_SPECIAL_CONFLICT = 10004;
-
-public:
-
- /**
- * @brief reset the list of categories
- **/
- void reset();
-
- /**
- * @brief save the categories to the categories.dat file
- **/
- void saveCategories();
-
- /**
- * @brief retrieve the number of available categories
- *
- * @return unsigned int number of categories
- **/
- unsigned numCategories() const { return m_Categories.size(); }
-
- /**
- * @brief get the id of the parent category
- *
- * @param index the index to look up
- * @return int id of the parent category
- **/
- int getParentID(unsigned int index) const;
-
- /**
- * @brief determine if a category exists (by id)
- *
- * @param id the id to check for existance
- * @return true if the category exists, false otherwise
- **/
- bool categoryExists(int id) const;
-
- /**
- * @brief test if a category is child of a second one
- * @param id the presumed child id
- * @param parentID the parent id to test for
- * @return true if id is a child of parentID
- **/
- bool isDecendantOf(int id, int parentID) const;
-
- /**
- * @brief test if the specified category has child categories
- *
- * @param index index of the category to look up
- * @return bool true if the category has child categories
- **/
- bool hasChildren(unsigned int index) const;
-
- /**
- * @brief retrieve the name of a category
- *
- * @param index index of the category to look up
- * @return QString name of the category
- **/
- QString getCategoryName(unsigned int index) const;
-
- /**
- * @brief look up the id of a category by its index
- *
- * @param index index of the category to look up
- * @return int id of the category
- **/
- int getCategoryID(unsigned int index) const;
-
- /**
- * @brief look up the index of a category by its id
- *
- * @param id index of the category to look up
- * @return unsigned int index of the category
- **/
- int getCategoryIndex(int ID) const;
-
- /**
- * @brief retrieve the index of a category by its nexus id
- *
- * @param nexusID nexus id of the category to look up
- * @return unsigned int index of the category or 0 if no category matches
- **/
- unsigned int resolveNexusID(int nexusID) const;
-
-public:
-
- /**
- * @brief retrieve a reference to the singleton instance
- *
- * @return the reference to the singleton
- **/
- static CategoryFactory &instance();
-
-private:
-
- struct Category {
- Category(int sortValue, int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
- : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false),
- m_NexusIDs(nexusIDs), m_ParentID(parentID) {}
- int m_SortValue;
- int m_ID;
- int m_ParentID;
- bool m_HasChildren;
- QString m_Name;
- std::vector<int> m_NexusIDs;
-
- friend bool operator<(const Category &LHS, const Category &RHS) {
- return LHS.m_SortValue < RHS.m_SortValue;
- }
- };
-
-private:
-
- CategoryFactory();
-
- void loadDefaultCategories();
-
- void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID);
-
- void setParents();
-
-private:
-
- static CategoryFactory *s_Instance;
-
- std::vector<Category> m_Categories;
- std::map<int, unsigned int> m_IDMap;
- std::map<int, unsigned int> m_NexusMap;
-
-};
-
-
-#endif // CATEGORIES_H
+#ifndef CATEGORIES_H +#define CATEGORIES_H + + +#include <QString> +#include <vector> +#include <map> +#include <functional> + + +/** + * @brief Manage the available mod categories + * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, + * optimized to where the request comes from. Therefore be very careful which of the two you have available + **/ +class CategoryFactory { + + friend class CategoriesDialog; + +public: + + static const int CATEGORY_NONE = 0; + + static const int CATEGORY_SPECIAL_FIRST = 10000; + static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; + static const int CATEGORY_SPECIAL_UNCHECKED = 10001; + static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; + static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; + static const int CATEGORY_SPECIAL_CONFLICT = 10004; + +public: + + struct Category { + Category(int sortValue, int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), + m_NexusIDs(nexusIDs), m_ParentID(parentID) {} + int m_SortValue; + int m_ID; + int m_ParentID; + bool m_HasChildren; + QString m_Name; + std::vector<int> m_NexusIDs; + + friend bool operator<(const Category &LHS, const Category &RHS) { + return LHS.m_SortValue < RHS.m_SortValue; + } + }; + +public: + + /** + * @brief reset the list of categories + **/ + void reset(); + + /** + * @brief save the categories to the categories.dat file + **/ + void saveCategories(); + + /** + * @brief retrieve the number of available categories + * + * @return unsigned int number of categories + **/ + unsigned numCategories() const { return m_Categories.size(); } + + /** + * @brief count all categories that match a specified filter + * @param filter the filter to test + * @return number of matching categories + */ + unsigned int countCategories(std::tr1::function<bool (const Category &category)> filter); + + /** + * @brief get the id of the parent category + * + * @param index the index to look up + * @return int id of the parent category + **/ + int getParentID(unsigned int index) const; + + /** + * @brief determine if a category exists (by id) + * + * @param id the id to check for existance + * @return true if the category exists, false otherwise + **/ + bool categoryExists(int id) const; + + /** + * @brief test if a category is child of a second one + * @param id the presumed child id + * @param parentID the parent id to test for + * @return true if id is a child of parentID + **/ + bool isDecendantOf(int id, int parentID) const; + + /** + * @brief test if the specified category has child categories + * + * @param index index of the category to look up + * @return bool true if the category has child categories + **/ + bool hasChildren(unsigned int index) const; + + /** + * @brief retrieve the name of a category + * + * @param index index of the category to look up + * @return QString name of the category + **/ + QString getCategoryName(unsigned int index) const; + + /** + * @brief look up the id of a category by its index + * + * @param index index of the category to look up + * @return int id of the category + **/ + int getCategoryID(unsigned int index) const; + + /** + * @brief look up the index of a category by its id + * + * @param id index of the category to look up + * @return unsigned int index of the category + **/ + int getCategoryIndex(int ID) const; + + /** + * @brief retrieve the index of a category by its nexus id + * + * @param nexusID nexus id of the category to look up + * @return unsigned int index of the category or 0 if no category matches + **/ + unsigned int resolveNexusID(int nexusID) const; + +public: + + /** + * @brief retrieve a reference to the singleton instance + * + * @return the reference to the singleton + **/ + static CategoryFactory &instance(); + + /** + * @return path to the file that contains the categories list + */ + static QString categoriesFilePath(); + +private: + + CategoryFactory(); + + void loadDefaultCategories(); + + void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID); + + void setParents(); + +private: + + static CategoryFactory *s_Instance; + + std::vector<Category> m_Categories; + std::map<int, unsigned int> m_IDMap; + std::map<int, unsigned int> m_NexusMap; + +}; + + +#endif // CATEGORIES_H diff --git a/src/fomodinstallerdialog.cpp b/src/fomodinstallerdialog.cpp deleted file mode 100644 index 97476092..00000000 --- a/src/fomodinstallerdialog.cpp +++ /dev/null @@ -1,959 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "fomodinstallerdialog.h" -#include "report.h" -#include "utility.h" -#include "ui_fomodinstallerdialog.h" - -#include <QFile> -#include <QDir> -#include <QImage> -#include <QCheckBox> -#include <QRadioButton> -#include <QScrollArea> -#include <Shellapi.h> - - -using namespace MOBase; - - -bool ControlsAscending(QAbstractButton *LHS, QAbstractButton *RHS) -{ - return LHS->text() < RHS->text(); -} - - -bool ControlsDescending(QAbstractButton *LHS, QAbstractButton *RHS) -{ - return LHS->text() > RHS->text(); -} - - -bool PagesAscending(QGroupBox *LHS, QGroupBox *RHS) -{ - return LHS->title() < RHS->title(); -} - - -bool PagesDescending(QGroupBox *LHS, QGroupBox *RHS) -{ - return LHS->title() > RHS->title(); -} - - -FomodInstallerDialog::FomodInstallerDialog(const QString &modName, bool nameWasGuessed, const QString &fomodPath, QWidget *parent) - : QDialog(parent), ui(new Ui::FomodInstallerDialog), m_NameWasGuessed(nameWasGuessed), m_FomodPath(fomodPath), m_Manual(false) -{ - ui->setupUi(this); - ui->nameEdit->setText(modName); -} - -FomodInstallerDialog::~FomodInstallerDialog() -{ - delete ui; -} - - -int FomodInstallerDialog::bomOffset(const QByteArray &buffer) -{ - static const unsigned char BOM_UTF8[] = { 0xEF, 0xBB, 0xBF }; - static const unsigned char BOM_UTF16BE[] = { 0xFE, 0xFF }; - static const unsigned char BOM_UTF16LE[] = { 0xFF, 0xFE }; - - if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF8))) return 3; - if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16BE)) || - buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16LE))) return 2; - - return 0; -} - -#pragma message("implement module dependencies->file dependencies") - -void FomodInstallerDialog::initData() -{ - { // parse provided package information - QFile file(QDir::tempPath().append("/info.xml")); - if (file.open(QIODevice::ReadOnly)) { - // nmm allows files with wrong encoding and of course there are now files with broken - // so, let's do as nmm does and ignore the standard. yay - QByteArray header = file.readLine(); - if (strncmp(header.constData() + bomOffset(header), "<?", 2) != 0) { - // not a header, rewind - file.seek(0); - } - parseInfo(file.readAll()); - } - file.close(); - } - - QImage screenshot(QDir::tempPath().append("/screenshot.png")); - if (!screenshot.isNull()) { - screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width()); - ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot)); - } - - { // parse xml installer file - QFile file(QDir::tempPath().append("/ModuleConfig.xml")); - if (!file.open(QIODevice::ReadOnly)) { - throw MyException(tr("ModuleConfig.xml missing")); - } - // nmm allows files with wrong encoding and of course there are now files that are broken - QByteArray header = file.readLine(); - - if (strncmp(header.constData() + bomOffset(header), "<?", 2) != 0) { - // not a header, rewind - if (!file.seek(0)) { - qCritical("failed to rewind file"); - } - } - parseModuleConfig(file.readAll()); - file.close(); - } -} - - -QString FomodInstallerDialog::getName() const -{ - return ui->nameEdit->text(); -} - - -void FomodInstallerDialog::moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source) -{ - for (DirectoryTree::const_node_iterator iter = source->nodesBegin(); iter != source->nodesEnd();) { - target->addNode(*iter, true); - iter = source->detach(iter); - } - - for (DirectoryTree::const_leaf_reverse_iterator iter = source->leafsRBegin(); - iter != source->leafsREnd(); ++iter) { - target->addLeaf(*iter); - } -} - - -DirectoryTree::Node *FomodInstallerDialog::findNode(DirectoryTree::Node *node, const QString &path, bool create) -{ - if (path.length() == 0) { - return node; - } - -// static QRegExp pathSeparator("[/\\]"); - int pos = path.indexOf('\\'); - if (pos == -1) { - pos = path.indexOf('/'); - } - QString subPath = path; - if (pos > 0) { - subPath = path.mid(0, pos); - } - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - if ((*iter)->getData().name.compare(subPath, Qt::CaseInsensitive) == 0) { - if (pos <= 0) { - return *iter; - } else { - return findNode(*iter, path.mid(pos + 1), create); - } - } - } - if (create) { - DirectoryTree::Node *newNode = new DirectoryTree::Node; - newNode->setData(subPath); - node->addNode(newNode, false); - if (pos <= 0) { - return newNode; - } else { - return findNode(newNode, path.mid(pos + 1), create); - } - } else { - throw MyException(QString("%1 not found in archive").arg(path)); - } -} - -void FomodInstallerDialog::copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath, - DirectoryTree::Node *destinationTree, const QString &destinationPath) -{ - int sourceFileIndex = sourcePath.lastIndexOf('\\'); - if (sourceFileIndex == -1) { - sourceFileIndex = sourcePath.lastIndexOf('/'); - if (sourceFileIndex == -1) { - sourceFileIndex = 0; - } - } - DirectoryTree::Node *sourceNode = sourceFileIndex == 0 ? sourceTree : findNode(sourceTree, sourcePath.mid(0, sourceFileIndex), false); - - int destinationFileIndex = destinationPath.lastIndexOf('\\'); - if (destinationFileIndex == -1) { - destinationFileIndex = destinationPath.lastIndexOf('/'); - if (destinationFileIndex == -1) { - destinationFileIndex = 0; - } - } - - DirectoryTree::Node *destinationNode = - destinationFileIndex == 0 ? destinationTree - : findNode(destinationTree, destinationPath.mid(0, destinationFileIndex), true); - - QString sourceName = sourcePath.mid((sourceFileIndex != 0) ? sourceFileIndex + 1 : 0); - QString destinationName = (destinationFileIndex != 0) ? destinationPath.mid(destinationFileIndex + 1) : destinationPath; - if (destinationName.length() == 0) { - destinationName = sourceName; - } - - bool found = false; - for (DirectoryTree::const_leaf_reverse_iterator iter = sourceNode->leafsRBegin(); - iter != sourceNode->leafsREnd(); ++iter) { - if (iter->getName().compare(sourceName, Qt::CaseInsensitive) == 0) { - destinationNode->addLeaf(*iter); - found = true; - } - } - if (!found) { - qCritical("%s not found!", sourceName.toUtf8().constData()); - } -} - - -void dumpTree(DirectoryTree::Node *node, int indent) -{ - for (DirectoryTree::const_leaf_reverse_iterator iter = node->leafsRBegin(); - iter != node->leafsREnd(); ++iter) { - qDebug("%.*s%s", indent, " ", iter->getName().toUtf8().constData()); - } - - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - qDebug("%.*s-- %s", indent, " ", (*iter)->getData().name.toUtf8().constData()); - dumpTree(*iter, indent + 2); - } -} - - -bool FomodInstallerDialog::copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor) -{ - QString source = (m_FomodPath.length() != 0) ? m_FomodPath.mid(0).append("\\").append(descriptor->m_Source) - : descriptor->m_Source; - QString destination = descriptor->m_Destination; - try { - if (descriptor->m_IsFolder) { - DirectoryTree::Node *sourceNode = findNode(sourceTree, source, false); - DirectoryTree::Node *targetNode = findNode(destinationTree, destination, true); - moveTree(targetNode, sourceNode); - } else { - copyLeaf(sourceTree, source, destinationTree, destination); - } - return true; - } catch (const MyException &e) { - qCritical("failed to extract %s to %s: %s", - source.toUtf8().constData(), destination.toUtf8().constData(), e.what()); - return false; - } -} - - -DirectoryTree *FomodInstallerDialog::updateTree(DirectoryTree *tree) -{ - DirectoryTree *newTree = new DirectoryTree; - - for (std::vector<FileDescriptor*>::iterator iter = m_RequiredFiles.begin(); iter != m_RequiredFiles.end(); ++iter) { - copyFileIterator(tree, newTree, *iter); - } - - for (std::vector<ConditionalInstall>::iterator installIter = m_ConditionalInstalls.begin(); - installIter != m_ConditionalInstalls.end(); ++installIter) { - bool match = installIter->m_Operator == ConditionalInstall::OP_AND; - for (std::vector<Condition>::iterator conditionIter = installIter->m_Conditions.begin(); - conditionIter != installIter->m_Conditions.end(); ++conditionIter) { - bool conditionMatches = testCondition(ui->stepsStack->count(), conditionIter->m_Name, conditionIter->m_Value); - if (conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_OR)) { - match = true; - break; - } else if (!conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_AND)) { - match = false; - break; - } - } - if (match) { - for (std::vector<FileDescriptor*>::iterator fileIter = installIter->m_Files.begin(); - fileIter != installIter->m_Files.end(); ++fileIter) { - copyFileIterator(tree, newTree, *fileIter); - } - } - } - - QList<QAbstractButton*> choices = ui->stepsStack->findChildren<QAbstractButton*>("choice"); - foreach (QAbstractButton* choice, choices) { - if (choice->isChecked()) { - QVariantList fileList = choice->property("files").toList(); - foreach (QVariant fileVariant, fileList) { - copyFileIterator(tree, newTree, fileVariant.value<FileDescriptor*>()); - } - } - } - -// dumpTree(newTree, 0); - - return newTree; -} - - -void FomodInstallerDialog::highlightControl(QAbstractButton *button) -{ - QVariant screenshotName = button->property("screenshot"); - if (screenshotName.isValid()) { - QString screenshotFileName = screenshotName.toString(); - if (!screenshotFileName.isEmpty()) { - QString temp = QFileInfo(screenshotFileName).fileName(); - QImage screenshot(QDir::tempPath().append("/").append(temp)); - if (screenshot.isNull()) { - qWarning(">%s< is a null image", screenshotName.toString().toUtf8().constData()); - } - screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width()); - ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot)); - } else { - ui->screenshotLabel->setPixmap(QPixmap()); - } - } - ui->descriptionText->setText(button->property("description").toString()); -} - - -bool FomodInstallerDialog::eventFilter(QObject *object, QEvent *event) -{ - QAbstractButton *button = qobject_cast<QAbstractButton*>(object); - if ((button != NULL) && (event->type() == QEvent::HoverEnter)) { - highlightControl(button); - - } - return QDialog::eventFilter(object, event); -} - - -QString FomodInstallerDialog::readContent(QXmlStreamReader &reader) -{ - if (reader.readNext() == QXmlStreamReader::Characters) { - return reader.text().toString(); - } else { - return QString(); - } -} - - -void FomodInstallerDialog::parseInfo(const QByteArray &data) -{ - QXmlStreamReader reader(data); -/* while (reader.readNext() != QXmlStreamReader::StartDocument) {} - QTextDecoder *decoder = QTextCodec::codecForName(reader.documentEncoding().toLocal8Bit())->makeDecoder(QTextCodec::ConvertInvalidToNull); - QString test(decoder->toUnicode(data)); - qDebug("test: %d, %s", test.isNull(), test.toUtf8().constData()); - - qDebug(">%s<", reader.documentEncoding().toUtf8().constData());*/ - while (!reader.atEnd() && !reader.hasError()) { - switch (reader.readNext()) { - case QXmlStreamReader::StartElement: { - if (reader.name() == "Name") { - if (ui->nameEdit->text().isEmpty() || m_NameWasGuessed) { - ui->nameEdit->setText(readContent(reader)); - } - } else if (reader.name() == "Author") { - ui->authorLabel->setText(readContent(reader)); - } else if (reader.name() == "Version") { - ui->versionLabel->setText(readContent(reader)); - } else if (reader.name() == "Website") { - QString url = readContent(reader); - ui->websiteLabel->setText(tr("<a href=\"%1\">Link</a>").arg(url)); - ui->websiteLabel->setToolTip(url); - } - } break; - default: {} break; - } - } - if (reader.hasError()) { - throw MyException(tr("failed to parse info.xml: %1 (%2) (line %3, column %4)") - .arg(reader.errorString()) - .arg(reader.error()) - .arg(reader.lineNumber()) - .arg(reader.columnNumber())); - - } -} - - -FomodInstallerDialog::ItemOrder FomodInstallerDialog::getItemOrder(const QString &orderString) -{ - if (orderString == "Ascending") { - return ORDER_ASCENDING; - } else if (orderString == "Descending") { - return ORDER_DESCENDING; - } else if (orderString == "Explicit") { - return ORDER_EXPLICIT; - } else { - throw MyException(tr("unsupported order type %1").arg(orderString)); - } -} - - -FomodInstallerDialog::GroupType FomodInstallerDialog::getGroupType(const QString &typeString) -{ - if (typeString == "SelectAtLeastOne") { - return TYPE_SELECTATLEASTONE; - } else if (typeString == "SelectAtMostOne") { - return TYPE_SELECTATMOSTONE; - } else if (typeString == "SelectExactlyOne") { - return TYPE_SELECTEXACTLYONE; - } else if (typeString == "SelectAny") { - return TYPE_SELECTANY; - } else if (typeString == "SelectAll") { - return TYPE_SELECTALL; - } else { - throw MyException(tr("unsupported group type %1").arg(typeString)); - } -} - - -FomodInstallerDialog::PluginType FomodInstallerDialog::getPluginType(const QString &typeString) -{ - if (typeString == "Required") { - return FomodInstallerDialog::TYPE_REQUIRED; - } else if (typeString == "Optional") { - return FomodInstallerDialog::TYPE_OPTIONAL; - } else if (typeString == "Recommended") { - return FomodInstallerDialog::TYPE_RECOMMENDED; - } else if (typeString == "NotUsable") { - return FomodInstallerDialog::TYPE_NOTUSABLE; - } else if (typeString == "CouldBeUsable") { - return FomodInstallerDialog::TYPE_COULDBEUSABLE; - } else { - qCritical("invalid plugin type %s", typeString.toUtf8().constData()); - return FomodInstallerDialog::TYPE_OPTIONAL; - } -} - - -void FomodInstallerDialog::readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList) -{ - QStringRef openTag = reader.name(); - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == openTag))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if ((reader.name() == "folder") || - (reader.name() == "file")) { - QXmlStreamAttributes attributes = reader.attributes(); - FileDescriptor *file = new FileDescriptor(this); - file->m_Source = attributes.value("source").toString(); - file->m_Destination = attributes.hasAttribute("destination") ? attributes.value("destination").toString() - : file->m_Source; - file->m_Priority = attributes.hasAttribute("priority") ? attributes.value("priority").string()->toInt() - : 0; - file->m_IsFolder = reader.name() == "folder"; - file->m_InstallIfUsable = attributes.hasAttribute("installIfUsable") ? (attributes.value("installIfUsable").compare("true") == 0) - : false; - file->m_AlwaysInstall = attributes.hasAttribute("alwaysInstall") ? (attributes.value("alwaysInstall").compare("true") == 0) - : false; - - fileList.push_back(file); - } - } - } -} - - -void FomodInstallerDialog::readPluginType(QXmlStreamReader &reader, Plugin &plugin) -{ - plugin.m_Type = TYPE_OPTIONAL; - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "typeDescriptor"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "type") { - plugin.m_Type = getPluginType(reader.attributes().value("name").toString()); - } - } - } -} - - -void FomodInstallerDialog::readConditionFlags(QXmlStreamReader &reader, Plugin &plugin) -{ - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "conditionFlags"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "flag") { - QString name = reader.attributes().value("name").toString(); - plugin.m_Conditions.push_back(Condition(name, readContent(reader))); - } - } - } -} - - -bool FomodInstallerDialog::byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS) -{ - return LHS->m_Priority < RHS->m_Priority; -} - - -FomodInstallerDialog::Plugin FomodInstallerDialog::readPlugin(QXmlStreamReader &reader) -{ - Plugin result; - result.m_Name = reader.attributes().value("name").toString(); - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "plugin"))) { -// QXmlStreamReader::TokenType type = reader.tokenType(); -// QString name = reader.name().toUtf8(); - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "description") { - result.m_Description = readContent(reader).trimmed(); - } else if (reader.name() == "image") { - result.m_ImagePath = reader.attributes().value("path").toString(); - } else if (reader.name() == "typeDescriptor") { - readPluginType(reader, result); - } else if (reader.name() == "conditionFlags") { - readConditionFlags(reader, result); - } else if (reader.name() == "files") { - readFileList(reader, result.m_Files); - } - } - } - - std::sort(result.m_Files.begin(), result.m_Files.end(), byPriority); - - return result; -} - - -void FomodInstallerDialog::readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout) -{ - ItemOrder pluginOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString()) - : ORDER_ASCENDING; - bool first = true; - bool maySelectMore = true; - - std::vector<QAbstractButton*> controls; - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "plugins"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "plugin") { - Plugin plugin = readPlugin(reader); - QAbstractButton *newControl = NULL; - switch (groupType) { - case TYPE_SELECTATLEASTONE: - case TYPE_SELECTANY: { - newControl = new QCheckBox(plugin.m_Name); - } break; - case TYPE_SELECTATMOSTONE: { - newControl = new QRadioButton(plugin.m_Name); - } break; - case TYPE_SELECTEXACTLYONE: { - newControl = new QRadioButton(plugin.m_Name); - if (first) { - newControl->setChecked(true); - } - } break; - case TYPE_SELECTALL: { - newControl = new QCheckBox(plugin.m_Name); - newControl->setChecked(true); - newControl->setEnabled(false); - } break; - } - newControl->setObjectName("choice"); - switch (plugin.m_Type) { - case TYPE_REQUIRED: { - newControl->setChecked(true); - newControl->setEnabled(false); - newControl->setToolTip(tr("This component is required")); - } break; - case TYPE_RECOMMENDED: { - if (maySelectMore) { - newControl->setChecked(true); - } - newControl->setToolTip(tr("It is recommended you enable this component")); - if ((groupType == TYPE_SELECTATMOSTONE) || (groupType == TYPE_SELECTEXACTLYONE)) { - maySelectMore = false; - } - } break; - case TYPE_OPTIONAL: { - newControl->setToolTip(tr("Optional component")); - } break; - case TYPE_NOTUSABLE: { - newControl->setChecked(false); - newControl->setEnabled(false); - newControl->setToolTip(tr("This component is not usable in combination with other installed plugins")); - } break; - case TYPE_COULDBEUSABLE: { - newControl->setCheckable(true); - newControl->setIcon(QIcon(":/new/guiresources/resources/dialog-warning_16.png")); - newControl->setToolTip(tr("You may be experiencing instability in combination with other installed plugins")); - } break; - } - - newControl->setProperty("plugintype", plugin.m_Type); - newControl->setProperty("screenshot", plugin.m_ImagePath); - newControl->setProperty("description", plugin.m_Description); - QVariantList fileList; - for (std::vector<FileDescriptor*>::iterator iter = plugin.m_Files.begin(); iter != plugin.m_Files.end(); ++iter) { - fileList.append(qVariantFromValue(*iter)); - } - newControl->setProperty("files", fileList); - QVariantList conditionFlags; - for (std::vector<Condition>::const_iterator iter = plugin.m_Conditions.begin(); iter != plugin.m_Conditions.end(); ++iter) { - if (iter->m_Name.length() != 0) { - conditionFlags.append(qVariantFromValue(Condition(iter->m_Name, iter->m_Value))); - } - } - newControl->setProperty("conditionFlags", conditionFlags); - newControl->installEventFilter(this); - controls.push_back(newControl); - first = false; - } - } - } - - if (pluginOrder == ORDER_ASCENDING) { - std::sort(controls.begin(), controls.end(), ControlsAscending); - } else if (pluginOrder == ORDER_DESCENDING) { - std::sort(controls.begin(), controls.end(), ControlsDescending); - } - - for (std::vector<QAbstractButton*>::const_iterator iter = controls.begin(); iter != controls.end(); ++iter) { - layout->addWidget(*iter); - } - - if (groupType == TYPE_SELECTATMOSTONE) { - QRadioButton *newButton = new QRadioButton(tr("None")); - if (maySelectMore) { - newButton->setChecked(true); - } - layout->addWidget(newButton); - } -} - - -void FomodInstallerDialog::readGroup(QXmlStreamReader &reader, QLayout *layout) -{ - //FileGroup result; - QString name = reader.attributes().value("name").toString(); - GroupType type = getGroupType(reader.attributes().value("type").toString()); - - if (type == TYPE_SELECTATLEASTONE) { - QLabel *label = new QLabel(tr("Select one or more of these options:")); - layout->addWidget(label); - } - - QGroupBox *groupBox = new QGroupBox(name); - - QVBoxLayout *groupLayout = new QVBoxLayout; - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "group"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "plugins") { - readPlugins(reader, type, groupLayout); - } - } - } - - groupBox->setLayout(groupLayout); - layout->addWidget(groupBox); -} - - -void FomodInstallerDialog::readGroups(QXmlStreamReader &reader, QLayout *layout) -{ - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "optionalFileGroups"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "group") { - readGroup(reader, layout); - } - } - } -} - - -void FomodInstallerDialog::readVisible(QXmlStreamReader &reader, QVariantList &conditions) -{ - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "visible"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "flagDependency") { - Condition condition(reader.attributes().value("flag").toString(), - reader.attributes().value("value").toString()); - conditions.append(qVariantFromValue(condition)); - } - } - } -} - -QGroupBox *FomodInstallerDialog::readInstallerStep(QXmlStreamReader &reader) -{ - QString name = reader.attributes().value("name").toString(); - QGroupBox *page = new QGroupBox(name); - QVBoxLayout *pageLayout = new QVBoxLayout; - QScrollArea *scrollArea = new QScrollArea; - QFrame *scrolledArea = new QFrame; - QVBoxLayout *scrollLayout = new QVBoxLayout; - - QVariantList conditions; - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "installStep"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "optionalFileGroups") { - readGroups(reader, scrollLayout); - } else if (reader.name() == "visible") { - readVisible(reader, conditions); - } - } - } - if (conditions.length() != 0) { - page->setProperty("conditions", conditions); - } - - scrolledArea->setLayout(scrollLayout); - scrollArea->setWidget(scrolledArea); - scrollArea->setWidgetResizable(true); - pageLayout->addWidget(scrollArea); - page->setLayout(pageLayout); - return page; -} - - -void FomodInstallerDialog::readInstallerSteps(QXmlStreamReader &reader) -{ - ItemOrder stepOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString()) - : ORDER_ASCENDING; - - std::vector<QGroupBox*> pages; - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "installSteps"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "installStep") { - pages.push_back(readInstallerStep(reader)); - } - } - } - - if (stepOrder == ORDER_ASCENDING) { - std::sort(pages.begin(), pages.end(), PagesAscending); - } else if (stepOrder == ORDER_DESCENDING) { - std::sort(pages.begin(), pages.end(), PagesDescending); - } - - for (std::vector<QGroupBox*>::const_iterator iter = pages.begin(); iter != pages.end(); ++iter) { - ui->stepsStack->addWidget(*iter); - } -} - - -FomodInstallerDialog::ConditionalInstall FomodInstallerDialog::readConditionalPattern(QXmlStreamReader &reader) -{ - ConditionalInstall result; - result.m_Operator = ConditionalInstall::OP_AND; - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "pattern"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "dependencies") { - QStringRef dependencyOperator = reader.attributes().value("operator"); - if (dependencyOperator == "And") { - result.m_Operator = ConditionalInstall::OP_AND; - } else if (dependencyOperator == "Or") { - result.m_Operator = ConditionalInstall::OP_OR; - } // otherwise operator is not set (which we can ignore) or invalid (which we should report actually) - - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "dependencies"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "flagDependency") { - result.m_Conditions.push_back(Condition(reader.attributes().value("flag").toString(), - reader.attributes().value("value").toString())); - } - } - } - } else if (reader.name() == "files") { - readFileList(reader, result.m_Files); - } - } - } - return result; -} - - -void FomodInstallerDialog::readConditionalFileInstalls(QXmlStreamReader &reader) -{ - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "conditionalFileInstalls"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "patterns") { - while (!((reader.readNext() == QXmlStreamReader::EndElement) && - (reader.name() == "patterns"))) { - if (reader.tokenType() == QXmlStreamReader::StartElement) { - if (reader.name() == "pattern") { - m_ConditionalInstalls.push_back(readConditionalPattern(reader)); - } - } - } - } - } - } -} - - -void FomodInstallerDialog::parseModuleConfig(const QByteArray &data) -{ - QXmlStreamReader reader(data); - while (!reader.atEnd() && !reader.hasError()) { - switch (reader.readNext()) { - case QXmlStreamReader::StartElement: { - if (reader.name() == "installSteps") { - readInstallerSteps(reader); - } else if (reader.name() == "requiredInstallFiles") { - readFileList(reader, m_RequiredFiles); - } else if (reader.name() == "conditionalFileInstalls") { - readConditionalFileInstalls(reader); - } - } break; - default: {} break; - } - } - if (reader.hasError()) { - reportError(tr("failed to parse ModuleConfig.xml: %1 - %2").arg(reader.errorString()).arg(reader.lineNumber())); - } - activateCurrentPage(); -} - - -void FomodInstallerDialog::on_manualBtn_clicked() -{ - m_Manual = true; - this->reject(); -} - -void FomodInstallerDialog::on_cancelBtn_clicked() -{ - this->reject(); -} - - -void FomodInstallerDialog::on_websiteLabel_linkActivated(const QString &link) -{ - ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - - -void FomodInstallerDialog::activateCurrentPage() -{ - QList<QAbstractButton*> choices = ui->stepsStack->currentWidget()->findChildren<QAbstractButton*>("choice"); - if (choices.count() > 0) { - highlightControl(choices.at(0)); - } -} - - -bool FomodInstallerDialog::testCondition(int maxIndex, const QString &flag, const QString &value) -{ - // iterate through all set condition flags on all activated controls on all visible pages if one of them matches the condition - for (int i = 0; i < maxIndex; ++i) { - if (testVisible(i)) { - QWidget *page = ui->stepsStack->widget(i); - QList<QAbstractButton*> choices = page->findChildren<QAbstractButton*>("choice"); - foreach (QAbstractButton* choice, choices) { - if (choice->isChecked()) { - QVariant temp = choice->property("conditionFlags"); - if (temp.isValid()) { - QVariantList conditionFlags = temp.toList(); - for (QVariantList::const_iterator iter = conditionFlags.begin(); iter != conditionFlags.end(); ++iter) { - Condition condition = iter->value<Condition>(); - if ((condition.m_Name == flag) && (condition.m_Value == value)) { - return true; - } - } - } - } - } - } - } - return false; -} - - -bool FomodInstallerDialog::testVisible(int pageIndex) -{ - QWidget *page = ui->stepsStack->widget(pageIndex); - QVariant temp = page->property("conditions"); - if (temp.isValid()) { - QVariantList conditions = temp.toList(); - for (QVariantList::const_iterator iter = conditions.begin(); iter != conditions.end(); ++iter) { - Condition condition = iter->value<Condition>(); - if (!testCondition(pageIndex, condition.m_Name, condition.m_Value)) { - return false; - } - } - return true; - } else { - return true; - } -} - - -bool FomodInstallerDialog::nextPage() -{ - int index = ui->stepsStack->currentIndex() + 1; - while (index < ui->stepsStack->count()) { - if (testVisible(index)) { - ui->stepsStack->setCurrentIndex(index); - return true; - } - ++index; - } - // no more visible pages -> install - return false; -} - - -void FomodInstallerDialog::on_nextBtn_clicked() -{ - if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) { - this->accept(); - } else { - if (nextPage()) { - if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) { - ui->nextBtn->setText(tr("Install")); - } - ui->prevBtn->setEnabled(true); - activateCurrentPage(); - } else { - this->accept(); - } - } -} - -void FomodInstallerDialog::on_prevBtn_clicked() -{ - if (ui->stepsStack->currentIndex() != 0) { - ui->stepsStack->setCurrentIndex(ui->stepsStack->currentIndex() - 1); - ui->nextBtn->setText(tr("Next")); - } - if (ui->stepsStack->currentIndex() == 0) { - ui->prevBtn->setEnabled(false); - } - activateCurrentPage(); -} diff --git a/src/fomodinstallerdialog.h b/src/fomodinstallerdialog.h deleted file mode 100644 index be4e34b2..00000000 --- a/src/fomodinstallerdialog.h +++ /dev/null @@ -1,214 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef FOMODINSTALLERDIALOG_H -#define FOMODINSTALLERDIALOG_H - -#include "installdialog.h" -#include <QDialog> -#include <QAbstractButton> -#include <QXmlStreamReader> -#include <QGroupBox> -#include <QSharedPointer> - -namespace Ui { -class FomodInstallerDialog; -} - - -class Condition : public QObject { - Q_OBJECT -public: - Condition(QObject *parent = NULL) : QObject(parent) { } - Condition(const Condition &reference) : QObject(reference.parent()), m_Name(reference.m_Name), m_Value(reference.m_Value) { } - Condition(const QString &name, const QString &value) : QObject(), m_Name(name), m_Value(value) { } - QString m_Name; - QString m_Value; -private: - Condition &operator=(const Condition&); -}; - -Q_DECLARE_METATYPE(Condition) - - -class FileDescriptor : public QObject { - Q_OBJECT -public: - FileDescriptor(QObject *parent) - : QObject(parent), m_Source(), m_Destination(), m_Priority(0), m_IsFolder(false), m_AlwaysInstall(false), - m_InstallIfUsable(false) {} - FileDescriptor(const FileDescriptor &reference) - : QObject(reference.parent()), m_Source(reference.m_Source), m_Destination(reference.m_Destination), - m_Priority(reference.m_Priority), m_IsFolder(reference.m_IsFolder), m_AlwaysInstall(reference.m_AlwaysInstall), - m_InstallIfUsable(reference.m_InstallIfUsable) {} - QString m_Source; - QString m_Destination; - int m_Priority; - bool m_IsFolder; - bool m_AlwaysInstall; - bool m_InstallIfUsable; -private: - FileDescriptor &operator=(const FileDescriptor&); -}; - -Q_DECLARE_METATYPE(FileDescriptor*) - - -class FomodInstallerDialog : public QDialog -{ - Q_OBJECT - -public: - explicit FomodInstallerDialog(const QString &modName, bool nameWasGuessed, const QString &fomodPath, QWidget *parent = 0); - ~FomodInstallerDialog(); - - void initData(); - - /** - * @return bool true if the user requested the manual dialog - **/ - bool manualRequested() const { return m_Manual; } - - /** - * @return QString the (user-modified) name to be used for the mod - **/ - QString getName() const; - - /** - * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree. - * - * @note This call is destructive on the input tree! - * - * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?) - * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer! - **/ - MOBase::DirectoryTree *updateTree(MOBase::DirectoryTree *tree); - -protected: - - virtual bool eventFilter(QObject *object, QEvent *event); - -private slots: - - void on_cancelBtn_clicked(); - - void on_manualBtn_clicked(); - - void on_websiteLabel_linkActivated(const QString &link); - - void on_nextBtn_clicked(); - - void on_prevBtn_clicked(); - -private: - - enum ItemOrder { - ORDER_ASCENDING, - ORDER_DESCENDING, - ORDER_EXPLICIT - }; - - enum GroupType { - TYPE_SELECTATLEASTONE, - TYPE_SELECTATMOSTONE, - TYPE_SELECTEXACTLYONE, - TYPE_SELECTANY, - TYPE_SELECTALL - }; - - enum PluginType { - TYPE_REQUIRED, - TYPE_RECOMMENDED, - TYPE_OPTIONAL, - TYPE_NOTUSABLE, - TYPE_COULDBEUSABLE - }; - - struct Plugin { - QString m_Name; - QString m_Description; - QString m_ImagePath; - PluginType m_Type; - std::vector<Condition> m_Conditions; - std::vector<FileDescriptor*> m_Files; - }; - - struct ConditionalInstall { - enum { - OP_AND, - OP_OR - } m_Operator; - std::vector<Condition> m_Conditions; - std::vector<FileDescriptor*> m_Files; - }; - -private: - - static int bomOffset(const QByteArray &buffer); - - QString readContent(QXmlStreamReader &reader); - void parseInfo(const QByteArray &data); - - static ItemOrder getItemOrder(const QString &orderString); - static GroupType getGroupType(const QString &typeString); - static PluginType getPluginType(const QString &typeString); - static bool byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS); - - bool copyFileIterator(MOBase::DirectoryTree *sourceTree, MOBase::DirectoryTree *destinationTree, FileDescriptor *descriptor); - void readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList); - void readPluginType(QXmlStreamReader &reader, Plugin &plugin); - void readConditionFlags(QXmlStreamReader &reader, Plugin &plugin); - FomodInstallerDialog::Plugin readPlugin(QXmlStreamReader &reader); - void readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout); - void readGroup(QXmlStreamReader &reader, QLayout *layout); - void readGroups(QXmlStreamReader &reader, QLayout *layout); - void readVisible(QXmlStreamReader &reader, QVariantList &conditions); - QGroupBox *readInstallerStep(QXmlStreamReader &reader); - ConditionalInstall readConditionalPattern(QXmlStreamReader &reader); - void readConditionalFileInstalls(QXmlStreamReader &reader); - void readInstallerSteps(QXmlStreamReader &reader); - void parseModuleConfig(const QByteArray &data); - void highlightControl(QAbstractButton *button); - - bool testCondition(int maxIndex, const QString &flag, const QString &value); - bool testVisible(int pageIndex); - bool nextPage(); - void activateCurrentPage(); - void moveTree(MOBase::DirectoryTree::Node *target, MOBase::DirectoryTree::Node *source); - MOBase::DirectoryTree::Node *findNode(MOBase::DirectoryTree::Node *node, const QString &path, bool create); - void copyLeaf(MOBase::DirectoryTree::Node *sourceTree, const QString &sourcePath, - MOBase::DirectoryTree::Node *destinationTree, const QString &destinationPath); - -private: - - Ui::FomodInstallerDialog *ui; - - bool m_NameWasGuessed; - - QString m_FomodPath; - bool m_Manual; - -// ItemOrder m_StepOrder; -// std::vector<InstallationStep> m_Steps; - std::vector<FileDescriptor*> m_RequiredFiles; - std::vector<ConditionalInstall> m_ConditionalInstalls; - -}; - -#endif // FOMODINSTALLERDIALOG_H diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 859311fc..2540e1d5 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -17,61 +17,65 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#include "icondelegate.h"
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QPainter>
-
-
-IconDelegate::IconDelegate(QAbstractProxyModel *proxyModel, QObject *parent)
- : QStyledItemDelegate(parent), m_ProxyModel(proxyModel)
-{
-}
-
-
-QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const
-{
- switch (flag) {
- case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup");
- case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem");
- case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed");
- case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes");
- case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite");
- case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten");
- case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed");
- case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant");
- default: return QIcon();
- }
-}
-
-
-void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
-{
- QStyledItemDelegate::paint(painter, option, index);
- ModInfo::Ptr info = ModInfo::getByIndex(m_ProxyModel->mapToSource(index).row());
- std::vector<ModInfo::EFlag> flags = info->getFlags();
-
- int x = 4;
- painter->save();
- painter->translate(option.rect.topLeft());
- for (auto iter = flags.begin(); iter != flags.end(); ++iter) {
- QIcon temp = getFlagIcon(*iter);
- painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16)));
- x += 20;
- }
-
- painter->restore();
-}
-
-
-QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const
-{
- unsigned int index = m_ProxyModel->mapToSource(modelIndex).row();
- if (index < ModInfo::getNumMods()) {
- ModInfo::Ptr info = ModInfo::getByIndex(index);
- return QSize(info->getFlags().size() * 20, 20);
- } else {
- return QSize(1, 20);
- }
-}
-
+#include "icondelegate.h" +#include <QHBoxLayout> +#include <QLabel> +#include <QPainter> + + +IconDelegate::IconDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + + +QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return QIcon(":/MO/gui/emblem_backup"); + case ModInfo::FLAG_INVALID: return QIcon(":/MO/gui/emblem_problem"); + case ModInfo::FLAG_NOTENDORSED: return QIcon(":/MO/gui/emblem_notendorsed"); + case ModInfo::FLAG_NOTES: return QIcon(":/MO/gui/emblem_notes"); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return QIcon(":/MO/gui/emblem_conflict_overwrite"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return QIcon(":/MO/gui/emblem_conflict_overwritten"); + case ModInfo::FLAG_CONFLICT_MIXED: return QIcon(":/MO/gui/emblem_conflict_mixed"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return QIcon(":MO/gui/emblem_conflict_redundant"); + default: return QIcon(); + } +} + + +void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + QVariant modid = index.data(Qt::UserRole + 1); + if (!modid.isValid()) { + return; + } + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + std::vector<ModInfo::EFlag> flags = info->getFlags(); + + int x = 4; + painter->save(); + painter->translate(option.rect.topLeft()); + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + QIcon temp = getFlagIcon(*iter); + painter->drawPixmap(x, 2, 16, 16, temp.pixmap(QSize(16, 16))); + x += 20; + } + + painter->restore(); +} + + +QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const +{ + unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); + if (index < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(index); + return QSize(info->getFlags().size() * 20, 20); + } else { + return QSize(1, 20); + } +} + diff --git a/src/icondelegate.h b/src/icondelegate.h index de49d810..dd9f9dfc 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -17,35 +17,33 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef ICONDELEGATE_H
-#define ICONDELEGATE_H
-
-#include "modinfo.h"
-#include <QStyledItemDelegate>
-#include <QAbstractProxyModel>
-
-
-class IconDelegate : public QStyledItemDelegate
-{
- Q_OBJECT
-public:
-
- explicit IconDelegate(QAbstractProxyModel *proxyModel, QObject *parent = 0);
-
- virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
- virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
-signals:
-
-public slots:
-
-private:
-
- QIcon getFlagIcon(ModInfo::EFlag flag) const;
-
-private:
-
- QAbstractProxyModel *m_ProxyModel;
-
-};
-
-#endif // ICONDELEGATE_H
+#ifndef ICONDELEGATE_H +#define ICONDELEGATE_H + +#include "modinfo.h" +#include <QStyledItemDelegate> +#include <QAbstractProxyModel> + + +class IconDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + + explicit IconDelegate(QObject *parent = 0); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +signals: + +public slots: + +private: + + QIcon getFlagIcon(ModInfo::EFlag flag) const; + +private: + +}; + +#endif // ICONDELEGATE_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index d92cae15..96d3743f 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -20,10 +20,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "installationmanager.h" #include "utility.h" -#include "installdialog.h" -#include "simpleinstalldialog.h" -#include "baincomplexinstallerdialog.h" -#include "fomodinstallerdialog.h" + #include "report.h" #include "categories.h" #include "questionboxmemory.h" @@ -268,11 +265,11 @@ QStringList InstallationManager::extractFiles(const QStringList &filesOrig) return result; } -IPluginInstaller::EInstallResult InstallationManager::installArchive(const QString &modName, const QString &archiveName) +IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName) { - QString temp = modName; + GuessedValue<QString> temp(modName); bool iniTweaks; - if (install(archiveName, "", "modsdir", false, true, temp, iniTweaks)) { + if (install(archiveName, "modsdir", temp, iniTweaks)) { return IPluginInstaller::RESULT_SUCCESS; } else { return IPluginInstaller::RESULT_FAILED; @@ -484,7 +481,7 @@ QString InstallationManager::generateBackupName(const QString &directoryName) } -bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &modName) +bool InstallationManager::testOverwrite(const QString &modsDirectory, GuessedValue<QString> &modName) { QString targetDirectory = QDir::fromNativeSeparators(modsDirectory.mid(0).append("\\").append(modName)); @@ -503,7 +500,7 @@ bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &m QString name = QInputDialog::getText(m_ParentWidget, tr("Mod Name"), tr("Name"), QLineEdit::Normal, modName, &ok); if (ok && !name.isEmpty()) { - modName = name; + modName.update(name, GUESS_USER); if (!ensureValidModName(modName)) { return false; } @@ -547,45 +544,48 @@ bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &m return true; } - -void InstallationManager::fixModName(QString &name) +/* +bool InstallationManager::fixModName(QString &name) { -// name = name.remove("^[ ]*").trimmed(); - name = name.simplified(); - while (name.endsWith('.')) name.chop(1); - - name.replace(QRegExp("[<>:\"/\\|?*]"), ""); + QString temp = name.simplified(); + while (temp.endsWith('.')) temp.chop(1); + temp.replace(QRegExp("[<>:\"/\\|?*]"), ""); static QString invalidNames[] = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; for (int i = 0; i < sizeof(invalidNames) / sizeof(QString); ++i) { - if (name == invalidNames[i]) { - name = ""; + if (temp == invalidNames[i]) { + temp = ""; break; } } -} + if (temp.length() > 1) { + name = temp; + return true; + } else { + return false; + } +} +*/ -bool InstallationManager::ensureValidModName(QString &name) +bool InstallationManager::ensureValidModName(GuessedValue<QString> &name) { - fixModName(name); - - while (name.isEmpty()) { + while (name->isEmpty()) { bool ok; - name = QInputDialog::getText(m_ParentWidget, tr("Invalid name"), - tr("The name you entered is invalid, please enter a different one."), - QLineEdit::Normal, "", &ok); + name.update(QInputDialog::getText(m_ParentWidget, tr("Invalid name"), + tr("The name you entered is invalid, please enter a different one."), + QLineEdit::Normal, "", &ok), + GUESS_USER); if (!ok) { return false; } - fixModName(name); } return true; } -bool InstallationManager::doInstall(const QString &modsDirectory, QString &modName, int modID, +bool InstallationManager::doInstall(const QString &modsDirectory, GuessedValue<QString> &modName, int modID, const QString &version, const QString &newestVersion, int categoryID) { if (!ensureValidModName(modName)) { @@ -906,7 +906,7 @@ bool InstallationManager::installFomodExternal(const QString &fileName, const QS errorOccured = true; } } // if it's a directory and the target exists that isn't really a problem - // TODO: use shellRename? + if (!QFile::rename(fileInfo.absoluteFilePath(), newName)) { // moving doesn't work when merging if (!copyDir(fileInfo.absoluteFilePath(), newName, true)) { @@ -924,7 +924,7 @@ bool InstallationManager::installFomodExternal(const QString &fileName, const QS } QString dataDir = modDirectory.mid(0).append("/Data"); - if (!shellDelete(QStringList(dataDir), NULL)) { + if (!removeDir(dataDir)) { qCritical("failed to remove data directory from %s", dataDir.toUtf8().constData()); errorOccured = true; } @@ -953,8 +953,8 @@ bool InstallationManager::wasCancelled() } -bool InstallationManager::install(const QString &fileName, const QString &pluginsFileName, const QString &modsDirectory, - bool preferIntegrated, bool enableQuickInstall, QString &modName, bool &hasIniTweaks) +bool InstallationManager::install(const QString &fileName, const QString &modsDirectory, + GuessedValue<QString> &modName, bool &hasIniTweaks) { QFileInfo fileInfo(fileName); bool success = false; @@ -963,6 +963,8 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin return false; } + modName.setFilter(&fixDirectoryName); + // read out meta information from the download if available int modID = 0; QString version = ""; @@ -974,14 +976,9 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin if (QFile(metaName).exists()) { QSettings metaFile(metaName, QSettings::IniFormat); modID = metaFile.value("modID", 0).toInt(); - if (modName.isEmpty()) { - modName = metaFile.value("modName", "").toString(); - // it is possible we have a file-name but not the correct mod name. in this case, - // the stored mod name may be "\0" - if (modName.isEmpty() || (modName.length() < 2)) { - modName = metaFile.value("name", "").toString(); - } - } + modName.update(metaFile.value("name", "").toString(), GUESS_FALLBACK); + modName.update(metaFile.value("modName", "").toString(), GUESS_META); + version = metaFile.value("version", "").toString(); newestVersion = metaFile.value("newestVersion", "").toString(); unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt()); @@ -997,15 +994,10 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin } else if (modID != guessedModID) { qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); } - - if (modName.isEmpty()) { - modName = guessedModName; - nameGuessed = true; - } + modName.update(guessedModName, GUESS_GOOD); } - fixModName(modName); - qDebug("using mod name \"%s\" (id %d)", modName.toUtf8().constData(), modID); + qDebug("using mod name \"%s\" (id %d)", modName->toUtf8().constData(), modID); m_CurrentFile = fileInfo.fileName(); // open the archive and construct the directory tree the installers work on @@ -1014,7 +1006,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin DirectoryTree *filesTree = archiveOpen ? createFilesTree() : NULL; -/* IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; + IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { return LHS->priority() > RHS->priority(); @@ -1080,216 +1072,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin } reportError(tr("None of the available installer plugins were able to handle that archive")); - return false;*/ - - - - hasIniTweaks = false; - - - DirectoryTree::Node *baseNode = NULL; - bool manualRequest = false; - - if (!archiveOpen) { - reportError(tr("Failed to open \"%1\": %2").arg(QDir::toNativeSeparators(fileName)).arg(getErrorString(m_CurrentArchive->getLastError()))); - return false; - } - - // bundled fomod? - if ((baseNode == NULL) && !manualRequest) { - QStringList bundledFomods; - for (DirectoryTree::const_leaf_iterator fileIter = filesTree->leafsBegin(); fileIter != filesTree->leafsEnd(); ++fileIter) { - if (fileIter->getName().endsWith(".fomod", Qt::CaseInsensitive)) { - bundledFomods.append(fileIter->getName()); - } - } - QString bundledFomodInst; - if (bundledFomods.count() > 1) { - SelectionDialog selection(tr("This seems like a bundle of fomods, which one do you want to install?"), m_ParentWidget); - foreach (const QString &fomod, bundledFomods) { - selection.addChoice(fomod, fomod, QVariant()); - } - if (selection.exec() == QDialog::Accepted) { - bundledFomodInst = selection.getChoiceString(); - } else { - return false; - } - } else if (bundledFomods.count() == 1) { - bundledFomodInst = bundledFomods.at(0); - qDebug("archive contains fomod: %s", qPrintable(bundledFomodInst)); - } - if (!bundledFomodInst.isEmpty()) { - unpackSingleFile(bundledFomodInst); - m_CurrentArchive->close(); - return install(QDir::tempPath().append("/").append(bundledFomodInst), pluginsFileName, modsDirectory, preferIntegrated, - enableQuickInstall, modName, hasIniTweaks); - } - } - - // fomod installer? - if ((baseNode == NULL) && !manualRequest) { - QString fomodPath; - bool xmlInstaller = false; - if (checkFomodPackage(filesTree, fomodPath, xmlInstaller)) { - baseNode = filesTree; - bool nmmInstaller = checkNMMInstaller(); - - if (xmlInstaller || nmmInstaller) { - if (!xmlInstaller || (nmmInstaller && !preferIntegrated)) { - if (!ensureValidModName(modName) || - !testOverwrite(modsDirectory, modName)) { - return false; - } - - QString targetDirectory = QDir::fromNativeSeparators(modsDirectory.mid(0).append("\\").append(modName)); - - if (installFomodExternal(fileName, pluginsFileName, targetDirectory)) { - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - // overwrite settings only if they are actually are available or haven't been set before - if ((modID != 0) || !settingsFile.contains("modid")) { - settingsFile.setValue("modid", modID); - } - if (!settingsFile.contains("version") || - (!version.isEmpty() && - (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString())))) { - settingsFile.setValue("version", version); - } - if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { - settingsFile.setValue("newestVersion", newestVersion); - } - if (!settingsFile.contains("category")) { - settingsFile.setValue("category", QString::number(categoryID)); - } - settingsFile.setValue("installationFile", m_CurrentFile); - - success = true; - } - } else { - if (installFomodInternal(baseNode, fomodPath, modsDirectory, - modID, version, newestVersion, categoryID, - modName, nameGuessed, manualRequest)) { - success = true; - } - } - if (success) { - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - if (baseNode != filesTree) { - delete baseNode; - } - } else { - if (QuestionBoxMemory::query(m_ParentWidget, Settings::instance().directInterface(), "missingNCC", - tr("Installer missing"), - tr("This package contains a scripted installer. To use this installer " - "you need the optional \"NCC\"-package and the .net runtime. " - "Do you want to continue, treating this as a manual installer?"), - QDialogButtonBox::Yes | QDialogButtonBox::Cancel) == QMessageBox::Yes) { - manualRequest = true; - } else { - MessageDialog::showMessage(tr("Please install NCC"), m_ParentWidget); - } - } - } - } - - // simple installer? - if ((baseNode == NULL) && enableQuickInstall && !manualRequest) { - baseNode = getSimpleArchiveBase(filesTree); - if (baseNode != NULL) { - qDebug("treating as simple archive (%d)", baseNode->numLeafs()); - SimpleInstallDialog dialog(modName, m_ParentWidget); - if (dialog.exec() == QDialog::Accepted) { - mapToArchive(baseNode); - modName = dialog.getName(); - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - } else { - if (dialog.manualRequested()) { - manualRequest = true; - modName = dialog.getName(); - } - } - } - } - - // bain complex package? - if ((baseNode == NULL) && !manualRequest) { - if (checkBainPackage(filesTree)) { - bool hasPackageTXT = unpackPackageTXT(); - - baseNode = filesTree; - qDebug("treating as complex archive (%d)", filesTree->numNodes()); - BainComplexInstallerDialog dialog(filesTree, modName, hasPackageTXT, m_ParentWidget); - if (dialog.exec() == QDialog::Accepted) { - modName = dialog.getName(); - // create a new tree with the selected directories mapped to the - // base directory. This is destructive on the original tree - baseNode = dialog.updateTree(baseNode); - mapToArchive(baseNode); - - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - delete baseNode; - } else { - if (dialog.manualRequested()) { - manualRequest = true; - modName = dialog.getName(); - } - } - QFile::remove(QDir::tempPath().append("/package.txt")); - } - } - - // final option: manual installer - if ((baseNode == NULL) || manualRequest) { - qDebug("offering installation dialog"); - InstallDialog dialog(filesTree, modName, m_ParentWidget); - connect(&dialog, SIGNAL(openFile(QString)), this, SLOT(openFile(QString))); - if (dialog.exec() == QDialog::Accepted) { - modName = dialog.getModName(); - baseNode = dialog.getDataTree(); - mapToArchive(baseNode); - if (doInstall(modsDirectory, modName, modID, version, newestVersion, categoryID)) { - success = true; - - DirectoryTree::node_iterator iniTweakNode = baseNode->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != baseNode->nodesEnd()) && - ((*iniTweakNode)->numLeafs() != 0); - } - delete baseNode; // baseNode is a new tree, independent of filesTree - } - } - - delete filesTree; - m_CurrentArchive->close(); - - for (std::set<QString>::iterator iter = m_FilesToDelete.begin(); - iter != m_FilesToDelete.end(); ++iter) { - QFile(*iter).remove(); - } - m_FilesToDelete.clear(); - - for (std::set<QString>::iterator iter = m_TempFilesToDelete.begin(); - iter != m_TempFilesToDelete.end(); ++iter) { - QFile(QDir::tempPath().append("/").append(*iter)).remove(); - } - - m_TempFilesToDelete.clear(); - - return success; + return false; } diff --git a/src/installationmanager.h b/src/installationmanager.h index 7d864647..5bf64e94 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -20,10 +20,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H -#include "installdialog.h" #include <iinstallationmanager.h> #include <iplugininstaller.h> +#include <guessedvalue.h> #include <QObject> #define WIN32_LEAN_AND_MEAN @@ -61,12 +61,12 @@ public: * @brief install a mod from an archive * * @param fileName absolute file name of the archive to install + * @param modsDirectory directory to install mods to * @param modName suggested name of the mod. If this is empty (the default), a name will be guessed based on the filename. The user will always have a chance to rename the mod - * @param preferIntegrated if true, integrated installers are chosen over external installers * @return true if the archive was installed, false if installation failed or was refused * @exception std::exception an exception may be thrown if the archive can't be opened (maybe the format is invalid or the file is damaged) **/ - bool install(const QString &fileName, const QString &pluginsFileName, const QString &modsDirectory, bool preferIntegrated, bool enableQuickInstall, QString &modName, bool &hasIniTweaks); + bool install(const QString &fileName, const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, bool &hasIniTweaks); /** * @return true if the installation was canceled @@ -123,7 +123,7 @@ public: * @param archiveFile path to the archive to install * @return the installation result */ - virtual MOBase::IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName); + virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue<QString> &modName, const QString &archiveName); private: @@ -152,11 +152,11 @@ private: bool checkFomodPackage(MOBase::DirectoryTree *dataTree, QString &offset, bool &xmlInstaller); bool checkNMMInstaller(); - void fixModName(QString &name); +// static bool fixModName(QString &name); - bool testOverwrite(const QString &modsDirectory, QString &modName); + bool testOverwrite(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName); - bool doInstall(const QString &modsDirectory, QString &modName, + bool doInstall(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, int modID, const QString &version, const QString &newestVersion, int categoryID); bool installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory); @@ -165,7 +165,7 @@ private: int categoryID, QString &modName, bool nameGuessed, bool &manualRequest); QString generateBackupName(const QString &directoryName); - bool ensureValidModName(QString &name); + bool ensureValidModName(MOBase::GuessedValue<QString> &name); private slots: diff --git a/src/installdialog.cpp b/src/installdialog.cpp deleted file mode 100644 index ac508516..00000000 --- a/src/installdialog.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "installdialog.h" -#include "ui_installdialog.h" - -#include "report.h" -#include "utility.h" -#include "installationtester.h" - -#include <QMenu> -#include <QInputDialog> -#include <QMessageBox> - - -using namespace MOBase; - - -InstallDialog::InstallDialog(DirectoryTree *tree, const QString &modName, QWidget *parent) - : TutorableDialog("InstallDialog", parent), ui(new Ui::InstallDialog), - m_DataTree(tree), m_TreeRoot(NULL), m_DataRoot(NULL), m_TreeSelection(NULL), - m_Updating(false) -{ - ui->setupUi(this); - - QLineEdit *editName = findChild<QLineEdit*>("editName"); - editName->setText(modName); - - m_Tree = findChild<ArchiveTree*>("treeContent"); - - m_ProblemLabel = findChild<QLabel*>("problemLabel"); - - connect(m_Tree, SIGNAL(changed()), this, SLOT(treeChanged())); - - updatePreview(); -} - -InstallDialog::~InstallDialog() -{ - delete ui; -} - - -QString InstallDialog::getModName() const -{ - QLineEdit *editName = findChild<QLineEdit*>("editName"); - return editName->text(); -} - - -void InstallDialog::mapDataNode(DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const -{ - for (int i = 0; i < baseItem->childCount(); ++i) { - QTreeWidgetItem *currentItem = baseItem->child(i); - - if (currentItem->checkState(0) != Qt::Unchecked) { - if (currentItem->data(0, Qt::UserRole).isNull()) { - DirectoryTree::Node *newNode = new DirectoryTree::Node; - newNode->setData(currentItem->text(0)); - mapDataNode(newNode, currentItem); - node->addNode(newNode, true); - } else { - node->addLeaf(FileTreeInformation(currentItem->text(0), currentItem->data(0, Qt::UserRole).toInt())); - } - } - } -} - - -DirectoryTree *InstallDialog::getDataTree() const -{ - DirectoryTree *base = new DirectoryTree; - - mapDataNode(base, m_Tree->topLevelItem(0)); - return base; -} - - -QString InstallDialog::getFullPath(const DirectoryTree::Node *node) -{ - QString result(node->getData().name); - const DirectoryTree::Node *parent = node->getParent(); - while (parent != NULL) { - if (parent->getParent() != NULL) { - result.prepend("\\"); - } - result.prepend(parent->getData().name); - parent = parent->getParent(); - } - return result; -} - - -void InstallDialog::addDataToTree(DirectoryTree::Node *node, QTreeWidgetItem *treeItem) -{ - QString path = getFullPath(node); - - // add directory elements - for (DirectoryTree::node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - QStringList fields((*iter)->getData().name); - QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(treeItem, fields); - newNodeItem->setFlags(newNodeItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate); - newNodeItem->setCheckState(0, Qt::Checked); - addDataToTree(*iter, newNodeItem); - treeItem->addChild(newNodeItem); - } - - // add file elements - for (DirectoryTree::leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - QStringList fields(iter->getName()); - - QTreeWidgetItem *newLeafItem = new QTreeWidgetItem(treeItem, fields); - newLeafItem->setFlags(newLeafItem->flags() | Qt::ItemIsUserCheckable); - newLeafItem->setCheckState(0, Qt::Checked); - if (path.size() != 0) { - newLeafItem->setToolTip(0, path.mid(0).append("\\").append(iter->getName())); - } else { - newLeafItem->setToolTip(0, iter->getName()); - } - newLeafItem->setData(0, Qt::UserRole, iter->getIndex()); - - treeItem->addChild(newLeafItem); - } -} - - -void InstallDialog::updatePreview() -{ - m_Updating = true; - m_Tree->clear(); - delete m_TreeRoot; - - m_TreeRoot = new QTreeWidgetItem(m_Tree, QStringList("<data>")); - - addDataToTree(m_DataTree, m_TreeRoot); - - setDataRoot(m_TreeRoot); - m_Updating = false; - updateProblems(); -} - - -bool InstallDialog::testForProblem() -{ - bool ok = false; - QTreeWidgetItem *tlWidget = m_Tree->topLevelItem(0); - for (int i = 0; i < tlWidget->childCount(); ++i) { - QTreeWidgetItem *widget = tlWidget->child(i); - if (widget->checkState(0) == Qt::Unchecked) { - continue; - } - - if (widget->data(0, Qt::UserRole).isValid()) { - // file - if (InstallationTester::isTopLevelSuffix(widget->text(0))) { - ok = true; - break; - } - } else { - // directory - if (InstallationTester::isTopLevelDirectory(widget->text(0))) { - ok = true; - break; - } - } - } - return ok; -} - - -void InstallDialog::updateProblems() -{ - - if (testForProblem()) { - m_ProblemLabel->setText(tr("Looks good")); - m_ProblemLabel->setToolTip(tr("No problem detected")); - m_ProblemLabel->setStyleSheet("color: darkGreen;"); - } else { - m_ProblemLabel->setText(tr("No game data on top level")); - m_ProblemLabel->setToolTip(tr("There is no esp/esm file or asset directory (textures, meshes, interface, ...) " - "on the top level.")); - m_ProblemLabel->setStyleSheet("color: red;"); - } -} - - -void InstallDialog::setDataRoot(QTreeWidgetItem* root) -{ - if (root != NULL) { - m_DataRoot = root; - - m_Tree->takeTopLevelItem(0); - QTreeWidgetItem *temp = root->clone(); -// temp->setCheckState(0, Qt::Checked); - temp->setFlags(temp->flags() & ~(Qt::ItemIsUserCheckable | Qt::ItemIsTristate)); - temp->setText(0, "<data>"); - temp->setData(0, Qt::CheckStateRole, QVariant()); - m_Tree->addTopLevelItem(temp); - temp->setExpanded(true); - } else { - m_Tree->takeTopLevelItem(0); - } - updateProblems(); -} - - -void InstallDialog::use_as_data() -{ - if (m_TreeSelection != NULL) { - setDataRoot(m_TreeSelection); - m_TreeSelection = NULL; - } - updateProblems(); -} - - -void InstallDialog::unset_data() -{ - m_TreeSelection = NULL; - - setDataRoot(m_TreeRoot); - updateProblems(); -} - - -void InstallDialog::create_directory() -{ - bool ok = false; - QString result = QInputDialog::getText(this, tr("Enter a directory name"), tr("Name"), - QLineEdit::Normal, QString(), &ok); - if (ok && !result.isEmpty()) { - for (int i = 0; i < m_TreeSelection->childCount(); ++i) { - if (m_TreeSelection->child(i)->text(0) == result) { - reportError(tr("A directory with that name exists")); - return; - } - } - QStringList fields(result); - QTreeWidgetItem *newItem = new QTreeWidgetItem(m_TreeSelection, fields); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, Qt::Checked); - m_TreeSelection->addChild(newItem); - updateProblems(); - } -} - -void InstallDialog::open_file() -{ - emit openFile(m_TreeSelection->toolTip(0)); -} - - -void InstallDialog::on_treeContent_customContextMenuRequested(QPoint pos) -{ - m_TreeSelection = m_Tree->itemAt(pos); - if (m_TreeSelection == 0) { - return; - } - - QMenu menu; - menu.addAction(tr("Set data directory"), this, SLOT(use_as_data())); - menu.addAction(tr("Unset data directory"), this, SLOT(unset_data())); - if (m_TreeSelection->data(0, Qt::UserRole).isNull()) { - menu.addAction(tr("Create directory..."), this, SLOT(create_directory())); - } else { - menu.addAction(tr("&Open"), this, SLOT(open_file())); - } - menu.exec(m_Tree->mapToGlobal(pos)); -} - - -void InstallDialog::treeChanged() -{ - updateProblems(); -} - - -void InstallDialog::on_okButton_clicked() -{ - if (!testForProblem()) { - if (QMessageBox::question(this, tr("Continue?"), tr("This mod was probably NOT set up correctly, most likely it will NOT work. Really continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - return; - } - } - this->accept(); -} - -void InstallDialog::on_cancelButton_clicked() -{ - this->reject(); -} diff --git a/src/installdialog.h b/src/installdialog.h deleted file mode 100644 index e44d7eee..00000000 --- a/src/installdialog.h +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef INSTALLDIALOG_H -#define INSTALLDIALOG_H - -#include "mytree.h" -#include "archivetree.h" -#include "tutorabledialog.h" -#include <QDialog> -#include <QUuid> -#include <QTreeWidgetItem> -#include <QProgressDialog> -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include <archive.h> -#include <directorytree.h> - - -namespace Ui { - class InstallDialog; -} - - -/** - * a dialog presented to manually define how a mod is to be installed. It provides - * a tree view of the file contents that can modified directly - **/ -class InstallDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param tree tree structure describing the vanilla archive structure. The InstallDialog does NOT take custody of this pointer! - * @param modName name of the mod. The name can be modified through the dialog - * @param parent parent widget - **/ - explicit InstallDialog(MOBase::DirectoryTree *tree, const QString &modName, QWidget *parent = 0); - ~InstallDialog(); - - /** - * @brief retrieve the (modified) mod name - * - * @return updated mod name - **/ - QString getModName() const; - - /** - * @brief retrieve the user-modified directory structure - * - * @return modified data structure. This is a NEW datatree object for which the caller takes custody - **/ - MOBase::DirectoryTree *getDataTree() const; - -signals: - - void openFile(const QString fileName); - -private: - - void updatePreview(); - bool testForProblem(); - void updateProblems(); - - void setDataRoot(QTreeWidgetItem* root); - - void updateFileList(QTreeWidgetItem *item, QString targetName, FileData* const *fileData, size_t size) const; - - void updateCheckState(QTreeWidgetItem *item); -// void recursiveCheck(QTreeWidgetItem *item); -// void recursiveUncheck(QTreeWidgetItem *item); - - void addDataToTree(MOBase::DirectoryTree::Node *node, QTreeWidgetItem *treeItem); - - void mapDataNode(MOBase::DirectoryTree::Node *node, QTreeWidgetItem *baseItem) const; - - static QString getFullPath(const MOBase::DirectoryTree::Node *node); - -private slots: - - void on_treeContent_customContextMenuRequested(QPoint pos); - - void unset_data(); - void use_as_data(); - void create_directory(); - void open_file(); - - void treeChanged(); - - void on_cancelButton_clicked(); - - void on_okButton_clicked(); - -private: - Ui::InstallDialog *ui; - - MOBase::DirectoryTree *m_DataTree; - - ArchiveTree *m_Tree; - QLabel *m_ProblemLabel; - QTreeWidgetItem *m_TreeRoot; - QTreeWidgetItem *m_DataRoot; - QTreeWidgetItem *m_TreeSelection; - bool m_Updating; - -}; - -#endif // INSTALLDIALOG_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f2f3af65..2f6710cc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,9 +23,10 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "spawn.h" #include "report.h" #include "modlist.h" +#include "modlistsortproxy.h" +#include "qtgroupingproxy.h" #include "profile.h" #include "pluginlist.h" -#include "installdialog.h" #include "profilesdialog.h" #include "editexecutablesdialog.h" #include "categories.h" @@ -43,7 +44,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "syncoverwritedialog.h" #include "logbuffer.h" #include "downloadlistsortproxy.h" -#include "modlistsortproxy.h" #include "motddialog.h" #include "filedialogmemory.h" #include "questionboxmemory.h" @@ -84,6 +84,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QPluginLoader> #include <QRadioButton> #include <QDesktopWidget> +#include <QIdentityProxyModel> #include <boost/bind.hpp> #include <boost/foreach.hpp> #include <Psapi.h> @@ -99,7 +100,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), m_ExeName(exeName), m_OldProfileIndex(-1), m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(NexusInterface::instance()), + m_ModList(NexusInterface::instance()), m_ModListSortProxy(NULL), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), m_NexusDialog(NexusInterface::instance()->getAccessManager(), NULL), m_DownloadManager(NexusInterface::instance(), this), @@ -132,13 +133,17 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget // set up mod list m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); m_ModListSortProxy->setSourceModel(&m_ModList); - ui->modList->setModel(m_ModListSortProxy); + + QAbstractProxyModel *proxyModel = new QIdentityProxyModel(this); + proxyModel->setSourceModel(m_ModListSortProxy); + ui->modList->setModel(proxyModel); ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(m_ModListSortProxy)); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(ui->modList)); ui->modList->header()->installEventFilter(&m_ModList); ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); ui->modList->installEventFilter(&m_ModList); + // restoreState also seems to restores the resize mode from previous session, // I don't really like that #if QT_VERSION >= 0x50000 @@ -205,7 +210,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), m_ModListSortProxy, SLOT(invalidate())); + connect(&m_ModList, SIGNAL(modlist_changed(int)), this, SLOT(modlistChanged(int))); connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); @@ -269,6 +274,25 @@ void MainWindow::resizeEvent(QResizeEvent *event) } +static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) +{ + QModelIndex result = idx; + const QAbstractItemModel *model = idx.model(); + while (model != targetModel) { + if (model == NULL) { + return QModelIndex(); + } + const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(model); + if (proxyModel == NULL) { + return QModelIndex(); + } + result = proxyModel->mapToSource(result); + model = proxyModel->sourceModel(); + } + return result; +} + + void MainWindow::actionToToolButton(QAction *&sourceAction) { QToolButton *button = new QToolButton(ui->toolBar); @@ -1188,11 +1212,11 @@ void MainWindow::on_profileBox_currentIndexChanged(int index) } if (ui->profileBox->currentIndex() == 0) { - ui->profileBox->setCurrentIndex(previousIndex); ProfilesDialog(m_GamePath).exec(); while (!refreshProfiles()) { ProfilesDialog(m_GamePath).exec(); } + ui->profileBox->setCurrentIndex(previousIndex); } else { activateSelectedProfile(); } @@ -1625,6 +1649,9 @@ void MainWindow::readSettings() languageChange(m_Settings.language()); int selectedExecutable = settings.value("selected_executable").toInt(); setExecutableIndex(selectedExecutable); + + int grouping = settings.value("group_state").toInt(); + ui->groupCombo->setCurrentIndex(grouping); } @@ -1645,8 +1672,10 @@ void MainWindow::storeSettings() } settings.setValue("mod_list_state", ui->modList->header()->saveState()); - settings.setValue("plugin_list_state", ui->espList->header()->saveState()); + + settings.setValue("group_state", ui->groupCombo->currentIndex()); + settings.setValue("compact_downloads", ui->compactBox->isChecked()); settings.setValue("ask_for_nexuspw", m_AskForNexusPW); @@ -1712,14 +1741,13 @@ void MainWindow::on_tabWidget_currentChanged(int index) void MainWindow::installMod(const QString &fileName) { bool hasIniTweaks = false; - QString modName; + GuessedValue<QString> modName; m_CurrentProfile->writeModlistNow(); - if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(), - m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, m_Settings.getModDirectory(), modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), this); refreshModList(); - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, modName); + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -2220,6 +2248,12 @@ void MainWindow::modRenamed(const QString &oldName, const QString &newName) } +void MainWindow::modlistChanged(int) +{ + m_ModListSortProxy->invalidate(); +} + + QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID) { QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); @@ -2299,9 +2333,11 @@ void MainWindow::refreshFilters() void MainWindow::renameMod_clicked() { try { - QModelIndex treeIdx = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); +/* QModelIndex treeIdx = m_ModListGroupProxy->mapFromSource(m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0))); ui->modList->setCurrentIndex(treeIdx); - ui->modList->edit(treeIdx); + ui->modList->edit(treeIdx);*/ + + ui->modList->edit(ui->modList->currentIndex()); } catch (const std::exception &e) { reportError(tr("failed to rename mod: %1").arg(e.what())); } @@ -2364,7 +2400,8 @@ void MainWindow::removeMod_clicked() QString mods; QStringList modNames; foreach (QModelIndex idx, selection->selectedRows()) { - QString name = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(idx).row())->name(); +// QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name(); + QString name = idx.data().toString(); mods += "<li>" + name + "</li>"; modNames.append(name); } @@ -2647,9 +2684,19 @@ void MainWindow::cancelModListEditor() void MainWindow::on_modList_doubleClicked(const QModelIndex &index) { + if (!index.isValid()) { + return; + } +// QModelIndex sourceIdx = m_ModListGroupProxy->mapToSource(index); + QModelIndex sourceIdx = mapToModel(&m_ModList, index); + if (!sourceIdx.isValid()) { + return; + } + try { m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - displayModInformation(m_ModListSortProxy->mapToSource(index).row()); +// displayModInformation(m_ModListSortProxy->mapToSource(index).row()); + displayModInformation(sourceIdx.row()); // workaround to cancel the editor that might have opened because of // selection-click ui->modList->closePersistentEditor(index); @@ -2889,13 +2936,12 @@ void MainWindow::exportModListCSV() } } - void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) { try { QTreeView *modList = findChild<QTreeView*>("modList"); - m_ContextRow = m_ModListSortProxy->mapToSource(modList->indexAt(pos)).row(); + m_ContextRow = mapToModel(&m_ModList, modList->indexAt(pos)).row(); QMenu menu; menu.addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); @@ -2971,6 +3017,7 @@ void MainWindow::on_categoriesList_currentItemChanged(QTreeWidgetItem *current, int filter = current->data(0, Qt::UserRole).toInt(); m_ModListSortProxy->setCategoryFilter(filter); ui->currentCategoryLabel->setText(QString("(%1)").arg(current->text(0))); + ui->modList->reset(); } } @@ -3278,32 +3325,29 @@ void MainWindow::installDownload(int index) try { QString fileName = m_DownloadManager.getFilePath(index); int modID = m_DownloadManager.getModID(index); - QString modName; + GuessedValue<QString> modName; // see if there already are mods with the specified mod id if (modID != 0) { - ModInfo::Ptr modInfo = ModInfo::getByModID(modID, true); - if (!modInfo.isNull()) { - std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); + std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID); + for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { + std::vector<ModInfo::EFlag> flags = (*iter)->getFlags(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) { - modName = modInfo->name(); - modInfo->saveMeta(); + modName.update((*iter)->name(), GUESS_PRESET); + (*iter)->saveMeta(); } } - // TODO there may be multiple mods with the same id! -// modName = m_ModList.getModByModID(modID); } m_CurrentProfile->writeModlistNow(); bool hasIniTweaks = false; - if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(), - m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) { + if (m_InstallationManager.install(fileName, m_Settings.getModDirectory(), modName, hasIniTweaks)) { MessageDialog::showMessage(tr("Installation successful"), this); refreshModList(); - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, modName); + QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); if (posList.count() == 1) { ui->modList->scrollTo(posList.at(0)); } @@ -3702,13 +3746,13 @@ void MainWindow::nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant us ui->actionEndorseMO->setVisible(true); } } else { - ModInfo::Ptr info = ModInfo::getByModID(result["id"].toInt(), true); - if (!info.isNull()) { - info->setNewestVersion(VersionInfo(result["version"].toString())); - info->setNexusDescription(result["description"].toString()); + std::vector<ModInfo::Ptr> info = ModInfo::getByModID(result["id"].toInt()); + for (auto iter = info.begin(); iter != info.end(); ++iter) { + (*iter)->setNewestVersion(VersionInfo(result["version"].toString())); + (*iter)->setNexusDescription(result["description"].toString()); if (NexusInterface::instance()->getAccessManager()->loggedIn()) { // don't use endorsement info if we're not logged in - info->setIsEndorsed(result["voted_by_user"].toBool()); + (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); } } } @@ -4013,3 +4057,31 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) reportError(tr("Unknown exception")); } } + +void MainWindow::on_groupCombo_currentIndexChanged(int index) +{ + QAbstractProxyModel *newModel = NULL; + switch (index) { + case 1: { + newModel = new QtGroupingProxy(m_ModListSortProxy, QModelIndex(), ModList::COL_CATEGORY); + } break; + case 2: { + newModel = new QtGroupingProxy(m_ModListSortProxy, QModelIndex(), ModList::COL_MODID); + } break; + default: { + newModel = NULL; + } break; + } + + if (newModel != NULL) { + newModel->setSourceModel(m_ModListSortProxy); + connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); + connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); + connect(newModel, SIGNAL(expandItem(QModelIndex)), ui->modList, SLOT(expand(QModelIndex))); + + ui->modList->setModel(newModel); + } else { + ui->modList->setModel(m_ModListSortProxy); + } + // ui->modList->setSelectionMode(QAbstractItemView::ExtendedSelection); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 1e6eef44..91c2b17d 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -56,6 +56,8 @@ namespace Ui { } class QToolButton; +class ModListSortProxy; +class ModListGroupCategoriesProxy; class MainWindow : public QMainWindow, public MOBase::IOrganizer { @@ -239,6 +241,7 @@ private: ModList m_ModList; ModListSortProxy *m_ModListSortProxy; + PluginList m_PluginList; PluginListSortProxy *m_PluginListSortProxy; @@ -365,6 +368,7 @@ private slots: void addPrimaryCategoryCandidates(); void modDetailsUpdated(bool success); + void modlistChanged(int row); void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID); void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); @@ -435,7 +439,7 @@ private slots: // ui slots void on_espList_customContextMenuRequested(const QPoint &pos); void on_displayCategoriesBtn_toggled(bool checked); - + void on_groupCombo_currentIndexChanged(int index); }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 74f8c686..e9fa8db6 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -322,7 +322,7 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="0,0,1,1,1">
<item>
<widget class="QPushButton" name="displayCategoriesBtn">
<property name="maximumSize">
@@ -372,6 +372,25 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
+ <widget class="QComboBox" name="groupCombo">
+ <item>
+ <property name="text">
+ <string>No groups</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Categories</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Nexus IDs</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
<widget class="MOBase::LineEditClear" name="modFilterEdit">
<property name="placeholderText">
<string>Namefilter</string>
@@ -669,6 +688,9 @@ p, li { white-space: pre-wrap; } <property name="indentation">
<number>0</number>
</property>
+ <property name="uniformRowHeights">
+ <bool>true</bool>
+ </property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index aebab059..d84a6aa0 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -44,7 +44,7 @@ using namespace MOShared; std::vector<ModInfo::Ptr> ModInfo::s_Collection; std::map<QString, unsigned int> ModInfo::s_ModsByName; -std::map<int, unsigned int> ModInfo::s_ModsByModID; +std::map<int, std::vector<unsigned int> > ModInfo::s_ModsByModID; int ModInfo::s_NextID; QMutex ModInfo::s_Mutex(QMutex::Recursive); @@ -128,20 +128,21 @@ ModInfo::Ptr ModInfo::getByIndex(unsigned int index) } -ModInfo::Ptr ModInfo::getByModID(int modID, bool missingAcceptable) +std::vector<ModInfo::Ptr> ModInfo::getByModID(int modID) { QMutexLocker locker(&s_Mutex); - std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modID); + auto iter = s_ModsByModID.find(modID); if (iter == s_ModsByModID.end()) { - if (missingAcceptable) { - return ModInfo::Ptr(); - } else { - throw MyException(tr("invalid mod id %1").arg(modID)); - } + return std::vector<ModInfo::Ptr>(); + } + + std::vector<ModInfo::Ptr> result; + for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { + result.push_back(getByIndex(*idxIter)); } - return getByIndex(iter->second); + return result; } @@ -157,12 +158,11 @@ bool ModInfo::removeMod(unsigned int index) ModInfo::Ptr modInfo = s_Collection[index]; s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - //TODO this is a bit more complicated since multiple mods may have the - // same mod id but only one appears in the index - std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modInfo->getNexusID()) ; - if ((iter != s_ModsByModID.end()) && - (iter->second == index)) { - s_ModsByModID.erase(iter); + auto iter = s_ModsByModID.find(modInfo->getNexusID()); + if (iter != s_ModsByModID.end()) { + std::vector<unsigned int> indices = iter->second; + std::remove(indices.begin(), indices.end(), index); + s_ModsByModID[modInfo->getNexusID()] = indices; } // physically remove the mod directory @@ -234,12 +234,7 @@ void ModInfo::updateIndices() QString modName = s_Collection[i]->name(); int modID = s_Collection[i]->getNexusID(); s_ModsByName[modName] = i; - - // don't overwrite a modid-entry with a backup entry. This is a bit of a workaround - if ((s_ModsByModID.find(modID) == s_ModsByModID.end()) || - !backupRegEx.exactMatch(modName)) { - s_ModsByModID[modID] = i; - } + s_ModsByModID[modID].push_back(i); } } diff --git a/src/modinfo.h b/src/modinfo.h index 4c5cfd50..adf194dd 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -126,12 +126,11 @@ public: * @brief retrieve a ModInfo object based on its nexus mod id * * @param modID the nexus mod id to look up - * @param missingAcceptable if true, this function will return a null-pointer if no mod has the specified mod id, otherwise an exception is thrown * @return a reference counting pointer to the mod info * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, * this function will return only one of them **/ - static ModInfo::Ptr getByModID(int modID, bool missingAcceptable); + static std::vector<ModInfo::Ptr> getByModID(int modID); /** * @brief remove a mod by index @@ -396,14 +395,14 @@ public: bool categorySet(int categoryID) const; /** - * @brief retrive the whole list of categories this mod belongs to + * @brief retrive the whole list of categories (as ids) this mod belongs to * * @return list of categories **/ const std::set<int> &getCategories() const { return m_Categories; } /** - * @return the primary category of this mod + * @return id of the primary category of this mod */ int getPrimaryCategory() const { return m_PrimaryCategory; } @@ -465,7 +464,7 @@ protected: private: static QMutex s_Mutex; - static std::map<int, unsigned int> s_ModsByModID; + static std::map<int, std::vector<unsigned int> > s_ModsByModID; static int s_NextID; bool m_Valid; diff --git a/src/modlist.cpp b/src/modlist.cpp index 1d5db5a0..84e76b75 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -121,6 +121,7 @@ QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const QVariant ModList::data(const QModelIndex &modelIndex, int role) const { if (m_Profile == NULL) return QVariant(); + if (!modelIndex.isValid()) return QVariant(); unsigned int modIndex = modelIndex.row(); int column = modelIndex.column(); @@ -196,6 +197,8 @@ QVariant ModList::data(const QModelIndex &modelIndex, int role) const } } else if (role == Qt::UserRole) { return modInfo->getNexusID(); + } else if (role == Qt::UserRole + 1) { + return modIndex; } else if (role == Qt::FontRole) { QFont result; if (column == COL_NAME) { @@ -632,10 +635,20 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) (keyEvent->modifiers() == Qt::ControlModifier) && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model()); + const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model()); + const QSortFilterProxyModel *filterModel = NULL; + while ((filterModel == NULL) && (proxyModel != NULL)) { + filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel); + if (filterModel == NULL) { + proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel()); + } + } + if (filterModel == NULL) { + return true; + } int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { + if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) { diff = 1; } QModelIndexList rows = selectionModel->selectedRows(); @@ -645,8 +658,8 @@ bool ModList::eventFilter(QObject *obj, QEvent *event) } } foreach (QModelIndex idx, rows) { - if (proxyModel != NULL) { - idx = proxyModel->mapToSource(idx); + if (filterModel != NULL) { + idx = filterModel->mapToSource(idx); } int newPriority = m_Profile->getModPriority(idx.row()) + diff; if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) { diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a18174c6..071e0384 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -108,6 +108,7 @@ void ModListSortProxy::enableAllVisible() int modID = mapToSource(index(i, 0)).row(); m_Profile->setModEnabled(modID, true); } + invalidate(); } @@ -124,8 +125,8 @@ void ModListSortProxy::disableAllVisible() bool ModListSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { - int leftIndex = left.internalId(); - int rightIndex = right.internalId(); + int leftIndex = left.row(); + int rightIndex = right.row(); ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); @@ -202,7 +203,7 @@ bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UNCHECKED) && !enabled) || ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) && info->updateAvailable()) || ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY) && (info->getCategories().size() == 0)) || - ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags())))); + ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags())))); } diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index d1e3e238..8b86b222 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -54,6 +54,8 @@ public: bool filterMatches(ModInfo::Ptr info, bool enabled) const; +//virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; + public slots: void displayColumnSelection(const QPoint &pos); diff --git a/src/organizer.pro b/src/organizer.pro index 076bcde9..560394a4 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -18,7 +18,6 @@ SOURCES += \ syncoverwritedialog.cpp \
spawn.cpp \
singleinstance.cpp \
- simpleinstalldialog.cpp \
settingsdialog.cpp \
settings.cpp \
selfupdater.cpp \
@@ -53,10 +52,8 @@ SOURCES += \ lockeddialog.cpp \
loadmechanism.cpp \
json.cpp \
- installdialog.cpp \
installationmanager.cpp \
helper.cpp \
- fomodinstallerdialog.cpp \
finddialog.cpp \
filedialogmemory.cpp \
executableslist.cpp \
@@ -72,7 +69,6 @@ SOURCES += \ categoriesdialog.cpp \
categories.cpp \
bbcode.cpp \
- baincomplexinstallerdialog.cpp \
archivetree.cpp \
activatemodsdialog.cpp \
moapplication.cpp \
@@ -80,14 +76,14 @@ SOURCES += \ icondelegate.cpp \
gameinfoimpl.cpp \
csvbuilder.cpp \
- savetextasdialog.cpp
+ savetextasdialog.cpp \
+ qtgroupingproxy.cpp
HEADERS += \
transfersavesdialog.h \
syncoverwritedialog.h \
spawn.h \
singleinstance.h \
- simpleinstalldialog.h \
settingsdialog.h \
settings.h \
selfupdater.h \
@@ -121,10 +117,8 @@ HEADERS += \ lockeddialog.h \
loadmechanism.h \
json.h \
- installdialog.h \
installationmanager.h \
helper.h \
- fomodinstallerdialog.h \
finddialog.h \
filedialogmemory.h \
executableslist.h \
@@ -140,7 +134,6 @@ HEADERS += \ categoriesdialog.h \
categories.h \
bbcode.h \
- baincomplexinstallerdialog.h \
archivetree.h \
activatemodsdialog.h \
moapplication.h \
@@ -148,7 +141,8 @@ HEADERS += \ icondelegate.h \
gameinfoimpl.h \
csvbuilder.h \
- savetextasdialog.h
+ savetextasdialog.h \
+ qtgroupingproxy.h
FORMS += \
transfersavesdialog.ui \
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 93f511a4..dfac8ccb 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -340,7 +340,6 @@ void PluginList::readLockedOrderFrom(const QString &fileName) } } } - file.close(); } @@ -525,7 +524,6 @@ void PluginList::syncLoadOrder() void PluginList::refreshLoadOrder() { syncLoadOrder(); - // set priorities according to locked load order std::map<int, QString> lockedLoadOrder; std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), @@ -545,10 +543,16 @@ void PluginList::refreshLoadOrder() ++targetPrio; } } + + if (static_cast<size_t>(targetPrio) >= m_ESPs.size()) { + continue; + } + int temp = targetPrio; - if (m_ESPs[nameIter->second].m_Priority != temp) { - setPluginPriority(nameIter->second, temp); - m_ESPs[nameIter->second].m_LoadOrder = iter->first; + int index = nameIter->second; + if (m_ESPs[index].m_Priority != temp) { + setPluginPriority(index, temp); + m_ESPs[index].m_LoadOrder = iter->first; syncLoadOrder(); startSaveTime(); } diff --git a/src/profile.cpp b/src/profile.cpp index d67c6b8f..204a71aa 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -25,6 +25,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "modinfo.h" #include <utility.h> #include <util.h> +#include <error_report.h> #include <appconfig.h> #include <QMessageBox> #include <QApplication> @@ -50,14 +51,19 @@ Profile::Profile(const QString &name, bool useDefaultSettings) QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); QDir profileBase(profilesDir); - if (!profileBase.exists() || !profileBase.mkdir(name)) { - throw std::runtime_error(QObject::tr("failed to create %1").arg(name).toUtf8().constData()); + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(name)); } - QString fullPath = profilesDir + "/" + name; + + if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { + throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + fixedName; m_Directory = QDir(fullPath); QFile modList(m_Directory.filePath("modlist.txt")); if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(name); + profileBase.rmdir(fixedName); throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); } modList.close(); @@ -66,7 +72,7 @@ Profile::Profile(const QString &name, bool useDefaultSettings) GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); } catch (...) { // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(name)), NULL); + shellDelete(QStringList(profileBase.absoluteFilePath(fixedName)), NULL); throw; } refreshModStatus(); @@ -137,11 +143,16 @@ void Profile::writeModlistNow(bool onlyOnTimer) const if (onlyOnTimer && !m_SaveTimer->isActive()) return; m_SaveTimer->stop(); + if (!m_Directory.exists()) return; #pragma message("right now, this is doing unnecessary saves. Need a flag that says that mod priority, enabled-state or name of a mod has changed") QString fileName = getModlistFileName(); QFile file(fileName); - file.open(QIODevice::WriteOnly); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to open \"%1\" for writing").arg(fileName)); + return; + } + file.resize(0); file.write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); if (m_ModStatus.empty()) { @@ -172,9 +183,7 @@ void Profile::writeModlistNow(bool onlyOnTimer) const void Profile::createTweakedIniFile() { - QFileInfo iniInfo(getIniFileName()); - - QString tweakedIni = iniInfo.absolutePath() + "/initweaks.ini"; + QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); if (!shellDelete(QStringList(tweakedIni), NULL)) { reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); @@ -187,6 +196,28 @@ void Profile::createTweakedIniFile() mergeTweaks(modInfo, tweakedIni); } } + + + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (localSavesEnabled()) { + if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } + + if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", + AppConfig::localSavePlaceholder(), + ToWString(tweakedIni).c_str())) { + error = true; + } + } + + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } } @@ -427,6 +458,14 @@ Profile Profile::createFrom(const QString &name, const Profile &reference) } +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) +{ + QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory)); +} + + void Profile::copyFilesTo(QString &target) const { copyDir(m_Directory.absolutePath(), target, false); @@ -499,16 +538,6 @@ void Profile::mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const iter != iniTweaks.end(); ++iter) { mergeTweak(*iter, tweakedIni); } - - ::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str()); - - if (localSavesEnabled()) { - ::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str()); - - ::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - ToWString(tweakedIni).c_str()); - } } @@ -667,7 +696,6 @@ QString Profile::getPluginsFileName() const return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); } - QString Profile::getLoadOrderFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); @@ -701,3 +729,10 @@ QString Profile::getPath() const { return QDir::cleanPath(m_Directory.absolutePath()); } + +void Profile::rename(const QString &newName) +{ + QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); + profileDir.rename(getName(), newName); + m_Directory = profileDir.absoluteFilePath(newName); +} diff --git a/src/profile.h b/src/profile.h index 32647591..9c678a23 100644 --- a/src/profile.h +++ b/src/profile.h @@ -41,6 +41,10 @@ class Profile : public QObject public: + typedef boost::shared_ptr<Profile> Ptr; + +public: + /** * @brief default constructor * @todo This constructor initialised nothing, the resulting object is not usable @@ -76,14 +80,18 @@ public: bool exists() const; /** - * @brief copy constructor - * * @param name of the new profile * @param reference profile to copy from **/ static Profile createFrom(const QString &name, const Profile &reference); /** + * @param name of the new profile + * @param reference profile to copy from + **/ + static Profile *createPtrFrom(const QString &name, const Profile &reference); + + /** * @brief write out the modlist.txt **/ void writeModlist() const; @@ -174,6 +182,8 @@ public: **/ QString getPath() const; + void rename(const QString &newName); + /** * @brief create the ini file to be used by the game * @@ -302,7 +312,6 @@ private: unsigned int m_NumRegularMods; QTimer *m_SaveTimer; - }; Q_DECLARE_METATYPE(Profile) diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index ac2c534f..54e9147b 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -37,6 +37,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. using namespace MOBase; using namespace MOShared; +Q_DECLARE_METATYPE(Profile::Ptr) + ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) @@ -89,11 +91,11 @@ void ProfilesDialog::on_closeButton_clicked() void ProfilesDialog::addItem(const QString &name) { try { - QVariant temp; +// QVariant temp; QDir profileDir(name); - temp.setValue(Profile(profileDir)); +// temp.setValue(Profile(profileDir)); QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir)))); m_FailState = false; } catch (const std::exception& e) { reportError(tr("failed to create profile: %1").arg(e.what())); @@ -104,11 +106,11 @@ void ProfilesDialog::addItem(const QString &name) void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) { try { - QVariant temp; - temp.setValue(Profile(name, useDefaultSettings)); +// QVariant temp; +// temp.setValue(Profile(name, useDefaultSettings)); QListWidget *profilesList = findChild<QListWidget*>("profilesList"); QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings)))); profilesList->addItem(newItem); m_FailState = false; } catch (const std::exception&) { @@ -121,13 +123,13 @@ void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) void ProfilesDialog::createProfile(const QString &name, const Profile &reference) { try { - Profile newProfile = Profile::createFrom(name, reference); +// Profile newProfile = Profile::createFrom(name, reference); - QVariant temp; - temp.setValue(newProfile); +// QVariant temp; +// temp.setValue(newProfile); QListWidget *profilesList = findChild<QListWidget*>("profilesList"); QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, temp); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference)))); profilesList->addItem(newItem); m_FailState = false; } catch (const std::exception&) { @@ -160,8 +162,8 @@ void ProfilesDialog::on_copyProfileButton_clicked() QListWidget *profilesList = findChild<QListWidget*>("profilesList"); try { - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - createProfile(name, currentProfile); + const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + createProfile(name, *currentProfile); } catch (const std::exception &e) { reportError(tr("failed to copy profile: %1").arg(e.what())); } @@ -176,11 +178,11 @@ void ProfilesDialog::on_removeProfileButton_clicked() if (confirmBox.exec() == QMessageBox::Yes) { QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - const Profile ¤tProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile>(); + Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); // on destruction, the profile object would write the profile.ini file again, so // we have to get rid of the it before deleting the directory - QString profilePath = currentProfile.getPath(); + QString profilePath = currentProfile->getPath(); QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); if (item != NULL) { delete item; @@ -190,6 +192,33 @@ void ProfilesDialog::on_removeProfileButton_clicked() } +void ProfilesDialog::on_renameButton_clicked() +{ + Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + + bool valid = false; + QString name; + + while (!valid) { + bool ok = false; + name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), + QLineEdit::Normal, currentProfile->getName(), + &ok); + valid = fixDirectoryName(name); + if (!ok) { + return; + } + } + + ui->profilesList->currentItem()->setText(name); + currentProfile->rename(name); + +// QVariant temp; +// temp.setValue(currentProfile); +// ui->profilesList->currentItem()->setData(Qt::UserRole, temp); +} + + void ProfilesDialog::on_invalidationBox_stateChanged(int state) { QListWidget *profilesList = findChild<QListWidget*>("profilesList"); @@ -206,11 +235,11 @@ void ProfilesDialog::on_invalidationBox_stateChanged(int state) if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { return; } - const Profile ¤tProfile = currentItem->data(Qt::UserRole).value<Profile>(); + const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value<Profile::Ptr>(); if (state == Qt::Unchecked) { - currentProfile.deactivateInvalidation(); + currentProfile->deactivateInvalidation(); } else { - currentProfile.activateInvalidation(m_GamePath + "/data"); + currentProfile->activateInvalidation(m_GamePath + "/data"); } } catch (const std::exception &e) { reportError(tr("failed to change archive invalidation state: %1").arg(e.what())); @@ -225,18 +254,19 @@ void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton"); QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton"); QPushButton *transferButton = findChild<QPushButton*>("transferButton"); + QPushButton *renameButton = findChild<QPushButton*>("renameButton"); if (current != NULL) { - const Profile ¤tProfile = current->data(Qt::UserRole).value<Profile>(); + const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>(); try { bool invalidationSupported = false; invalidationBox->blockSignals(true); - invalidationBox->setChecked(currentProfile.invalidationActive(&invalidationSupported)); + invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); invalidationBox->setEnabled(invalidationSupported); invalidationBox->blockSignals(false); - bool localSaves = currentProfile.localSavesEnabled(); + bool localSaves = currentProfile->localSavesEnabled(); transferButton->setEnabled(localSaves); // prevent the stateChanged-event for the saves-box from triggering, otherwise it may think local saves // were disabled and delete the files/rename the dir @@ -246,23 +276,27 @@ void ProfilesDialog::on_profilesList_currentItemChanged(QListWidgetItem *current copyButton->setEnabled(true); removeButton->setEnabled(true); + renameButton->setEnabled(true); } catch (const std::exception& E) { reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); copyButton->setEnabled(false); removeButton->setEnabled(false); + renameButton->setEnabled(false); invalidationBox->setChecked(false); } } else { invalidationBox->setChecked(false); copyButton->setEnabled(false); removeButton->setEnabled(false); + renameButton->setEnabled(false); } } void ProfilesDialog::on_localSavesBox_stateChanged(int state) { - Profile currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - if (currentProfile.enableLocalSaves(state == Qt::Checked)) { + Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + + if (currentProfile->enableLocalSaves(state == Qt::Checked)) { ui->transferButton->setEnabled(state == Qt::Checked); } else { // revert checkbox-state @@ -272,7 +306,7 @@ void ProfilesDialog::on_localSavesBox_stateChanged(int state) void ProfilesDialog::on_transferButton_clicked() { - const Profile ¤tProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile>(); - TransferSavesDialog transferDialog(currentProfile, this); + const Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); + TransferSavesDialog transferDialog(*currentProfile, this); transferDialog.exec(); } diff --git a/src/profilesdialog.h b/src/profilesdialog.h index df08aa45..f864a7e0 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -83,6 +83,8 @@ private slots: void on_transferButton_clicked(); + void on_renameButton_clicked(); + private: Ui::ProfilesDialog *ui; QString m_GamePath; diff --git a/src/profilesdialog.ui b/src/profilesdialog.ui index a2983436..0c952877 100644 --- a/src/profilesdialog.ui +++ b/src/profilesdialog.ui @@ -115,6 +115,16 @@ p, li { white-space: pre-wrap; } </widget>
</item>
<item>
+ <widget class="QPushButton" name="renameButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="transferButton">
<property name="enabled">
<bool>false</bool>
diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp new file mode 100644 index 00000000..5c1e0275 --- /dev/null +++ b/src/qtgroupingproxy.cpp @@ -0,0 +1,897 @@ +/****************************************************************************************
+ * Copyright (c) 2007-2011 Bart Cerneels <bart.cerneels@kde.org> *
+ * *
+ * This program is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License as published by the Free Software *
+ * Foundation; either version 2 of the License, or (at your option) any later *
+ * version. *
+ * *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License along with *
+ * this program. If not, see <http://www.gnu.org/licenses/>. *
+ ****************************************************************************************/
+
+#include "QtGroupingProxy.h"
+
+#include <QDebug>
+#include <QIcon>
+#include <QInputDialog>
+
+/*!
+ \class QtGroupingProxy
+ \brief The QtGroupingProxy class will group source model rows by adding a new top tree-level.
+ The source model can be flat or tree organized, but only the original top level rows are used
+ for determining the grouping.
+ \ingroup model-view
+*/
+
+QtGroupingProxy::QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootNode, int groupedColumn )
+ : QAbstractProxyModel()
+ , m_rootNode( rootNode )
+ , m_groupedColumn( 0 )
+{
+ setSourceModel( model );
+
+ // signal proxies
+ connect( sourceModel(),
+ SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ),
+ this, SLOT( modelDataChanged( const QModelIndex&, const QModelIndex& ) )
+ );
+ connect( sourceModel(), SIGNAL( rowsInserted( const QModelIndex&, int, int ) ),
+ SLOT( modelRowsInserted( const QModelIndex &, int, int ) ) );
+ connect( sourceModel(), SIGNAL(rowsAboutToBeInserted( const QModelIndex &, int ,int )),
+ SLOT(modelRowsAboutToBeInserted( const QModelIndex &, int ,int )));
+ connect( sourceModel(), SIGNAL( rowsRemoved( const QModelIndex&, int, int ) ),
+ SLOT( modelRowsRemoved( const QModelIndex&, int, int ) ) );
+ connect( sourceModel(), SIGNAL(rowsAboutToBeRemoved( const QModelIndex &, int ,int )),
+ SLOT(modelRowsAboutToBeRemoved(QModelIndex,int,int)) );
+ connect( sourceModel(), SIGNAL(layoutChanged()), SLOT(buildTree()) );
+ connect( sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ SLOT(modelDataChanged(QModelIndex,QModelIndex)) );
+
+ if( groupedColumn != -1 )
+ setGroupedColumn( groupedColumn );
+}
+
+QtGroupingProxy::~QtGroupingProxy()
+{
+}
+
+void
+QtGroupingProxy::setGroupedColumn( int groupedColumn )
+{
+ m_groupedColumn = groupedColumn;
+ buildTree();
+}
+
+/** Maps to what groups the source row belongs by returning the data of those groups.
+ *
+ * @returns a list of data for the rows the argument belongs to. In common cases this list will
+ * contain only one entry. An empty list means that the source item will be placed in the root of
+ * this proxyModel. There is no support for hiding source items.
+ *
+ * Group data can be pre-loaded in the return value so it's added to the cache maintained by this
+ * class. This is required if you want to have data that is not present in the source model.
+ */
+QList<RowData>
+QtGroupingProxy::belongsTo( const QModelIndex &idx )
+{
+ //qDebug() << __FILE__ << __FUNCTION__;
+ QList<RowData> rowDataList;
+
+ //get all the data for this index from the model
+ ItemData itemData = sourceModel()->itemData( idx );
+ QMapIterator<int, QVariant> i( itemData );
+ while( i.hasNext() )
+ {
+ i.next();
+ int role = i.key();
+ QVariant variant = i.value();
+ // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant;
+ if( variant.type() == QVariant::List )
+ {
+ //a list of variants get's expanded to multiple rows
+ QVariantList list = variant.toList();
+ for( int i = 0; i < list.length(); i++ )
+ {
+ //take an existing row data or create a new one
+ RowData rowData = (rowDataList.count() > i) ? rowDataList.takeAt( i )
+ : RowData();
+
+ //we only gather data for the first column
+ ItemData indexData = rowData.contains( 0 ) ? rowData.take( 0 ) : ItemData();
+ indexData.insert( role, list.value( i ) );
+ rowData.insert( 0, indexData );
+ //for the grouped column the data should not be gathered from the children
+ //this will allow filtering on the content of this column with a
+ //QSortFilterProxyModel
+ rowData.insert( m_groupedColumn, indexData );
+ rowDataList.insert( i, rowData );
+ }
+ }
+ else if( !variant.isNull() )
+ {
+ //it's just a normal item. Copy all the data and break this loop.
+ RowData rowData;
+ rowData.insert( 0, itemData );
+ rowDataList << rowData;
+ break;
+ }
+ }
+
+ return rowDataList;
+}
+
+/* m_groupHash layout
+* key : index of the group in m_groupMaps
+* value : a QList of the original rows in sourceModel() for the children of this group
+*
+* key = -1 contains a QList of the non-grouped indexes
+*
+* TODO: sub-groups
+*/
+void
+QtGroupingProxy::buildTree()
+{
+ if( !sourceModel() )
+ return;
+ beginResetModel();
+
+ m_groupHash.clear();
+ //don't clear the data maps since most of it will probably be needed again.
+ m_parentCreateList.clear();
+
+ int max = sourceModel()->rowCount( m_rootNode );
+ //qDebug() << QString("building tree with %1 leafs.").arg( max );
+ //WARNING: these have to be added in order because the addToGroups function is optimized for
+ //modelRowsInserted(). Failure to do so will result in wrong data shown in the view at best.
+ for( int row = 0; row < max; row++ )
+ {
+ QModelIndex idx = sourceModel()->index( row, m_groupedColumn, m_rootNode );
+ addSourceRow( idx );
+ }
+ //dumpGroups();
+
+ endResetModel();
+
+ for( int row = 0; row < rowCount(); row++ ) {
+ QModelIndex idx = index( row, 0, QModelIndex() );
+ if (m_expandedItems.contains(idx.data(Qt::UserRole).toString())) {
+ emit expandItem(idx);
+ }
+ }
+}
+
+QList<int>
+QtGroupingProxy::addSourceRow( const QModelIndex &idx )
+{
+ QList<int> updatedGroups;
+
+ QList<RowData> groupData = belongsTo( idx );
+ //an empty list here means it's supposed to go in root.
+ if( groupData.isEmpty() )
+ {
+ updatedGroups << -1;
+ if( !m_groupHash.keys().contains( std::numeric_limits<quint32>::max() ) )
+ m_groupHash.insert( std::numeric_limits<quint32>::max(), QList<int>() ); //add an empty placeholder
+ }
+
+ //an item can be in multiple groups
+ foreach( RowData data, groupData )
+ {
+ int updatedGroup = -1;
+ if( !data.isEmpty() )
+ {
+ // qDebug() << QString("index %1 belongs to group %2").arg( row )
+ // .arg( data[0][Qt::DisplayRole].toString() );
+
+ foreach( const RowData &cachedData, m_groupMaps )
+ {
+ //when this matches the index belongs to an existing group
+ if( data[0][Qt::DisplayRole] == cachedData[0][Qt::DisplayRole] )
+ {
+ data = cachedData;
+ break;
+ }
+ }
+
+ updatedGroup = m_groupMaps.indexOf( data );
+ //-1 means not found
+ if( updatedGroup == -1 )
+ {
+ //new groups are added to the end of the existing list
+ m_groupMaps << data;
+ updatedGroup = m_groupMaps.count() - 1;
+ }
+
+ if( !m_groupHash.keys().contains( updatedGroup ) )
+ m_groupHash.insert( updatedGroup, QList<int>() ); //add an empty placeholder
+ }
+
+ if( !updatedGroups.contains( updatedGroup ) )
+ updatedGroups << updatedGroup;
+ }
+
+
+ //update m_groupHash to the new source-model layout (one row added)
+ QMutableHashIterator<quint32, QList<int> > i( m_groupHash );
+ while( i.hasNext() )
+ {
+ i.next();
+ QList<int> &groupList = i.value();
+ int insertedProxyRow = groupList.count();
+ for( ; insertedProxyRow > 0 ; insertedProxyRow-- )
+ {
+ int &rowValue = groupList[insertedProxyRow-1];
+ if( idx.row() <= rowValue )
+ {
+ //increment the rows that come after the new row since they moved one place up.
+ rowValue++;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if( updatedGroups.contains( i.key() ) )
+ {
+ //the row needs to be added to this group
+ beginInsertRows( index( i.key() ), insertedProxyRow, insertedProxyRow );
+ groupList.insert( insertedProxyRow, idx.row() );
+ endInsertRows();
+ }
+ }
+
+ return updatedGroups;
+}
+
+/** Each ModelIndex has in it's internalId a position in the parentCreateList.
+ * struct ParentCreate are the instructions to recreate the parent index.
+ * It contains the proxy row number of the parent and the postion in this list of the grandfather.
+ * This function creates the ParentCreate structs and saves them in a list.
+ */
+int
+QtGroupingProxy::indexOfParentCreate( const QModelIndex &parent ) const
+{
+ if( !parent.isValid() )
+ return -1;
+
+ struct ParentCreate pc;
+ for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
+ {
+ pc = m_parentCreateList[i];
+ if( pc.parentCreateIndex == parent.internalId() && pc.row == parent.row() )
+ return i;
+ }
+ //there is no parentCreate yet for this index, so let's create one.
+ pc.parentCreateIndex = parent.internalId();
+ pc.row = parent.row();
+ m_parentCreateList << pc;
+
+ //dumpParentCreateList();
+ // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() );
+ // for( int i = 0 ; i < m_parentCreateList.size() ; i++ )
+ // {
+ // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex <<
+ // " | " << m_parentCreateList[i].row;
+ // }
+
+ return m_parentCreateList.size() - 1;
+}
+
+QModelIndex
+QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const
+{
+ // qDebug() << "index requested for: (" << row << "," << column << "), " << parent;
+ if( !hasIndex(row, column, parent) )
+ return QModelIndex();
+
+ if( parent.column() > 0 )
+ return QModelIndex();
+
+ /* We save the instructions to make the parent of the index in a struct.
+ * The place of the struct in the list is stored in the internalId
+ */
+ int parentCreateIndex = indexOfParentCreate( parent );
+
+ return createIndex( row, column, parentCreateIndex );
+}
+
+QModelIndex
+QtGroupingProxy::parent( const QModelIndex &index ) const
+{
+ //qDebug() << "parent: " << index;
+ if( !index.isValid() )
+ return QModelIndex();
+
+ int parentCreateIndex = index.internalId();
+ //qDebug() << "parentCreateIndex: " << parentCreateIndex;
+ if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() )
+ return QModelIndex();
+
+ struct ParentCreate pc = m_parentCreateList[parentCreateIndex];
+ //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")";
+ //only items at column 0 have children
+ return createIndex( pc.row, 0, pc.parentCreateIndex );
+}
+
+int
+QtGroupingProxy::rowCount( const QModelIndex &index ) const
+{
+ //qDebug() << "rowCount: " << index;
+ if( !index.isValid() )
+ {
+ //the number of top level groups + the number of non-grouped items
+ int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits<quint32>::max() ).count();
+ //qDebug() << rows << " in root group";
+ return rows;
+ }
+
+ //TODO:group in group support.
+ if( isGroup( index ) )
+ {
+ qint64 groupIndex = index.row();
+ int rows = m_groupHash.value( groupIndex ).count();
+ //qDebug() << rows << " in group " << m_groupMaps[groupIndex];
+ return rows;
+ }
+
+ QModelIndex originalIndex = mapToSource( index );
+ int rowCount = sourceModel()->rowCount( originalIndex );
+ //qDebug() << "original item: rowCount == " << rowCount;
+ return rowCount;
+}
+
+int
+QtGroupingProxy::columnCount( const QModelIndex &index ) const
+{
+ if( !index.isValid() )
+ return sourceModel()->columnCount( m_rootNode );
+
+ if( index.column() != 0 )
+ return 0;
+
+ return sourceModel()->columnCount( mapToSource( index ) );
+}
+
+QVariant
+QtGroupingProxy::data( const QModelIndex &index, int role ) const
+{
+ if( !index.isValid() )
+ return QVariant();
+ // qDebug() << __FUNCTION__ << index << " role: " << role;
+ int row = index.row();
+ int column = index.column();
+ if( isGroup( index ) )
+ {
+ if (column != 0) return QVariant();
+
+ //qDebug() << __FUNCTION__ << "is a group";
+ //use cached or precalculated data
+ if( m_groupMaps[row][column].contains( Qt::DisplayRole ) )
+ {
+ //qDebug() << "Using cached data";
+ switch (role) {
+ case Qt::DisplayRole: {
+ QString value = m_groupMaps[row][column].value( role ).toString();
+ return QString("----- %1 -----").arg(value.isEmpty() ? tr("<unset>") : value);
+ } break;
+ case Qt::ForegroundRole: {
+ return QBrush(Qt::gray);
+ } break;
+ case Qt::FontRole: {
+ QFont font(m_groupMaps[row][column].value(Qt::FontRole).value<QFont>());
+ font.setItalic(true);
+ return font;
+ } break;
+ case Qt::TextAlignmentRole: {
+ return Qt::AlignHCenter;
+ } break;
+ case Qt::UserRole: {
+ return m_groupMaps[row][column].value( Qt::DisplayRole ).toString();
+ } break;
+ default: {
+ return QVariant();
+ // return m_groupMaps[row][column].value( role );
+ } break;
+ }
+ }
+
+ //for column 0 we gather data from the grouped column instead
+ if( column == 0 )
+ column = m_groupedColumn;
+
+ //map all data from children to columns of group to allow grouping one level up
+ QVariantList variantsOfChildren;
+ int childCount = m_groupHash.value( row ).count();
+ if( childCount == 0 )
+ return QVariant();
+
+ //qDebug() << __FUNCTION__ << "childCount: " << childCount;
+ //Need a parentIndex with column == 0 because only those have children.
+ QModelIndex parentIndex = this->index( row, 0, index.parent() );
+ for( int childRow = 0; childRow < childCount; childRow++ )
+ {
+ QModelIndex childIndex = this->index( childRow, column, parentIndex );
+ QVariant data = mapToSource( childIndex ).data( role );
+ //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type());
+ if( data.isValid() && !variantsOfChildren.contains( data ) )
+ variantsOfChildren << data;
+ }
+ //qDebug() << "gathered this data from children: " << variantsOfChildren;
+ //saving in cache
+ ItemData roleMap = m_groupMaps[row].value( column );
+ foreach( const QVariant &variant, variantsOfChildren )
+ {
+ if( roleMap[ role ] != variant )
+ roleMap.insert( role, variantsOfChildren );
+ }
+
+ //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role];
+ //only one unique variant? No need to return a list
+ if( variantsOfChildren.count() == 1 )
+ return variantsOfChildren.first();
+
+ if( variantsOfChildren.count() == 0 )
+ return QVariant();
+
+ return variantsOfChildren;
+ }
+
+ return mapToSource( index ).data( role );
+}
+
+bool
+QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role )
+{
+ if( !idx.isValid() )
+ return false;
+
+ //no need to set data to exactly the same value
+ if( idx.data( role ) == value )
+ return false;
+
+ if( isGroup( idx ) )
+ {
+ ItemData columnData = m_groupMaps[idx.row()][idx.column()];
+
+ columnData.insert( role, value );
+ //QItemDelegate will always use Qt::EditRole
+ if( role == Qt::EditRole )
+ columnData.insert( Qt::DisplayRole, value );
+
+ //and make sure it's stored in the map
+ m_groupMaps[idx.row()].insert( idx.column(), columnData );
+
+ int columnToChange = idx.column() ? idx.column() : m_groupedColumn;
+ foreach( int originalRow, m_groupHash.value( idx.row() ) )
+ {
+ QModelIndex childIdx = sourceModel()->index( originalRow, columnToChange,
+ m_rootNode );
+ if( childIdx.isValid() )
+ sourceModel()->setData( childIdx, value, role );
+ }
+ //TODO: we might need to reload the data from the children at this point
+
+ emit dataChanged( idx, idx );
+ return true;
+ }
+
+ return sourceModel()->setData( mapToSource( idx ), value, role );
+}
+
+bool
+QtGroupingProxy::isGroup( const QModelIndex &index ) const
+{
+ int parentCreateIndex = index.internalId();
+ if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() )
+ return true;
+ return false;
+}
+
+QModelIndex
+QtGroupingProxy::mapToSource( const QModelIndex &index ) const
+{
+ //qDebug() << "mapToSource: " << index;
+ if( !index.isValid() )
+ return m_rootNode;
+
+ if( isGroup( index ) )
+ {
+ //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString();
+ return m_rootNode;
+ }
+
+ QModelIndex proxyParent = index.parent();
+ //qDebug() << "parent: " << proxyParent;
+ QModelIndex originalParent = mapToSource( proxyParent );
+ //qDebug() << "originalParent: " << originalParent;
+ int originalRow = index.row();
+ if( originalParent == m_rootNode )
+ {
+ int indexInGroup = index.row();
+ if( !proxyParent.isValid() )
+ indexInGroup -= m_groupMaps.count();
+ //qDebug() << "indexInGroup" << indexInGroup;
+ QList<int> childRows = m_groupHash.value( proxyParent.row() );
+ if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 )
+ return QModelIndex();
+
+ originalRow = childRows.at( indexInGroup );
+ //qDebug() << "originalRow: " << originalRow;
+ }
+ return sourceModel()->index( originalRow, index.column(), originalParent );
+}
+
+QModelIndexList
+QtGroupingProxy::mapToSource( const QModelIndexList& list ) const
+{
+ QModelIndexList originalList;
+ foreach( const QModelIndex &index, list )
+ {
+ QModelIndex originalIndex = mapToSource( index );
+ if( originalIndex.isValid() )
+ originalList << originalIndex;
+ }
+ return originalList;
+}
+
+QModelIndex
+QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const
+{
+ if( !idx.isValid() )
+ return QModelIndex();
+
+ QModelIndex proxyParent;
+ QModelIndex sourceParent = idx.parent();
+ //qDebug() << "sourceParent: " << sourceParent;
+ int proxyRow = idx.row();
+ int sourceRow = idx.row();
+
+ if( sourceParent.isValid() && ( sourceParent != m_rootNode ) )
+ {
+ //idx is a child of one of the items in the source model
+ proxyParent = mapFromSource( sourceParent );
+ }
+ else
+ {
+ //idx is an item in the top level of the source model (child of the rootnode)
+ int groupRow = -1;
+ QHashIterator<quint32, QList<int> > iterator( m_groupHash );
+ while( iterator.hasNext() )
+ {
+ iterator.next();
+ if( iterator.value().contains( sourceRow ) )
+ {
+ groupRow = iterator.key();
+ break;
+ }
+ }
+
+ if( groupRow != -1 ) //it's in a group, let's find the correct row.
+ {
+ proxyParent = this->index( groupRow, 0, QModelIndex() );
+ proxyRow = m_groupHash.value( groupRow ).indexOf( sourceRow );
+ }
+ else
+ {
+ proxyParent = QModelIndex();
+ // if the proxy item is not in a group it will be below the groups.
+ int groupLength = m_groupMaps.count();
+ //qDebug() << "groupNames length: " << groupLength;
+ int i = m_groupHash.value( std::numeric_limits<quint32>::max() ).indexOf( sourceRow );
+ //qDebug() << "index in hash: " << i;
+ proxyRow = groupLength + i;
+ }
+ }
+
+ //qDebug() << "proxyParent: " << proxyParent;
+ //qDebug() << "proxyRow: " << proxyRow;
+ return this->index( proxyRow, 0, proxyParent );
+}
+
+Qt::ItemFlags
+QtGroupingProxy::flags( const QModelIndex &idx ) const
+{
+ if( !idx.isValid() )
+ {
+ Qt::ItemFlags rootFlags = sourceModel()->flags( m_rootNode );
+ if( rootFlags.testFlag( Qt::ItemIsDropEnabled ) )
+ return Qt::ItemFlags( Qt::ItemIsDropEnabled );
+
+ return 0;
+ }
+ //only if the grouped column has the editable flag set allow the
+ //actions leading to setData on the source (edit & drop)
+ // qDebug() << idx;
+ if( isGroup( idx ) )
+ {
+ // dumpGroups();
+ // Qt::ItemFlags defaultFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable );
+ Qt::ItemFlags defaultFlags(Qt::NoItemFlags);
+ bool groupIsEditable = true;
+
+ //it's possible to have empty groups
+ if( m_groupHash.value( idx.row() ).count() == 0 )
+ {
+ //check the flags of this column with the root node
+ QModelIndex originalRootNode = sourceModel()->index( m_rootNode.row(), m_groupedColumn,
+ m_rootNode.parent() );
+ groupIsEditable = originalRootNode.flags().testFlag( Qt::ItemIsEditable );
+ }
+ else
+ {
+ foreach( int originalRow, m_groupHash.value( idx.row() ) )
+ {
+ QModelIndex originalIdx = sourceModel()->index( originalRow, m_groupedColumn,
+ m_rootNode );
+
+ groupIsEditable = groupIsEditable
+ ? originalIdx.flags().testFlag( Qt::ItemIsEditable )
+ : false;
+ if( !groupIsEditable ) //all children need to have an editable grouped column
+ break;
+ }
+ }
+
+ if( groupIsEditable )
+ return ( defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDropEnabled );
+ return defaultFlags;
+ }
+
+ QModelIndex originalIdx = mapToSource( idx );
+ Qt::ItemFlags originalItemFlags = sourceModel()->flags( originalIdx );
+
+ //check the source model to see if the grouped column is editable;
+ QModelIndex groupedColumnIndex =
+ sourceModel()->index( originalIdx.row(), m_groupedColumn, originalIdx.parent() );
+ bool groupIsEditable = sourceModel()->flags( groupedColumnIndex ).testFlag( Qt::ItemIsEditable );
+ if( groupIsEditable )
+ return originalItemFlags | Qt::ItemIsDragEnabled;
+
+ return originalItemFlags;
+}
+
+QVariant
+QtGroupingProxy::headerData( int section, Qt::Orientation orientation, int role ) const
+{
+ return sourceModel()->headerData( section, orientation, role );
+}
+
+bool
+QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const
+{
+ if( !parent.isValid() )
+ return false;
+
+ if( isGroup( parent ) )
+ return false;
+
+ return sourceModel()->canFetchMore( mapToSource( parent ) );
+}
+
+void
+QtGroupingProxy::fetchMore ( const QModelIndex & parent )
+{
+ if( !parent.isValid() )
+ return;
+
+ if( isGroup( parent ) )
+ return;
+
+ return sourceModel()->fetchMore( mapToSource( parent ) );
+}
+
+QModelIndex
+QtGroupingProxy::addEmptyGroup( const RowData &data )
+{
+ int newRow = m_groupMaps.count();
+ beginInsertRows( QModelIndex(), newRow, newRow );
+ m_groupMaps << data;
+ endInsertRows();
+ return index( newRow, 0, QModelIndex() );
+}
+
+bool
+QtGroupingProxy::removeGroup( const QModelIndex &idx )
+{
+ beginRemoveRows( idx.parent(), idx.row(), idx.row() );
+ m_groupHash.remove( idx.row() );
+ m_groupMaps.removeAt( idx.row() );
+ m_parentCreateList.removeAt( idx.internalId() );
+ endRemoveRows();
+
+ //TODO: only true if all data could be unset.
+ return true;
+}
+
+bool
+QtGroupingProxy::hasChildren( const QModelIndex &parent ) const
+{
+ if( !parent.isValid() )
+ return true;
+
+ if( isGroup( parent ) )
+ return !m_groupHash.value( parent.row() ).isEmpty();
+
+ return sourceModel()->hasChildren( mapToSource( parent ) );
+}
+
+void
+QtGroupingProxy::modelRowsAboutToBeInserted( const QModelIndex &parent, int start, int end )
+{
+ if( parent != m_rootNode )
+ {
+ //an item will be added to an original index, remap and pass it on
+ // qDebug() << parent;
+ QModelIndex proxyParent = mapFromSource( parent );
+ // qDebug() << proxyParent;
+ beginInsertRows( proxyParent, start, end );
+ }
+}
+
+void
+QtGroupingProxy::modelRowsInserted( const QModelIndex &parent, int start, int end )
+{
+ if( parent == m_rootNode )
+ {
+ //top level of the model changed, these new rows need to be put in groups
+ for( int modelRow = start; modelRow <= end ; modelRow++ )
+ {
+ addSourceRow( sourceModel()->index( modelRow, m_groupedColumn, m_rootNode ) );
+ }
+ }
+ else
+ {
+ //an item was added to an original index, remap and pass it on
+ QModelIndex proxyParent = mapFromSource( parent );
+ qDebug() << proxyParent;
+ //beginInsertRows had to be called in modelRowsAboutToBeInserted()
+ endInsertRows();
+ }
+}
+
+void
+QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end )
+{
+ if( parent == m_rootNode )
+ {
+ QHash<quint32, QList<int> >::const_iterator i;
+ //HACK, we are going to call beginRemoveRows() multiple times without
+ // endRemoveRows() if a source index is in multiple groups.
+ // This can be a problem for some views/proxies, but Q*Views can handle it.
+ // TODO: investigate a queue for applying proxy model changes in the correct order
+ for( i = m_groupHash.constBegin(); i != m_groupHash.constEnd(); ++i )
+ {
+ int groupIndex = i.key();
+ const QList<int> &groupList = i.value();
+ QModelIndex proxyParent = index( groupIndex, 0 );
+ foreach( int originalRow, groupList )
+ {
+ if( originalRow >= start && originalRow <= end )
+ {
+ int proxyRow = groupList.indexOf( originalRow );
+ if( groupIndex == -1 ) //adjust for non-grouped (root level) original items
+ proxyRow += m_groupMaps.count();
+ //TODO: optimize for continues original rows in the same group
+ beginRemoveRows( proxyParent, proxyRow, proxyRow );
+ }
+ }
+ }
+ }
+ else
+ {
+ //child item(s) of an original item will be removed, remap and pass it on
+ // qDebug() << parent;
+ QModelIndex proxyParent = mapFromSource( parent );
+ // qDebug() << proxyParent;
+ beginRemoveRows( proxyParent, start, end );
+ }
+}
+
+void
+QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end )
+{
+ if( parent == m_rootNode )
+ {
+ //TODO: can be optimised by iterating over m_groupHash and checking start <= r < end
+
+ //rather than increasing i we change the stored sourceRows in-place and reuse argument start
+ //X-times (where X = end - start).
+ for( int i = start; i <= end; i++ )
+ {
+ //HACK: we are going to iterate the hash in reverse so calls to endRemoveRows()
+ // are matched up with the beginRemoveRows() in modelRowsAboutToBeRemoved()
+ //NOTE: easier to do reverse with java style iterator
+ QMutableHashIterator<quint32, QList<int> > iter( m_groupHash );
+ iter.toBack();
+ while( iter.hasPrevious() )
+ {
+ iter.previous();
+ int groupIndex = iter.key();
+ //has to be a modifiable reference for remove and replace operations
+ QList<int> &groupList = iter.value();
+ int rowIndex = groupList.indexOf( start );
+ if( rowIndex != -1 )
+ {
+ QModelIndex proxyParent = index( groupIndex, 0 );
+ groupList.removeAt( rowIndex );
+ }
+ //Now decrement all source rows that are after the removed row
+ for( int j = 0; j < groupList.count(); j++ )
+ {
+ int sourceRow = groupList.at( j );
+ if( sourceRow > start )
+ groupList.replace( j, sourceRow-1 );
+ }
+ if( rowIndex != -1)
+ endRemoveRows(); //end remove operation only after group was updated.
+ }
+ }
+
+ return;
+ }
+
+ //beginRemoveRows had to be called in modelRowsAboutToBeRemoved();
+ endRemoveRows();
+}
+
+void
+QtGroupingProxy::modelDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
+{
+ //TODO: need to look in the groupedColumn and see if it changed and changed grouping accordingly
+ QModelIndex proxyTopLeft = mapFromSource( topLeft );
+ if( !proxyTopLeft.isValid() )
+ return;
+
+ if( topLeft == bottomRight )
+ {
+ emit dataChanged( proxyTopLeft, proxyTopLeft );
+ }
+ else
+ {
+ QModelIndex proxyBottomRight = mapFromSource( bottomRight );
+ emit dataChanged( proxyTopLeft, proxyBottomRight );
+ }
+}
+
+bool
+QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const
+{
+ foreach( const QModelIndex &index, list )
+ {
+ if( isGroup( index ) )
+ return true;
+ }
+ return false;
+}
+
+void
+QtGroupingProxy::dumpGroups() const
+{
+ qDebug() << "m_groupHash: ";
+ for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ )
+ {
+ qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex );
+ }
+
+ qDebug() << "m_groupMaps: ";
+ for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ )
+ qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex );
+ qDebug() << m_groupHash.value( std::numeric_limits<quint32>::max() );
+}
+
+
+void QtGroupingProxy::expanded(const QModelIndex &index)
+{
+ m_expandedItems.insert(index.data(Qt::UserRole).toString());
+}
+
+void QtGroupingProxy::collapsed(const QModelIndex &index)
+{
+ m_expandedItems.remove(index.data(Qt::UserRole).toString());
+}
diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h new file mode 100644 index 00000000..6ec34481 --- /dev/null +++ b/src/qtgroupingproxy.h @@ -0,0 +1,144 @@ +/****************************************************************************************
+ * Copyright (c) 2007-2010 Bart Cerneels <bart.cerneels@kde.org> *
+ * *
+ * This program is free software; you can redistribute it and/or modify it under *
+ * the terms of the GNU General Public License as published by the Free Software *
+ * Foundation; either version 2 of the License, or (at your option) any later *
+ * version. *
+ * *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License along with *
+ * this program. If not, see <http://www.gnu.org/licenses/>. *
+ ****************************************************************************************/
+
+#ifndef GROUPINGPROXY_H
+#define GROUPINGPROXY_H
+
+#include <QAbstractProxyModel>
+#include <QModelIndex>
+#include <QMultiHash>
+#include <QStringList>
+#include <QIcon>
+#include <QSet>
+
+typedef QMap<int, QVariant> ItemData;
+typedef QMap<int, ItemData> RowData;
+
+class QtGroupingProxy : public QAbstractProxyModel
+{
+ Q_OBJECT
+public:
+ explicit QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootNode = QModelIndex(),
+ int groupedColumn = -1 );
+ ~QtGroupingProxy();
+
+ void setGroupedColumn( int groupedColumn );
+
+ /* QAbstractProxyModel methods */
+ virtual QModelIndex index( int, int c = 0,
+ const QModelIndex& parent = QModelIndex() ) const;
+ virtual Qt::ItemFlags flags( const QModelIndex &idx ) const;
+ virtual QModelIndex parent( const QModelIndex &idx ) const;
+ virtual int rowCount( const QModelIndex &idx = QModelIndex() ) const;
+ virtual int columnCount( const QModelIndex &idx ) const;
+ virtual QModelIndex mapToSource( const QModelIndex &idx ) const;
+ virtual QModelIndexList mapToSource( const QModelIndexList &list ) const;
+ virtual QModelIndex mapFromSource( const QModelIndex &idx ) const;
+ virtual QVariant data( const QModelIndex &idx, int role ) const;
+ virtual bool setData( const QModelIndex &index, const QVariant &value,
+ int role = Qt::EditRole );
+ virtual QVariant headerData ( int section, Qt::Orientation orientation,
+ int role ) const;
+ virtual bool canFetchMore( const QModelIndex &parent ) const;
+ virtual void fetchMore( const QModelIndex &parent );
+ virtual bool hasChildren( const QModelIndex &parent = QModelIndex() ) const;
+
+ /* QtGroupingProxy methods */
+ virtual QModelIndex addEmptyGroup( const RowData &data );
+ virtual bool removeGroup( const QModelIndex &idx );
+
+ QStringList expandedState();
+
+signals:
+ void expandItem(const QModelIndex &index);
+
+public slots:
+ /**
+ * @brief update expanded state
+ * @param index index of the expanded/collapsed item (from the base model!)
+ */
+ void expanded(const QModelIndex &index);
+ /**
+ * @brief update expanded state
+ * @param index index of the expanded/collapsed item (from the base model!)
+ */
+ void collapsed(const QModelIndex &index);
+protected slots:
+ virtual void buildTree();
+
+private slots:
+ void modelDataChanged( const QModelIndex &, const QModelIndex & );
+ void modelRowsAboutToBeInserted( const QModelIndex &, int ,int );
+ void modelRowsInserted( const QModelIndex &, int, int );
+ void modelRowsAboutToBeRemoved( const QModelIndex &, int ,int );
+ void modelRowsRemoved( const QModelIndex &, int, int );
+
+protected:
+ /** Maps an item to a group.
+ * The return value is a list because an item can put in multiple groups.
+ * Inside the list is a 2 dimensional map.
+ * Mapped to column-number is another map of role-number to QVariant.
+ * This data prepolulates the group-data cache. The rest is gathered on demand
+ * from the children of the group.
+ */
+ virtual QList<RowData> belongsTo( const QModelIndex &idx );
+
+ /**
+ * calls belongsTo(), checks cached data and adds the index to existing or new groups.
+ * @returns the groups this index was added to where -1 means it was added to the root.
+ */
+ QList<int> addSourceRow( const QModelIndex &idx );
+
+ bool isGroup( const QModelIndex &index ) const;
+ bool isAGroupSelected( const QModelIndexList &list ) const;
+
+ /** Maintains the group -> sourcemodel row mapping
+ * The reason a QList<int> is use instead of a QMultiHash is that the values have to be
+ * reordered when rows are inserted or removed.
+ * TODO:use some auto-incrementing container class (steveire's?) for the list
+ */
+ QHash<quint32, QList<int> > m_groupHash;
+ /** The data cache of the groups.
+ * This can be pre-loaded with data in belongsTo()
+ */
+ QList<RowData> m_groupMaps;
+
+ /** "instuctions" how to create an item in the tree.
+ * This is used by parent( QModelIndex )
+ */
+ struct ParentCreate
+ {
+ int parentCreateIndex;
+ int row;
+ };
+ mutable QList<struct ParentCreate> m_parentCreateList;
+ /** @returns index of the "instructions" to recreate the parent. Will create new if it doesn't exist yet.
+ */
+ int indexOfParentCreate( const QModelIndex &parent ) const;
+
+ QModelIndexList m_selectedGroups;
+
+ QModelIndex m_rootNode;
+ int m_groupedColumn;
+
+ /* debug function */
+ void dumpGroups() const;
+
+private:
+ QSet<QString> m_expandedItems;
+};
+
+#endif //GROUPINGPROXY_H
diff --git a/src/simpleinstalldialog.cpp b/src/simpleinstalldialog.cpp deleted file mode 100644 index ef694780..00000000 --- a/src/simpleinstalldialog.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "simpleinstalldialog.h"
-#include "ui_simpleinstalldialog.h"
-
-SimpleInstallDialog::SimpleInstallDialog(const QString &preset, QWidget *parent) :
- QDialog(parent), ui(new Ui::SimpleInstallDialog), m_Manual(false)
-{
- ui->setupUi(this);
- ui->nameEdit->setText(preset);
- setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
-}
-
-SimpleInstallDialog::~SimpleInstallDialog()
-{
- delete ui;
-}
-
-QString SimpleInstallDialog::getName() const
-{
- return ui->nameEdit->text();
-}
-
-void SimpleInstallDialog::on_okBtn_clicked()
-{
- this->accept();
-}
-
-void SimpleInstallDialog::on_cancelBtn_clicked()
-{
- this->reject();
-}
-
-void SimpleInstallDialog::on_manualBtn_clicked()
-{
- m_Manual = true;
- this->reject();
-}
diff --git a/src/simpleinstalldialog.h b/src/simpleinstalldialog.h deleted file mode 100644 index 5e8765de..00000000 --- a/src/simpleinstalldialog.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SIMPLEINSTALLDIALOG_H
-#define SIMPLEINSTALLDIALOG_H
-
-#include <QDialog>
-
-namespace Ui {
- class SimpleInstallDialog;
-}
-
-/**
- * @brief Dialog for the installation of a simple archive
- * a simple archive is one that doesn't require any manual changes to work correctly
- **/
-class SimpleInstallDialog : public QDialog
-{
- Q_OBJECT
-
-public:
- /**
- * @brief constructor
- *
- * @param preset suggested name for the mod
- * @param parent parent widget
- **/
- explicit SimpleInstallDialog(const QString &preset, QWidget *parent = 0);
- ~SimpleInstallDialog();
-
- /**
- * @return true if the user requested the manual installation dialog
- **/
- bool manualRequested() const { return m_Manual; }
- /**
- * @return the (user-modified) mod name
- **/
- QString getName() const;
-
-private slots:
-
- void on_okBtn_clicked();
-
- void on_cancelBtn_clicked();
-
- void on_manualBtn_clicked();
-
-private:
- Ui::SimpleInstallDialog *ui;
- bool m_Manual;
-};
-
-#endif // SIMPLEINSTALLDIALOG_H
|
