[Merge] lp:~boiko/history-service/keep_models_sorted into lp:history-service

Tiago Salem Herrmann tiago.herrmann at canonical.com
Fri Sep 12 19:55:21 UTC 2014


Review: Needs Information



Diff comments:

> === modified file 'Ubuntu/History/CMakeLists.txt'
> --- Ubuntu/History/CMakeLists.txt	2014-09-09 23:16:29 +0000
> +++ Ubuntu/History/CMakeLists.txt	2014-09-09 23:16:29 +0000
> @@ -4,30 +4,30 @@
>      contactmatcher.cpp
>      historyeventmodel.cpp
>      historygroupedeventsmodel.cpp
> -    historythreadgroupingproxymodel.cpp
> -    historyqmltexteventattachment.cpp
> +    historygroupedthreadsmodel.cpp
> +    historymodel.cpp
>      historyqmlfilter.cpp
>      historyqmlintersectionfilter.cpp
>      historyqmlplugin.cpp
>      historyqmlsort.cpp
> +    historyqmltexteventattachment.cpp
>      historyqmlunionfilter.cpp
>      historythreadmodel.cpp
> -    sortproxymodel.cpp
>      )
>  
>  set(plugin_HDRS
>      contactmatcher_p.h
>      historyeventmodel.h
>      historygroupedeventsmodel.h
> -    historythreadgroupingproxymodel.h
> -    historyqmltexteventattachment.h
> +    historygroupedthreadsmodel.h
> +    historymodel.cpp
>      historyqmlfilter.h
>      historyqmlintersectionfilter.h
>      historyqmlplugin.h
>      historyqmlsort.h
> +    historyqmltexteventattachment.h
>      historyqmlunionfilter.h
>      historythreadmodel.h
> -    sortproxymodel.h
>  )
>  
>  include_directories(
> 
> === modified file 'Ubuntu/History/historyeventmodel.cpp'
> --- Ubuntu/History/historyeventmodel.cpp	2014-09-09 23:16:29 +0000
> +++ Ubuntu/History/historyeventmodel.cpp	2014-09-09 23:16:29 +0000
> @@ -20,44 +20,23 @@
>   */
>  
>  #include "historyeventmodel.h"
> -#include "historyqmlfilter.h"
> -#include "historyqmlsort.h"
>  #include "eventview.h"
> -#include "intersectionfilter.h"
> +#include "historyqmltexteventattachment.h"
>  #include "manager.h"
> -#include "thread.h"
> -#include "textevent.h"
> -#include "texteventattachment.h"
> -#include "historyqmltexteventattachment.h"
> -#include "phoneutils_p.h"
> -#include "thread.h"
> -#include "types.h"
> -#include "voiceevent.h"
> -#include "contactmatcher_p.h"
> +#include <QDBusMetaType>
>  #include <QDebug>
>  #include <QTimerEvent>
> -#include <QDBusMetaType>
>  
>  HistoryEventModel::HistoryEventModel(QObject *parent) :
> -    QAbstractListModel(parent), mCanFetchMore(true), mFilter(0),
> -    mSort(new HistoryQmlSort(this)), mType(HistoryThreadModel::EventTypeText),
> -    mMatchContacts(false), mEventWritingTimer(0), mUpdateTimer(0)
> +    HistoryModel(parent), mCanFetchMore(true), mEventWritingTimer(0)
>  {
> -    connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
> -    connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()));
> -    connect(this, SIGNAL(modelReset()), this, SIGNAL(countChanged()));
> -
>      // configure the roles
> -    mRoles[AccountIdRole] = "accountId";
> -    mRoles[ThreadIdRole] = "threadId";
> -    mRoles[ParticipantsRole] = "participants";
> -    mRoles[TypeRole] = "type";
> +    mRoles = HistoryModel::roleNames();
>      mRoles[EventIdRole] = "eventId";
>      mRoles[SenderIdRole] = "senderId";
>      mRoles[TimestampRole] = "timestamp";
>      mRoles[DateRole] = "date";
>      mRoles[NewEventRole] = "newEvent";
> -    mRoles[PropertiesRole] = "properties";
>      mRoles[TextMessageRole] = "textMessage";
>      mRoles[TextMessageTypeRole] = "textMessageType";
>      mRoles[TextMessageStatusRole] = "textMessageStatus";
> @@ -66,13 +45,6 @@
>      mRoles[TextReadSubjectRole] = "textSubject";
>      mRoles[CallMissedRole] = "callMissed";
>      mRoles[CallDurationRole] = "callDuration";
> -
> -    connect(ContactMatcher::instance(),
> -            SIGNAL(contactInfoChanged(QString,QVariantMap)),
> -            SLOT(onContactInfoChanged(QString,QVariantMap)));
> -
> -    // create the view and get some objects
> -    triggerQueryUpdate();
>  }
>  
>  int HistoryEventModel::rowCount(const QModelIndex &parent) const
> @@ -90,8 +62,12 @@
>          return QVariant();
>      }
>  
> -    return eventData(mEvents[index.row()], role);
> -}
> +    QVariant result = eventData(mEvents[index.row()], role);
> +    if (result.isNull()) {
> +        result = HistoryModel::data(index, role);
> +    }
> +    return result;
> + }
>  
>  QVariant HistoryEventModel::eventData(const History::Event &event, int role) const
>  {
> @@ -110,22 +86,6 @@
>      QVariant result;
>  
>      switch (role) {
> -    case AccountIdRole:
> -        result = event.accountId();
> -        break;
> -    case ThreadIdRole:
> -        result = event.threadId();
> -        break;
> -    case ParticipantsRole:
> -        if (mMatchContacts) {
> -            result = ContactMatcher::instance()->contactInfo(event.participants());
> -        } else {
> -            result = event.participants();
> -        }
> -        break;
> -    case TypeRole:
> -        result = event.type();
> -        break;
>      case EventIdRole:
>          result = event.eventId();
>          break;
> @@ -215,7 +175,6 @@
>  
>      History::Events events = fetchNextPage();
>  
> -    qDebug() << "Got events:" << events.count();
>      if (events.isEmpty()) {
>          mCanFetchMore = false;
>          Q_EMIT canFetchMoreChanged();
> @@ -231,97 +190,6 @@
>      return mRoles;
>  }
>  
> -HistoryQmlFilter *HistoryEventModel::filter() const
> -{
> -    return mFilter;
> -}
> -
> -void HistoryEventModel::setFilter(HistoryQmlFilter *value)
> -{
> -    if (mFilter) {
> -        mFilter->disconnect(this);
> -    }
> -
> -    mFilter = value;
> -    if (mFilter) {
> -        connect(mFilter,
> -                SIGNAL(filterChanged()),
> -                SLOT(triggerQueryUpdate()));
> -    }
> -
> -    Q_EMIT filterChanged();
> -    triggerQueryUpdate();
> -}
> -
> -HistoryQmlSort *HistoryEventModel::sort() const
> -{
> -    return mSort;
> -}
> -
> -void HistoryEventModel::setSort(HistoryQmlSort *value)
> -{
> -    // disconnect the previous sort
> -    if (mSort) {
> -        mSort->disconnect(this);
> -    }
> -
> -    mSort = value;
> -    if (mSort) {
> -        connect(mSort,
> -                SIGNAL(sortChanged()),
> -                SLOT(triggerQueryUpdate()));
> -    }
> -
> -    Q_EMIT sortChanged();
> -    triggerQueryUpdate();
> -}
> -
> -HistoryThreadModel::EventType HistoryEventModel::type() const
> -{
> -    return mType;
> -}
> -
> -void HistoryEventModel::setType(HistoryThreadModel::EventType value)
> -{
> -    mType = value;
> -    Q_EMIT typeChanged();
> -    triggerQueryUpdate();
> -}
> -
> -bool HistoryEventModel::matchContacts() const
> -{
> -    return mMatchContacts;
> -}
> -
> -void HistoryEventModel::setMatchContacts(bool value)
> -{
> -    mMatchContacts = value;
> -    Q_EMIT matchContactsChanged();
> -
> -    // mark all indexes as changed
> -    if (rowCount() > 0) {
> -        Q_EMIT dataChanged(index(0), index(rowCount()-1));
> -    }
> -}
> -
> -QString HistoryEventModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
> -{
> -    if (participants.isEmpty()) {
> -        return QString::null;
> -    }
> -
> -    History::Thread thread = History::Manager::instance()->threadForParticipants(accountId,
> -                                                                                 (History::EventType)eventType,
> -                                                                                 participants,
> -                                                                                 (History::MatchFlags)matchFlags,
> -                                                                                 create);
> -    if (!thread.isNull()) {
> -        return thread.threadId();
> -    }
> -
> -    return QString::null;
> -}
> -
>  bool HistoryEventModel::removeEvent(const QString &accountId, const QString &threadId, const QString &eventId, int eventType)
>  {
>      History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId);
> @@ -333,7 +201,7 @@
>      History::TextEvent textEvent;
>      History::Event event = History::Manager::instance()->getSingleEvent((History::EventType)eventType, accountId, threadId, eventId);
>      if (event.type() != History::EventTypeText) {
> -        qDebug() << "Trying to remove an attachment from a non text event";
> +        qWarning() << "Trying to remove an attachment from a non text event";
>          return false;
>      }
>      QVariantMap properties = event.properties();
> @@ -347,7 +215,7 @@
>          }
>      }
>      if (count == attachmentProperties.size()) {
> -        qDebug() << "No attachment found for id " << attachmentId;
> +        qWarning() << "No attachment found for id " << attachmentId;
>          return false;
>      }
>      properties[History::FieldAttachments] = QVariant::fromValue(newAttachmentProperties);
> @@ -435,18 +303,17 @@
>          return;
>      }
>  
> -    // filter the list for items already in the model
> -    History::Events filteredEvents;
>      Q_FOREACH(const History::Event &event, events) {
> -        if (!mEvents.contains(event)) {
> -            filteredEvents << event;
> +        // if the event is already on the model, skip it
> +        if (mEvents.contains(event)) {
> +            continue;
>          }
> +
> +        int pos = positionForItem(event.properties());
> +        beginInsertRows(QModelIndex(), pos, pos);
> +        mEvents.insert(pos, event);
> +        endInsertRows();
>      }
> -
> -    //FIXME: handle sorting
> -    beginInsertRows(QModelIndex(), mEvents.count(), mEvents.count() + filteredEvents.count() - 1);
> -    mEvents << filteredEvents;
> -    endInsertRows();
>  }
>  
>  void HistoryEventModel::onEventsModified(const History::Events &events)
> @@ -489,36 +356,9 @@
>      // should be handle internally in History::EventView?
>  }
>  
> -void HistoryEventModel::onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo)
> -{
> -    Q_UNUSED(contactInfo)
> -    if (!mMatchContacts) {
> -        return;
> -    }
> -
> -    QList<QModelIndex> changedIndexes;
> -    int count = rowCount();
> -    for (int i = 0; i < count; ++i) {
> -        // WARNING: do not use mEvents directly to verify which indexes to change as there is the
> -        // HistoryGroupedEventsModel which is based on this model and handles the items in a different way
> -        QModelIndex idx = index(i);
> -        QVariantMap eventProperties = idx.data(PropertiesRole).toMap();
> -        QStringList participants = eventProperties[History::FieldParticipants].toStringList();
> -        Q_FOREACH(const QString &participant, participants) {
> -            if (PhoneUtils::comparePhoneNumbers(participant, phoneNumber)) {
> -                changedIndexes << idx;
> -            }
> -        }
> -    }
> -
> -    // now emit the dataChanged signal to all changed indexes
> -    Q_FOREACH(const QModelIndex &idx, changedIndexes) {
> -        Q_EMIT dataChanged(idx, idx);
> -    }
> -}
> -
>  void HistoryEventModel::timerEvent(QTimerEvent *event)
>  {
> +    HistoryModel::timerEvent(event);
>      if (event->timerId() == mEventWritingTimer) {
>          killTimer(mEventWritingTimer);
>          mEventWritingTimer = 0;
> @@ -532,10 +372,6 @@
>              qDebug() << "... succeeded!";
>              mEventWritingQueue.clear();
>          }
> -    } else if (event->timerId() == mUpdateTimer) {
> -        killTimer(mUpdateTimer);
> -        mUpdateTimer = 0;
> -        updateQuery();
>      }
>  }
>  
> @@ -543,21 +379,3 @@
>  {
>      return mView->nextPage();
>  }
> -
> -QVariant HistoryEventModel::get(int row) const
> -{
> -    if (row >= this->rowCount() || row < 0) {
> -        return QVariant();
> -    }
> -
> -    return QVariant(mEvents[row].properties());
> -}
> -
> -void HistoryEventModel::triggerQueryUpdate()
> -{
> -    if (mUpdateTimer) {
> -        killTimer(mUpdateTimer);
> -    }
> -    // delay the loading of the model data until the settings settle down
> -    mUpdateTimer = startTimer(100);
> -}
> 
> === modified file 'Ubuntu/History/historyeventmodel.h'
> --- Ubuntu/History/historyeventmodel.h	2014-09-09 23:16:29 +0000
> +++ Ubuntu/History/historyeventmodel.h	2014-09-09 23:16:29 +0000
> @@ -22,32 +22,22 @@
>  #ifndef HISTORYEVENTMODEL_H
>  #define HISTORYEVENTMODEL_H
>  
> -#include <QAbstractListModel>
> +#include "historymodel.h"
> +#include "textevent.h"
> +#include "voiceevent.h"
>  #include <QStringList>
> -#include "historythreadmodel.h"
>  
> -class HistoryEventModel : public QAbstractListModel
> +class HistoryEventModel : public HistoryModel
>  {
>      Q_OBJECT
> -    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
> -    Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged)
> -    Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged)
> -    Q_PROPERTY(HistoryThreadModel::EventType type READ type WRITE setType NOTIFY typeChanged)
> -    Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
> -    Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
> -    Q_ENUMS(Role)
> +    Q_ENUMS(EventRole)
>  public:
> -    enum Role {
> -        AccountIdRole = Qt::UserRole,
> -        ThreadIdRole,
> -        ParticipantsRole,
> -        TypeRole,
> -        EventIdRole,
> +    enum EventRole {
> +        EventIdRole = HistoryModel::LastRole,
>          SenderIdRole,
>          TimestampRole,
>          DateRole,
>          NewEventRole,
> -        PropertiesRole,
>          TextMessageRole,
>          TextMessageTypeRole,
>          TextMessageStatusRole,
> @@ -56,7 +46,7 @@
>          TextMessageAttachmentsRole,
>          CallMissedRole,
>          CallDurationRole,
> -        LastRole
> +        LastEventRole
>      };
>  
>      explicit HistoryEventModel(QObject *parent = 0);
> @@ -70,45 +60,15 @@
>  
>      virtual QHash<int, QByteArray> roleNames() const;
>  
> -    HistoryQmlFilter *filter() const;
> -    void setFilter(HistoryQmlFilter *value);
> -
> -    HistoryQmlSort *sort() const;
> -    void setSort(HistoryQmlSort *value);
> -
> -    HistoryThreadModel::EventType type() const;
> -    void setType(HistoryThreadModel::EventType value);
> -
> -    bool matchContacts() const;
> -    void setMatchContacts(bool value);
> -
> -    Q_INVOKABLE QString threadIdForParticipants(const QString &accountId,
> -                                                int eventType,
> -                                                const QStringList &participants,
> -                                                int matchFlags = (int)History::MatchCaseSensitive,
> -                                                bool create = false);
> -
>      Q_INVOKABLE bool removeEvent(const QString &accountId, const QString &threadId, const QString &eventId, int eventType);
>      Q_INVOKABLE bool markEventAsRead(const QString &accountId, const QString &threadId, const QString &eventId, int eventType);
> -
>      Q_INVOKABLE bool removeEventAttachment(const QString &accountId, const QString &threadId, const QString &eventId, int eventType, const QString &attachmentId);
> -    Q_INVOKABLE virtual QVariant get(int row) const;
> -
> -Q_SIGNALS:
> -    void countChanged();
> -    void filterChanged();
> -    void sortChanged();
> -    void typeChanged();
> -    void matchContactsChanged();
> -    void canFetchMoreChanged();
>  
>  protected Q_SLOTS:
> -    void triggerQueryUpdate();
>      virtual void updateQuery();
>      virtual void onEventsAdded(const History::Events &events);
>      virtual void onEventsModified(const History::Events &events);
>      virtual void onEventsRemoved(const History::Events &events);
> -    void onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
>  
>  protected:
>      void timerEvent(QTimerEvent *event);
> @@ -118,15 +78,10 @@
>      History::EventViewPtr mView;
>      History::Events mEvents;
>      bool mCanFetchMore;
> -    HistoryQmlFilter *mFilter;
> -    HistoryQmlSort *mSort;
> -    HistoryThreadModel::EventType mType;
> -    bool mMatchContacts;
>      QHash<int, QByteArray> mRoles;
>      mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
>      History::Events mEventWritingQueue;
>      int mEventWritingTimer;
> -    int mUpdateTimer;
>  };
>  
>  #endif // HISTORYEVENTMODEL_H
> 
> === modified file 'Ubuntu/History/historygroupedeventsmodel.cpp'
> --- Ubuntu/History/historygroupedeventsmodel.cpp	2014-08-21 19:01:40 +0000
> +++ Ubuntu/History/historygroupedeventsmodel.cpp	2014-09-09 23:16:29 +0000
> @@ -63,6 +63,11 @@
>          break;
>      }
>  
> +    if (result.isNull()) {
> +        // for the shared roles
> +        result = HistoryModel::data(index, role);
> +    }
> +
>      return result;
>  }
>  
> @@ -88,7 +93,8 @@
>                  found = true;
>                  addEventToGroup(event, group, pos);
>                  break;
> -            } else if (isAscending() ? lessThan(group.displayedEvent, event) : lessThan(event, group.displayedEvent)) {
> +            } else if (isAscending() ? lessThan(group.displayedEvent.properties(), event.properties()) :
> +                       lessThan(event.properties(), group.displayedEvent.properties())) {
>                  break;
>              }
>          }
> @@ -134,7 +140,7 @@
>      }
>  
>      Q_FOREACH(const History::Event &event, events) {
> -        int pos = positionForEvent(event);
> +        int pos = positionForItem(event.properties());
>  
>          // check if the event belongs to the group at the position
>          if (pos >= 0 && pos < mEventGroups.count()) {
> @@ -167,7 +173,7 @@
>  void HistoryGroupedEventsModel::onEventsRemoved(const History::Events &events)
>  {
>      Q_FOREACH(const History::Event &event, events) {
> -        int pos = positionForEvent(event);
> +        int pos = positionForItem(event.properties());
>          if (pos < 0 || pos >= rowCount()) {
>              continue;
>          }
> @@ -179,25 +185,6 @@
>      }
>  }
>  
> -bool HistoryGroupedEventsModel::compareParticipants(const QStringList &list1, const QStringList &list2)
> -{
> -    if (list1.count() != list2.count()) {
> -        return false;
> -    }
> -
> -    int found = 0;
> -    Q_FOREACH(const QString &participant, list1) {
> -        Q_FOREACH(const QString &item, list2) {
> -            if (PhoneUtils::comparePhoneNumbers(participant, item)) {
> -                found++;
> -                break;
> -            }
> -        }
> -    }
> -
> -    return found == list1.count();
> -}
> -
>  bool HistoryGroupedEventsModel::areOfSameGroup(const History::Event &event1, const History::Event &event2)
>  {
>      QVariantMap props1 = event1.properties();
> @@ -231,7 +218,8 @@
>          bool append = true;
>          for (int i = 0; i < group.events.count(); ++i) {
>              History::Event &otherEvent = group.events[i];
> -            if (isAscending() ? lessThan(event, otherEvent) : lessThan(otherEvent, event)) {
> +            if (isAscending() ? lessThan(event.properties(), otherEvent.properties()) :
> +                                lessThan(otherEvent.properties(), event.properties())) {
>                  group.events.insert(i, event);
>                  append = false;
>                  break;
> @@ -270,7 +258,8 @@
>          // check what is the event that should be displayed
>         group.displayedEvent =  group.events.first();
>          Q_FOREACH(const History::Event &other, group.events) {
> -            if (isAscending() ? lessThan(other, group.displayedEvent) : lessThan(group.displayedEvent, other)) {
> +            if (isAscending() ? lessThan(other.properties(), group.displayedEvent.properties()) :
> +                                lessThan(group.displayedEvent.properties(), other.properties())) {
>                  group.displayedEvent = other;
>              }
>          }
> @@ -279,45 +268,6 @@
>      Q_EMIT dataChanged(idx, idx);
>  }
>  
> -bool HistoryGroupedEventsModel::lessThan(const History::Event &left, const History::Event &right) const
> -{
> -
> -    QVariant leftValue = left.properties()[sort()->sortField()];
> -    QVariant rightValue = right.properties()[sort()->sortField()];
> -    return leftValue < rightValue;
> -}
> -
> -int HistoryGroupedEventsModel::positionForEvent(const History::Event &event) const
> -{
> -    // do a binary search for the item position on the list
> -    int lowerBound = 0;
> -    int upperBound = mEventGroups.count() - 1;
> -    if (upperBound < 0) {
> -        return 0;
> -    }
> -
> -    while (true) {
> -        int pos = (upperBound + lowerBound) / 2;
> -        const History::Event &posEvent = mEventGroups[pos].displayedEvent;
> -        if (lowerBound == pos) {
> -            if (isAscending() ? lessThan(event, posEvent) : lessThan(posEvent, event)) {
> -                return pos;
> -            }
> -        }
> -        if (isAscending() ? lessThan(posEvent, event) : lessThan(event, posEvent)) {
> -            lowerBound = pos + 1;          // its in the upper
> -            if (lowerBound > upperBound) {
> -                return pos += 1;
> -            }
> -        } else if (lowerBound > upperBound) {
> -            return pos;
> -        } else {
> -            upperBound = pos - 1;          // its in the lower
> -        }
> -    }
> -
> -}
> -
>  QVariant HistoryGroupedEventsModel::get(int row) const
>  {
>      if (row >= rowCount() || row < 0) {
> @@ -339,8 +289,3 @@
>      Q_EMIT groupingPropertiesChanged();
>      triggerQueryUpdate();
>  }
> -
> -bool HistoryGroupedEventsModel::isAscending() const
> -{
> -    return sort() && sort()->sort().sortOrder() == Qt::AscendingOrder;
> -}
> 
> === modified file 'Ubuntu/History/historygroupedeventsmodel.h'
> --- Ubuntu/History/historygroupedeventsmodel.h	2014-08-16 22:11:30 +0000
> +++ Ubuntu/History/historygroupedeventsmodel.h	2014-09-09 23:16:29 +0000
> @@ -39,7 +39,7 @@
>      Q_ENUMS(GroupedRole)
>  public:
>      enum GroupedRole {
> -        EventsRole = HistoryEventModel::LastRole,
> +        EventsRole = HistoryEventModel::LastEventRole,
>          EventCountRole
>      };
>  
> @@ -55,8 +55,6 @@
>      QStringList groupingProperties() const;
>      void setGroupingProperties(const QStringList &properties);
>  
> -    bool isAscending() const;
> -
>  Q_SIGNALS:
>      void groupingPropertiesChanged();
>  
> @@ -67,12 +65,9 @@
>      void onEventsRemoved(const History::Events &events);
>  
>  protected:
> -    bool compareParticipants(const QStringList &list1, const QStringList &list2);
>      bool areOfSameGroup(const History::Event &event1, const History::Event &event2);
>      void addEventToGroup(const History::Event &event, HistoryEventGroup &group, int row);
>      void removeEventFromGroup(const History::Event &event, HistoryEventGroup &group, int row);
> -    bool lessThan(const History::Event &left, const History::Event &right) const;
> -    int positionForEvent(const History::Event &event) const;
>  
>  private:
>      QStringList mGroupingProperties;
> 
> === renamed file 'Ubuntu/History/historythreadgroupingproxymodel.cpp' => 'Ubuntu/History/historygroupedthreadsmodel.cpp'
> --- Ubuntu/History/historythreadgroupingproxymodel.cpp	2014-07-22 19:13:53 +0000
> +++ Ubuntu/History/historygroupedthreadsmodel.cpp	2014-09-09 23:16:29 +0000
> @@ -19,339 +19,290 @@
>   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>   */
>  
> -#include "historythreadgroupingproxymodel.h"
> -#include "historythreadmodel.h"
> +#include "historygroupedthreadsmodel.h"
>  #include "phoneutils_p.h"
>  #include <QTimer>
>  #include <QDebug>
>  
> -HistoryThreadGroupingProxyModel::HistoryThreadGroupingProxyModel(QObject *parent) :
> -    SortProxyModel(parent)
> +HistoryGroupedThreadsModel::HistoryGroupedThreadsModel(QObject *parent) :
> +    HistoryThreadModel(parent)
>  {
> -    connect(this, SIGNAL(sourceModelChanged()), SLOT(onSourceModelChanged()));
> +    mRoles = HistoryThreadModel::roleNames();
> +    mRoles[ThreadsRole] = "threads";
>  }
>  
> -QVariant HistoryThreadGroupingProxyModel::data(const QModelIndex &index, int role) const
> +QVariant HistoryGroupedThreadsModel::data(const QModelIndex &index, int role) const
>  {
>      if (!index.isValid()) {
>          return QVariant();
>      }
>  
> -    QModelIndex sourceIndex = mapToSource(index);
> -    HistoryThreadGroup group = groupForSourceIndex(sourceIndex);
> +    const HistoryThreadGroup &group = mGroups[index.row()];
>  
> -    // fill the result using the standard QSortFilterProxyModel data function
> -    // and overwrite it if necessary
> -    QVariant result = SortProxyModel::data(index, role);
> +    // get the data from the latest thread, and overwrite if necessary
> +    QVariant result = threadData(group.displayedThread, role);
>      switch (role) {
>          case HistoryThreadModel::CountRole: {
>              int count = 0;
> -            Q_FOREACH(const QPersistentModelIndex &row, group.rows) {
> -                count += row.data(HistoryThreadModel::CountRole).toInt();
> +            Q_FOREACH(const History::Thread &thread, group.threads) {
> +                count += thread.count();
>              }
>              result = count;
>              break;
>          }
>          case HistoryThreadModel::UnreadCountRole: {
>              int count = 0;
> -            Q_FOREACH(const QPersistentModelIndex &row, group.rows) {
> -                count += row.data(HistoryThreadModel::UnreadCountRole).toInt();
> +            Q_FOREACH(const History::Thread &thread, group.threads) {
> +                count += thread.unreadCount();
>              }
>              result = count;
>              break;
>          }
>          case ThreadsRole: {
>              QVariantList threads;
> -            Q_FOREACH(const QPersistentModelIndex &row, group.rows) {
> -                threads << row.data(HistoryThreadModel::PropertiesRole).toMap();
> +            Q_FOREACH(const History::Thread &thread, group.threads) {
> +                threads << thread.properties();
>              }
>              result = threads;
>              break;
>          }
>      }
>  
> +    if (result.isNull()) {
> +        // get the shared roles
> +        result = HistoryModel::data(index, role);
> +    }
> +
>      return result;
>  }
>  
> -QHash<int, QByteArray> HistoryThreadGroupingProxyModel::roleNames() const
> +void HistoryGroupedThreadsModel::fetchMore(const QModelIndex &parent)
> +{
> +    if (!canFetchMore(parent)) {
> +        return;
> +    }
> +
> +    const History::Threads &threads = fetchNextPage();
> +    Q_FOREACH(const History::Thread &thread, threads) {
> +        processThreadGrouping(thread);
> +    }
> +    notifyDataChanged();
> +}
> +
> +QHash<int, QByteArray> HistoryGroupedThreadsModel::roleNames() const
>  {
>      return mRoles;
>  }
>  
> -bool HistoryThreadGroupingProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
> -{
> -    QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent);
> -    const HistoryThreadModel *model = qobject_cast<const HistoryThreadModel*>(sourceModel());
> -
> -    if (!model || !sourceIndex.isValid()) {
> -        return false;
> -    }
> -
> -    HistoryThreadGroup group = groupForSourceIndex(sourceIndex);
> -    return (group.displayedIndex == sourceIndex);
> -}
> -
> -HistoryThreadGroup & HistoryThreadGroupingProxyModel::groupForEntry(const QVariant &propertyValue) const
> -{
> -    QString finalValue;
> -    if (mGroupingProperty == "participants") {
> -        QStringList participants = propertyValue.toStringList();
> -        HistoryThreadGroupMap::iterator it = mGroups.begin();
> -        for (; it != mGroups.end(); ++it) {
> -            if (compareParticipants(it.value().participants, participants)) {
> -                finalValue = it.key();
> -                break;
> -            }
> -        }
> -
> -        if (finalValue.isEmpty()) {
> -            // FIXME: find a separator that is not used in any IM service id
> -            finalValue = participants.join("||");
> -            HistoryThreadGroup &group = mGroups[finalValue];
> -            // set participants, otherwise they will be empty and further phone comparison will fail
> -            group.participants = propertyValue.toStringList();
> -            return group;
> -        }
> -    }
> -    if (finalValue.isEmpty()) {
> -        finalValue = propertyValue.toString();
> -    }
> -    HistoryThreadGroup &group = mGroups[finalValue];
> -    return group;
> -}
> -
> -void HistoryThreadGroupingProxyModel::removeGroup(const QVariant &propertyValue)
> -{
> -    if (mGroupingProperty == "participants") {
> -        QStringList participants = propertyValue.toStringList();
> -        HistoryThreadGroupMap::iterator it = mGroups.begin();
> -        for (; it != mGroups.end(); ++it) {
> -            if (compareParticipants(it.value().participants, participants)) {
> -                mGroups.erase(it);
> -                break;
> -            }
> -        }
> +QVariant HistoryGroupedThreadsModel::get(int row) const
> +{
> +    if (row >= rowCount() || row < 0) {
> +        return QVariant();
> +    }
> +
> +    return data(index(row), ThreadsRole);
> +}
> +
> +int HistoryGroupedThreadsModel::existingPositionForEntry(const QVariant &propertyValue) const
> +{
> +    int pos = -1;
> +    for (int i = 0; i < mGroups.count(); ++i) {
> +        const HistoryThreadGroup &group = mGroups[i];
> +        if (mGroupingProperty == "participants") {

Don't we have a macro for "participants" ?

> +            QStringList participants = propertyValue.toStringList();
> +            if (compareParticipants(group.displayedThread.participants(), participants)) {
> +                pos = i;
> +                break;
> +            }
> +        } else if (propertyValue == group.displayedThread.properties()[mGroupingProperty]) {
> +            pos = i;
> +            break;
> +        }
> +    }
> +
> +    return pos;
> +}
> +
> +void HistoryGroupedThreadsModel::removeGroup(const HistoryThreadGroup &group)
> +{
> +    int pos = mGroups.indexOf(group);
> +    if (pos >= 0){
> +        beginRemoveRows(QModelIndex(), pos, pos);
> +        mGroups.removeAt(pos);
> +        endRemoveRows();
> +    }
> +}
> +
> +void HistoryGroupedThreadsModel::updateDisplayedThread(HistoryThreadGroup &group)
> +{
> +    int pos = mGroups.indexOf(group);
> +    if (pos < 0) {
> +        qWarning() << "Group not found!!";
> +        return;
> +    }
> +
> +    History::Thread displayedThread = group.threads.first();
> +    QVariantMap displayedProperties = displayedThread.properties();
> +    Q_FOREACH(const History::Thread &other, group.threads) {
> +        if (isAscending() ? lessThan(other.properties(), displayedProperties) :
> +                            lessThan(displayedProperties, other.properties())) {
> +            displayedThread = other;
> +            displayedProperties = displayedThread.properties();
> +        }
> +    }
> +
> +    // check if we need to update the order
> +    int newPos = positionForItem(displayedProperties);
> +
> +    // NOTE: only set the new displayedThread AFTER calling positionForItem
> +    group.displayedThread = displayedThread;
> +
> +    // the positionForItem function might return the pos+1 value for the current item as it considers
> +    // this to be the position for a new insertion
> +    if (newPos != pos && newPos != pos+1) {
> +        beginMoveRows(QModelIndex(), pos, pos, QModelIndex(), newPos);
> +        // QList::move() behaves in a different way than the QAbstractItemModel moving functions
> +        // that's why the delta was added
> +        mGroups.move(pos, newPos > pos ? newPos-1 : newPos);
> +        endMoveRows();
> +    }
> +}
> +
> +void HistoryGroupedThreadsModel::updateQuery()
> +{
> +    // remove all entries and call the query update
> +    if (!mGroups.isEmpty()) {
> +        beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
> +        mGroups.clear();
> +        endRemoveRows();
> +    }
> +
> +    HistoryThreadModel::updateQuery();
> +}
> +
> +void HistoryGroupedThreadsModel::onThreadsAdded(const History::Threads &threads)
> +{
> +    Q_FOREACH(const History::Thread &thread, threads) {
> +        processThreadGrouping(thread);
> +    }
> +
> +    notifyDataChanged();
> +}
> +
> +void HistoryGroupedThreadsModel::onThreadsModified(const History::Threads &threads)
> +{
> +    Q_FOREACH(const History::Thread &thread, threads) {
> +        processThreadGrouping(thread);
> +    }
> +
> +    notifyDataChanged();
> +}
> +
> +void HistoryGroupedThreadsModel::onThreadsRemoved(const History::Threads &threads)
> +{
> +    Q_FOREACH(const History::Thread &thread, threads) {
> +        removeThreadFromGroup(thread);
> +    }
> +
> +    notifyDataChanged();
> +}
> +
> +void HistoryGroupedThreadsModel::processThreadGrouping(const History::Thread &thread)
> +{
> +    QVariantMap properties = thread.properties();
> +    int pos = existingPositionForEntry(properties[mGroupingProperty]);
> +
> +    // if the group is empty, we need to insert it into the map
> +    if (pos < 0) {
> +        HistoryThreadGroup group;
> +        int newPos = positionForItem(thread.properties());
> +        group.threads.append(thread);
> +        group.displayedThread = thread;
> +        beginInsertRows(QModelIndex(), newPos, newPos);
> +        mGroups.insert(newPos, group);
> +        endInsertRows();
> +        return;
> +    }
> +
> +    HistoryThreadGroup &group = mGroups[pos];
> +    if (!group.threads.contains(thread)) {
> +        group.threads.append(thread);
>      } else {
> -         mGroups.remove(propertyValue.toString());
> -    }
> -}
> -
> -void HistoryThreadGroupingProxyModel::processGrouping()
> -{
> -    HistoryThreadModel *model = qobject_cast<HistoryThreadModel*>(sourceModel());
> -    if (!model) {
> -        return;
> -    }
> -
> -    mGroups.clear();
> -    int count = model->rowCount();
> -    for (int row = 0; row < count; ++row) {
> -        processRowGrouping(row);
> -    }
> -}
> -
> -void HistoryThreadGroupingProxyModel::processRowGrouping(int sourceRow)
> -{
> -    HistoryThreadModel *model = qobject_cast<HistoryThreadModel*>(sourceModel());
> -    if (!model) {
> -        return;
> -    }
> -
> -    QModelIndex sourceIndex = model->index(sourceRow, 0, QModelIndex());
> -
> -    QVariantMap properties = sourceIndex.data(HistoryThreadModel::PropertiesRole).toMap();
> -    HistoryThreadGroup &group = groupForEntry(properties[mGroupingProperty]);
> -
> -    if (!group.rows.contains(sourceIndex)) {
> -        group.rows.append(sourceIndex);
> -    }
> -
> -    QDateTime timestamp = sourceIndex.data(HistoryThreadModel::LastEventTimestampRole).toDateTime();
> -    if (timestamp > group.latestTime || !group.displayedIndex.isValid()) {
> -        QPersistentModelIndex oldDisplayedIndex = group.displayedIndex;
> -        group.latestTime = timestamp;
> -        group.displayedIndex = sourceIndex;
> -
> -        if (oldDisplayedIndex.isValid() && oldDisplayedIndex != group.displayedIndex) {
> -            markIndexAsChanged(oldDisplayedIndex);
> -        }
> -        markIndexAsChanged(group.displayedIndex);
> -    }
> -}
> -
> -void HistoryThreadGroupingProxyModel::removeRowFromGroup(int sourceRow)
> -{
> -    HistoryThreadModel *model = qobject_cast<HistoryThreadModel*>(sourceModel());
> -    if (!model) {
> -        return;
> -    }
> -
> -    QModelIndex sourceIndex = model->index(sourceRow, 0, QModelIndex());
> -    QVariantMap properties = sourceIndex.data(HistoryThreadModel::PropertiesRole).toMap();
> -
> -    HistoryThreadGroup &group = groupForEntry(properties[mGroupingProperty]);
> -
> -    group.rows.removeAll(sourceIndex);
> -    if (group.displayedIndex == sourceIndex) {
> -        QDateTime latestTimestamp;
> -        QPersistentModelIndex latestIndex;
> -        Q_FOREACH(QPersistentModelIndex index, group.rows) {
> -            QDateTime timestamp = index.data(HistoryThreadModel::LastEventTimestampRole).toDateTime();
> -            if (timestamp > latestTimestamp) {
> -                latestTimestamp = timestamp;
> -                latestIndex = index;
> -            }
> -        }
> -
> -        if (group.rows.isEmpty()) {
> -            removeGroup(properties[mGroupingProperty]);
> +        // save the updated copy of the thread
> +        group.threads.removeAll(thread);
> +        group.threads.append(thread);
> +    }
> +
> +    updateDisplayedThread(group);
> +    markGroupAsChanged(group);
> +}
> +
> +void HistoryGroupedThreadsModel::removeThreadFromGroup(const History::Thread &thread)
> +{
> +    QVariantMap properties = thread.properties();
> +
> +    int pos = existingPositionForEntry(properties[mGroupingProperty]);
> +    if (pos < 0) {
> +        qWarning() << "Could not find group for property " << properties[mGroupingProperty];
> +        return;
> +    }
> +
> +    HistoryThreadGroup &group = mGroups[pos];
> +    group.threads.removeAll(thread);
> +
> +    if (group.threads.isEmpty()) {
> +        removeGroup(group);
> +    } else if (group.displayedThread == thread) {
> +        updateDisplayedThread(group);
> +        markGroupAsChanged(group);
> +    }
> +}
> +
> +void HistoryGroupedThreadsModel::markGroupAsChanged(const HistoryThreadGroup &group)
> +{
> +    if (!mChangedGroups.contains(group)) {
> +        mChangedGroups.append(group);
> +    }
> +}
> +
> +void HistoryGroupedThreadsModel::notifyDataChanged()
> +{
> +    Q_FOREACH(const HistoryThreadGroup &group, mChangedGroups) {
> +        int pos = mGroups.indexOf(group);
> +        if (pos >= 0) {
> +            QModelIndex idx = index(pos);
> +            Q_EMIT dataChanged(idx, idx);
>          } else {
> -            group.displayedIndex = latestIndex;
> -            group.latestTime = latestTimestamp;
> -            markIndexAsChanged(group.displayedIndex);
> -        }
> -    }
> -}
> -
> -void HistoryThreadGroupingProxyModel::triggerDataChanged()
> -{
> -    if (mDataChangedTriggered) {
> -        return;
> -    }
> -
> -    QTimer::singleShot(0, this, SLOT(notifyDataChanged()));
> -    mDataChangedTriggered = true;
> -}
> -
> -void HistoryThreadGroupingProxyModel::markIndexAsChanged(const QModelIndex &index)
> -{
> -    if (!mChangedIndexes.contains(index)) {
> -        mChangedIndexes.append(index);
> -    }
> -}
> -
> -void HistoryThreadGroupingProxyModel::notifyDataChanged()
> -{
> -    QAbstractItemModel *model = sourceModel();
> -    Q_FOREACH(const QPersistentModelIndex &index, mChangedIndexes) {
> -        if (index.isValid()) {
> -            Q_EMIT model->dataChanged(index, index);
> -        }
> -    }
> -    mChangedIndexes.clear();
> -    mDataChangedTriggered = false;
> -}
> -
> -void HistoryThreadGroupingProxyModel::onRowsInserted(const QModelIndex &parent, int start, int end)
> -{
> -    // we don't support tree models yet
> -    if (parent.isValid()) {
> -        return;
> -    }
> -
> -
> -    // update the group for the added indexes
> -    for (int row = start; row <= end; ++row) {
> -        processRowGrouping(row);
> -    }
> -
> -    triggerDataChanged();
> -}
> -
> -void HistoryThreadGroupingProxyModel::onRowsRemoved(const QModelIndex &parent, int start, int end)
> -{
> -    // we don't support tree models yet
> -    if (parent.isValid()) {
> -        return;
> -    }
> -
> -    for (int row = start; row <= end; ++row) {
> -        removeRowFromGroup(row);
> -    }
> -
> -    triggerDataChanged();
> -}
> -
> -void HistoryThreadGroupingProxyModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
> -{
> -    // we don't support tree models yet
> -    if (topLeft.parent().isValid() || bottomRight.parent().isValid()) {
> -        return;
> -    }
> -
> -    int start = topLeft.row();
> -    int end = bottomRight.row();
> -
> -    for (int row = start; row <= end; ++row) {
> -        processRowGrouping(row); 
> -    }
> -    triggerDataChanged();
> -}
> -
> -void HistoryThreadGroupingProxyModel::onSourceModelChanged()
> -{
> -    QAbstractItemModel *model = sourceModel();
> -    if (model) {
> -
> -        connect(model,
> -                SIGNAL(rowsInserted(QModelIndex,int,int)),
> -                SLOT(onRowsInserted(QModelIndex,int,int)));
> -        connect(model,
> -                SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
> -                SLOT(onRowsRemoved(QModelIndex,int,int)));
> -        connect(model,
> -                SIGNAL(modelReset()),
> -                SLOT(processGrouping()));
> -        connect(model,
> -                SIGNAL(dataChanged(QModelIndex,QModelIndex)),
> -                SLOT(onDataChanged(QModelIndex,QModelIndex)));
> -        Q_EMIT threadModelChanged();
> -    }
> -
> -    mRoles = SortProxyModel::roleNames();
> -    mRoles[ThreadsRole] = "threads";
> -
> -    processGrouping();
> -    triggerDataChanged();
> -}
> -
> -bool HistoryThreadGroupingProxyModel::compareParticipants(const QStringList &list1, const QStringList &list2) const
> -{
> -    // FIXME: add support for match flags
> -    if (list1.count() != list2.count()) {
> -        return false;
> -    }
> -
> -    int found = 0;
> -    Q_FOREACH(const QString &participant, list1) {
> -        Q_FOREACH(const QString &item, list2) {
> -            if (PhoneUtils::comparePhoneNumbers(participant, item)) {
> -                found++;
> -                break;
> -            }
> -        }
> -    }
> -
> -    return found == list1.count();
> -}
> -
> -
> -HistoryThreadGroup HistoryThreadGroupingProxyModel::groupForSourceIndex(const QModelIndex &sourceIndex) const
> -{
> -    QVariantMap properties = sourceIndex.data(HistoryThreadModel::PropertiesRole).toMap();
> -    return groupForEntry(properties[mGroupingProperty]);
> -}
> -
> -
> -QString HistoryThreadGroupingProxyModel::groupingProperty() const
> +            qWarning() << "Group not found!";
> +        }
> +    }
> +    mChangedGroups.clear();
> +}
> +
> +QString HistoryGroupedThreadsModel::groupingProperty() const
>  {
>      return mGroupingProperty;
>  }
>  
> -void HistoryThreadGroupingProxyModel::setGroupingProperty(const QString &value)
> +void HistoryGroupedThreadsModel::setGroupingProperty(const QString &value)
>  {
>      mGroupingProperty = value;
>      Q_EMIT groupingPropertyChanged();
> -    processGrouping();
> -    triggerDataChanged();
> +
> +    triggerQueryUpdate();
> +}
> +
> +int HistoryGroupedThreadsModel::rowCount(const QModelIndex &parent) const
> +{
> +    if (parent.isValid()) {
> +        return 0;
> +    }
> +
> +    return mGroups.count();
> +}
> +
> +
> +bool HistoryThreadGroup::operator==(const HistoryThreadGroup &other) const
> +{
> +    return displayedThread == other.displayedThread;
>  }
> 
> === renamed file 'Ubuntu/History/historythreadgroupingproxymodel.h' => 'Ubuntu/History/historygroupedthreadsmodel.h'
> --- Ubuntu/History/historythreadgroupingproxymodel.h	2014-07-22 18:02:40 +0000
> +++ Ubuntu/History/historygroupedthreadsmodel.h	2014-09-09 23:16:29 +0000
> @@ -19,25 +19,23 @@
>   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
>   */
>  
> -#ifndef HISTORYTHREADGROUPINGPROXYMODEL_H
> -#define HISTORYTHREADGROUPINGPROXYMODEL_H
> +#ifndef HISTORYGROUPEDTHREADSMODEL_H
> +#define HISTORYGROUPEDTHREADSMODEL_H
>  
> -#include "sortproxymodel.h"
> +#include "historythreadmodel.h"
>  #include <QDateTime>
>  
> -class HistoryThreadModel;
> -
>  class HistoryThreadGroup {
>  public:
> -    QStringList participants;
> -    QDateTime latestTime;
> -    QPersistentModelIndex displayedIndex;
> -    QList<QPersistentModelIndex> rows;
> +    History::Thread displayedThread;
> +    History::Threads threads;
> +
> +    bool operator==(const HistoryThreadGroup &other) const;
>  };
>  
> -typedef QMap<QString, HistoryThreadGroup> HistoryThreadGroupMap;
> +typedef QList<HistoryThreadGroup> HistoryThreadGroupList;
>  
> -class HistoryThreadGroupingProxyModel : public SortProxyModel
> +class HistoryGroupedThreadsModel : public HistoryThreadModel
>  {
>      Q_OBJECT
>      Q_PROPERTY(QString groupingProperty
> @@ -48,50 +46,46 @@
>  
>  public:
>      enum CustomRoles {
> -        ThreadsRole = (Qt::UserRole + 100),
> +        ThreadsRole = LastThreadRole
>      };
>  
> -    explicit HistoryThreadGroupingProxyModel(QObject *parent = 0);
> +    explicit HistoryGroupedThreadsModel(QObject *parent = 0);
>  
>      QString groupingProperty() const;
>      void setGroupingProperty(const QString &value);
>  
> +    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
>      virtual QVariant data(const QModelIndex &index, int role) const;
> +    Q_INVOKABLE void fetchMore(const QModelIndex &parent = QModelIndex());
>      virtual QHash<int, QByteArray> roleNames() const;
> +    Q_INVOKABLE QVariant get(int row) const;
>  
>  Q_SIGNALS:
> -    void threadModelChanged();
>      void groupingPropertyChanged();
>  
>  protected:
> -    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
> -    HistoryThreadGroup groupForSourceIndex(const QModelIndex &sourceIndex) const;
> -    HistoryThreadGroup &groupForEntry(const QVariant &propertyValue) const;
> -    void removeGroup(const QVariant &propertyValue);
> +    int existingPositionForEntry(const QVariant &propertyValue) const;
> +    void removeGroup(const HistoryThreadGroup &group);
> +    void updateDisplayedThread(HistoryThreadGroup &group);
> +
> +protected Q_SLOTS:
> +    virtual void updateQuery();
> +    virtual void onThreadsAdded(const History::Threads &threads);
> +    virtual void onThreadsModified(const History::Threads &threads);
> +    virtual void onThreadsRemoved(const History::Threads &threads);
>  
>  private Q_SLOTS:
> -    void processGrouping();
> -    void processRowGrouping(int sourceRow);
> -    void removeRowFromGroup(int sourceRow);
> -    void triggerDataChanged();
> -    void markIndexAsChanged(const QModelIndex &index);
> +    void processThreadGrouping(const History::Thread &thread);
> +    void removeThreadFromGroup(const History::Thread &thread);
> +    void markGroupAsChanged(const HistoryThreadGroup &group);
>      void notifyDataChanged();
>  
> -    void onRowsInserted(const QModelIndex &parent, int start, int end);
> -    void onRowsRemoved(const QModelIndex &parent, int start, int end);
> -    void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
> -    void onSourceModelChanged();
> -
> -protected:
> -    bool compareParticipants(const QStringList &list1, const QStringList &list2) const;
> -
>  private:
> -    bool mDataChangedTriggered;
>      QString mGroupingProperty;
>  
> -    mutable HistoryThreadGroupMap mGroups;
> -    QList<QPersistentModelIndex> mChangedIndexes;
> +    HistoryThreadGroupList mGroups;
> +    QList<HistoryThreadGroup> mChangedGroups;
>      QHash<int, QByteArray> mRoles;
>  };
>  
> -#endif // HISTORYTHREADGROUPINGPROXYMODEL_H
> +#endif // HISTORYGROUPEDTHREADSMODEL_H
> 
> === added file 'Ubuntu/History/historymodel.cpp'
> --- Ubuntu/History/historymodel.cpp	1970-01-01 00:00:00 +0000
> +++ Ubuntu/History/historymodel.cpp	2014-09-09 23:16:29 +0000
> @@ -0,0 +1,311 @@
> +/*
> + * Copyright (C) 2013-2014 Canonical, Ltd.
> + *
> + * Authors:
> + *  Gustavo Pichorim Boiko <gustavo.boiko at canonical.com>
> + *
> + * This file is part of history-service.
> + *
> + * history-service 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; version 3.
> + *
> + * history-service 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 "historymodel.h"
> +#include "historyqmlfilter.h"
> +#include "historyqmlsort.h"
> +#include "contactmatcher_p.h"
> +#include "phoneutils_p.h"
> +#include "thread.h"
> +#include "manager.h"
> +#include <QTimerEvent>
> +#include <QDebug>
> +
> +HistoryModel::HistoryModel(QObject *parent) :
> +    QAbstractListModel(parent), mFilter(0), mSort(new HistoryQmlSort(this)),
> +    mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0)
> +{
> +    // configure the roles
> +    mRoles[AccountIdRole] = "accountId";
> +    mRoles[ThreadIdRole] = "threadId";
> +    mRoles[ParticipantsRole] = "participants";
> +    mRoles[TypeRole] = "type";
> +    mRoles[PropertiesRole] = "properties";
> +
> +    connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()));
> +    connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()));
> +    connect(this, SIGNAL(modelReset()), this, SIGNAL(countChanged()));
> +    connect(ContactMatcher::instance(),
> +            SIGNAL(contactInfoChanged(QString,QVariantMap)),
> +            SLOT(onContactInfoChanged(QString,QVariantMap)));
> +
> +    // create the view and get some objects
> +    triggerQueryUpdate();
> +}
> +
> +bool HistoryModel::canFetchMore(const QModelIndex &parent) const
> +{
> +    return false;
> +}
> +
> +void HistoryModel::fetchMore(const QModelIndex &parent)
> +{
> +    Q_UNUSED(parent)
> +    // do nothing, just make the method invokable
> +}
> +
> +QHash<int, QByteArray> HistoryModel::roleNames() const
> +{
> +    return mRoles;
> +}
> +
> +QVariant HistoryModel::data(const QModelIndex &index, int role) const
> +{
> +    if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
> +        return QVariant();
> +    }
> +
> +    QVariantMap properties = index.data(PropertiesRole).toMap();
> +    QVariant result;
> +    switch (role) {
> +    case AccountIdRole:
> +        result = properties[History::FieldAccountId];
> +        break;
> +    case ThreadIdRole:
> +        result = properties[History::FieldThreadId];
> +        break;
> +    case TypeRole:
> +        result = properties[History::FieldType];
> +        break;
> +    case ParticipantsRole:
> +        if (mMatchContacts) {
> +            result = ContactMatcher::instance()->contactInfo(properties[History::FieldParticipants].toStringList());
> +        } else {
> +            result = properties[History::FieldParticipants];
> +        }
> +        break;
> +    }
> +    return result;
> +}
> +
> +HistoryQmlFilter *HistoryModel::filter() const
> +{
> +    return mFilter;
> +}
> +
> +void HistoryModel::setFilter(HistoryQmlFilter *value)
> +{
> +    if (mFilter) {
> +        mFilter->disconnect(this);
> +    }
> +
> +    mFilter = value;
> +    if (mFilter) {
> +        connect(mFilter,
> +                SIGNAL(filterChanged()),
> +                SLOT(triggerQueryUpdate()));
> +    }
> +
> +    Q_EMIT filterChanged();
> +    triggerQueryUpdate();
> +}
> +
> +HistoryQmlSort *HistoryModel::sort() const
> +{
> +    return mSort;
> +}
> +
> +void HistoryModel::setSort(HistoryQmlSort *value)
> +{
> +    // disconnect the previous sort
> +    if (mSort) {
> +        mSort->disconnect(this);
> +    }
> +
> +    mSort = value;
> +    if (mSort) {
> +        connect(mSort,
> +                SIGNAL(sortChanged()),
> +                SLOT(triggerQueryUpdate()));
> +    }
> +
> +    Q_EMIT sortChanged();
> +    triggerQueryUpdate();
> +}
> +
> +HistoryModel::EventType HistoryModel::type() const
> +{
> +    return mType;
> +}
> +
> +void HistoryModel::setType(EventType value)
> +{
> +    mType = value;
> +    Q_EMIT typeChanged();
> +    triggerQueryUpdate();
> +}
> +
> +bool HistoryModel::matchContacts() const
> +{
> +    return mMatchContacts;
> +}
> +
> +void HistoryModel::setMatchContacts(bool value)
> +{
> +    mMatchContacts = value;
> +    Q_EMIT matchContactsChanged();
> +
> +    // mark all indexes as changed
> +    if (rowCount() > 0) {
> +        Q_EMIT dataChanged(index(0), index(rowCount()-1));
> +    }
> +}
> +
> +QString HistoryModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
> +{
> +    if (participants.isEmpty()) {
> +        return QString::null;
> +    }
> +
> +    History::Thread thread = History::Manager::instance()->threadForParticipants(accountId,
> +                                                                                 (History::EventType)eventType,
> +                                                                                 participants,
> +                                                                                 (History::MatchFlags)matchFlags,
> +                                                                                 create);
> +    if (!thread.isNull()) {
> +        return thread.threadId();
> +    }
> +
> +    return QString::null;
> +}
> +
> +void HistoryModel::onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo)
> +{
> +    Q_UNUSED(contactInfo)
> +    if (!mMatchContacts) {
> +        return;
> +    }
> +
> +    QList<QModelIndex> changedIndexes;
> +    int count = rowCount();
> +    for (int i = 0; i < count; ++i) {
> +        // WARNING: do not use mEvents directly to verify which indexes to change as there is the
> +        // HistoryGroupedEventsModel which is based on this model and handles the items in a different way
> +        QModelIndex idx = index(i);
> +        QVariantMap properties = idx.data(PropertiesRole).toMap();
> +        QStringList participants = properties[History::FieldParticipants].toStringList();
> +        Q_FOREACH(const QString &participant, participants) {
> +            if (PhoneUtils::comparePhoneNumbers(participant, phoneNumber)) {
> +                changedIndexes << idx;
> +            }
> +        }
> +    }
> +
> +    // now emit the dataChanged signal to all changed indexes
> +    Q_FOREACH(const QModelIndex &idx, changedIndexes) {
> +        Q_EMIT dataChanged(idx, idx);
> +    }
> +}
> +
> +void HistoryModel::timerEvent(QTimerEvent *event)
> +{
> +    if (event->timerId() == mUpdateTimer) {
> +        killTimer(mUpdateTimer);
> +        mUpdateTimer = 0;
> +        updateQuery();
> +    }
> +}
> +
> +
> +bool HistoryModel::compareParticipants(const QStringList &list1, const QStringList &list2) const
> +{
> +    if (list1.count() != list2.count()) {
> +        return false;
> +    }
> +
> +    int found = 0;
> +    Q_FOREACH(const QString &participant, list1) {
> +        Q_FOREACH(const QString &item, list2) {
> +            if (PhoneUtils::comparePhoneNumbers(participant, item)) {
> +                found++;
> +                break;
> +            }
> +        }
> +    }
> +
> +    return found == list1.count();
> +}
> +
> +bool HistoryModel::lessThan(const QVariantMap &left, const QVariantMap &right) const
> +{
> +    QVariant leftValue = left[sort()->sortField()];
> +    QVariant rightValue = right[sort()->sortField()];
> +
> +    return leftValue < rightValue;
> +}
> +
> +int HistoryModel::positionForItem(const QVariantMap &item) const
> +{
> +    // do a binary search for the item position on the list
> +    int lowerBound = 0;
> +    int upperBound = rowCount() - 1;
> +    if (upperBound < 0) {
> +        return 0;
> +    }
> +
> +    while (true) {
> +        int pos = (upperBound + lowerBound) / 2;
> +        const QVariantMap posItem = index(pos).data(PropertiesRole).toMap();
> +        if (lowerBound == pos) {
> +            if (isAscending() ? lessThan(item, posItem) : lessThan(posItem, item)) {
> +                return pos;
> +            }
> +        }
> +        if (isAscending() ? lessThan(posItem, item) : lessThan(item, posItem)) {
> +            lowerBound = pos + 1;          // its in the upper
> +            if (lowerBound > upperBound) {
> +                return pos += 1;
> +            }
> +        } else if (lowerBound > upperBound) {
> +            return pos;
> +        } else {
> +            upperBound = pos - 1;          // its in the lower
> +        }
> +    }
> +}
> +
> +bool HistoryModel::isAscending() const
> +{
> +    return mSort && mSort->sort().sortOrder() == Qt::AscendingOrder;
> +}
> +
> +QVariant HistoryModel::get(int row) const
> +{
> +    QVariantMap data;
> +    QModelIndex idx = index(row, 0);
> +    if (idx.isValid()) {
> +        QHash<int, QByteArray> roles = roleNames();
> +        Q_FOREACH(int role, roles.keys()) {
> +            data.insert(roles[role], idx.data(role));
> +        }
> +    }
> +
> +    return data;
> +}
> +
> +void HistoryModel::triggerQueryUpdate()
> +{
> +    if (mUpdateTimer) {
> +        killTimer(mUpdateTimer);
> +    }
> +    // delay the loading of the model data until the settings settle down
> +    mUpdateTimer = startTimer(100);
> +}
> 
> === added file 'Ubuntu/History/historymodel.h'
> --- Ubuntu/History/historymodel.h	1970-01-01 00:00:00 +0000
> +++ Ubuntu/History/historymodel.h	2014-09-09 23:16:29 +0000
> @@ -0,0 +1,136 @@
> +/*
> + * Copyright (C) 2013-2014 Canonical, Ltd.
> + *
> + * Authors:
> + *  Gustavo Pichorim Boiko <gustavo.boiko at canonical.com>
> + *
> + * This file is part of history-service.
> + *
> + * history-service 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; version 3.
> + *
> + * history-service 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 HISTORYMODEL_H
> +#define HISTORYMODEL_H
> +
> +#include "types.h"
> +#include "historyqmlfilter.h"
> +#include "historyqmlsort.h"
> +#include <QAbstractListModel>
> +#include <QStringList>
> +
> +class HistoryModel : public QAbstractListModel
> +{
> +    Q_OBJECT
> +    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
> +    Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged)
> +    Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged)
> +    Q_PROPERTY(EventType type READ type WRITE setType NOTIFY typeChanged)
> +    Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
> +    Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
> +    Q_ENUMS(EventType)
> +    Q_ENUMS(MatchFlag)
> +    Q_ENUMS(MessageStatus)
> +    Q_ENUMS(Role)
> +
> +public:
> +    enum EventType {
> +        EventTypeText = History::EventTypeText,
> +        EventTypeVoice = History::EventTypeVoice
> +    };
> +
> +    enum MatchFlag {
> +        MatchCaseSensitive = History::MatchCaseSensitive,
> +        MatchCaseInsensitive = History::MatchCaseInsensitive,
> +        MatchContains = History::MatchContains,
> +        MatchPhoneNumber = History::MatchPhoneNumber
> +    };
> +
> +    enum MessageStatus
> +    {
> +        MessageStatusUnknown = History::MessageStatusUnknown,
> +        MessageStatusDelivered = History::MessageStatusDelivered,
> +        MessageStatusTemporarilyFailed = History::MessageStatusTemporarilyFailed,
> +        MessageStatusPermanentlyFailed = History::MessageStatusPermanentlyFailed,
> +        MessageStatusAccepted = History::MessageStatusAccepted,
> +        MessageStatusRead = History::MessageStatusRead,
> +        MessageStatusDeleted = History::MessageStatusDeleted,
> +        MessageStatusPending = History::MessageStatusPending // pending attachment download
> +    };
> +
> +    enum Role {
> +        AccountIdRole = Qt::UserRole,
> +        ThreadIdRole,
> +        ParticipantsRole,
> +        TypeRole,
> +        PropertiesRole,
> +        LastRole
> +    };
> +
> +    explicit HistoryModel(QObject *parent = 0);
> +
> +    Q_INVOKABLE virtual bool canFetchMore(const QModelIndex &parent = QModelIndex()) const;
> +    Q_INVOKABLE virtual void fetchMore(const QModelIndex &parent = QModelIndex());
> +    virtual QHash<int, QByteArray> roleNames() const;
> +    virtual QVariant data(const QModelIndex &index, int role) const;
> +
> +    HistoryQmlFilter *filter() const;
> +    void setFilter(HistoryQmlFilter *value);
> +
> +    HistoryQmlSort *sort() const;
> +    void setSort(HistoryQmlSort *value);
> +
> +    EventType type() const;
> +    void setType(EventType value);
> +
> +    bool matchContacts() const;
> +    void setMatchContacts(bool value);
> +
> +    Q_INVOKABLE QString threadIdForParticipants(const QString &accountId,
> +                                                int eventType,
> +                                                const QStringList &participants,
> +                                                int matchFlags = (int)History::MatchCaseSensitive,
> +                                                bool create = false);
> +
> +    Q_INVOKABLE virtual QVariant get(int row) const;
> +
> +Q_SIGNALS:
> +    void countChanged();
> +    void filterChanged();
> +    void sortChanged();
> +    void typeChanged();
> +    void matchContactsChanged();
> +    void canFetchMoreChanged();
> +
> +protected Q_SLOTS:
> +    void triggerQueryUpdate();
> +    virtual void updateQuery() = 0;
> +    void onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
> +
> +protected:
> +    virtual void timerEvent(QTimerEvent *event);
> +    bool compareParticipants(const QStringList &list1, const QStringList &list2) const;
> +    bool lessThan(const QVariantMap &left, const QVariantMap &right) const;
> +    int positionForItem(const QVariantMap &item) const;
> +    bool isAscending() const;
> +
> +    HistoryQmlFilter *mFilter;
> +    HistoryQmlSort *mSort;
> +    EventType mType;
> +    bool mMatchContacts;
> +
> +private:
> +    QHash<int, QByteArray> mRoles;
> +    int mUpdateTimer;
> +};
> +
> +#endif // HISTORYMODEL_H
> 
> === modified file 'Ubuntu/History/historyqmlplugin.cpp'
> --- Ubuntu/History/historyqmlplugin.cpp	2014-08-12 13:54:32 +0000
> +++ Ubuntu/History/historyqmlplugin.cpp	2014-09-09 23:16:29 +0000
> @@ -25,11 +25,10 @@
>  #include "historyqmlsort.h"
>  #include "historyqmlunionfilter.h"
>  #include "historythreadmodel.h"
> -#include "historythreadgroupingproxymodel.h"
> +#include "historygroupedthreadsmodel.h"
>  #include "historyeventmodel.h"
>  #include "historygroupedeventsmodel.h"
>  #include "historyqmltexteventattachment.h"
> -#include "sortproxymodel.h"
>  #include <QQmlEngine>
>  #include <qqml.h>
>  
> @@ -46,12 +45,11 @@
>      qmlRegisterType<HistoryEventModel>(uri, 0, 1, "HistoryEventModel");
>      qmlRegisterType<HistoryGroupedEventsModel>(uri, 0, 1, "HistoryGroupedEventsModel");
>      qmlRegisterType<HistoryThreadModel>(uri, 0, 1, "HistoryThreadModel");
> +    qmlRegisterType<HistoryGroupedThreadsModel>(uri, 0, 1, "HistoryGroupedThreadsModel");
>      qmlRegisterType<HistoryQmlFilter>(uri, 0, 1, "HistoryFilter");
>      qmlRegisterType<HistoryQmlIntersectionFilter>(uri, 0, 1, "HistoryIntersectionFilter");
>      qmlRegisterType<HistoryQmlSort>(uri, 0, 1, "HistorySort");
>      qmlRegisterType<HistoryQmlUnionFilter>(uri, 0, 1, "HistoryUnionFilter");
> -    qmlRegisterType<SortProxyModel>(uri, 0, 1, "SortProxyModel");
> -    qmlRegisterType<HistoryThreadGroupingProxyModel>(uri, 0, 1, "HistoryThreadGroupingProxyModel");
>      qmlRegisterUncreatableType<HistoryQmlTextEventAttachment>(uri, 0, 1, "HistoryTextEventAttachment", "");
>      qmlRegisterUncreatableType<QAbstractItemModel>(uri, 0, 1, "QAbstractItemModel", "");
>  }
> 
> === modified file 'Ubuntu/History/historythreadmodel.cpp'
> --- Ubuntu/History/historythreadmodel.cpp	2014-09-09 23:16:29 +0000
> +++ Ubuntu/History/historythreadmodel.cpp	2014-09-09 23:16:29 +0000
> @@ -20,34 +20,20 @@
>   */
>  
>  #include "historythreadmodel.h"
> -#include "thread.h"
> -#include "historyqmlfilter.h"
> -#include "historyqmlsort.h"
> +#include "historyqmltexteventattachment.h"
>  #include "manager.h"
>  #include "threadview.h"
> -#include "textevent.h"
> -#include "texteventattachment.h"
> -#include "historyqmltexteventattachment.h"
>  #include "voiceevent.h"
> -#include "contactmatcher_p.h"
> -#include "phoneutils_p.h"
> -#include <QDebug>
> -#include <QTimerEvent>
>  
>  Q_DECLARE_METATYPE(History::TextEventAttachments)
>  
>  HistoryThreadModel::HistoryThreadModel(QObject *parent) :
> -    QAbstractListModel(parent), mCanFetchMore(true), mFilter(0), mSort(0),
> -    mType(EventTypeText), mMatchContacts(false), mUpdateTimer(0)
> +    HistoryModel(parent), mCanFetchMore(true)
>  {
>      // configure the roles
> -    mRoles[AccountIdRole] = "accountId";
> -    mRoles[ThreadIdRole] = "threadId";
> -    mRoles[TypeRole] = "type";
> -    mRoles[ParticipantsRole] = "participants";
> +    mRoles = HistoryModel::roleNames();
>      mRoles[CountRole] = "count";
>      mRoles[UnreadCountRole] = "unreadCount";
> -    mRoles[PropertiesRole] = "properties";
>  
>      // roles related to the thread´s last event
>      mRoles[LastEventIdRole] = "eventId";
> @@ -63,16 +49,6 @@
>      mRoles[LastEventTextSubjectRole] = "eventTextSubject";
>      mRoles[LastEventCallMissedRole] = "eventCallMissed";
>      mRoles[LastEventCallDurationRole] = "eventCallDuration";
> -
> -    connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
> -    connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
> -    connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
> -    connect(ContactMatcher::instance(),
> -            SIGNAL(contactInfoChanged(QString,QVariantMap)),
> -            SLOT(onContactInfoChanged(QString,QVariantMap)));
> -
> -    // create the results view
> -    triggerQueryUpdate();
>  }
>  
>  int HistoryThreadModel::rowCount(const QModelIndex &parent) const
> @@ -91,6 +67,16 @@
>      }
>  
>      History::Thread thread = mThreads[index.row()];
> +    QVariant result = threadData(thread, role);
> +    if (result.isNull()) {
> +        result = HistoryModel::data(index, role);
> +    }
> +
> +    return result;
> +}
> +
> +QVariant HistoryThreadModel::threadData(const History::Thread &thread, int role) const
> +{
>      History::Event event = thread.lastEvent();
>      History::TextEvent textEvent;
>      History::VoiceEvent voiceEvent;
> @@ -108,22 +94,6 @@
>  
>      QVariant result;
>      switch (role) {
> -    case AccountIdRole:
> -        result = thread.accountId();
> -        break;
> -    case ThreadIdRole:
> -        result = thread.threadId();
> -        break;
> -    case TypeRole:
> -        result = (int) thread.type();
> -        break;
> -    case ParticipantsRole:
> -        if (mMatchContacts) {
> -            result = ContactMatcher::instance()->contactInfo(thread.participants());
> -        } else {
> -            result = thread.participants();
> -        }
> -        break;
>      case CountRole:
>          result = thread.count();
>          break;
> @@ -214,7 +184,7 @@
>  
>  bool HistoryThreadModel::canFetchMore(const QModelIndex &parent) const
>  {
> -    if (parent.isValid() || !mFilter) {
> +    if (parent.isValid() || !mFilter || mThreadView.isNull()) {
>          return false;
>      }
>  
> @@ -227,7 +197,7 @@
>          return;
>      }
>  
> -    History::Threads threads = mThreadView->nextPage();
> +    History::Threads threads = fetchNextPage();
>      if (threads.isEmpty()) {
>          mCanFetchMore = false;
>          Q_EMIT canFetchMoreChanged();
> @@ -243,121 +213,12 @@
>      return mRoles;
>  }
>  
> -HistoryQmlFilter *HistoryThreadModel::filter() const
> -{
> -    return mFilter;
> -}
> -
> -void HistoryThreadModel::setFilter(HistoryQmlFilter *value)
> -{
> -    // disconnect the previous filter
> -    if (mFilter) {
> -        mFilter->disconnect(this);
> -    }
> -
> -    mFilter = value;
> -    if (mFilter) {
> -        connect(mFilter,
> -                SIGNAL(filterChanged()),
> -                SLOT(triggerQueryUpdate()));
> -    }
> -
> -    Q_EMIT filterChanged();
> -    triggerQueryUpdate();
> -}
> -
> -HistoryQmlSort *HistoryThreadModel::sort() const
> -{
> -    return mSort;
> -}
> -
> -void HistoryThreadModel::setSort(HistoryQmlSort *value)
> -{
> -    // disconnect the previous sort
> -    if (mSort) {
> -        mSort->disconnect(this);
> -    }
> -
> -    mSort = value;
> -    if (mSort) {
> -        connect(mSort,
> -                SIGNAL(sortChanged()),
> -                SLOT(triggerQueryUpdate()));
> -    }
> -
> -    Q_EMIT sortChanged();
> -    triggerQueryUpdate();
> -}
> -
> -HistoryThreadModel::EventType HistoryThreadModel::type() const
> -{
> -    return mType;
> -}
> -
> -void HistoryThreadModel::setType(HistoryThreadModel::EventType value)
> -{
> -    mType = value;
> -    Q_EMIT typeChanged();
> -    triggerQueryUpdate();
> -}
> -
> -bool HistoryThreadModel::matchContacts() const
> -{
> -    return mMatchContacts;
> -}
> -
> -void HistoryThreadModel::setMatchContacts(bool value)
> -{
> -    mMatchContacts = value;
> -    Q_EMIT matchContactsChanged();
> -
> -    // mark all indexes as changed
> -    if (rowCount() > 0) {
> -        Q_EMIT dataChanged(index(0), index(rowCount()-1));
> -    }
> -}
> -
> -QString HistoryThreadModel::threadIdForParticipants(const QString &accountId, int eventType, const QStringList &participants, int matchFlags, bool create)
> -{
> -    if (participants.isEmpty()) {
> -        return QString::null;
> -    }
> -
> -    History::Thread thread = History::Manager::instance()->threadForParticipants(accountId,
> -                                                                                 (History::EventType)eventType,
> -                                                                                 participants,
> -                                                                                 (History::MatchFlags)matchFlags,
> -                                                                                 create);
> -    if (!thread.isNull()) {
> -        return thread.threadId();
> -    }
> -
> -    return QString::null;
> -}
> -
>  bool HistoryThreadModel::removeThread(const QString &accountId, const QString &threadId, int eventType)
>  {
>      History::Thread thread = History::Manager::instance()->getSingleThread((History::EventType)eventType, accountId, threadId);
>      return History::Manager::instance()->removeThreads(History::Threads() << thread);
>  }
>  
> -QVariant HistoryThreadModel::get(int row) const
> -{
> -    if (row < 0 || row >= mThreads.count()) {
> -        return QVariant();
> -    }
> -
> -    return mThreads[row].properties();
> -}
> -
> -void HistoryThreadModel::triggerQueryUpdate()
> -{
> -    if (mUpdateTimer) {
> -        killTimer(mUpdateTimer);
> -    }
> -    mUpdateTimer = startTimer(100);
> -}
> -
>  void HistoryThreadModel::updateQuery()
>  {
>      // remove all events from the model
> @@ -419,25 +280,37 @@
>          return;
>      }
>  
> -    // FIXME: handle sorting
> -    beginInsertRows(QModelIndex(), mThreads.count(), mThreads.count() + threads.count() - 1);
> -    mThreads << threads;
> -    endInsertRows();
> +    Q_FOREACH(const History::Thread &thread, threads) {
> +        // if the thread is already inserted, skip it
> +        if (mThreads.contains(thread)) {
> +            continue;
> +        }
> +
> +        int pos = positionForItem(thread.properties());
> +        beginInsertRows(QModelIndex(), pos, pos);
> +        mThreads.insert(pos, thread);
> +        endInsertRows();
> +    }
>  }
>  
>  void HistoryThreadModel::onThreadsModified(const History::Threads &threads)
>  {
> +    History::Threads newThreads;
>      Q_FOREACH(const History::Thread &thread, threads) {
>          int pos = mThreads.indexOf(thread);
>          if (pos >= 0) {
>              mThreads[pos] = thread;
>              QModelIndex idx = index(pos);
>              Q_EMIT dataChanged(idx, idx);
> +        } else {
> +            newThreads << thread;
>          }
>      }
>  
> -    // FIXME: append modified threads that are not loaded yet and make sure they don´t
> -    // get added twice to the model
> +    // add threads that were not yet on the model
> +    if (!newThreads.isEmpty()) {
> +        onThreadsAdded(newThreads);
> +    }
>  }
>  
>  void HistoryThreadModel::onThreadsRemoved(const History::Threads &threads)
> @@ -456,40 +329,7 @@
>      // should be handle internally in History::ThreadView?
>  }
>  
> -void HistoryThreadModel::timerEvent(QTimerEvent *event)
> -{
> -    if (event->timerId() == mUpdateTimer) {
> -        killTimer(mUpdateTimer);
> -        mUpdateTimer = 0;
> -        updateQuery();
> -    }
> -}
> -
> -
> -void HistoryThreadModel::onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo)
> -{
> -    Q_UNUSED(contactInfo)
> -    if (!mMatchContacts) {
> -        return;
> -    }
> -
> -    QList<QModelIndex> changedIndexes;
> -    int count = rowCount();
> -    for (int i = 0; i < count; ++i) {
> -        // WARNING: do not use mEvents directly to verify which indexes to change as there is the
> -        // HistoryGroupedEventsModel which is based on this model and handles the items in a different way
> -        QModelIndex idx = index(i);
> -        QVariantMap eventProperties = idx.data(PropertiesRole).toMap();
> -        QStringList participants = eventProperties[History::FieldParticipants].toStringList();
> -        Q_FOREACH(const QString &participant, participants) {
> -            if (PhoneUtils::comparePhoneNumbers(participant, phoneNumber)) {
> -                changedIndexes << idx;
> -            }
> -        }
> -    }
> -
> -    // now emit the dataChanged signal to all changed indexes
> -    Q_FOREACH(const QModelIndex &idx, changedIndexes) {
> -        Q_EMIT dataChanged(idx, idx);
> -    }
> +History::Threads HistoryThreadModel::fetchNextPage()
> +{
> +    return mThreadView->nextPage();
>  }
> 
> === modified file 'Ubuntu/History/historythreadmodel.h'
> --- Ubuntu/History/historythreadmodel.h	2014-09-09 23:16:29 +0000
> +++ Ubuntu/History/historythreadmodel.h	2014-09-09 23:16:29 +0000
> @@ -22,7 +22,7 @@
>  #ifndef HISTORYTHREADMODEL_H
>  #define HISTORYTHREADMODEL_H
>  
> -#include <QAbstractListModel>
> +#include "historymodel.h"
>  #include "types.h"
>  #include "textevent.h"
>  #include "thread.h"
> @@ -30,52 +30,16 @@
>  class HistoryQmlFilter;
>  class HistoryQmlSort;
>  
> -class HistoryThreadModel : public QAbstractListModel
> +class HistoryThreadModel : public HistoryModel
>  {
>      Q_OBJECT
> -    Q_PROPERTY(HistoryQmlFilter *filter READ filter WRITE setFilter NOTIFY filterChanged)
> -    Q_PROPERTY(HistoryQmlSort *sort READ sort WRITE setSort NOTIFY sortChanged)
> -    Q_PROPERTY(EventType type READ type WRITE setType NOTIFY typeChanged)
> -    Q_PROPERTY(bool matchContacts READ matchContacts WRITE setMatchContacts NOTIFY matchContactsChanged)
> -    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
> -    Q_PROPERTY(bool canFetchMore READ canFetchMore NOTIFY canFetchMoreChanged)
> -    Q_ENUMS(EventType)
> -    Q_ENUMS(Role)
> -    Q_ENUMS(MatchFlag)
> -    Q_ENUMS(MessageStatus)
> +    Q_ENUMS(ThreadRole)
> +
>  public:
> -    enum EventType {
> -        EventTypeText = History::EventTypeText,
> -        EventTypeVoice = History::EventTypeVoice
> -    };
> -
> -    enum MatchFlag {
> -        MatchCaseSensitive = History::MatchCaseSensitive,
> -        MatchCaseInsensitive = History::MatchCaseInsensitive,
> -        MatchContains = History::MatchContains,
> -        MatchPhoneNumber = History::MatchPhoneNumber
> -    };
> -
> -    enum MessageStatus
> -    {
> -        MessageStatusUnknown = History::MessageStatusUnknown,
> -        MessageStatusDelivered = History::MessageStatusDelivered,
> -        MessageStatusTemporarilyFailed = History::MessageStatusTemporarilyFailed,
> -        MessageStatusPermanentlyFailed = History::MessageStatusPermanentlyFailed,
> -        MessageStatusAccepted = History::MessageStatusAccepted,
> -        MessageStatusRead = History::MessageStatusRead,
> -        MessageStatusDeleted = History::MessageStatusDeleted,
> -        MessageStatusPending = History::MessageStatusPending // pending attachment download
> -    };
> -
> -    enum Role {
> -        AccountIdRole = Qt::UserRole,
> -        ThreadIdRole,
> -        TypeRole,
> -        ParticipantsRole,
> -        CountRole,
> +
> +    enum ThreadRole {
> +        CountRole = HistoryModel::LastRole,
>          UnreadCountRole,
> -        PropertiesRole,
>          LastEventIdRole,
>          LastEventSenderIdRole,
>          LastEventTimestampRole,
> @@ -88,69 +52,38 @@
>          LastEventTextSubjectRole,
>          LastEventTextAttachmentsRole,
>          LastEventCallMissedRole,
> -        LastEventCallDurationRole
> +        LastEventCallDurationRole,
> +        LastThreadRole
>      };
>  
>      explicit HistoryThreadModel(QObject *parent = 0);
>  
> -    int rowCount(const QModelIndex &parent = QModelIndex()) const;
> -    QVariant data(const QModelIndex &index, int role) const;
> +    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
> +    virtual QVariant data(const QModelIndex &index, int role) const;
> +    QVariant threadData(const History::Thread &thread, int role) const;
>  
>      bool canFetchMore(const QModelIndex &parent = QModelIndex()) const;
>      void fetchMore(const QModelIndex &parent);
>  
> -    QHash<int, QByteArray> roleNames() const;
> -
> -    HistoryQmlFilter *filter() const;
> -    void setFilter(HistoryQmlFilter *value);
> -
> -    HistoryQmlSort *sort() const;
> -    void setSort(HistoryQmlSort *value);
> -
> -    EventType type() const;
> -    void setType(EventType value);
> -
> -    bool matchContacts() const;
> -    void setMatchContacts(bool value);
> -
> -    Q_INVOKABLE QString threadIdForParticipants(const QString &accountId,
> -                                                int eventType,
> -                                                const QStringList &participants,
> -                                                int matchFlags = (int)History::MatchCaseSensitive,
> -                                                bool create = false);
> +    virtual QHash<int, QByteArray> roleNames() const;
> +
>      Q_INVOKABLE bool removeThread(const QString &accountId, const QString &threadId, int eventType);
> -    Q_INVOKABLE QVariant get(int row) const;
> -
> -Q_SIGNALS:
> -    void filterChanged();
> -    void sortChanged();
> -    void typeChanged();
> -    void matchContactsChanged();
> -    void countChanged();
> -    void canFetchMoreChanged();
>  
>  protected Q_SLOTS:
> -    void triggerQueryUpdate();
> -    void updateQuery();
> -    void onThreadsAdded(const History::Threads &threads);
> -    void onThreadsModified(const History::Threads &threads);
> -    void onThreadsRemoved(const History::Threads &threads);
> -    void onContactInfoChanged(const QString &phoneNumber, const QVariantMap &contactInfo);
> +    virtual void updateQuery();
> +    virtual void onThreadsAdded(const History::Threads &threads);
> +    virtual void onThreadsModified(const History::Threads &threads);
> +    virtual void onThreadsRemoved(const History::Threads &threads);
>  
>  protected:
> -    void timerEvent(QTimerEvent *event);
> +    History::Threads fetchNextPage();
>  
>  private:
>      History::ThreadViewPtr mThreadView;
>      History::Threads mThreads;
>      bool mCanFetchMore;
> -    HistoryQmlFilter *mFilter;
> -    HistoryQmlSort *mSort;
> -    EventType mType;
> -    bool mMatchContacts;
>      QHash<int, QByteArray> mRoles;
>      mutable QMap<History::TextEvent, QList<QVariant> > mAttachmentCache;
> -    int mUpdateTimer;
>  };
>  
>  #endif // HISTORYTHREADMODEL_H
> 
> === removed file 'Ubuntu/History/sortproxymodel.cpp'
> --- Ubuntu/History/sortproxymodel.cpp	2014-08-06 14:36:36 +0000
> +++ Ubuntu/History/sortproxymodel.cpp	1970-01-01 00:00:00 +0000
> @@ -1,68 +0,0 @@
> -/*
> - * Copyright (C) 2013 Canonical, Ltd.
> - *
> - * Authors:
> - *  Tiago Salem Herrmann <tiago.herrmann at canonical.com>
> - *
> - * This file is part of history-service.
> - *
> - * history-service 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; version 3.
> - *
> - * history-service 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 "sortproxymodel.h"
> -#include <QDebug>
> -
> -SortProxyModel::SortProxyModel(QObject *parent) :
> -    QSortFilterProxyModel(parent), mAscending(true)
> -{
> -    setDynamicSortFilter(true);
> -    updateSorting();
> -
> -    connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
> -    connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
> -    connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
> -}
> -
> -bool SortProxyModel::ascending() const
> -{
> -    return mAscending;
> -}
> -
> -void SortProxyModel::setAscending(bool value)
> -{
> -    if (mAscending != value) {
> -        mAscending = value;
> -        updateSorting();
> -        Q_EMIT ascendingChanged();
> -    }
> -}
> -
> -QVariant SortProxyModel::get(int row) const
> -{
> -    QVariantMap data;
> -    QModelIndex sourceIndex = mapToSource(index(row, 0));
> -    if (sourceIndex.isValid()) {
> -        QAbstractItemModel *source = sourceModel();
> -        QHash<int, QByteArray> roles = source->roleNames();
> -        Q_FOREACH(int role, roles.keys()) {
> -            data.insert(roles[role], source->data(sourceIndex, role));
> -        }
> -    }
> -
> -    return data;
> -}
> -
> -void SortProxyModel::updateSorting()
> -{
> -    sort(0, mAscending ? Qt::AscendingOrder : Qt::DescendingOrder);
> -}
> 
> === removed file 'Ubuntu/History/sortproxymodel.h'
> --- Ubuntu/History/sortproxymodel.h	2014-08-06 14:36:36 +0000
> +++ Ubuntu/History/sortproxymodel.h	1970-01-01 00:00:00 +0000
> @@ -1,55 +0,0 @@
> -/*
> - * Copyright (C) 2013 Canonical, Ltd.
> - *
> - * Authors:
> - *  Tiago Salem Herrmann <tiago.herrmann at canonical.com>
> - *
> - * This file is part of history-service.
> - *
> - * history-service 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; version 3.
> - *
> - * history-service 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 SORTPROXYMODEL_H
> -#define SORTPROXYMODEL_H
> -
> -#include <QSortFilterProxyModel>
> -
> -class SortProxyModel : public QSortFilterProxyModel
> -{
> -    Q_OBJECT
> -    Q_PROPERTY(bool ascending
> -               READ ascending
> -               WRITE setAscending
> -               NOTIFY ascendingChanged)
> -    Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
> -
> -public:
> -    explicit SortProxyModel(QObject *parent = 0);
> -
> -    bool ascending() const;
> -    void setAscending(bool value);
> -
> -    Q_INVOKABLE QVariant get(int row) const;
> -
> -private Q_SLOTS:
> -    void updateSorting();
> -
> -Q_SIGNALS:
> -    void ascendingChanged();
> -    void countChanged();
> -
> -private:
> -    bool mAscending;
> -};
> -
> -#endif // SORTPROXYMODEL_H
> 
> === modified file 'src/thread.cpp'
> --- src/thread.cpp	2013-12-03 20:04:18 +0000
> +++ src/thread.cpp	2014-09-09 23:16:29 +0000
> @@ -164,6 +164,8 @@
>      map[FieldParticipants] = d->participants;
>      map[FieldCount] = d->count;
>      map[FieldUnreadCount] = d->unreadCount;
> +    map[FieldLastEventId] = lastEvent().eventId();
> +    map[FieldLastEventTimestamp] = lastEvent().timestamp();
>  
>      return map;
>  }
> 
> === modified file 'src/types.h'
> --- src/types.h	2014-09-09 23:16:29 +0000
> +++ src/types.h	2014-09-09 23:16:29 +0000
> @@ -109,6 +109,10 @@
>  static const char* FieldDate = "date";
>  static const char* FieldNewEvent = "newEvent";
>  
> +// thread fields
> +static const char* FieldLastEventId = "lastEventId";
> +static const char* FieldLastEventTimestamp = "lastEventTimestamp";
> +
>  // text event fields
>  static const char* FieldMessage = "message";
>  static const char* FieldMessageType = "messageType";
> 


-- 
https://code.launchpad.net/~boiko/history-service/keep_models_sorted/+merge/234024
Your team Ubuntu Phablet Team is subscribed to branch lp:history-service.



More information about the Ubuntu-reviews mailing list