// This file is part of RSS Guard. // // Copyright (C) 2011-2014 by Martin Rotter <rotter.martinos@gmail.com> // // RSS Guard 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. // // RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>. #include "core/feedsmodel.h" #include "core/defs.h" #include "core/databasefactory.h" #include "core/feedsmodelstandardcategory.h" #include "core/feedsmodelstandardfeed.h" #include "core/textfactory.h" #include "gui/iconthemefactory.h" #include "gui/iconfactory.h" #include <QSqlError> #include <QSqlQuery> #include <QSqlRecord> #include <QPair> #include <algorithm> FeedsModel::FeedsModel(QObject *parent) : QAbstractItemModel(parent) { setObjectName("FeedsModel"); m_rootItem = new FeedsModelRootItem(); m_rootItem->setId(NO_PARENT_CATEGORY); m_rootItem->setTitle(tr("Root")); m_rootItem->setIcon(IconThemeFactory::instance()->fromTheme("folder-root")); m_countsIcon = IconThemeFactory::instance()->fromTheme("mail-mark-unread"); m_headerData << tr("Title"); m_tooltipData << tr("Titles of feeds/categories.") << tr("Counts of unread/all meesages."); loadFromDatabase(); } FeedsModel::~FeedsModel() { qDebug("Destroying FeedsModel instance."); // Delete all model items. delete m_rootItem; } QModelIndexList FeedsModel::persistentIndexList() const { return QAbstractItemModel::persistentIndexList(); } QVariant FeedsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (section == FDS_MODEL_TITLE_INDEX) { return m_headerData.at(FDS_MODEL_TITLE_INDEX); } else { return QVariant(); } case Qt::ToolTipRole: return m_tooltipData.at(section); case Qt::DecorationRole: if (section == FDS_MODEL_COUNTS_INDEX) { return m_countsIcon; } else { return QVariant(); } default: return QVariant(); } } QModelIndex FeedsModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } FeedsModelRootItem *parent_item; if (!parent.isValid()) { parent_item = m_rootItem; } else { parent_item = static_cast<FeedsModelRootItem*>(parent.internalPointer()); } FeedsModelRootItem *child_item = parent_item->child(row); if (child_item) { return createIndex(row, column, child_item); } else { return QModelIndex(); } } QModelIndex FeedsModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return QModelIndex(); } FeedsModelRootItem *child_item = static_cast<FeedsModelRootItem*>(child.internalPointer()); FeedsModelRootItem *parent_item = child_item->parent(); if (parent_item == m_rootItem) { return QModelIndex(); } else { return createIndex(parent_item->row(), 0, parent_item); } } int FeedsModel::rowCount(const QModelIndex &parent) const { FeedsModelRootItem *parent_item; if (parent.column() > 0) { return 0; } if (!parent.isValid()) { parent_item = m_rootItem; } else { parent_item = static_cast<FeedsModelRootItem*>(parent.internalPointer()); } return parent_item->childCount(); } bool FeedsModel::removeItem(const QModelIndex &index) { if (index.isValid()) { QModelIndex parent_index = index.parent(); FeedsModelRootItem *deleting_item = itemForIndex(index); FeedsModelRootItem *parent_item = deleting_item->parent(); // Try to persistently remove the item. if (deleting_item->removeItself()) { // Item was persistently removed. // Remove it from the model. beginRemoveRows(parent_index, index.row(), index.row()); parent_item->removeChild(deleting_item); endRemoveRows(); delete deleting_item; return true; } } // Item was not removed successfully. return false; } bool FeedsModel::addStandardCategory(FeedsModelStandardCategory *category, FeedsModelRootItem *parent) { // Get index of parent item (parent standard category). QModelIndex parent_index = indexForItem(parent); // Now, add category to persistent storage. // Children are removed, remove this standard category too. QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); QSqlQuery query_add(database); query_add.setForwardOnly(true); query_add.prepare("INSERT INTO Categories " "(parent_id, title, description, date_created, icon, type) " "VALUES (:parent_id, :title, :description, :date_created, :icon, :type);"); query_add.bindValue(":parent_id", parent->id()); query_add.bindValue(":title", category->title()); query_add.bindValue(":description", category->description()); query_add.bindValue(":date_created", category->creationDate().toMSecsSinceEpoch()); query_add.bindValue(":icon", IconFactory::toByteArray(category->icon())); query_add.bindValue(":type", (int) FeedsModelCategory::Standard); if (!query_add.exec()) { // Query failed. return false; } query_add.prepare("SELECT id FROM Categories WHERE date_created = :date_created;"); query_add.bindValue(":date_created", category->creationDate().toMSecsSinceEpoch()); if (query_add.exec() && query_add.next()) { // New category was added, fetch is primary id // from the database. category->setId(query_add.value(0).toInt()); } else { // Something failed. return false; } // Category was added to the persistent storage, // so add it to the model. beginInsertRows(parent_index, parent->childCount(), parent->childCount()); parent->appendChild(category); endInsertRows(); return true; } bool FeedsModel::editStandardCategory(FeedsModelStandardCategory *original_category, FeedsModelStandardCategory *new_category) { QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); QSqlQuery query_update_category(database); FeedsModelRootItem *original_parent = original_category->parent(); FeedsModelRootItem *new_parent = new_category->parent(); query_update_category.setForwardOnly(true); query_update_category.prepare("UPDATE Categories " "SET title = :title, description = :description, icon = :icon, parent_id = :parent_id " "WHERE id = :id;"); query_update_category.bindValue(":title", new_category->title()); query_update_category.bindValue(":description", new_category->description()); query_update_category.bindValue(":icon", IconFactory::toByteArray(new_category->icon())); query_update_category.bindValue(":parent_id", new_parent->id()); query_update_category.bindValue(":id", original_category->id()); if (!query_update_category.exec()) { // Persistent storage update failed, no way to continue now. return false; } // Setup new model data for the original item. original_category->setDescription(new_category->description()); original_category->setIcon(new_category->icon()); original_category->setTitle(new_category->title()); if (original_parent != new_parent) { // User edited category and set it new parent item, // se we need to move the item in the model too. int original_index_of_category = original_parent->childItems().indexOf(original_category); int new_index_of_category = new_parent->childCount(); // Remove the original item from the model... beginRemoveRows(indexForItem(original_parent), original_index_of_category, original_index_of_category); original_parent->removeChild(original_category); endRemoveRows(); // ... and insert it under the new parent. beginInsertRows(indexForItem(new_parent), new_index_of_category, new_index_of_category); new_parent->appendChild(original_category); endInsertRows(); } // Free temporary category from memory. delete new_category; // Editing is done. return true; } bool FeedsModel::addStandardFeed(FeedsModelStandardFeed *feed, FeedsModelRootItem *parent) { // Get index of parent item (parent standard category). QModelIndex parent_index = indexForItem(parent); // Now, add category to persistent storage. // Children are removed, remove this standard category too. QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); QSqlQuery query_add_feed(database); query_add_feed.setForwardOnly(true); query_add_feed.prepare("INSERT INTO Feeds " "(title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type) " "VALUES (:title, :description, :date_created, :icon, :category, :encoding, :url, :protected, :username, :password, :update_type, :update_interval, :type);"); query_add_feed.bindValue(":title", feed->title()); query_add_feed.bindValue(":description", feed->description()); query_add_feed.bindValue(":date_created", feed->creationDate().toMSecsSinceEpoch()); query_add_feed.bindValue(":icon", IconFactory::toByteArray(feed->icon())); query_add_feed.bindValue(":category", parent->id()); query_add_feed.bindValue(":encoding", feed->encoding()); query_add_feed.bindValue(":url", feed->url()); query_add_feed.bindValue(":protected", (int) feed->passwordProtected()); query_add_feed.bindValue(":username", feed->username()); query_add_feed.bindValue(":password", feed->password()); query_add_feed.bindValue(":update_type", (int) feed->autoUpdateType()); query_add_feed.bindValue(":update_interval", feed->autoUpdateInitialInterval()); query_add_feed.bindValue(":type", (int) feed->type()); if (!query_add_feed.exec()) { // Query failed. return false; } query_add_feed.prepare("SELECT id FROM Feeds WHERE date_created = :date_created;"); query_add_feed.bindValue(":date_created", feed->creationDate().toMSecsSinceEpoch()); if (query_add_feed.exec() && query_add_feed.next()) { // New category was added, fetch is primary id // from the database. feed->setId(query_add_feed.value(0).toInt()); } else { // Something failed. return false; } // Category was added to the persistent storage, // so add it to the model. beginInsertRows(parent_index, parent->childCount(), parent->childCount()); parent->appendChild(feed); endInsertRows(); return true; } bool FeedsModel::editStandardFeed(FeedsModelStandardFeed *original_feed, FeedsModelStandardFeed *new_feed) { QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); QSqlQuery query_update_feed(database); FeedsModelRootItem *original_parent = original_feed->parent(); FeedsModelRootItem *new_parent = new_feed->parent(); query_update_feed.setForwardOnly(true); query_update_feed.prepare("UPDATE Feeds " "SET title = :title, description = :description, icon = :icon, category = :category, encoding = :encoding, url = :url, protected = :protected, username = :username, password = :password, update_type = :update_type, update_interval = :update_interval, type = :type " "WHERE id = :id;"); query_update_feed.bindValue(":title", new_feed->title()); query_update_feed.bindValue(":description", new_feed->description()); query_update_feed.bindValue(":icon", IconFactory::toByteArray(new_feed->icon())); query_update_feed.bindValue(":category", new_parent->id()); query_update_feed.bindValue(":encoding", new_feed->encoding()); query_update_feed.bindValue(":url", new_feed->url()); query_update_feed.bindValue(":protected", (int) new_feed->passwordProtected()); query_update_feed.bindValue(":username", new_feed->username()); query_update_feed.bindValue(":password", new_feed->password()); query_update_feed.bindValue(":update_type", (int) new_feed->autoUpdateType()); query_update_feed.bindValue(":update_interval", new_feed->autoUpdateInitialInterval()); query_update_feed.bindValue(":type", new_feed->type()); query_update_feed.bindValue(":id", original_feed->id()); if (!query_update_feed.exec()) { // Persistent storage update failed, no way to continue now. return false; } // Setup new model data for the original item. original_feed->setTitle(new_feed->title()); original_feed->setDescription(new_feed->description()); original_feed->setIcon(new_feed->icon()); original_feed->setEncoding(new_feed->encoding()); original_feed->setDescription(new_feed->description()); original_feed->setUrl(new_feed->url()); original_feed->setPasswordProtected(new_feed->passwordProtected()); original_feed->setUsername(new_feed->username()); original_feed->setPassword(new_feed->password()); original_feed->setAutoUpdateType(new_feed->autoUpdateType()); original_feed->setAutoUpdateInitialInterval(new_feed->autoUpdateInitialInterval()); original_feed->setType(new_feed->type()); if (original_parent != new_parent) { // User edited category and set it new parent item, // se we need to move the item in the model too. int original_index_of_feed = original_parent->childItems().indexOf(original_feed); int new_index_of_feed = new_parent->childCount(); // Remove the original item from the model... beginRemoveRows(indexForItem(original_parent), original_index_of_feed, original_index_of_feed); original_parent->removeChild(original_feed); endRemoveRows(); // ... and insert it under the new parent. beginInsertRows(indexForItem(new_parent), new_index_of_feed, new_index_of_feed); new_parent->appendChild(original_feed); endInsertRows(); } // Free temporary category from memory. delete new_feed; // Editing is done. return true; } QList<FeedsModelFeed*> FeedsModel::feedsForScheduledUpdate(bool auto_update_now) { QList<FeedsModelFeed*> feeds_for_update; foreach (FeedsModelFeed *feed, allFeeds()) { FeedsModelStandardFeed *std_feed = static_cast<FeedsModelStandardFeed*>(feed); switch (std_feed->autoUpdateType()) { case FeedsModelStandardFeed::DontAutoUpdate: // Do not auto-update this feed ever. continue; case FeedsModelStandardFeed::DefaultAutoUpdate: if (auto_update_now) { feeds_for_update.append(feed); } break; case FeedsModelStandardFeed::SpecificAutoUpdate: default: int remaining_interval = std_feed->autoUpdateRemainingInterval(); if (--remaining_interval <= 0) { // Interval of this feed passed, include this feed in the output list // and reset the interval. feeds_for_update.append(feed); std_feed->setAutoUpdateRemainingInterval(std_feed->autoUpdateInitialInterval()); } else { // Interval did not pass, set new decremented interval and do NOT // include this feed in the output list. std_feed->setAutoUpdateRemainingInterval(remaining_interval); } break; } } return feeds_for_update; } QList<Message> FeedsModel::messagesForFeeds(const QList<FeedsModelFeed*> &feeds) { QList<Message> messages; QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); QSqlQuery query_read_msg(database); query_read_msg.setForwardOnly(true); query_read_msg.prepare("SELECT title, url, author, date_created, contents " "FROM Messages " "WHERE is_deleted = 0 AND feed = :feed;"); foreach (FeedsModelFeed *feed, feeds) { query_read_msg.bindValue(":feed", feed->id()); if (query_read_msg.exec()) { while (query_read_msg.next()) { Message message; message.m_title = query_read_msg.value(0).toString(); message.m_url = query_read_msg.value(1).toString(); message.m_author = query_read_msg.value(2).toString(); message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>()); message.m_contents = query_read_msg.value(4).toString(); messages.append(message); } } } return messages; } int FeedsModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return static_cast<FeedsModelRootItem*>(parent.internalPointer())->columnCount(); } else { return m_rootItem->columnCount(); } } FeedsModelRootItem *FeedsModel::itemForIndex(const QModelIndex &index) const { if (index.isValid() && index.model() == this) { return static_cast<FeedsModelRootItem*>(index.internalPointer()); } else { return m_rootItem; } } FeedsModelCategory *FeedsModel::categoryForIndex(const QModelIndex &index) const { FeedsModelRootItem *item = itemForIndex(index); if (item->kind() == FeedsModelRootItem::Category) { return static_cast<FeedsModelCategory*>(item); } else { return NULL; } } QModelIndex FeedsModel::indexForItem(FeedsModelRootItem *item) const { if (item == NULL || item->kind() == FeedsModelRootItem::RootItem) { // Root item lies on invalid index. return QModelIndex(); } QList<QModelIndex> parents; // Start with root item. parents << indexForItem(m_rootItem); while (!parents.isEmpty()) { QModelIndex active_index = parents.takeFirst(); int row_count = rowCount(active_index); if (row_count > 0) { // This index has children. // Lets take a look if our target item is among them. FeedsModelRootItem *active_item = itemForIndex(active_index); int candidate_index = active_item->childItems().indexOf(item); if (candidate_index >= 0) { // We found our item. return index(candidate_index, 0, active_index); } else { // Item is not found, add all "categories" from active_item. for (int i = 0; i < row_count; i++) { FeedsModelRootItem *possible_category = active_item->child(i); if (possible_category->kind() == FeedsModelRootItem::Category) { parents << index(i, 0, active_index); } } } } } return QModelIndex(); } void FeedsModel::reloadChangedLayout(QModelIndexList list) { while (!list.isEmpty()) { QModelIndex indx = list.takeFirst(); QModelIndex indx_parent = indx.parent(); // Underlying data are changed. emit dataChanged(index(indx.row(), 0, indx_parent), index(indx.row(), FDS_MODEL_COUNTS_INDEX, indx_parent)); if (indx_parent.isValid()) { // Make sure that data of parent are changed too. list.prepend(indx_parent); } } } QStringList FeedsModel::textualFeedIds(const QList<FeedsModelFeed*> &feeds) { QStringList stringy_ids; stringy_ids.reserve(feeds.size()); foreach (FeedsModelFeed *feed, feeds) { stringy_ids.append(QString::number(feed->id())); } return stringy_ids; } void FeedsModel::reloadWholeLayout() { emit layoutAboutToBeChanged(); emit layoutChanged(); } void FeedsModel::loadFromDatabase() { // Delete all childs of the root node and clear them from the memory. qDeleteAll(m_rootItem->childItems()); m_rootItem->clearChildren(); QSqlDatabase database = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); CategoryAssignment categories; FeedAssignment feeds; // Obtain data for categories from the database. QSqlQuery query_categories(database); query_categories.setForwardOnly(true); if (!query_categories.exec("SELECT * FROM Categories;") || query_categories.lastError().isValid()) { qFatal("Query for obtaining categories failed. Error message: '%s'.", qPrintable(query_categories.lastError().text())); } while (query_categories.next()) { // Process this category. FeedsModelCategory::Type type = static_cast<FeedsModelCategory::Type>(query_categories.value(CAT_DB_TYPE_INDEX).toInt()); switch (type) { case FeedsModelCategory::Standard: { CategoryAssignmentItem pair; pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); pair.second = FeedsModelStandardCategory::loadFromRecord(query_categories.record()); categories << pair; break; } case FeedsModelCategory::Feedly: default: break; } } // All categories are now loaded. QSqlQuery query_feeds(database); query_feeds.setForwardOnly(true); if (!query_feeds.exec("SELECT * FROM Feeds;") || query_feeds.lastError().isValid()) { qFatal("Query for obtaining feeds failed."); } while (query_feeds.next()) { // Process this feed. FeedsModelFeed::Type type = static_cast<FeedsModelFeed::Type>(query_feeds.value(FDS_DB_TYPE_INDEX).toInt()); switch (type) { case FeedsModelFeed::StandardAtom10: case FeedsModelFeed::StandardRdf: case FeedsModelFeed::StandardRss0X: case FeedsModelFeed::StandardRss2X: { FeedAssignmentItem pair; pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); pair.second = FeedsModelStandardFeed::loadFromRecord(query_feeds.record()); pair.second->setType(type); feeds << pair; break; } default: break; } } // All data are now obtained, lets create the hierarchy. assembleCategories(categories); assembleFeeds(feeds); } QList<FeedsModelFeed*> FeedsModel::feedsForIndex(const QModelIndex &index) { FeedsModelRootItem *item = itemForIndex(index); return feedsForItem(item); } FeedsModelFeed *FeedsModel::feedForIndex(const QModelIndex &index) { FeedsModelRootItem *item = itemForIndex(index); if (item->kind() == FeedsModelRootItem::Feed) { return static_cast<FeedsModelFeed*>(item); } else { return NULL; } } QList<FeedsModelFeed*> FeedsModel::feedsForIndexes(const QModelIndexList &indexes) { QList<FeedsModelFeed*> feeds; // Get selected feeds for each index. foreach (const QModelIndex &index, indexes) { feeds.append(feedsForIndex(index)); } // Now we obtained all feeds from corresponding indexes. if (indexes.size() != feeds.size()) { // Selection contains duplicate feeds (for // example situation where feed and its parent category are both // selected). So, remove duplicates from the list. qSort(feeds.begin(), feeds.end(), FeedsModelRootItem::lessThan); feeds.erase(std::unique(feeds.begin(), feeds.end(), FeedsModelRootItem::isEqual), feeds.end()); } return feeds; } bool FeedsModel::markFeedsRead(const QList<FeedsModelFeed*> &feeds, int read) { QSqlDatabase db_handle = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); if (!db_handle.transaction()) { qWarning("Starting transaction for feeds read change."); return false; } QSqlQuery query_read_msg(db_handle); query_read_msg.setForwardOnly(true); if (!query_read_msg.prepare(QString("UPDATE Messages SET is_read = :read " "WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(", ")))) { qWarning("Query preparation failed for feeds read change."); db_handle.rollback(); return false; } query_read_msg.bindValue(":read", read); if (!query_read_msg.exec()) { qDebug("Query execution for feeds read change failed."); db_handle.rollback(); } // Commit changes. if (db_handle.commit()) { return true; } else { return db_handle.rollback(); } } bool FeedsModel::markFeedsDeleted(const QList<FeedsModelFeed*> &feeds, int deleted, bool read_only) { QSqlDatabase db_handle = DatabaseFactory::instance()->connection(objectName(), DatabaseFactory::FromSettings); if (!db_handle.transaction()) { qWarning("Starting transaction for feeds clearing."); return false; } QSqlQuery query_delete_msg(db_handle); query_delete_msg.setForwardOnly(true); if (read_only) { if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " "WHERE feed IN (%1) AND is_deleted = 0 AND is_read = 1;").arg(textualFeedIds(feeds).join(", ")))) { qWarning("Query preparation failed for feeds clearing."); db_handle.rollback(); return false; } } else { if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " "WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(", ")))) { qWarning("Query preparation failed for feeds clearing."); db_handle.rollback(); return false; } } query_delete_msg.bindValue(":deleted", deleted); if (!query_delete_msg.exec()) { qDebug("Query execution for feeds clearing failed."); db_handle.rollback(); } // Commit changes. if (db_handle.commit()) { return true; } else { return db_handle.rollback(); } } QHash<int, FeedsModelCategory*> FeedsModel::allCategories() { return categoriesForItem(m_rootItem); } QHash<int, FeedsModelCategory*> FeedsModel::categoriesForItem(FeedsModelRootItem *root) { QHash<int, FeedsModelCategory*> categories; QList<FeedsModelRootItem*> parents; parents.append(root->childItems()); while (!parents.isEmpty()) { FeedsModelRootItem *item = parents.takeFirst(); if (item->kind() == FeedsModelRootItem::Category) { // This item is category, add it to the output list and // scan its children. int category_id = item->id(); FeedsModelCategory *category = static_cast<FeedsModelCategory*>(item); if (!categories.contains(category_id)) { categories.insert(category_id, category); } parents.append(category->childItems()); } } return categories; } QList<FeedsModelFeed*> FeedsModel::allFeeds() { return feedsForItem(m_rootItem); } QList<FeedsModelFeed*> FeedsModel::feedsForItem(FeedsModelRootItem *root) { QList<FeedsModelFeed*> feeds; if (root->kind() == FeedsModelRootItem::Feed) { // Root itself is a FEED. feeds.append(static_cast<FeedsModelFeed*>(root)); } else { // Root itself is a CATEGORY or ROOT item. QList<FeedsModelRootItem*> traversable_items; traversable_items.append(root); // Iterate all nested categories. while (!traversable_items.isEmpty()) { FeedsModelRootItem *active_category = traversable_items.takeFirst(); foreach (FeedsModelRootItem *child, active_category->childItems()) { if (child->kind() == FeedsModelRootItem::Feed) { // This child is feed. feeds.append(static_cast<FeedsModelFeed*>(child)); } else if (child->kind() == FeedsModelRootItem::Category) { // This child is category, add its child feeds too. traversable_items.append(static_cast<FeedsModelCategory*>(child)); } } } } return feeds; } void FeedsModel::assembleFeeds(FeedAssignment feeds) { QHash<int, FeedsModelCategory*> categories = allCategories(); foreach (const FeedAssignmentItem &feed, feeds) { if (feed.first == NO_PARENT_CATEGORY) { // This is top-level feed, add it to the root item. m_rootItem->appendChild(feed.second); } else if (categories.contains(feed.first)) { // This feed belongs to this category. categories.value(feed.first)->appendChild(feed.second); } else { qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title())); } } } void FeedsModel::assembleCategories(CategoryAssignment categories) { QHash<int, FeedsModelRootItem*> assignments; assignments.insert(NO_PARENT_CATEGORY, m_rootItem); // Add top-level categories. while (!categories.isEmpty()) { for (int i = 0; i < categories.size(); i++) { if (assignments.contains(categories.at(i).first)) { // Parent category of this category is already added. assignments.value(categories.at(i).first)->appendChild(categories.at(i).second); // Now, added category can be parent for another categories, add it. assignments.insert(categories.at(i).second->id(), categories.at(i).second); // Remove the category from the list, because it was // added to the final collection. categories.removeAt(i); i--; } } } }