diff options
| -rw-r--r-- | src/categories.cpp | 11 | ||||
| -rw-r--r-- | src/categories.h | 17 | ||||
| -rw-r--r-- | src/icondelegate.cpp | 12 | ||||
| -rw-r--r-- | src/icondelegate.h | 62 | ||||
| -rw-r--r-- | src/mainwindow.cpp | 96 | ||||
| -rw-r--r-- | src/mainwindow.h | 4 | ||||
| -rw-r--r-- | src/modlist.cpp | 23 | ||||
| -rw-r--r-- | src/modlistgroupcategoriesproxy.cpp | 123 | ||||
| -rw-r--r-- | src/modlistgroupcategoriesproxy.h | 40 | ||||
| -rw-r--r-- | src/modlistgroupnexusidproxy.cpp | 130 | ||||
| -rw-r--r-- | src/modlistgroupnexusidproxy.h | 49 | ||||
| -rw-r--r-- | src/modlistsortproxy.cpp | 9 | ||||
| -rw-r--r-- | src/organizer.pro | 6 | ||||
| -rw-r--r-- | src/qtgroupingproxy.cpp | 897 | ||||
| -rw-r--r-- | src/qtgroupingproxy.h | 144 | ||||
| -rw-r--r-- | src/version.rc | 4 |
16 files changed, 1182 insertions, 445 deletions
diff --git a/src/categories.cpp b/src/categories.cpp index 7b765afe..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")); diff --git a/src/categories.h b/src/categories.h index 47b15711..8ec573b2 100644 --- a/src/categories.h +++ b/src/categories.h @@ -157,12 +157,17 @@ public: public: - /** - * @brief retrieve a reference to the singleton instance - * - * @return the reference to the singleton - **/ - static CategoryFactory &instance(); + /** + * @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: diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 05a5c0c3..2540e1d5 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -23,8 +23,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include <QPainter> -IconDelegate::IconDelegate(QAbstractProxyModel *proxyModel, QObject *parent) - : QStyledItemDelegate(parent), m_ProxyModel(proxyModel) +IconDelegate::IconDelegate(QObject *parent) + : QStyledItemDelegate(parent) { } @@ -48,11 +48,11 @@ QIcon IconDelegate::getFlagIcon(ModInfo::EFlag flag) const void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); - QModelIndex sourceIndex = m_ProxyModel->mapToSource(index); - if (!sourceIndex.isValid()) { + QVariant modid = index.data(Qt::UserRole + 1); + if (!modid.isValid()) { return; } - ModInfo::Ptr info = ModInfo::getByIndex(sourceIndex.row()); + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); std::vector<ModInfo::EFlag> flags = info->getFlags(); int x = 4; @@ -70,7 +70,7 @@ void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, QSize IconDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex &modelIndex) const { - unsigned int index = m_ProxyModel->mapToSource(modelIndex).row(); + 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); 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/mainwindow.cpp b/src/mainwindow.cpp index 67a0de49..37d041c0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -24,8 +24,7 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "report.h" #include "modlist.h" #include "modlistsortproxy.h" -#include "modlistgroupcategoriesproxy.h" -#include "modlistgroupnexusidproxy.h" +#include "qtgroupingproxy.h" #include "profile.h" #include "pluginlist.h" #include "profilesdialog.h" @@ -85,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> @@ -100,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_ModListSortProxy(NULL), m_ModListGroupProxy(NULL), + 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), @@ -130,22 +130,20 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure); - // set up mod list m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); m_ModListSortProxy->setSourceModel(&m_ModList); -// m_ModListGroupProxy = new ModListGroupProxy(this); - m_ModListGroupProxy = new QIdentityProxyModel(this); - m_ModListGroupProxy->setSourceModel(m_ModListSortProxy); - - ui->modList->setModel(m_ModListGroupProxy); + 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_ModListGroupProxy, ui->modList)); + 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 @@ -210,7 +208,7 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString))); connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(int)), m_ModListSortProxy, SLOT(invalidate())); + connect(&m_ModList, SIGNAL(modlist_changed(int)), this, SLOT(modlistChanged(int))); connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); @@ -267,6 +265,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); @@ -1588,6 +1605,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); } @@ -1608,8 +1628,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); @@ -2195,6 +2217,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)); @@ -2270,9 +2298,11 @@ void MainWindow::refreshFilters() void MainWindow::renameMod_clicked() { try { - QModelIndex treeIdx = m_ModListGroupProxy->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())); } @@ -2312,7 +2342,8 @@ void MainWindow::removeMod_clicked() QString mods; QStringList modNames; foreach (QModelIndex idx, selection->selectedRows()) { - QString name = ModInfo::getByIndex(m_ModListGroupProxy->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); } @@ -2580,7 +2611,8 @@ void MainWindow::on_modList_doubleClicked(const QModelIndex &index) if (!index.isValid()) { return; } - QModelIndex sourceIdx = m_ModListGroupProxy->mapToSource(index); +// QModelIndex sourceIdx = m_ModListGroupProxy->mapToSource(index); + QModelIndex sourceIdx = mapToModel(&m_ModList, index); if (!sourceIdx.isValid()) { return; } @@ -2814,13 +2846,12 @@ void MainWindow::exportModListCSV() } } - void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) { try { QTreeView *modList = findChild<QTreeView*>("modList"); - m_ContextRow = m_ModListGroupProxy->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())); @@ -3935,29 +3966,28 @@ void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) void MainWindow::on_groupCombo_currentIndexChanged(int index) { - if (m_ModListGroupProxy == NULL) return; - QAbstractProxyModel *newModel = NULL; switch (index) { - case 0: { - newModel = new QIdentityProxyModel(this); - } break; case 1: { - newModel = new ModListGroupCategoriesProxy(this); + newModel = new QtGroupingProxy(m_ModListSortProxy, QModelIndex(), ModList::COL_CATEGORY); } break; case 2: { - newModel = new ModListGroupNexusIDProxy(m_CurrentProfile, this); + newModel = new QtGroupingProxy(m_ModListSortProxy, QModelIndex(), ModList::COL_MODID); } break; default: { - qDebug("invalid modlist grouping %d", index); - return; + newModel = NULL; } break; } - newModel->setSourceModel(m_ModListSortProxy); - ui->modList->setModel(newModel); - delete ui->modList->itemDelegateForColumn(ModList::COL_FLAGS); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(newModel, ui->modList)); - delete m_ModListGroupProxy; - m_ModListGroupProxy = newModel; + 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 25071127..552c1bf6 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -201,8 +201,6 @@ private: bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); - void checkBSAList(); - bool checkForProblems(QString &problemDescription); int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); @@ -243,7 +241,6 @@ private: ModList m_ModList; ModListSortProxy *m_ModListSortProxy; - QAbstractProxyModel *m_ModListGroupProxy; PluginList m_PluginList; PluginListSortProxy *m_PluginListSortProxy; @@ -374,6 +371,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); diff --git a/src/modlist.cpp b/src/modlist.cpp index bc838a98..9b99b488 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -581,7 +581,7 @@ void ModList::notifyChange(int row) QModelIndex ModList::index(int row, int column, const QModelIndex&) const { - return createIndex(row, column, -1); + return createIndex(row, column, row); } @@ -637,11 +637,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(); -#pragma message("no longer a sortfilter model") - 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(); @@ -651,8 +660,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/modlistgroupcategoriesproxy.cpp b/src/modlistgroupcategoriesproxy.cpp deleted file mode 100644 index 4320be97..00000000 --- a/src/modlistgroupcategoriesproxy.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "modlistgroupcategoriesproxy.h"
-#include "categories.h"
-#include "modinfo.h"
-
-
-ModListGroupCategoriesProxy::ModListGroupCategoriesProxy(QObject *parent)
- : QAbstractProxyModel(parent)
-{
-}
-
-int ModListGroupCategoriesProxy::rowCount(const QModelIndex &parent) const
-{
- if (!parent.isValid()) {
- return CategoryFactory::instance().numCategories();
- } else {
- unsigned int count = 0;
- for (int i = 0; i < sourceModel()->rowCount(); ++i) {
- if (ModInfo::getByIndex(i)->getPrimaryCategory() ==
- CategoryFactory::instance().getCategoryID(parent.row())) {
- ++count;
- }
- }
- return count;
- }
-}
-
-int ModListGroupCategoriesProxy::columnCount(const QModelIndex &parent) const
-{
- return sourceModel()->columnCount(mapToSource(parent));
-}
-
-QModelIndex ModListGroupCategoriesProxy::mapToSource(const QModelIndex &proxyIndex) const
-{
- auto iter = m_IndexMap.find(proxyIndex);
- if (iter != m_IndexMap.end()) {
- return iter->second;
- } else {
- return QModelIndex();
- }
-}
-
-QModelIndex ModListGroupCategoriesProxy::mapFromSource(const QModelIndex &sourceIndex) const
-{
- if (!sourceIndex.isValid()) {
- return QModelIndex();
- } else {
- ModInfo::Ptr mod = ModInfo::getByIndex(sourceIndex.data(Qt::UserRole + 1).toInt());
- return index(sourceIndex.row(), sourceIndex.column(), index(mod->getPrimaryCategory(), 0, QModelIndex()));
- }
-}
-
-QModelIndex ModListGroupCategoriesProxy::index(int row, int column, const QModelIndex &parent) const
-{
- if (parent.isValid()) {
- // mod
- int categoryId = CategoryFactory::instance().getCategoryID(parent.row());
-
- int srcRow = 0;
- int offset = row;
- for (; srcRow < sourceModel()->rowCount(); ++srcRow) {
- int modId = sourceModel()->index(srcRow, column).data(Qt::UserRole + 1).toInt();
- if (ModInfo::getByIndex(modId)->getPrimaryCategory() == categoryId) {
-
- if (--offset < 0) {
- break;
- }
- }
- }
- QPersistentModelIndex idx = createIndex(row, column, categoryId);
- m_IndexMap[idx] = QPersistentModelIndex(sourceModel()->index(srcRow, column));
- return idx;
- } else {
- // category
- return createIndex(row, column, -1);
- }
-}
-
-QModelIndex ModListGroupCategoriesProxy::parent(const QModelIndex &child) const
-{
- if (!child.isValid()) {
- return QModelIndex();
- } else if (child.internalId() == -1) {
- return QModelIndex(); // top level
- } else {
- return index(CategoryFactory::instance().getCategoryIndex(child.internalId()), 0, QModelIndex());
- }
-}
-
-bool ModListGroupCategoriesProxy::hasChildren(const QModelIndex &parent) const
-{
- // root item always has children (the categories), mods don't
- if (!parent.isValid()) return true;
- else if (parent.internalId() != -1) return false;
-
- for (int i = 0; i < sourceModel()->rowCount(); ++i) {
- ModInfo::Ptr mod = ModInfo::getByIndex(i);
- if (mod->getPrimaryCategory() == CategoryFactory::instance().getCategoryID(parent.row())) {
- return true;
- }
- }
- return false;
-}
-
-QVariant ModListGroupCategoriesProxy::data(const QModelIndex &proxyIndex, int role) const
-{
- auto iter = m_IndexMap.find(proxyIndex);
- if (iter != m_IndexMap.end()) {
- // mod
- return sourceModel()->data(iter->second, role);
- } else {
- // category
- if ((role == Qt::DisplayRole) && (proxyIndex.column() == 0)) {
- return CategoryFactory::instance().getCategoryName(proxyIndex.row());
- } else {
- return QVariant();
- }
- }
-}
-
-QVariant ModListGroupCategoriesProxy::headerData(int section, Qt::Orientation orientation, int role) const
-{
- return sourceModel()->headerData(section, orientation, role);
-}
diff --git a/src/modlistgroupcategoriesproxy.h b/src/modlistgroupcategoriesproxy.h deleted file mode 100644 index 23fd36df..00000000 --- a/src/modlistgroupcategoriesproxy.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef MODLISTGROUPPROXY_H
-#define MODLISTGROUPPROXY_H
-
-#include <QIdentityProxyModel>
-#include <tuple>
-
-class ModListGroupCategoriesProxy : public QAbstractProxyModel
-{
-
- Q_OBJECT
-
-public:
-
- explicit ModListGroupCategoriesProxy(QObject *parent = 0);
-
- virtual int rowCount(const QModelIndex &parent) const;
- virtual int columnCount(const QModelIndex &parent) const;
-
- virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
- virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
-
- virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
- virtual QModelIndex parent(const QModelIndex &child) const;
-
- virtual bool hasChildren(const QModelIndex &parent) const;
-
- virtual QVariant data(const QModelIndex &proxyIndex, int role) const;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
-
-signals:
-
-public slots:
-
-private:
-
- mutable std::map<QPersistentModelIndex, QPersistentModelIndex> m_IndexMap;
-
-};
-
-#endif // MODLISTGROUPPROXY_H
diff --git a/src/modlistgroupnexusidproxy.cpp b/src/modlistgroupnexusidproxy.cpp deleted file mode 100644 index be072829..00000000 --- a/src/modlistgroupnexusidproxy.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include "modlistgroupnexusidproxy.h"
-#include "modinfo.h"
-
-ModListGroupNexusIDProxy::ModListGroupNexusIDProxy(Profile *profile, QObject *parent)
- : QAbstractProxyModel(parent)
-{
- refreshMap(profile);
-}
-
-void ModListGroupNexusIDProxy::refreshMap(Profile *profile)
-{
- int row = 0;
- for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) {
- int nexusID = ModInfo::getByIndex(i)->getNexusID();
- if (m_GroupMap.find(nexusID) == m_GroupMap.end()) {
- m_RowIdxMap[row] = i;
- m_IdxRowMap[i] = row;
- ++row;
- }
- m_GroupMap[nexusID].push_back(i);
- }
-
- for (auto iter = m_GroupMap.begin(); iter != m_GroupMap.end(); ++iter) {
- std::sort(iter->second.begin(), iter->second.end(),
- [profile] (unsigned int LHS, unsigned int RHS) {
- return profile->getModPriority(LHS) < profile->getModPriority(RHS);
- });
- }
-}
-
-int ModListGroupNexusIDProxy::rowCount(const QModelIndex &parent) const
-{
- int res = 0;
- if (!parent.isValid()) {
- res = m_GroupMap.size();
- } else {
- ModInfo::Ptr info = ModInfo::getByIndex(parent.internalId());
- res = m_GroupMap.at(info->getNexusID()).size();
- }
- return res;
-}
-
-int ModListGroupNexusIDProxy::columnCount(const QModelIndex &parent) const
-{
- return sourceModel()->columnCount(mapToSource(parent));
-}
-
-QModelIndex ModListGroupNexusIDProxy::mapToSource(const QModelIndex &proxyIndex) const
-{
- auto iter = m_IndexMap.find(proxyIndex);
- if (iter != m_IndexMap.end()) {
- return iter->second;
- } else {
- return QModelIndex();
- }
-}
-
-QModelIndex ModListGroupNexusIDProxy::mapFromSource(const QModelIndex &sourceIndex) const
-{
- unsigned int modID = sourceIndex.data(Qt::UserRole + 1).toInt();
- ModInfo::Ptr mod = ModInfo::getByIndex(modID);
- const std::vector<unsigned int> &subMods = m_GroupMap.at(mod->getNexusID());
-
- if (subMods.size() == 0) return QModelIndex();
-
- QModelIndex parent = QModelIndex();
- if (modID != subMods[0]) {
- parent = index(m_IdxRowMap.at(subMods[0]), 0, QModelIndex());
- }
- return index(sourceIndex.row(), sourceIndex.column(), parent);
-}
-
-QModelIndex ModListGroupNexusIDProxy::index(int row, int column, const QModelIndex &parent) const
-{
- if (parent.isValid()) {
- // sub-mod
- ModInfo::Ptr parentMod = ModInfo::getByIndex(parent.internalId());
- const std::vector<unsigned int> &subMods = m_GroupMap.at(parentMod->getNexusID());
- if ((row < 0) || (row >= static_cast<int>(subMods.size()))) {
- qCritical("invalid index: %dx%d", row, column);
- return QModelIndex();
- }
-
- QPersistentModelIndex idx = createIndex(row, column, subMods[row]);
- m_IndexMap[idx] = QPersistentModelIndex(sourceModel()->index(subMods[row], column));
-
- return idx;
- } else {
- // top-level mod
- return createIndex(row, column, m_RowIdxMap.at(row));
- }
-}
-
-QModelIndex ModListGroupNexusIDProxy::parent(const QModelIndex &child) const
-{
- if (!child.isValid()) return QModelIndex();
-
- ModInfo::Ptr mod = ModInfo::getByIndex(child.internalId());
- if (m_GroupMap.at(mod->getNexusID()).size() < 1) {
- return QModelIndex();
- }
- if (m_GroupMap.at(mod->getNexusID())[0] == child.internalId()) {
- return QModelIndex();
- } else {
- return index(m_IdxRowMap.at(m_GroupMap.at(mod->getNexusID())[0]), 0, QModelIndex());
- }
-}
-
-bool ModListGroupNexusIDProxy::hasChildren(const QModelIndex &parent) const
-{
- if (!parent.isValid()) return true;
- ModInfo::Ptr mod = ModInfo::getByIndex(parent.internalId());
- return m_GroupMap.at(mod->getNexusID()).size() > 1;
-}
-
-QVariant ModListGroupNexusIDProxy::data(const QModelIndex &proxyIndex, int role) const
-{
- auto iter = m_IndexMap.find(proxyIndex);
- if (iter != m_IndexMap.end()) {
- // mod
- return sourceModel()->data(iter->second, role);
- } else {
- return QVariant();
- }
-}
-
-QVariant ModListGroupNexusIDProxy::headerData(int section, Qt::Orientation orientation, int role) const
-{
- return sourceModel()->headerData(section, orientation, role);
-}
diff --git a/src/modlistgroupnexusidproxy.h b/src/modlistgroupnexusidproxy.h deleted file mode 100644 index 01d8a97d..00000000 --- a/src/modlistgroupnexusidproxy.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef MODLISTGROUPNEXUSIDPROXY_H
-#define MODLISTGROUPNEXUSIDPROXY_H
-
-
-#include <QAbstractProxyModel>
-#include <vector>
-#include <map>
-#include "profile.h"
-
-
-class ModListGroupNexusIDProxy : public QAbstractProxyModel
-{
- Q_OBJECT
-public:
- explicit ModListGroupNexusIDProxy(Profile *profile, QObject *parent = 0);
-
- virtual int rowCount(const QModelIndex &parent) const;
- virtual int columnCount(const QModelIndex &parent) const;
-
- virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
- virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
-
- virtual QModelIndex index(int row, int column, const QModelIndex &parent) const;
- virtual QModelIndex parent(const QModelIndex &child) const;
-
- virtual bool hasChildren(const QModelIndex &parent) const;
-
- virtual QVariant data(const QModelIndex &proxyIndex, int role) const;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const;
-signals:
-
-public slots:
-
-private:
-
- void refreshMap(Profile *profile);
-
-private:
-
- std::map<int, std::vector<unsigned int> > m_GroupMap;
-
- std::map<unsigned int, unsigned int> m_RowIdxMap; // maps row to mod id
- std::map<unsigned int, unsigned int> m_IdxRowMap; // maps mod id to row
-
- mutable std::map<QPersistentModelIndex, QPersistentModelIndex> m_IndexMap;
-
-};
-
-#endif // MODLISTGROUPNEXUSIDPROXY_H
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index a5f2ee82..1b96af3e 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -103,6 +103,7 @@ void ModListSortProxy::enableAllVisible() int modID = mapToSource(index(i, 0)).row(); m_Profile->setModEnabled(modID, true); } + invalidate(); } @@ -199,14 +200,6 @@ bool ModListSortProxy::filterMatches(ModInfo::Ptr info, bool enabled) const ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags())))); } -/*QModelIndex ModListSortProxy::mapToSource(const QModelIndex &proxyIndex) const -{ -if (proxyIndex.model() && (proxyIndex.model() != this)) { - qDebug("%p - %p - %p - %d - %d - %d", proxyIndex.model(), this, this->sourceModel(), proxyIndex.row(), proxyIndex.column(), proxyIndex.parent().isValid()); -} - return QSortFilterProxyModel::mapToSource(proxyIndex); -}*/ - bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const { diff --git a/src/organizer.pro b/src/organizer.pro index 0dbd2427..ee2d6453 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -77,8 +77,7 @@ SOURCES += \ gameinfoimpl.cpp \
csvbuilder.cpp \
savetextasdialog.cpp \
- modlistgroupcategoriesproxy.cpp \
- modlistgroupnexusidproxy.cpp
+ qtgroupingproxy.cpp
HEADERS += \
transfersavesdialog.h \
@@ -143,8 +142,7 @@ HEADERS += \ gameinfoimpl.h \
csvbuilder.h \
savetextasdialog.h \
- modlistgroupcategoriesproxy.h \
- modlistgroupnexusidproxy.h
+ qtgroupingproxy.h
FORMS += \
transfersavesdialog.ui \
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/version.rc b/src/version.rc index 9f083ed4..3b2c4804 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h"
-#define VER_FILEVERSION 0,12,9,0
-#define VER_FILEVERSION_STR 0,12,9,0
+#define VER_FILEVERSION 0,13,0,0
+#define VER_FILEVERSION_STR 0,13,0,0
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
|
