diff --git a/src/resultmodel.cpp b/src/resultmodel.cpp index 50102f6..f45414b 100644 --- a/src/resultmodel.cpp +++ b/src/resultmodel.cpp @@ -1,1065 +1,1089 @@ /* * Copyright (C) 2015, 2016 Ivan Cukic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. * If not, see . */ // Self #include "resultmodel.h" // Qt #include #include #include #include // STL and Boost #include #include -#include +#include // KDE #include #include // Local #include #include #include #include #include "resultset.h" #include "resultwatcher.h" #include "cleaning.h" #include "kactivities/consumer.h" #include #define MAX_CHUNK_LOAD_SIZE 50 #define MAX_RELOAD_CACHE_SIZE 50 #define QDBG qDebug() << "KActivitiesStats(" << (void*)this << ")" namespace KActivities { namespace Stats { class ResultModelPrivate { public: ResultModelPrivate(Query query, const QString &clientId, ResultModel *parent) : cache(this, clientId, query.limit()) , query(query) , watcher(query) , hasMore(true) , q(parent) { using Common::Database; database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); s_privates << this; } ~ResultModelPrivate() { s_privates.removeAll(this); } enum Fetch { FetchReset, // Remove old data and reload FetchReload, // Update all data FetchMore // Load more data if there is any }; class Cache { //_ public: typedef QList Items; Cache(ResultModelPrivate *d, const QString &clientId, int limit) : d(d) , m_countLimit(limit) , m_clientId(clientId) { if (!m_clientId.isEmpty()) { m_configFile = KSharedConfig::openConfig("kactivitymanagerd-statsrc"); - qDebug() << "Configuration activated " << m_configFile->name(); } } ~Cache() { } inline int size() const { return m_items.size(); } - inline void setLinkedResultPosition(const QString &resource, + inline void setLinkedResultPosition(const QString &resourcePath, int position) { if (!m_orderingConfig.isValid()) { qWarning() << "We can not reorder the results, no clientId was specified"; return; } // Preconditions: // - cache is ordered properly, first on the user's desired order, // then on the query specified order - // - the resource that needs to be moved is already in the cache // - the resource that needs to be moved is a linked resource, not // one that comes from the stats (there are overly many // corner-cases that need to be covered in order to support // reordering of the statistics-based resources) // - the new position for the resource is not outside of the cache - qDebug() << "Searching for " << resource; - auto resourcePosition = find(resource); + auto resourcePosition = find(resourcePath); - qDebug() << "Was resource found? " << (bool)resourcePosition; if (resourcePosition) { - qDebug() << "What is the status? " << resourcePosition.iterator->linkStatus(); + if (resourcePosition.index == position) { + return; + } + if (resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { + return; + } } // Lets make a list of linked items - we can only reorder them, // not others QStringList linkedItems; foreach (const ResultSet::Result &item, m_items) { if (item.linkStatus() == ResultSet::Result::NotLinked) break; linkedItems << item.resource(); } - // We can not accept the new position to be outside - // of the linked items area - if (position > linkedItems.size()) { - position = linkedItems.size(); - } - // We have two options: // - we are planning to add an item to the desired position, // but the item is not yet in the model // - we want to move an existing item if (!resourcePosition || resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { - qDebug() << "Trying to reposition a resource that we do not have, or is not linked"; - - linkedItems.insert(position, resource); + linkedItems.insert(position, resourcePath); m_fixedOrderedItems = linkedItems; } else { + // We can not accept the new position to be outside + // of the linked items area + if (position >= linkedItems.size()) { + position = linkedItems.size() - 1; + } - auto oldPosition = linkedItems.indexOf(resource); + Q_ASSERT(resourcePosition.index == linkedItems.indexOf(resourcePath)); + auto oldPosition = linkedItems.indexOf(resourcePath); - kamd::utils::slide_one( + const auto oldLinkedItems = linkedItems; + + kamd::utils::move_one( linkedItems.begin() + oldPosition, linkedItems.begin() + position); + // When we change this, the cache is not valid anymore, + // destinationFor will fail and we can not use it m_fixedOrderedItems = linkedItems; // We are prepared to reorder the cache d->repositionResult(resourcePosition, d->destinationFor(*resourcePosition)); } m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); m_orderingConfig.sync(); // We need to notify others to reload for (const auto& other: s_privates) { if (other != d && other->cache.m_clientId == m_clientId) { other->fetch(FetchReset); } } } inline void debug() const { for (const auto& item: m_items) { qDebug() << "Item: " << item; } } void loadOrderingConfig(const QString &activityTag) { if (!m_configFile) { qDebug() << "Nothing to load - the client id is empty"; return; } - qDebug() << "Loading fixed items - to preserve the ordering"; - m_orderingConfig = KConfigGroup(m_configFile, "ResultModel-OrderingFor-" + m_clientId + activityTag); - m_fixedOrderedItems = m_orderingConfig.readEntry("kactivitiesLinkedItemsOrder", - QStringList()); + if (m_orderingConfig.hasKey("kactivitiesLinkedItemsOrder")) { + // If we have the ordering defined, use it + m_fixedOrderedItems = m_orderingConfig.readEntry("kactivitiesLinkedItemsOrder", + QStringList()); + } else { + // Otherwise, copy the order from the previous activity to this one + m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); + m_orderingConfig.sync(); + + } } private: ResultModelPrivate *const d; QList m_items; int m_countLimit; QString m_clientId; KSharedConfig::Ptr m_configFile; KConfigGroup m_orderingConfig; QStringList m_fixedOrderedItems; friend QDebug operator<< (QDebug out, const Cache &cache) { for (const auto& item: cache.m_items) { out << "Cache item: " << item << "\n"; } return out; } public: inline const QStringList &fixedOrderedItems() const { return m_fixedOrderedItems; } //_ Fancy iterator, find, lowerBound struct FindCacheResult { Cache *const cache; Items::iterator iterator; int index; FindCacheResult(Cache *cache, Items::iterator iterator) : cache(cache) , iterator(iterator) , index(std::distance(cache->m_items.begin(), iterator)) { } operator bool() const { return iterator != cache->m_items.end(); } ResultSet::Result &operator*() const { return *iterator; } ResultSet::Result *operator->() const { return &(*iterator); } - - // const ResultSet::Result &operator*() const - // { - // return *iterator; - // } - // - // const ResultSet::Result *operator->() const - // { - // return &(*iterator); - // } }; inline FindCacheResult find(const QString &resource) { using namespace kamd::utils::member_matcher; using boost::find_if; return FindCacheResult( this, find_if(m_items, member(&ResultSet::Result::resource) == resource)); } - template - inline FindCacheResult lowerBound(What &&what, Predicate &&predicate) - { - return FindCacheResult( - this, boost::lower_bound(m_items, std::forward(what), - std::forward(predicate))); - } - template - inline FindCacheResult lowerBound(Predicate &&predicate) + inline FindCacheResult lowerBoundWithSkippedResource(Predicate &&lessThanPredicate) { using namespace kamd::utils::member_matcher; - return FindCacheResult( - this, boost::lower_bound(m_items, _, - std::forward(predicate))); - } - //^ + const int count = boost::count_if(m_items, + [&] (const ResultSet::Result &result) { + return lessThanPredicate(result, _); + }); - inline int indexOf(const FindCacheResult &result) - { - return std::distance(m_items.begin(), result.iterator); + return FindCacheResult(this, m_items.begin() + count); + + + // using namespace kamd::utils::member_matcher; + // + // const auto position = + // std::lower_bound(m_items.begin(), m_items.end(), + // _, std::forward(lessThanPredicate)); + // + // // We seem to have found the position for the item. + // // The problem is that we might have found the same position + // // we were previously at. Since this function is usually used + // // to reposition the result, we might not be in a completely + // // sorted collection, so the next item(s) could be less than us. + // // We could do this with count_if, but it would be slower + // + // if (position >= m_items.cend() - 1) { + // return FindCacheResult(this, position); + // + // } else if (lessThanPredicate(_, *(position + 1))) { + // return FindCacheResult(this, position); + // + // } else { + // return FindCacheResult( + // this, std::lower_bound(position + 1, m_items.end(), + // _, std::forward(lessThanPredicate))); + // } } + //^ inline void insertAt(const FindCacheResult &at, const ResultSet::Result &result) { m_items.insert(at.iterator, result); } inline void removeAt(const FindCacheResult &at) { m_items.removeAt(at.index); } inline const ResultSet::Result &operator[] (int index) const { return m_items[index]; } inline void clear() { if (m_items.size() == 0) return; d->q->beginRemoveRows(QModelIndex(), 0, m_items.size()); m_items.clear(); d->q->endRemoveRows(); } // Algorithm to calculate the edit operations to allow //_ replaceing items without model reset inline void replace(const Items &newItems, int from = 0) { using namespace kamd::utils::member_matcher; #if 0 QDBG << "===\nOld items {"; for (const auto& item: m_items) { QDBG << item; } QDBG << "}"; QDBG << "New items to be added at " << from << " {"; for (const auto& item: newItems) { QDBG << item; } QDBG << "}"; #endif // Based on 'The string to string correction problem // with block moves' paper by Walter F. Tichy // // In essence, it goes like this: // // Take the first element from the new list, and try to find // it in the old one. If you can not find it, it is a new item // item - send the 'inserted' event. // If you did find it, test whether the following items also // match. This detects blocks of items that have moved. // // In this example, we find 'b', and then detect the rest of the // moved block 'b' 'c' 'd' // // Old items: a[b c d]e f g // ^ // / // New items: [b c d]a f g // // After processing one block, just repeat until the end of the // new list is reached. // // Then remove all remaining elements from the old list. // // The main addition here compared to the original papers is that // our 'strings' can not hold two instances of the same element, // and that we support updating from arbitrary position. auto newBlockStart = newItems.cbegin(); // How many items should we add? // This should remove the need for post-replace-trimming // in the case where somebody called this with too much new items. const int maxToReplace = m_countLimit - from; if (maxToReplace <= 0) return; const auto newItemsEnd = newItems.size() <= maxToReplace ? newItems.cend() : newItems.cbegin() + maxToReplace; // Finding the blocks until we reach the end of the newItems list // // from = 4 // Old items: X Y Z U a b c d e f g // ^ oldBlockStart points to the first element // of the currently processed block in the old list // // New items: _ _ _ _ b c d a f g // ^ newBlockStartIndex is the index of the first // element of the block that is currently being // processed (with 'from' offset) while (newBlockStart != newItemsEnd) { const int newBlockStartIndex = from + std::distance(newItems.cbegin(), newBlockStart); const auto oldBlockStart = std::find_if( m_items.begin() + from, m_items.end(), member(&ResultSet::Result::resource) == newBlockStart->resource()); if (oldBlockStart == m_items.end()) { // This item was not found in the old cache, so we are // inserting a new item at the same position it had in // the newItems array d->q->beginInsertRows(QModelIndex(), newBlockStartIndex, newBlockStartIndex); m_items.insert(newBlockStartIndex, *newBlockStart); d->q->endInsertRows(); // This block contained only one item, move on to find // the next block - it starts from the next item ++newBlockStart; } else { // We are searching for a block of matching items. // This is a reimplementation of std::mismatch that // accepts two complete ranges that is available only // since C++14, so we can not use it. auto newBlockEnd = newBlockStart; auto oldBlockEnd = oldBlockStart; while (newBlockEnd != newItemsEnd && oldBlockEnd != m_items.end() && newBlockEnd->resource() == oldBlockEnd->resource()) { ++newBlockEnd; ++oldBlockEnd; } // We have found matching blocks // [newBlockStart, newBlockEnd) and [oldBlockStart, newBlockEnd) const int oldBlockStartIndex = std::distance(m_items.begin() + from, oldBlockStart); const int blockSize = std::distance(oldBlockStart, oldBlockEnd); if (oldBlockStartIndex != newBlockStartIndex) { // If these blocks do not have the same start, // we need to send the move event. // Note: If there is a crash here, it means we // are getting a bad query which has duplicate // results d->q->beginMoveRows(QModelIndex(), oldBlockStartIndex, oldBlockStartIndex + blockSize - 1, QModelIndex(), newBlockStartIndex); // Moving the items from the old location to the new one kamd::utils::slide( oldBlockStart, oldBlockEnd, m_items.begin() + newBlockStartIndex); d->q->endMoveRows(); } // Skip all the items in this block, and continue with // the search newBlockStart = newBlockEnd; } } // We have avoided the need for trimming for the most part, // but if the newItems list was shorter than needed, we still // need to trim the rest. trim(from + newItems.size()); // Check whether we got an item representing a non-existent file, // if so, schedule its removal from the database for (const auto &item: newItems) { if (item.resource().startsWith('/') && !QFile(item.resource()).exists()) { d->q->forgetResource(item.resource()); } } } //^ inline void trim() { trim(m_countLimit); } inline void trim(int limit) { if (m_items.size() <= limit) return; // Example: // limit is 5, // current cache (0, 1, 2, 3, 4, 5, 6, 7), size = 8 // We need to delete from 5 to 7 d->q->beginRemoveRows(QModelIndex(), limit, m_items.size() - 1); m_items.erase(m_items.begin() + limit, m_items.end()); d->q->endRemoveRows(); } } cache; //^ struct FixedItemsLessThan { //_ Compartor that orders the linked items by user-specified order typedef kamd::utils::member_matcher::placeholder placeholder; FixedItemsLessThan(const Cache &cache, const QString &matchResource = QString()) : cache(cache), matchResource(matchResource) { } bool lessThan(const QString &leftResource, const QString &rightResource) const { - const bool hasLeft = cache.fixedOrderedItems().contains(leftResource); - const bool hasRight = cache.fixedOrderedItems().contains(rightResource); + const auto fixedOrderedItems = cache.fixedOrderedItems(); + + const auto indexLeft = fixedOrderedItems.indexOf(leftResource); + const auto indexRight = fixedOrderedItems.indexOf(rightResource); + + const bool hasLeft = indexLeft != -1; + const bool hasRight = indexRight != -1; return ( hasLeft && !hasRight) ? true : (!hasLeft && hasRight) ? false : - ( hasLeft && hasRight) ? cache.fixedOrderedItems().indexOf(leftResource) < - cache.fixedOrderedItems().indexOf(rightResource) : + ( hasLeft && hasRight) ? indexLeft < indexRight : leftResource < rightResource; } template bool operator() (const T &left, placeholder) const { return lessThan(left.resource(), matchResource); } template bool operator() (placeholder, const T &right) const { return lessThan(matchResource, right.resource()); } template bool operator() (const T &left, const V &right) const { return lessThan(left.resource(), right.resource()); } const Cache &cache; const QString matchResource; //^ }; inline Cache::FindCacheResult destinationFor(const ResultSet::Result &result) { using namespace kamd::utils::member_matcher; using namespace Terms; const auto resource = result.resource(); const auto score = result.score(); const auto firstUpdate = result.firstUpdate(); const auto lastUpdate = result.lastUpdate(); const auto linkStatus = result.linkStatus(); #define ORDER_BY(Field) member(&ResultSet::Result::Field) > Field #define ORDER_BY_FULL(Field) \ (query.selection() == Terms::AllResources ? \ - cache.lowerBound(FixedItemsLessThan(cache, resource) \ + cache.lowerBoundWithSkippedResource( \ + FixedItemsLessThan(cache, resource) \ && ORDER_BY(linkStatus) \ && ORDER_BY(Field) \ && ORDER_BY(resource)) : \ - cache.lowerBound(FixedItemsLessThan(cache, resource) \ + cache.lowerBoundWithSkippedResource( \ + FixedItemsLessThan(cache, resource) \ && ORDER_BY(Field) \ && ORDER_BY(resource)) \ ) const auto destination = query.ordering() == HighScoredFirst ? ORDER_BY_FULL(score): query.ordering() == RecentlyUsedFirst ? ORDER_BY_FULL(lastUpdate): query.ordering() == RecentlyCreatedFirst ? ORDER_BY_FULL(firstUpdate): /* otherwise */ ORDER_BY_FULL(resource) ; #undef ORDER_BY #undef ORDER_BY_FULL return destination; } inline void removeResult(const Cache::FindCacheResult &result) { q->beginRemoveRows(QModelIndex(), result.index, result.index); cache.removeAt(result); q->endRemoveRows(); if (query.selection() != Terms::LinkedResources) { fetch(cache.size(), 1); } } inline void repositionResult(const Cache::FindCacheResult &result, const Cache::FindCacheResult &destination) { - using kamd::utils::slide_one; - // We already have the resource in the cache // So, it is the time for a reshuffle - const int currentIndex = result.index; + const int oldPosition = result.index; + int position = destination.index; + + q->dataChanged(q->index(oldPosition), q->index(oldPosition)); - q->dataChanged(q->index(currentIndex), q->index(currentIndex)); + if (oldPosition == position) { + return; + } + + if (position > oldPosition) { + position++; + } bool moving - = q->beginMoveRows(QModelIndex(), currentIndex, currentIndex, - QModelIndex(), destination.index); + = q->beginMoveRows(QModelIndex(), oldPosition, oldPosition, + QModelIndex(), position); - slide_one(result.iterator, destination.iterator); + kamd::utils::move_one(result.iterator, destination.iterator); if (moving) { q->endMoveRows(); } } void reload() { fetch(FetchReload); } void init() { using namespace std::placeholders; QObject::connect( &watcher, &ResultWatcher::resultScoreUpdated, q, std::bind(&ResultModelPrivate::onResultScoreUpdated, this, _1, _2, _3, _4)); QObject::connect( &watcher, &ResultWatcher::resultRemoved, q, std::bind(&ResultModelPrivate::onResultRemoved, this, _1)); QObject::connect( &watcher, &ResultWatcher::resultLinked, q, std::bind(&ResultModelPrivate::onResultLinked, this, _1)); QObject::connect( &watcher, &ResultWatcher::resultUnlinked, q, std::bind(&ResultModelPrivate::onResultUnlinked, this, _1)); QObject::connect( &watcher, &ResultWatcher::resourceTitleChanged, q, std::bind(&ResultModelPrivate::onResourceTitleChanged, this, _1, _2)); QObject::connect( &watcher, &ResultWatcher::resourceMimetypeChanged, q, std::bind(&ResultModelPrivate::onResourceMimetypeChanged, this, _1, _2)); QObject::connect( &watcher, &ResultWatcher::resultsInvalidated, q, std::bind(&ResultModelPrivate::reload, this)); if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { QObject::connect( &activities, &KActivities::Consumer::currentActivityChanged, q, std::bind(&ResultModelPrivate::onCurrentActivityChanged, this, _1)); } fetch(FetchReset); } void fetch(int from, int count) { using namespace Terms; if (from + count > query.limit()) { count = query.limit() - from; } if (count <= 0) return; // In order to see whether there are more results, we need to pass // the count increased by one ResultSet results(query | Offset(from) | Limit(count + 1)); auto it = results.begin(); Cache::Items newItems; while (count --> 0 && it != results.end()) { newItems << *it; ++it; } hasMore = (it != results.end()); // We need to sort the new items for the linked resources // user-defined reordering if (query.selection() != Terms::UsedResources) { std::stable_sort(newItems.begin(), newItems.end(), FixedItemsLessThan(cache)); } cache.replace(newItems, from); } void fetch(Fetch mode) { if (mode == FetchReset) { // Removing the previously cached data // and loading all from scratch cache.clear(); const QString activityTag = query.activities().contains(CURRENT_ACTIVITY_TAG) ? ("-ForActivity-" + activities.currentActivity()) : "-ForAllActivities"; cache.loadOrderingConfig(activityTag); fetch(0, MAX_CHUNK_LOAD_SIZE); } else if (mode == FetchReload) { if (cache.size() > MAX_RELOAD_CACHE_SIZE) { // If the cache is big, we are pretending // we were asked to reset the model fetch(FetchReset); } else { // We are only updating the currently // cached items, nothing more fetch(0, cache.size()); } } else { // FetchMore // Load a new batch of data fetch(cache.size(), MAX_CHUNK_LOAD_SIZE); } } void onResultScoreUpdated(const QString &resource, double score, uint lastUpdate, uint firstUpdate) { - using boost::lower_bound; - QDBG << "ResultModelPrivate::onResultScoreUpdated " << "result added:" << resource << "score:" << score << "last:" << lastUpdate << "first:" << firstUpdate; // This can also be called when the resource score // has been updated, so we need to check whether // we already have it in the cache const auto result = cache.find(resource); ResultSet::Result::LinkStatus linkStatus = result ? result->linkStatus() : query.selection() != Terms::UsedResources ? ResultSet::Result::Unknown : query.selection() != Terms::LinkedResources ? ResultSet::Result::Linked : ResultSet::Result::NotLinked; if (result) { // We are only updating a result we already had, // lets fill out the data and send the update signal. // Move it if necessary. auto &item = *result.iterator; item.setScore(score); item.setLinkStatus(linkStatus); item.setLastUpdate(lastUpdate); item.setFirstUpdate(firstUpdate); - const auto destination = destinationFor(item); - - repositionResult(result, destination); + repositionResult(result, destinationFor(item)); } else { // We do not have the resource in the cache, // lets fill out the data and insert it // at the desired position ResultSet::Result result; result.setResource(resource); result.setTitle(" "); result.setMimetype(" "); fillTitleAndMimetype(result); result.setScore(score); result.setLinkStatus(linkStatus); result.setLastUpdate(lastUpdate); result.setFirstUpdate(firstUpdate); const auto destination = destinationFor(result); q->beginInsertRows(QModelIndex(), destination.index, destination.index); cache.insertAt(destination, result); q->endInsertRows(); cache.trim(); } } void onResultRemoved(const QString &resource) { const auto result = cache.find(resource); if (!result) return; if (query.selection() == Terms::UsedResources || result->linkStatus() != ResultSet::Result::Linked) { removeResult(result); } } void onResultLinked(const QString &resource) { if (query.selection() != Terms::UsedResources) { onResultScoreUpdated(resource, 0, 0, 0); } } void onResultUnlinked(const QString &resource) { const auto result = cache.find(resource); if (!result) return; if (query.selection() == Terms::LinkedResources) { removeResult(result); } else if (query.selection() == Terms::AllResources) { result->setLinkStatus(ResultSet::Result::NotLinked); repositionResult(result, destinationFor(*result)); } } Query query; ResultWatcher watcher; bool hasMore; KActivities::Consumer activities; Common::Database::Ptr database; //_ Title and mimetype functions void fillTitleAndMimetype(ResultSet::Result &result) { auto query = database->execQuery( "SELECT " "title, mimetype " "FROM " "ResourceInfo " "WHERE " "targettedResource = '" + result.resource() + "'" ); // Only one item at most for (const auto &item: query) { result.setTitle(item["title"].toString()); result.setMimetype(item["mimetype"].toString()); } } void onResourceTitleChanged(const QString &resource, const QString &title) { const auto result = cache.find(resource); if (!result) return; result->setTitle(title); q->dataChanged(q->index(result.index), q->index(result.index)); } void onResourceMimetypeChanged(const QString &resource, const QString &mimetype) { // TODO: This can add or remove items from the model const auto result = cache.find(resource); if (!result) return; result->setMimetype(mimetype); q->dataChanged(q->index(result.index), q->index(result.index)); } //^ void onCurrentActivityChanged(const QString &activity) { Q_UNUSED(activity); // If the current activity has changed, and // the query lists items for the ':current' one, // reset the model (not a simple refresh this time) if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { fetch(FetchReset); } } private: ResultModel *const q; static QList s_privates; }; QList ResultModelPrivate::s_privates; ResultModel::ResultModel(Query query, QObject *parent) : QAbstractListModel(parent) , d(new ResultModelPrivate(query, QString(), this)) { d->init(); } ResultModel::ResultModel(Query query, const QString &clientId, QObject *parent) : QAbstractListModel(parent) , d(new ResultModelPrivate(query, clientId, this)) { d->init(); } ResultModel::~ResultModel() { delete d; } QHash ResultModel::roleNames() const { return { { ResourceRole , "resource" }, { TitleRole , "title" }, { ScoreRole , "score" }, { FirstUpdateRole , "created" }, { LastUpdateRole , "modified" }, { LinkStatusRole , "linkStatus" }, { LinkedActivitiesRole , "linkedActivities" } }; } QVariant ResultModel::data(const QModelIndex &item, int role) const { const auto row = item.row(); if (row < 0 || row >= d->cache.size()) { return QVariant(); } const auto &result = d->cache[row]; return role == Qt::DisplayRole ? ( result.title() + " " + result.resource() + " - " + QString::number(result.linkStatus()) + " - " + QString::number(result.score()) ) : role == ResourceRole ? result.resource() : role == TitleRole ? result.title() : role == ScoreRole ? result.score() : role == FirstUpdateRole ? result.firstUpdate() : role == LastUpdateRole ? result.lastUpdate() : role == LinkStatusRole ? result.linkStatus() : role == LinkedActivitiesRole ? result.linkedActivities() : QVariant() ; } QVariant ResultModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(section); Q_UNUSED(orientation); Q_UNUSED(role); return QVariant(); } int ResultModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : d->cache.size(); } void ResultModel::fetchMore(const QModelIndex &parent) { if (parent.isValid()) return; d->fetch(ResultModelPrivate::FetchMore); } bool ResultModel::canFetchMore(const QModelIndex &parent) const { return parent.isValid() ? false : d->cache.size() >= d->query.limit() ? false : d->hasMore; } void ResultModel::forgetResource(const QString &resource) { foreach (const QString &activity, d->query.activities()) { foreach (const QString &agent, d->query.agents()) { Stats::forgetResource( activity, agent == CURRENT_AGENT_TAG ? QCoreApplication::applicationName() : agent, resource); } } } void ResultModel::forgetResource(int row) { if (row >= d->cache.size()) return; foreach (const QString &activity, d->query.activities()) { foreach (const QString &agent, d->query.agents()) { Stats::forgetResource( activity, agent == CURRENT_AGENT_TAG ? QCoreApplication::applicationName() : agent, d->cache[row].resource()); } } } void ResultModel::forgetAllResources() { Stats::forgetResources(d->query); } void ResultModel::setResultPosition(const QString &resource, int position) { d->cache.setLinkedResultPosition(resource, position); } void ResultModel::sortItems(Qt::SortOrder sortOrder) { // TODO Q_UNUSED(sortOrder); } void ResultModel::linkToActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) { d->watcher.linkToActivity(resource, activity, agent); } void ResultModel::unlinkFromActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) { d->watcher.unlinkFromActivity(resource, activity, agent); } } // namespace Stats } // namespace KActivities // #include "resourcemodel.moc" diff --git a/src/utils/slide.h b/src/utils/slide.h index 154af98..53bc253 100644 --- a/src/utils/slide.h +++ b/src/utils/slide.h @@ -1,51 +1,70 @@ /* * Copyright (C) 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * 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, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef UTILS_SLIDE_H #define UTILS_SLIDE_H #include // Inspired by C++ Seasoning talk by Sean Parent namespace kamd { namespace utils { template void slide(Iterator f, Iterator l, Iterator p) { if (p < f) { std::rotate(p, f, l); } else if (l < p) { std::rotate(f, l, p); } } template void slide_one(Iterator f, Iterator p) { slide(f, f + 1, p); } +template +void move_one(Iterator from, Iterator to) +{ + if (from < to) { + while (from != to) { + using std::swap; + swap(*from, *(from + 1)); + ++from; + } + } else { + while (from != to) { + using std::swap; + swap(*from, *(from - 1)); + --from; + } + + } +} + } // namespace utils } // namespace kamd #endif // UTILS_SLIDE_H