I have made the following changes intended for :
  CE:Apps / qmlmessages

Please review and accept or decline.
BOSS has already run some checks on this request.
See the "Messages from BOSS" section below.

https://build.pub.meego.com//request/show/6061

Thank You,
John Brooks

[This message was auto-generated]

---

Request # 6061:

Messages from BOSS:

State: review at 2012-08-24T09:27:27 by bossbot

Reviews:
       accepted by bossbot : Prechecks succeeded.
       new for CE-maintainers : Please replace this text with a review and 
approve/reject the review (not the SR). BOSS will take care of the rest

Changes:
  submit: home:special:branches:CE:Apps / qmlmessages -> CE:Apps / qmlmessages
  
changes files:
--------------
--- qmlmessages.changes
+++ qmlmessages.changes
@@ -0,0 +1,7 @@
+* Fri Aug 24 2012 John Brooks <[email protected]> - 0.0.2
+- Fixes NEMO#320: Integrate with libcommhistory
+- Fixes NEMO#291: Incoming conversations should be merged with existing 
conversations
+- Fixes NEMO#313: Account selection doesn't work
+- Changed text boxes to fit to their text exactly and improve readability
+- Messages view now starts from the bottom of the screen
+

old:
----
  0001-Fix-MessagesView-scrolling.patch
  0001-add-a-.desktop-file-for-qmlmessages.patch
  qmlmessages-0.0.1.tar.bz2

new:
----
  qmlmessages-0.0.2.tar.bz2

spec files:
-----------
--- qmlmessages.spec
+++ qmlmessages.spec
@@ -9,22 +9,20 @@
 # << macros
 
 Summary:    Messaging application for nemo
-Version:    0.0.1
+Version:    0.0.2
 Release:    1
 Group:      Applications/System
 License:    BSD
 URL:        https://github.com/nemomobile/qmlmessages
 Source0:    %{name}-%{version}.tar.bz2
 Source100:  qmlmessages.yaml
-Patch0:     0001-add-a-.desktop-file-for-qmlmessages.patch
-Patch1:     0001-Fix-MessagesView-scrolling.patch
 Requires:   qt-components
-Requires:   nemo-qml-plugins-thumbnailer
 BuildRequires:  pkgconfig(QtCore) >= 4.7.0
 BuildRequires:  pkgconfig(QtDeclarative)
 BuildRequires:  pkgconfig(QtContacts)
 BuildRequires:  pkgconfig(qdeclarative-boostable)
 BuildRequires:  pkgconfig(TelepathyQt4)
+BuildRequires:  pkgconfig(commhistory)
 BuildRequires:  desktop-file-utils
 Provides:   meego-handset-sms > 0.1.2
 Provides:   meego-handset-sms-branding-upstream > 0.1.2
@@ -38,10 +36,6 @@
 %prep
 %setup -q -n %{name}
 
-# 0001-add-a-.desktop-file-for-qmlmessages.patch
-%patch0 -p1
-# 0001-Fix-MessagesView-scrolling.patch
-%patch1 -p1
 # >> setup
 # << setup
 

other changes:
--------------

++++++ qmlmessages-0.0.1.tar.bz2 -> qmlmessages-0.0.2.tar.bz2
--- qml/qmlmessages/AccountSelector.qml
+++ qml/qmlmessages/AccountSelector.qml
@@ -33,10 +33,12 @@
 import org.nemomobile.qmlmessages 1.0
 
 Rectangle {
+    id: selector
     color: "white"
 
     property alias model: dialog.model
     property alias selectedIndex: dialog.selectedIndex
+    property string selectedUid: ""
 
     Button {
         id: selectBtn
@@ -66,13 +68,18 @@
         when: dialog.model !== undefined && dialog.model.count > 0
 
         PropertyChanges {
+            target: dialog
+            selectedIndex: 0
+        }
+
+        PropertyChanges {
             target: selectBtn
             text: dialog.model.get(dialog.selectedIndex)
         }
 
         PropertyChanges {
-            target: dialog
-            selectedIndex: 0
+            target: selector
+            selectedUid: dialog.model.get(dialog.selectedIndex, 
AccountsModel.AccountUidRole)
         }
     }
 }
--- qml/qmlmessages/ConversationListWidget.qml
+++ qml/qmlmessages/ConversationListWidget.qml
@@ -41,15 +41,6 @@
         }
     }
 
-    ConversationsModel {
-        id: conversationsModel
-
-        Component.onCompleted: {
-            clientHandler.incomingChat.connect(conversationsModel.addChat)
-            clientHandler.outgoingChat.connect(conversationsModel.addChat)
-        }
-    }
-
     ListView {
         id: cardListView
         anchors.fill: parent
@@ -59,11 +50,11 @@
         keyNavigationWraps: false
         clip: true
         opacity: 0
-        model: conversationsModel
+        model: groupModel
 
         delegate: ConversationListDelegate {
             onClicked: {
-                pageStack.push(Qt.resolvedUrl("ConversationPage.qml"), { 
channel: model.conversation })
+                pageStack.push(Qt.resolvedUrl("ConversationPage.qml"), { 
group: model.groupId })
             }
         }
     }
--- qml/qmlmessages/ConversationPage.qml
+++ qml/qmlmessages/ConversationPage.qml
@@ -39,7 +39,13 @@
  * are different. */
 Page {
     id: conversationPage
-    property ConversationChannel channel
+
+    property int group: -1
+    onGroupChanged: if (group >= 0) channel.setGroup(group)
+
+    ConversationChannel {
+        id: channel
+    }
 
     PageHeader {
         id: header
@@ -67,7 +73,7 @@
             elide: Text.ElideRight
             smooth: true
             color: "#111111"
-            text: channel == null ? "" : channel.contactId
+            text: channel.contactId
             style: Text.Raised
             styleColor: "white"
 
@@ -107,9 +113,8 @@
             if (targetEditor.text.length < 1 || accountSelector.model == 
undefined
                 || accountSelector.selectedIndex < 0)
                 return
-            console.log("startConversation");
-            channel = accountSelector.model.ensureTextChat(
-                    accountSelector.model.selectedIndex, targetEditor.text)
+            console.log("startConversation", accountSelector.selectedUid, 
targetEditor.text);
+            channel.setGroup(accountSelector.selectedUid, targetEditor.text)
         }
     }
 
@@ -166,8 +171,8 @@
                         targetEditor.startConversation()
                     }
 
-                    if (conversationPage.channel !== null && 
textInput.text.length > 0) {
-                        conversationPage.channel.sendMessage(textInput.text)
+                    if (textInput.text.length > 0) {
+                        channel.sendMessage(textInput.text)
                         textInput.text = ""
                     }
                 }
@@ -178,7 +183,7 @@
     states: [
         State {
             name: "active"
-            when: conversationPage.channel !== null
+            when: channel.state != ConversationChannel.Null
 
             PropertyChanges {
                 target: messagesView
@@ -187,7 +192,7 @@
         },
         State {
             name: "new"
-            when: conversationPage.channel == null
+            when: channel.state == ConversationChannel.Null
 
             PropertyChanges {
                 target: targetEditor
--- qml/qmlmessages/MessagesView.qml
+++ qml/qmlmessages/MessagesView.qml
@@ -34,50 +34,65 @@
 
 Item {
     property alias model: view.model
-
-    onHeightChanged: view.updateHeight()
+    // The event model is in descending order, but we need to display 
ascending.
+    // There is no sane way to invert the view, but we can use this incredibly
+    // bad hack: rotate the view, then rotate the delegate to be upright.
+    rotation: 180
 
     ListView {
         id: view
         spacing: 20
-        anchors.left: parent.left
-        anchors.right: parent.right
-        anchors.bottom: parent.bottom
-        height: 1
+        anchors.fill: parent
         cacheBuffer: parent.height
 
-        // Bottom-align chat items when contentHeight is less than the screen.
-        // This requires a hack - adjusting the height of the view and 
anchoring
-        // it to the bottom, while disabling interactive scrolling.
-        // Also, update contentY to keep the latest item at the bottom.
-        onContentHeightChanged: { updateHeight() }
-        function updateHeight() {
-            height = Math.max(1, Math.min(parent.height, contentHeight))
-            interactive = parent.height < contentHeight
-            contentY = contentHeight - height
-        }
+        onCountChanged: view.positionViewAtBeginning()
+        // Necessary when opening VKB, for example
+        onHeightChanged: view.positionViewAtBeginning()
 
         delegate: BorderImage {
             id: messageBox
-            height: messageContent.height + 20
-            width: parent.width * 0.8
+            height: childrenRect.height + 20
+            width: childrenRect.width + 30
             cache: true
+            // Fix rotation from the view hack...
+            rotation: 180
+
+            property int status: model.status
 
             Item {
                 id: messageContent
-                anchors.left: parent.left
-                anchors.right: parent.right
-                anchors.margins: 10
-                height: Math.max(messageText.height, 60)
+                height: messageText.paintedHeight
+                width: messageText.paintedWidth
                 y: 10
+                x: 10
 
-                Label {
+                Text {
                     id: messageText
                     text: model.text
-                    width: parent.width
+                    width: messageBox.parent.width * 0.7
+                    wrapMode: Text.Wrap
+                    style: Text.Raised
+                    styleColor: "#eeeeee"
+                    font.family: labelStyle.fontFamily
+                    font.pixelSize: labelStyle.fontPixelSize
+
+                    // Cannot use Label, because it shadows the 'style' 
property.
+                    // Copy its text formatting instead.
+                    LabelStyle {
+                        id: labelStyle
+                    }
                 }
             }
 
+            function showError(details) {
+                details = "Cannot send message\n\n" + details
+                errorComponent.createObject(messageBox, { text: details })
+            }
+            onStatusChanged: {
+                if (status < 0)
+                    showError(model.statusMessage)
+            }
+
             // This should use meegotouch's speechbubble theme elements, but 
those SVG group
             // images are not supported in qt-components currently. 
incoming.svg and outgoing.svg
             // are extracted from the group SVG in meegotouch's base theme and 
included here.
@@ -88,18 +103,12 @@
 
                     PropertyChanges {
                         target: messageBox
-                        x: parent.width - width
                         source: "qrc:/images/incoming.svg"
                         border.left: 24
                         border.right: 24
                         border.top: 24
                         border.bottom: 24
                     }
-
-                    PropertyChanges {
-                        target: messageContent
-                        anchors.rightMargin: 20
-                    }
                 },
                 State {
                     name: "outgoing"
@@ -107,6 +116,7 @@
 
                     PropertyChanges {
                         target: messageBox
+                        x: parent.width - width
                         source: "qrc:/images/outgoing.svg"
                         border.left: 24
                         border.right: 24
@@ -116,15 +126,55 @@
 
                     PropertyChanges {
                         target: messageContent
-                        anchors.leftMargin: 20
+                        x: 20
                     }
                 }
             ]
+
+            Component {
+                id: errorComponent
+
+                Item {
+                    id: errorContent
+                    anchors.top: messageContent.bottom
+                    anchors.topMargin: 5
+                    anchors.left: messageContent.left
+                    width: Math.max(errorText.paintedWidth, 
messageText.paintedWidth)
+                    height: errorText.paintedHeight + 6
+
+                    property alias text: errorText.text
+
+                    Rectangle {
+                        anchors.left: parent.left
+                        anchors.right: parent.right
+                        height: 1
+                        color: "#afafaf"
+                    }
+
+                    Label {
+                        id: errorText
+                        width: messageText.width
+                        anchors.top: parent.top
+                        anchors.topMargin: 5
+
+                        platformStyle: LabelStyle {
+                            textColor: "red"
+                        }
+                    }
+                }
+            }
         }
     }
 
     ScrollDecorator {
         flickableItem: view
+
+        // The rotated view hack screws up ScrollDecorator. This is a (also 
very bad) workaround.
+        anchors.fill: undefined
+        anchors.right: view.right
+        anchors.rightMargin: view.width - childrenRect.width - 4 - 
(__hasPageWidth ? __rightPageMargin : 0)
+        anchors.top: view.top
+        anchors.bottom: view.bottom
     }
 }
 
--- qml/qmlmessages/main.qml
+++ qml/qmlmessages/main.qml
@@ -38,13 +38,6 @@
 
     initialPage: ConversationListPage {}
 
-    Connections {
-        target: clientHandler
-        onIncomingChat: {
-            pageStack.push(Qt.resolvedUrl("ConversationPage.qml"), { channel: 
conversation })
-        }
-    }
-
     // Shared AccountsModel
     AccountsModel {
         id: accountsModel
--- qmlmessages.desktop
+++ qmlmessages.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Type=Application
+Name=Messages
+Icon=icons-Applications-sms
+Exec=invoker -s --type=d /usr/bin/qmlmessages -fullscreen
+Comment=Nemo messaging application
--- qmlmessages.pro
+++ qmlmessages.pro
@@ -5,21 +5,28 @@
 LIBS += -ltelepathy-qt4
 CXXFLAGS += -fPIC
 
+CONFIG += link_pkgconfig
+PKGCONFIG += commhistory
+
 target.path = $$INSTALL_ROOT/usr/bin
 INSTALLS += target
 
+desktop.files = $${PROJECT_NAME}.desktop
+desktop.path = $$INSTALL_ROOT/usr/share/applications
+INSTALLS += desktop
+
 SOURCES += src/main.cpp \
     src/accountsmodel.cpp \
     src/clienthandler.cpp \
-    src/chatmodel.cpp \
-    src/conversationsmodel.cpp \
-    src/conversationchannel.cpp
+    src/conversationchannel.cpp \
+    src/qmlgroupmodel.cpp \
+    src/qmlchatmodel.cpp
 
 HEADERS += src/accountsmodel.h \
     src/clienthandler.h \
-    src/chatmodel.h \
-    src/conversationsmodel.h \
-    src/conversationchannel.h
+    src/conversationchannel.h \
+    src/qmlgroupmodel.h \
+    src/qmlchatmodel.h
 
 RESOURCES += res/res.qrc qml/qml.qrc
 
--- src/accountsmodel.cpp
+++ src/accountsmodel.cpp
@@ -45,6 +45,7 @@
     QHash<int,QByteArray> roles;
     roles[Qt::DisplayRole] = "name";
     roles[AccountPtrRole] = "accountPtr";
+    roles[AccountUidRole] = "accountUid";
     setRoleNames(roles);
 
     mAccountManager = 
Tp::AccountManager::create(Tp::AccountFactory::create(QDBusConnection::sessionBus(),
@@ -55,10 +56,8 @@
 
 void AccountsModel::accountManagerReady(Tp::PendingOperation *op)
 {
-    foreach (const Tp::AccountPtr &account, mAccountManager->allAccounts()) {
-        qDebug() << "Found account" << account->serviceName() << 
account->uniqueIdentifier() << account->displayName();
+    foreach (const Tp::AccountPtr &account, mAccountManager->allAccounts())
         newAccount(account);
-    }
 }
 
 void AccountsModel::newAccount(const Tp::AccountPtr &account)
@@ -70,30 +69,6 @@
     emit countChanged();
 }
 
-ConversationChannel *AccountsModel::ensureTextChat(int row, const QString 
&contactId) 
-{
-    Q_ASSERT(row >= 0 && row < mAccounts.size());
-    if (row < 0 || row >= mAccounts.size())
-        return 0;
-
-    Tp::AccountPtr account = mAccounts[row];
-    Q_ASSERT(!account.isNull());
-    if (account.isNull())
-        return 0;
-
-    qDebug() << "ensureTextChat with" << contactId;
-    Tp::PendingChannelRequest *pr = account->ensureTextChat(contactId,
-            QDateTime::currentDateTime(),
-            QLatin1String("org.freedesktop.Telepathy.Client.qmlmessages"));
-    Q_ASSERT(pr);
-    if (!pr)
-        return 0;
-
-    ConversationChannel *re = new ConversationChannel;
-    re->start(pr);
-    return re;
-}
-
 int AccountsModel::rowCount(const QModelIndex &parent) const
 {
     if (parent.isValid())
@@ -116,6 +91,7 @@
     {
         case Qt::DisplayRole: return account->displayName();
         case AccountPtrRole: return QVariant::fromValue(account);
+        case AccountUidRole: return account->objectPath();
     }
 
     return QVariant();
--- src/accountsmodel.h
+++ src/accountsmodel.h
@@ -40,6 +40,7 @@
 class AccountsModel : public QAbstractListModel
 {
     Q_OBJECT
+    Q_ENUMS(Roles)
 
     // Hack necessary for SelectionDialog in AccountSelector.qml.
     // The Qt Components dialog expects the model to have a count
@@ -47,8 +48,9 @@
     Q_PROPERTY(int count READ count NOTIFY countChanged);
 
 public:
-    enum {
-        AccountPtrRole = Qt::UserRole
+    enum Roles {
+        AccountPtrRole = Qt::UserRole,
+        AccountUidRole
     };
 
     AccountsModel(QObject *parent = 0);
@@ -63,12 +65,6 @@
 
     int count() const { return rowCount(); }
 
-public slots:
-    /* For convenience; call ensureTextChat on the account at row to create
-     * a text conversation with contactId. Returns a ConversationChannel.
-     */
-    ConversationChannel *ensureTextChat(int row, const QString &contactId);
-
 signals:
     void countChanged();
 
--- src/chatmodel.cpp
+++ src/chatmodel.cpp
@@ -1,113 +0,0 @@
-/* Copyright (C) 2012 John Brooks <[email protected]>
- *
- * You may use this file under the terms of the BSD license as follows:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *   * Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.
- *   * Neither the name of Nemo Mobile nor the names of its contributors
- *     may be used to endorse or promote products derived from this
- *     software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "chatmodel.h"
-#include "conversationchannel.h"
-#include <QDebug>
-
-#include <TelepathyQt4/ReceivedMessage>
-#include <TelepathyQt4/PendingReady>
-#include <TelepathyQt4/Contact>
-
-ChatModel::ChatModel(QObject *parent)
-    : QAbstractListModel(parent)
-{
-    QHash<int,QByteArray> roles;
-    roles[Qt::DisplayRole] = "text";
-    roles[ChatDirectionRole] = "direction";
-    roles[MessageDateRole] = "date";
-    setRoleNames(roles);
-}
-
-void ChatModel::setChannel(const Tp::TextChannelPtr &channel, 
ConversationChannel *c)
-{
-    Q_ASSERT(mChannel.isNull());
-    if (!mChannel.isNull())
-        return;
-
-    Q_ASSERT(channel->isReady(Tp::TextChannel::FeatureMessageQueue));
-    mChannel = channel;
-
-    connect(mChannel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)),
-            SLOT(messageReceived(Tp::ReceivedMessage)));
-    connect(c, SIGNAL(messageSending(QString)), SLOT(messageSent(QString)));
-
-    QList<Tp::ReceivedMessage> messages = mChannel->messageQueue();
-    foreach (Tp::ReceivedMessage msg, messages)
-        messageReceived(msg);
-}
-
-void ChatModel::messageReceived(const Tp::ReceivedMessage &message)
-{
-    qDebug() << "ChatModel:" << message.received() << message.text();
-
-    beginInsertRows(QModelIndex(), mMessages.size(), mMessages.size());
-    mMessages.append(Message(message.text(), message.received(), Incoming));
-    endInsertRows();
-
-    mChannel->acknowledge(QList<Tp::ReceivedMessage>() << message);
-}
-
-void ChatModel::messageSent(const QString &text)
-{
-    // XXX should have notification of when the message is actually sent
-    beginInsertRows(QModelIndex(), mMessages.size(), mMessages.size());
-    mMessages.append(Message(text, QDateTime::currentDateTime(), Outgoing));
-    endInsertRows();
-}
-
-int ChatModel::rowCount(const QModelIndex &parent) const
-{
-    if (!parent.isValid())
-        return mMessages.size();
-    else
-        return 0;
-}
-
-QVariant ChatModel::data(const QModelIndex &index, int role) const
-{
-    if (!index.isValid() || index.row() >= mMessages.size())
-        return QVariant();
-
-    const Message &message = mMessages[index.row()];
-    switch (role) {
-        case Qt::DisplayRole: return message.text;
-        case ChatDirectionRole: return message.direction;
-        case MessageDateRole: return message.date;
-    }
-
-    return QVariant();
-}
-
-ChatModel::Message::Message(const QString &t, const QDateTime &dt, Direction d)
-    : text(t), date(dt), direction(d)
-{
-}
-
--- src/chatmodel.h
+++ src/chatmodel.h
@@ -1,87 +0,0 @@
-/* Copyright (C) 2012 John Brooks <[email protected]>
- *
- * You may use this file under the terms of the BSD license as follows:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *   * Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.
- *   * Neither the name of Nemo Mobile nor the names of its contributors
- *     may be used to endorse or promote products derived from this
- *     software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef CHATMODEL_H
-#define CHATMODEL_H
-
-#include <QAbstractListModel>
-
-#include <TelepathyQt4/TextChannel>
-
-class ConversationChannel;
-
-/* ChatModel contains the history of a conversation, currently associated
- * with exactly one ConversationChannel. It does not handle any Telepathy
- * operations, other than reacting to sent and received messages to update
- * the history. */
-class ChatModel : public QAbstractListModel
-{
-    Q_OBJECT
-    Q_ENUMS(Direction)
-
-public:
-    enum {
-        ChatDirectionRole = Qt::UserRole,
-        MessageDateRole
-    };
-
-    enum Direction {
-        Incoming,
-        Outgoing
-    };
-
-    ChatModel(QObject *parent = 0);
-
-    void setChannel(const Tp::TextChannelPtr &channel, ConversationChannel *c);
-
-    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    virtual QVariant data(const QModelIndex &index, int role = 
Qt::DisplayRole) const;
-
-public slots:
-    void messageReceived(const Tp::ReceivedMessage &message);
-    void messageSent(const QString &text);
-
-private:
-    struct Message {
-        QString text;
-        QDateTime date;
-        Direction direction;
-
-        Message(const QString &text, const QDateTime &date, Direction 
direction);
-    };
-
-    Tp::TextChannelPtr mChannel;
-    QList<Message> mMessages;
-};
-
-Q_DECLARE_METATYPE(ChatModel*)
-
-#endif
-
--- src/clienthandler.cpp
+++ src/clienthandler.cpp
@@ -67,36 +67,7 @@
                                    const QList<ChannelRequestPtr> 
&requestsSatisfied, const QDateTime &userActionTime,
                                    const HandlerInfo &handlerInfo)
 {
-    /* This function assumes that no more than one conversation's channels can 
be satisfied in
-     * a single call. To my knowledge, that is safe, but there's no easy way 
to assert
-     * on it, and implementing it otherwise is very difficult. */
-    ConversationChannel *existingConversation = 0;
-
-    foreach (const ChannelRequestPtr &r, requestsSatisfied) {
-        QHash<QString,ConversationChannel*>::Iterator it = 
pendingRequests.find(r->objectPath());
-        if (it != pendingRequests.end()) {
-            Q_ASSERT(!existingConversation);
-            existingConversation = *it;
-            pendingRequests.erase(it);
-        }
-    }
-
-    foreach (const ChannelPtr &c, channels) {
-        if (!existingConversation) {
-            ConversationChannel *conversation = new ConversationChannel(this);
-            conversation->setChannel(c);
-            emit incomingChat(conversation);
-        }
-        // XXX Do we ever need to pass the channel? It would work around the 
succeeded() thing.
-    }
-
+    // XXX what do we need to take care of here?
     context->setFinished();
 }
 
-void ClientHandler::addChannelRequest(const ChannelRequestPtr &request, 
ConversationChannel *c)
-{
-    // XXX Crashable if the ConversationChannel is deleted...
-    pendingRequests.insert(request->objectPath(), c);
-    emit outgoingChat(c);
-}
-
--- src/clienthandler.h
+++ src/clienthandler.h
@@ -51,16 +51,6 @@
                                 const Tp::ConnectionPtr &connection, const 
QList<Tp::ChannelPtr> &channels,
                                 const QList<Tp::ChannelRequestPtr> 
&requestsSatisfied, const QDateTime &userActionTime,
                                 const HandlerInfo &handlerInfo);
-
-public slots:
-    void addChannelRequest(const Tp::ChannelRequestPtr &request, 
ConversationChannel *conversation);
-
-signals:
-    void incomingChat(ConversationChannel *conversation);
-    void outgoingChat(ConversationChannel *conversation);
-
-private:
-    QHash<QString,ConversationChannel*> pendingRequests;
 };
 
 #endif
--- src/conversationchannel.cpp
+++ src/conversationchannel.cpp
@@ -30,19 +30,100 @@
 
 #include "conversationchannel.h"
 #include "clienthandler.h"
-#include "chatmodel.h"
+#include "qmlgroupmodel.h"
+#include "qmlchatmodel.h"
 
 #include <TelepathyQt4/ChannelRequest>
 #include <TelepathyQt4/TextChannel>
 #include <TelepathyQt4/Channel>
 #include <TelepathyQt4/PendingReady>
 #include <TelepathyQt4/Contact>
+#include <TelepathyQt4/Account>
+#include <TelepathyQt4/AccountManager>
+
+// XXX
+extern Tp::AccountManagerPtr accountManager;
+extern QmlGroupModel *groupModel;
 
 ConversationChannel::ConversationChannel(QObject *parent)
     : QObject(parent), mPendingRequest(0), mState(Null), mModel(0)
 {
 }
 
+ConversationChannel::~ConversationChannel()
+{
+}
+
+void ConversationChannel::setGroup(int groupid)
+{
+    CommHistory::Group group;
+
+    for (int i = 0, c = groupModel->rowCount(); i < c; i++) {
+        int id = groupModel->index(i, 
0).data(QmlGroupModel::GroupIdRole).toInt();
+        if (id == groupid) {
+            group = groupModel->group(groupModel->index(i, 0));
+            break;
+        }
+    }
+
+    if (!group.isValid()) {
+        qWarning() << Q_FUNC_INFO << "Cannot find group id" << groupid;
+        return;
+    }
+
+    setupGroup(group);
+}
+
+void ConversationChannel::setGroup(const QString &localUid, const QString 
&remoteUid)
+{
+    CommHistory::Group group;
+
+    for (int i = 0, c = groupModel->rowCount(); i < c; i++) {
+        const CommHistory::Group &g = groupModel->group(groupModel->index(i, 
0));
+        if (g.localUid() == localUid && g.remoteUids().size() == 1
+                && g.remoteUids().first() == remoteUid) {
+            group = g;
+            break;
+        }
+    }
+
+    if (!group.isValid()) {
+        qDebug() << "ConversationChannel::setGroup creating group for" << 
localUid << remoteUid;
+        group.setLocalUid(localUid);
+        group.setRemoteUids(QStringList() << remoteUid);
+        group.setChatType(CommHistory::Group::ChatTypeP2P);
+        if (!groupModel->addGroup(group)) {
+            qWarning() << "ConversationChannel::setGroup failed creating 
group" << localUid << remoteUid;
+            return;
+        }
+        Q_ASSERT(group.isValid());
+    }
+
+    setupGroup(group);
+}
+
+void ConversationChannel::setupGroup(const CommHistory::Group &group)
+{
+    mContactId = group.remoteUids().value(0);
+    emit contactIdChanged();
+
+    Q_ASSERT(!mModel);
+    mModel = new QmlChatModel(group.id(), this);
+    emit chatModelReady(mModel);
+
+    qDebug() << Q_FUNC_INFO << group.localUid() << group.remoteUids().value(0);
+
+    // XXX wait for account manager if necessary?
+    Tp::AccountPtr account = accountManager->accountForPath(group.localUid());
+    // XXX error check
+    Q_ASSERT(account);
+    Q_ASSERT(account->isReady());
+    Tp::PendingChannelRequest *req = 
account->ensureTextChat(group.remoteUids().value(0),
+            QDateTime::currentDateTime(),
+            QLatin1String("org.freedesktop.Telepathy.Client.qmlmessages"));
+    start(req);
+}
+
 void ConversationChannel::start(Tp::PendingChannelRequest *pendingRequest)
 {
     Q_ASSERT(state() == Null || state() == Error);
@@ -68,14 +149,8 @@
     connect(mChannel->becomeReady(Tp::TextChannel::FeatureMessageQueue),
             SIGNAL(finished(Tp::PendingOperation*)),
             SLOT(channelReady()));
-
-    if (!mModel) {
-        // XXX This is ugly repetition, necessary for incoming chats
-        // which don't hit channelRequestCreated (but that can't be
-        // moved either, due to ClientHandler::addChannelRequest)
-        mModel = new ChatModel(this);
-        emit chatModelReady(mModel);
-    }
+    connect(mChannel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)),
+            SLOT(messageReceived(Tp::ReceivedMessage)));
 
     setState(PendingReady);
 }
@@ -88,6 +163,8 @@
     if (state() != PendingRequest)
         return;
 
+    qDebug() << Q_FUNC_INFO;
+
     mRequest = r;
     connect(mRequest.data(), SIGNAL(succeeded(Tp::ChannelPtr)),
             SLOT(channelRequestSucceeded(Tp::ChannelPtr)));
@@ -96,14 +173,6 @@
 
     mPendingRequest = 0;
     setState(Requested);
-
-    // XXX is this the best place to create? And object lifetime may be wrong.
-    mModel = new ChatModel(this);
-    foreach (QString msg, mPendingMessages)
-        mModel->messageSent(msg);
-    emit chatModelReady(mModel);
-
-    ClientHandler::instance()->addChannelRequest(mRequest, this);
 }
 
 void ConversationChannel::channelRequestSucceeded(const Tp::ChannelPtr 
&channel)
@@ -117,6 +186,7 @@
         return;
     }
 
+    qDebug() << Q_FUNC_INFO;
     setChannel(channel);
     mRequest.reset();
     emit requestSucceeded();
@@ -130,6 +200,8 @@
     mRequest.reset();
     setState(Error);
     emit requestFailed(errorName, errorMessage);
+
+    qDebug() << Q_FUNC_INFO << errorName << errorMessage;
 }
 
 void ConversationChannel::channelReady()
@@ -139,13 +211,9 @@
     if (state() != PendingReady || mChannel.isNull())
         return;
 
-    Q_ASSERT(mModel);
     Tp::TextChannelPtr textChannel = 
Tp::SharedPtr<Tp::TextChannel>::dynamicCast(mChannel);
     Q_ASSERT(!textChannel.isNull());
-    if (!textChannel.isNull())
-        mModel->setChannel(textChannel, this);
 
-    emit contactIdChanged();
     setState(Ready);
 
     if (!mPendingMessages.isEmpty())
@@ -153,6 +221,10 @@
     foreach (QString msg, mPendingMessages)
         textChannel->send(msg);
     mPendingMessages.clear();
+
+    // Blindly acknowledge all messages, assuming commhistory handled them
+    if (!textChannel->messageQueue().isEmpty())
+        textChannel->acknowledge(textChannel->messageQueue());
 }
 
 void ConversationChannel::setState(State newState)
@@ -164,17 +236,15 @@
     emit stateChanged(newState);
 }
 
-QString ConversationChannel::contactId() const
+void ConversationChannel::messageReceived(const Tp::ReceivedMessage &message)
 {
-    if (mChannel.isNull() || !mChannel->isReady())
-        return QString();
+    if (mChannel.isNull())
+        return;
 
-    Tp::ContactPtr contact = mChannel->targetContact();
-    // XXX Can happen if target is not a single contact type
-    if (contact.isNull())
-        return tr("Unknown Contact");
+    Tp::TextChannelPtr textChannel = 
Tp::SharedPtr<Tp::TextChannel>::dynamicCast(mChannel);
+    Q_ASSERT(!textChannel.isNull());
 
-    return contact->id();
+    textChannel->acknowledge(QList<Tp::ReceivedMessage>() << message);
 }
 
 void ConversationChannel::sendMessage(const QString &text)
@@ -183,7 +253,6 @@
         Q_ASSERT(state() != Ready);
         qDebug() << Q_FUNC_INFO << "Buffering:" << text;
         mPendingMessages.append(text);
-        emit messageSending(text);
         return;
     }
 
@@ -194,8 +263,8 @@
         return;
     }
 
+    // Note that buffered messages do not use this path. See channelReady.
     qDebug() << Q_FUNC_INFO << text;
-    textChannel->send(text);
-    emit messageSending(text);
+    Tp::PendingSendMessage *msg = textChannel->send(text);
 }
 
--- src/conversationchannel.h
+++ src/conversationchannel.h
@@ -35,26 +35,20 @@
 #include <TelepathyQt4/PendingChannelRequest>
 #include <TelepathyQt4/ChannelRequest>
 #include <TelepathyQt4/Channel>
+#include <TelepathyQt4/PendingSendMessage>
+#include <TelepathyQt4/ReceivedMessage>
+#include <CommHistory/Group>
 
-class ChatModel;
+class QmlChatModel;
 
-/* ConversationChannel represents a Tp::Channel, from pending requests
- * through to the lifetime of the channel itself, and provides operations
- * on that channel.
+/* ConversationChannel handles the relationship between a commhistory
+ * group and the associated Telepathy channel.
  *
- * They can be created either by starting a new channel, e.g. with
- * Tp::Account::ensureTextChat or AccountsModel::ensureTextChat, and calling
- * start(), or by ClientHandler in response to an incoming channel (which goes
- * through setChannel()).
+ * setGroup associates the instance with a commhistory group, either by
+ * groupId or the combination of localUid and remoteUid. When using
+ * UIDs, if the group does not exist, it will be created immediately.
  *
- * Currently, notification when they're created is done entirely through
- * ClientHandler, but that should probably change. It's also currently
- * responsible for creating the ChatModel, but that might be better done within
- * QML now.
- *
- * XXX The lifetime and deletion of this object is currently undefined, and 
it's
- * expected to leak.
- * XXX It might be nice to use PIMPL, too.
+ * A text channel and ConversationModel will be automatically established.
  */
 class ConversationChannel : public QObject
 {
@@ -64,7 +58,7 @@
     Q_PROPERTY(State state READ state NOTIFY stateChanged)
     Q_PROPERTY(QString contactId READ contactId NOTIFY contactIdChanged)
 
-    Q_PROPERTY(ChatModel* model READ model NOTIFY chatModelReady)
+    Q_PROPERTY(QmlChatModel* model READ model NOTIFY chatModelReady)
 
 public:
     enum State {
@@ -77,43 +71,54 @@
     };
 
     ConversationChannel(QObject *parent = 0);
+    virtual ~ConversationChannel();
 
-    Q_INVOKABLE void start(Tp::PendingChannelRequest *request);
-    void setChannel(const Tp::ChannelPtr &channel);
+    /* Set commhistory group by ID. Group must exist in the GroupModel. */
+    Q_INVOKABLE void setGroup(int groupid);
+    /* Find commhistory group for the combination of local and remote UIDs.
+     * Does not support multi-target groups currently.
+     *
+     * If the group doesn't exist, it will be created locally. */
+    Q_INVOKABLE void setGroup(const QString &localUid, const QString 
&remoteUid);
 
     State state() const { return mState; }
-    QString contactId() const;
-    ChatModel *model() const { return mModel; }
+    QString contactId() const { return mContactId; }
+    QmlChatModel *model() const { return mModel; }
 
 public slots:
     void sendMessage(const QString &text);
 
 signals:
     void stateChanged(int newState);
-    void chatModelReady(ChatModel *model);
+    void chatModelReady(QmlChatModel *model);
     void contactIdChanged();
 
     void requestSucceeded();
     void requestFailed(const QString &errorName, const QString &errorMessage);
 
-    void messageSending(const QString &text);
-
 private slots:
     void channelRequestCreated(const Tp::ChannelRequestPtr &request);
     void channelRequestSucceeded(const Tp::ChannelPtr &channel);
     void channelRequestFailed(const QString &errorName, const QString 
&errorMessage);
     void channelReady();
 
+    void messageReceived(const Tp::ReceivedMessage &message);
+
 private:
     Tp::PendingChannelRequest *mPendingRequest;
     Tp::ChannelRequestPtr mRequest;
     Tp::ChannelPtr mChannel;
     State mState;
-    ChatModel *mModel;
+    QmlChatModel *mModel;
+
+    QString mContactId;
 
     QList<QString> mPendingMessages;
 
     void setState(State newState);
+    void setupGroup(const CommHistory::Group &group);
+    void start(Tp::PendingChannelRequest *request);
+    void setChannel(const Tp::ChannelPtr &channel);
 };
 
 #endif
--- src/conversationsmodel.cpp
+++ src/conversationsmodel.cpp
@@ -1,132 +0,0 @@
-/* Copyright (C) 2012 John Brooks <[email protected]>
- *
- * You may use this file under the terms of the BSD license as follows:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *   * Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.
- *   * Neither the name of Nemo Mobile nor the names of its contributors
- *     may be used to endorse or promote products derived from this
- *     software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include "conversationsmodel.h"
-#include "conversationchannel.h"
-#include "chatmodel.h"
-
-ConversationsModel::ConversationsModel(QObject *parent)
-    : QAbstractListModel(parent)
-{
-    QHash<int,QByteArray> roles;
-    roles[Qt::DisplayRole] = "displayName";
-    roles[ConversationRole] = "conversation";
-    roles[MessagePreviewRole] = "messagePreview";
-    roles[LastMessageDateRole] = "messageDate";
-    setRoleNames(roles);
-}
-
-void ConversationsModel::addChat(ConversationChannel *conversation)
-{
-    if (mChats.contains(conversation))
-        return;
-
-    Q_ASSERT(conversation->model());
-    connect(conversation->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), 
SLOT(messagesChanged()));
-    connect(conversation, SIGNAL(contactIdChanged()), 
SLOT(conversationChanged()));
-
-    beginInsertRows(QModelIndex(), 0, 0);
-    mChats.insert(0, conversation);
-    endInsertRows();
-}
-
-void ConversationsModel::messagesChanged()
-{
-    ChatModel *model = qobject_cast<ChatModel*>(sender());
-    Q_ASSERT(model);
-    if (!model)
-        return;
-
-    int row;
-    for (row = 0; row < mChats.size(); ++row) {
-        if (mChats[row]->model() == model)
-            break;
-    }
-    Q_ASSERT(row < mChats.size());
-    if (row >= mChats.size())
-        return;
-
-    emit dataChanged(index(row, 0), index(row, 0));
-}
-
-void ConversationsModel::conversationChanged()
-{
-    ConversationChannel *channel = 
qobject_cast<ConversationChannel*>(sender());
-    Q_ASSERT(channel);
-    if (!channel)
-        return;
-
-    int row = mChats.indexOf(channel);
-    Q_ASSERT(row >= 0);
-    if (row < 0)
-        return;
-
-    emit dataChanged(index(row, 0), index(row, 0));
-}
-
-int ConversationsModel::rowCount(const QModelIndex &parent) const
-{
-    if (parent.isValid())
-        return 0;
-    else
-        return mChats.size();
-}
-
-QVariant ConversationsModel::data(const QModelIndex &index, int role) const
-{
-    if (!index.isValid() || index.row() >= mChats.size())
-        return QVariant();
-
-    ConversationChannel *c = mChats[index.row()];
-
-    switch (role) {
-        case Qt::DisplayRole: return c->contactId();
-        case ConversationRole: return QVariant::fromValue<QObject*>(c);
-        case MessagePreviewRole: {
-            ChatModel *model = c->model();
-            QModelIndex index = model->index(model->rowCount()-1, 0);
-            if (index.isValid())
-                return model->data(index, Qt::DisplayRole);
-            else
-                return QString();
-        }
-        case LastMessageDateRole: {
-            ChatModel *model = c->model();
-            QModelIndex index = model->index(model->rowCount()-1, 0);
-            if (index.isValid())
-                return model->data(index, ChatModel::MessageDateRole);
-            else
-                return QString();
-        }
-    }
-
-    return QVariant();
-}
-
--- src/conversationsmodel.h
+++ src/conversationsmodel.h
@@ -1,64 +0,0 @@
-/* Copyright (C) 2012 John Brooks <[email protected]>
- *
- * You may use this file under the terms of the BSD license as follows:
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *   * Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *   * Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in
- *     the documentation and/or other materials provided with the
- *     distribution.
- *   * Neither the name of Nemo Mobile nor the names of its contributors
- *     may be used to endorse or promote products derived from this
- *     software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef CONVERSATIONSMODEL_H
-#define CONVERSATIONSMODEL_H
-
-#include <QAbstractListModel>
-
-class ConversationChannel;
-
-class ConversationsModel : public QAbstractListModel
-{
-    Q_OBJECT
-
-public:
-    enum {
-        ConversationRole = Qt::UserRole,
-        MessagePreviewRole,
-        LastMessageDateRole,
-    };
-
-    ConversationsModel(QObject *parent = 0);
-
-    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
-    virtual QVariant data(const QModelIndex &index, int role = 
Qt::DisplayRole) const;
-
-public slots:
-    void addChat(ConversationChannel *channel);
-    void messagesChanged();
-    void conversationChanged();
-
-private:
-    QList<ConversationChannel*> mChats;
-};
-
-#endif
-
--- src/main.cpp
+++ src/main.cpp
@@ -45,12 +45,16 @@
 
 #include "src/accountsmodel.h"
 #include "src/clienthandler.h"
-#include "src/chatmodel.h"
-#include "src/conversationsmodel.h"
 #include "src/conversationchannel.h"
+#include "src/qmlgroupmodel.h"
+#include "src/qmlchatmodel.h"
 
 using namespace Tp;
 
+Tp::AccountManagerPtr accountManager;
+
+QmlGroupModel *groupModel = 0;
+
 #ifdef HAS_BOOSTER
 Q_DECL_EXPORT
 #endif
@@ -78,9 +82,6 @@
             Tp::enableDebug(true);
     }
 
-    // Features can be requested in the factories here, and are applied to all 
objects
-    // of that type created under the ClientHandler; list them here if they 
are needed
-    // often, or immediately after the instance is created.
     const QDBusConnection &dbus = QDBusConnection::sessionBus();
     ClientRegistrarPtr registrar = 
ClientRegistrar::create(AccountFactory::create(dbus),
             ConnectionFactory::create(dbus), ChannelFactory::create(dbus),
@@ -89,15 +90,21 @@
     AbstractClientPtr handler = 
AbstractClientPtr::dynamicCast(SharedPtr<ClientHandler>(clientHandler));
     registrar->registerClient(handler, "qmlmessages");
 
+    accountManager = 
Tp::AccountManager::create(Tp::AccountFactory::create(dbus,
+                         Tp::Account::FeatureCore));
+
     // Set up QML
-    qRegisterMetaType<ChatModel*>();
-    qmlRegisterType<ConversationsModel>("org.nemomobile.qmlmessages", 1, 0, 
"ConversationsModel");
     qmlRegisterType<AccountsModel>("org.nemomobile.qmlmessages", 1, 0, 
"AccountsModel");
-    qmlRegisterUncreatableType<ChatModel>("org.nemomobile.qmlmessages", 1, 0, 
"ChatModel", "Cannot be created");
+    qmlRegisterUncreatableType<QmlChatModel>("org.nemomobile.qmlmessages", 1, 
0, "ChatModel", "Cannot be created");
     qmlRegisterType<ConversationChannel>("org.nemomobile.qmlmessages", 1, 0, 
"ConversationChannel");
 
+    QmlGroupModel gm;
+    groupModel = &gm;
+
     // Set up view
+    view->setWindowTitle(qApp->translate("Window", "Messages"));
     view->rootContext()->setContextProperty("clientHandler", 
QVariant::fromValue<QObject*>(clientHandler));
+    view->rootContext()->setContextProperty("groupModel", 
QVariant::fromValue<QObject*>(groupModel));
     view->setSource(QUrl("qrc:qml/qmlmessages/main.qml"));
     view->setAttribute(Qt::WA_OpaquePaintEvent);
     view->setAttribute(Qt::WA_NoSystemBackground);
--- src/qmlchatmodel.cpp
+++ src/qmlchatmodel.cpp
@@ -0,0 +1,60 @@
+/* Copyright (C) 2012 John Brooks <[email protected]>
+ *
+ * You may use this file under the terms of the BSD license as follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Nemo Mobile nor the names of its contributors
+ *     may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qmlchatmodel.h"
+#include <QDebug>
+
+QmlChatModel::QmlChatModel(int groupid, QObject *parent)
+    : CommHistory::ConversationModel(parent)
+{
+    QHash<int,QByteArray> roles;
+    roles[Qt::DisplayRole] = "text";
+    roles[DirectionRole] = "direction";
+    roles[StatusRole] = "status";
+    roles[StartTimeRole] = "date";
+    setRoleNames(roles);
+
+    setTreeMode(false);
+    getEvents(groupid);
+}
+
+QVariant QmlChatModel::data(const QModelIndex &index, int role) const
+{
+    int column = index.column();
+    switch (role) {
+        case Qt::DisplayRole: column = FreeText; break;
+        case DirectionRole: column = 
CommHistory::ConversationModel::Direction; break;
+        case StartTimeRole: column = StartTime; break;
+        case StatusRole: column = Status; break;
+    }
+
+    return CommHistory::ConversationModel::data(this->index(index.row(), 
column, index.parent()), Qt::DisplayRole);
+}
+
--- src/qmlchatmodel.h
+++ src/qmlchatmodel.h
@@ -0,0 +1,62 @@
+/* Copyright (C) 2012 John Brooks <[email protected]>
+ *
+ * You may use this file under the terms of the BSD license as follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Nemo Mobile nor the names of its contributors
+ *     may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef QMLCHATMODEL_H
+#define QMLCHATMODEL_H
+
+#include <CommHistory/ConversationModel>
+
+class QmlChatModel : public CommHistory::ConversationModel
+{
+    Q_OBJECT
+    Q_ENUMS(Direction)
+
+public:
+    enum {
+        DirectionRole = Qt::UserRole,
+        StartTimeRole,
+        StatusRole
+    };
+
+    enum Direction {
+        UnknownDirection = 0,
+        Incoming,
+        Outgoing
+    };
+
+    QmlChatModel(int groupid, QObject *parent = 0);
+
+    virtual QVariant data(const QModelIndex &index, int role = 
Qt::DisplayRole) const;
+};
+
+Q_DECLARE_METATYPE(QmlChatModel*);
+
+#endif
+
--- src/qmlgroupmodel.cpp
+++ src/qmlgroupmodel.cpp
@@ -0,0 +1,71 @@
+/* Copyright (C) 2012 John Brooks <[email protected]>
+ *
+ * You may use this file under the terms of the BSD license as follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Nemo Mobile nor the names of its contributors
+ *     may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qmlgroupmodel.h"
+#include "qmlchatmodel.h"
+
+QmlGroupModel::QmlGroupModel(QObject *parent)
+    : CommHistory::GroupModel(parent)
+{
+    QHash<int,QByteArray> roles;
+    roles[Qt::DisplayRole] = "displayName";
+    roles[LastMessageTextRole] = "messagePreview";
+    roles[LastModifiedRole] = "messageDate";
+    roles[GroupIdRole] = "groupId";
+    setRoleNames(roles);
+
+    getGroups();
+}
+
+QVariant QmlGroupModel::data(const QModelIndex &index, int role) const
+{
+    int column = index.column();
+
+    switch (role) {
+        case Qt::DisplayRole: {
+            QList<CommHistory::Event::Contact> contacts = 
group(index).contacts();
+            if (!contacts.isEmpty())
+                return contacts[0].second;
+            column = RemoteUids;
+            break;
+        }
+        case LastMessageTextRole: column = LastMessageText; break;
+        case LastModifiedRole: column = LastModified; break;
+        case GroupIdRole: column = GroupId; break;
+    }
+
+    QVariant re = CommHistory::GroupModel::data(this->index(index.row(), 
column, index.parent()), Qt::DisplayRole);
+
+    if (column == RemoteUids) {
+        re = re.value<QVariantList>()[0];
+    }
+    return re;
+}
+
--- src/qmlgroupmodel.h
+++ src/qmlgroupmodel.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2012 John Brooks <[email protected]>
+ *
+ * You may use this file under the terms of the BSD license as follows:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *   * Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in
+ *     the documentation and/or other materials provided with the
+ *     distribution.
+ *   * Neither the name of Nemo Mobile nor the names of its contributors
+ *     may be used to endorse or promote products derived from this
+ *     software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef QMLGROUPMODEL_H
+#define QMLGROUPMODEL_H
+
+#include <CommHistory/GroupModel>
+
+class QmlGroupModel : public CommHistory::GroupModel
+{
+public:
+    enum {
+        LastMessageTextRole = Qt::UserRole,
+        LastModifiedRole,
+        GroupIdRole
+    };
+
+    QmlGroupModel(QObject *parent = 0);
+
+    virtual QVariant data(const QModelIndex &index, int role = 
Qt::DisplayRole) const;
+};
+
+#endif
+

++++++ qmlmessages.yaml
--- qmlmessages.yaml
+++ qmlmessages.yaml
@@ -1,15 +1,12 @@
 Name:  qmlmessages
 Summary: Messaging application for nemo
-Version: 0.0.1
+Version: 0.0.2
 Release: 1
 Group: Applications/System
 License: BSD
 URL: https://github.com/nemomobile/qmlmessages
 Sources:
     - "%{name}-%{version}.tar.bz2"
-Patches:
-    - 0001-add-a-.desktop-file-for-qmlmessages.patch
-    - 0001-Fix-MessagesView-scrolling.patch
 
 Description: Messaging application using Qt Quick for Nemo Mobile.
 Configure: none
@@ -26,11 +23,10 @@
     - QtContacts
     - qdeclarative-boostable
     - TelepathyQt4
+    - commhistory
 
 Requires:
     - qt-components
-    - nemo-qml-plugins-thumbnailer
-#    - nemo-qml-plugins-contacts
  
 Files:
     - "%{_bindir}/qmlmessages"

++++++ deleted files:
--- 0001-Fix-MessagesView-scrolling.patch
--- 0001-add-a-.desktop-file-for-qmlmessages.patch



Reply via email to