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/6763 Thank You, John Brooks [This message was auto-generated] --- Request # 6763: Messages from BOSS: State: review at 2012-09-21T13:28:39 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 Sep 21 2012 John Brooks <[email protected]> - 0.0.4 +- Open the conversation window after tapping on a commhistory-daemon notification +- Improve handling of incoming telepathy channels +- Use libcommhistory-declarative for QML bindings +- Fixes NEMO#378: Use a background thread for CommHistory models +- Use streamed queries for ConversationModel to improve load time + old: ---- 0001-Use-pkgconfig-to-find-TelepathyQt4.patch qmlmessages-0.0.3.tar.bz2 new: ---- qmlmessages-0.0.4.tar.bz2 spec files: ----------- --- qmlmessages.spec +++ qmlmessages.spec @@ -9,15 +9,15 @@ # << macros Summary: Messaging application for nemo -Version: 0.0.3 +Version: 0.0.4 Release: 1 Group: Applications/System License: BSD URL: https://github.com/nemomobile/qmlmessages Source0: %{name}-%{version}.tar.bz2 Source100: qmlmessages.yaml -Patch0: 0001-Use-pkgconfig-to-find-TelepathyQt4.patch Requires: qt-components +Requires: libcommhistory-declarative BuildRequires: pkgconfig(QtCore) >= 4.7.0 BuildRequires: pkgconfig(QtDeclarative) BuildRequires: pkgconfig(QtContacts) @@ -47,8 +47,6 @@ %prep %setup -q -n %{name} -# 0001-Use-pkgconfig-to-find-TelepathyQt4.patch -%patch0 -p1 # >> setup # << setup other changes: -------------- ++++++ qmlmessages-0.0.3.tar.bz2 -> qmlmessages-0.0.4.tar.bz2 --- qml/qmlmessages/ConversationListDelegate.qml +++ qml/qmlmessages/ConversationListDelegate.qml @@ -66,7 +66,7 @@ Label { id: nameFirst - text: model.displayName + text: model.remoteUids[0] platformStyle: LabelStyle { fontFamily: "Droid Sans" @@ -77,7 +77,7 @@ Label { id: messageDate // XXX This should be something more natural/useful - text: Qt.formatDateTime(model.messageDate, "M/d") + text: Qt.formatDateTime(model.lastModified, "M/d") anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -91,7 +91,7 @@ Label { id: messagePreview - text: model.messagePreview + text: model.lastMessageText elide: Text.ElideRight width: parent.width --- qml/qmlmessages/ConversationListWidget.qml +++ qml/qmlmessages/ConversationListWidget.qml @@ -54,7 +54,8 @@ delegate: ConversationListDelegate { onClicked: { - pageStack.push(Qt.resolvedUrl("ConversationPage.qml"), { group: model.groupId }) + var group = groupManager.getConversationById(model.groupId) + pageStack.push(Qt.resolvedUrl("ConversationPage.qml"), { channel: group }) } } } --- qml/qmlmessages/ConversationPage.qml +++ qml/qmlmessages/ConversationPage.qml @@ -40,12 +40,8 @@ Page { id: conversationPage - property int group: -1 - onGroupChanged: if (group >= 0) channel.setGroup(group) - - ConversationChannel { - id: channel - } + property QtObject channel: null + tools: null PageHeader { id: header @@ -73,7 +69,7 @@ elide: Text.ElideRight smooth: true color: "#111111" - text: channel.contactId + text: channel == null ? "" : channel.contactId style: Text.Raised styleColor: "white" @@ -114,7 +110,7 @@ || accountSelector.selectedIndex < 0) return console.log("startConversation", accountSelector.selectedUid, targetEditor.text); - channel.setGroup(accountSelector.selectedUid, targetEditor.text) + channel = groupManager.getConversation(accountSelector.selectedUid, targetEditor.text) } } @@ -183,7 +179,7 @@ states: [ State { name: "active" - when: channel.state != ConversationChannel.Null + when: channel !== null PropertyChanges { target: messagesView @@ -192,7 +188,7 @@ }, State { name: "new" - when: channel.state == ConversationChannel.Null + when: channel == null PropertyChanges { target: targetEditor @@ -221,5 +217,10 @@ } } ] + + onChannelChanged: { + if (channel != null) + channel.ensureChannel() + } } --- qml/qmlmessages/MessagesView.qml +++ qml/qmlmessages/MessagesView.qml @@ -31,6 +31,7 @@ import QtQuick 1.1 import com.nokia.meego 1.0 import org.nemomobile.qmlmessages 1.0 +import org.nemomobile.commhistory 1.0 Item { property alias model: view.model @@ -45,10 +46,18 @@ anchors.fill: parent cacheBuffer: parent.height - onCountChanged: view.positionViewAtBeginning() // Necessary when opening VKB, for example onHeightChanged: view.positionViewAtBeginning() + Connections { + target: model + onRowsInserted: { + if (first == 0) + view.positionViewAtBeginning() + } + onModelReset: view.positionViewAtBeginning() + } + delegate: BorderImage { id: messageBox height: childrenRect.height + 20 @@ -68,7 +77,7 @@ Text { id: messageText - text: model.text + text: model.freeText width: messageBox.parent.width * 0.7 wrapMode: Text.Wrap style: Text.Raised @@ -99,7 +108,7 @@ states: [ State { name: "incoming" - when: model.direction == ChatModel.Incoming + when: model.direction == CommHistory.Inbound PropertyChanges { target: messageBox @@ -112,7 +121,7 @@ }, State { name: "outgoing" - when: model.direction == ChatModel.Outgoing + when: model.direction == CommHistory.Outbound PropertyChanges { target: messageBox --- qml/qmlmessages/main.qml +++ qml/qmlmessages/main.qml @@ -36,11 +36,46 @@ PageStackWindow { id: window - initialPage: ConversationListPage {} - // Shared AccountsModel AccountsModel { id: accountsModel } + + Connections { + target: pageStack + onCurrentPageChanged: { + var group + try { + group = pageStack.currentPage.channel + } catch (e) { + } + if (group == undefined) + group = null + windowManager.currentGroup = group + } + } + + function showConversation(group) { + if (group == windowManager.currentGroup) + return + + var pages = [ ] + if (!pageStack.currentPage) { + pages.push(Qt.resolvedUrl("ConversationListPage.qml")) + } + pages.push({ page: Qt.resolvedUrl("ConversationPage.qml"), properties: { channel: group } }) + + if (pageStack.depth > 1) + pageStack.replace(pages) + else + pageStack.push(pages) + } + + function showGroupsList() { + if (!pageStack.currentPage) + pageStack.push(Qt.resolvedUrl("ConversationListPage.qml")) + else if (pageStack.depth > 1) + pageStack.pop(null, true) + } } --- qmlmessages.pro +++ qmlmessages.pro @@ -1,12 +1,8 @@ PROJECT_NAME = qmlmessages QT += dbus declarative svg -INCLUDEPATH += /usr/include/telepathy-1.0/ -LIBS += -ltelepathy-qt4 -CXXFLAGS += -fPIC - CONFIG += link_pkgconfig -PKGCONFIG += commhistory +PKGCONFIG += commhistory TelepathyQt4 target.path = $$INSTALL_ROOT/usr/bin INSTALLS += target @@ -17,18 +13,16 @@ src/accountsmodel.cpp \ src/clienthandler.cpp \ src/conversationchannel.cpp \ - src/qmlgroupmodel.cpp \ - src/qmlchatmodel.cpp \ src/windowmanager.cpp \ - src/dbusadaptor.cpp + src/dbusadaptor.cpp \ + src/groupmanager.cpp HEADERS += src/accountsmodel.h \ src/clienthandler.h \ src/conversationchannel.h \ - src/qmlgroupmodel.h \ - src/qmlchatmodel.h \ src/windowmanager.h \ - src/dbusadaptor.h + src/dbusadaptor.h \ + src/groupmanager.h RESOURCES += res/res.qrc qml/qml.qrc --- src/clienthandler.cpp +++ src/clienthandler.cpp @@ -30,11 +30,13 @@ #include "clienthandler.h" #include "conversationchannel.h" +#include "groupmanager.h" #include <TelepathyQt4/ChannelClassSpec> #include <TelepathyQt4/ReceivedMessage> #include <TelepathyQt4/TextChannel> #include <TelepathyQt4/ChannelRequest> +#include <TelepathyQt4/Account> using namespace Tp; @@ -67,7 +69,24 @@ const QList<ChannelRequestPtr> &requestsSatisfied, const QDateTime &userActionTime, const HandlerInfo &handlerInfo) { - // XXX what do we need to take care of here? + foreach (const ChannelPtr &channel, channels) { + QVariantMap properties = channel->immutableProperties(); + QString targetId = properties.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID")).toString(); + + if (targetId.isEmpty()) { + qWarning() << "handleChannels cannot get TargetID for channel"; + continue; + } + + ConversationChannel *g = GroupManager::instance()->getConversation(account->objectPath(), targetId); + if (!g) { + qWarning() << "handleChannels cannot create ConversationChannel"; + continue; + } + + g->setChannel(channel); + } + context->setFinished(); } --- src/conversationchannel.cpp +++ src/conversationchannel.cpp @@ -29,9 +29,8 @@ */ #include "conversationchannel.h" +#include "groupmanager.h" #include "clienthandler.h" -#include "qmlgroupmodel.h" -#include "qmlchatmodel.h" #include <TelepathyQt4/ChannelRequest> #include <TelepathyQt4/TextChannel> @@ -41,12 +40,14 @@ #include <TelepathyQt4/Account> #include <TelepathyQt4/AccountManager> +#include <CommHistory/ConversationModel> +#include <CommHistory/GroupModel> + // XXX extern Tp::AccountManagerPtr accountManager; -extern QmlGroupModel *groupModel; ConversationChannel::ConversationChannel(QObject *parent) - : QObject(parent), mPendingRequest(0), mState(Null), mModel(0) + : QObject(parent), mPendingRequest(0), mState(Null), mModel(0), mGroupId(-1) { } @@ -56,16 +57,7 @@ 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; - } - } - + CommHistory::Group group = GroupManager::instance()->groupFromId(groupid); if (!group.isValid()) { qWarning() << Q_FUNC_INFO << "Cannot find group id" << groupid; return; @@ -74,51 +66,36 @@ 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) { + mGroupId = group.id(); + mContactId = group.remoteUids().value(0); + mLocalUid = group.localUid(); emit contactIdChanged(); Q_ASSERT(!mModel); - mModel = new QmlChatModel(group.id(), this); + mModel = new CommHistory::ConversationModel(this); + mModel->setBackgroundThread(GroupManager::instance()->groupModel()->backgroundThread()); + mModel->setQueryMode(CommHistory::EventModel::StreamedAsyncQuery); + mModel->setFirstChunkSize(25); + mModel->setChunkSize(50); + mModel->setTreeMode(false); + mModel->getEvents(mGroupId); emit chatModelReady(mModel); +} - qDebug() << Q_FUNC_INFO << group.localUid() << group.remoteUids().value(0); +void ConversationChannel::ensureChannel() +{ + if (!mChannel.isNull() || mPendingRequest || !mRequest.isNull()) + return; // XXX wait for account manager if necessary? - Tp::AccountPtr account = accountManager->accountForPath(group.localUid()); + Tp::AccountPtr account = accountManager->accountForPath(mLocalUid); // XXX error check Q_ASSERT(account); Q_ASSERT(account->isReady()); - Tp::PendingChannelRequest *req = account->ensureTextChat(group.remoteUids().value(0), + Tp::PendingChannelRequest *req = account->ensureTextChat(mContactId, QDateTime::currentDateTime(), QLatin1String("org.freedesktop.Telepathy.Client.qmlmessages")); start(req); @@ -140,10 +117,12 @@ void ConversationChannel::setChannel(const Tp::ChannelPtr &c) { - Q_ASSERT(mChannel.isNull()); - Q_ASSERT(!c.isNull()); - if (!mChannel.isNull() || c.isNull()) + if (mChannel && c && mChannel->objectPath() == c->objectPath()) return; + if (!mChannel.isNull() || c.isNull()) { + qWarning() << Q_FUNC_INFO << "called with existing channel set"; + return; + } mChannel = c; connect(mChannel->becomeReady(Tp::TextChannel::FeatureMessageQueue), @@ -151,6 +130,8 @@ SLOT(channelReady())); connect(mChannel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)), SLOT(messageReceived(Tp::ReceivedMessage))); + connect(mChannel.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(channelInvalidated(Tp::DBusProxy*,QString,QString))); setState(PendingReady); } @@ -251,6 +232,7 @@ { if (mChannel.isNull() || !mChannel->isReady()) { Q_ASSERT(state() != Ready); + ensureChannel(); qDebug() << Q_FUNC_INFO << "Buffering:" << text; mPendingMessages.append(text); return; @@ -268,3 +250,13 @@ Tp::PendingSendMessage *msg = textChannel->send(text); } +void ConversationChannel::channelInvalidated(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + Q_UNUSED(proxy); + qDebug() << "Channel invalidated:" << errorName << errorMessage; + + mChannel.reset(); + setState(Null); +} + --- src/conversationchannel.h +++ src/conversationchannel.h @@ -38,8 +38,7 @@ #include <TelepathyQt4/PendingSendMessage> #include <TelepathyQt4/ReceivedMessage> #include <CommHistory/Group> - -class QmlChatModel; +#include <CommHistory/ConversationModel> /* ConversationChannel handles the relationship between a commhistory * group and the associated Telepathy channel. @@ -57,8 +56,11 @@ Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(QString contactId READ contactId NOTIFY contactIdChanged) + Q_PROPERTY(QString localUid READ localUid CONSTANT) + + Q_PROPERTY(QObject* model READ model NOTIFY chatModelReady) - Q_PROPERTY(QmlChatModel* model READ model NOTIFY chatModelReady) + friend class GroupManager; public: enum State { @@ -70,27 +72,22 @@ Error }; - ConversationChannel(QObject *parent = 0); virtual ~ConversationChannel(); - /* 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 { return mContactId; } - QmlChatModel *model() const { return mModel; } + QString localUid() const { return mLocalUid; } + QObject *model() const { return mModel; } + + Q_INVOKABLE void ensureChannel(); + void setChannel(const Tp::ChannelPtr &channel); public slots: void sendMessage(const QString &text); signals: void stateChanged(int newState); - void chatModelReady(QmlChatModel *model); + void chatModelReady(QObject *model); void contactIdChanged(); void requestSucceeded(); @@ -104,21 +101,29 @@ void messageReceived(const Tp::ReceivedMessage &message); + void channelInvalidated(Tp::DBusProxy *proxy, const QString &errorName, const QString &errorMessage); + private: Tp::PendingChannelRequest *mPendingRequest; Tp::ChannelRequestPtr mRequest; Tp::ChannelPtr mChannel; State mState; - QmlChatModel *mModel; + CommHistory::ConversationModel *mModel; + int mGroupId; QString mContactId; + QString mLocalUid; QList<QString> mPendingMessages; + ConversationChannel(QObject *parent = 0); + void setState(State newState); - void setupGroup(const CommHistory::Group &group); void start(Tp::PendingChannelRequest *request); - void setChannel(const Tp::ChannelPtr &channel); + + /* Set commhistory group by ID. Group must exist in the GroupModel. */ + void setGroup(int groupid); + void setupGroup(const CommHistory::Group &group); }; #endif --- src/dbusadaptor.cpp +++ src/dbusadaptor.cpp @@ -49,3 +49,8 @@ wm->showGroupsWindow(); } +void DBusAdaptor::startConversation(const QString &localUid, const QString &remoteUid, unsigned type) +{ + wm->showConversation(localUid, remoteUid, type); +} + --- src/dbusadaptor.h +++ src/dbusadaptor.h @@ -46,6 +46,8 @@ void showGroupsWindow(); void showGroupsWindow(const QStringList &a); + void startConversation(const QString &localUid, const QString &remoteUid, unsigned type); + private: WindowManager * const wm; }; --- src/groupmanager.cpp +++ src/groupmanager.cpp @@ -0,0 +1,128 @@ +/* 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 "groupmanager.h" +#include "conversationchannel.h" +#include <QThread> +#include <CommHistory/GroupModel> + +Q_GLOBAL_STATIC(GroupManager, gmInstance) + +GroupManager *GroupManager::instance() +{ + return gmInstance(); +} + +GroupManager::GroupManager(QObject *parent) + : QObject(parent) +{ + QThread *modelThread = new QThread(this); + modelThread->start(); + + mGroupModel = new CommHistory::GroupModel(this); + mGroupModel->setBackgroundThread(modelThread); + mGroupModel->getGroups(); +} + +ConversationChannel *GroupManager::getConversationById(int groupid) +{ + QHash<int,ConversationChannel*>::iterator it = groups.find(groupid); + if (it == groups.end()) { + ConversationChannel *c = new ConversationChannel(this); + c->setGroup(groupid); + addGroup(groupid, c); + return c; + } + return *it; +} + +CommHistory::Group GroupManager::groupFromUid(const QString &localUid, const QString &remoteUid) +{ + for (int i = 0, c = mGroupModel->rowCount(); i < c; i++) { + const CommHistory::Group &g = mGroupModel->group(mGroupModel->index(i, 0)); + if (g.localUid() == localUid && g.remoteUids().size() == 1 + && g.remoteUids().first() == remoteUid) { + return g; + } + } + return CommHistory::Group(); +} + +CommHistory::Group GroupManager::groupFromId(int groupid) +{ + for (int i = 0, c = mGroupModel->rowCount(); i < c; i++) { + const CommHistory::Group &g = mGroupModel->group(mGroupModel->index(i, 0)); + if (g.id() == groupid) + return g; + } + + return CommHistory::Group(); +} + +ConversationChannel *GroupManager::getConversation(const QString &localUid, const QString &remoteUid, bool create) +{ + CommHistory::Group group = groupFromUid(localUid, remoteUid); + if (group.isValid()) + return getConversationById(group.id()); + if (!create) + return 0; + + qDebug() << "ConversationChannel creating group for" << localUid << remoteUid; + group.setLocalUid(localUid); + group.setRemoteUids(QStringList() << remoteUid); + group.setChatType(CommHistory::Group::ChatTypeP2P); + if (!mGroupModel->addGroup(group)) { + qWarning() << "ConversationChannel failed creating group" << localUid << remoteUid; + return 0; + } + Q_ASSERT(group.isValid()); + + ConversationChannel *c = new ConversationChannel(this); + c->setupGroup(group); + addGroup(group.id(), c); + return c; +} + +void GroupManager::addGroup(int id, ConversationChannel *c) +{ + Q_ASSERT(!groups.contains(id)); + groups.insert(id, c); + connect(c, SIGNAL(destroyed(QObject*)), SLOT(groupDestroyed(QObject*))); +} + +void GroupManager::groupDestroyed(QObject *obj) +{ + for (QHash<int,ConversationChannel*>::iterator it = groups.begin(); it != groups.end(); it++) { + if (*it == obj) { + groups.erase(it); + break; + } + } +} --- src/groupmanager.h +++ src/groupmanager.h @@ -0,0 +1,79 @@ +/* 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 GROUPMANAGER_H +#define GROUPMANAGER_H + +#include <QObject> +#include <QHash> + +#include <TelepathyQt/Account> +#include <TelepathyQt/Channel> + +class ConversationChannel; + +namespace CommHistory { + class Group; + class GroupModel; +} + +class GroupManager : public QObject +{ + Q_OBJECT + +public: + static GroupManager *instance(); + + explicit GroupManager(QObject *parent = 0); + + CommHistory::GroupModel *groupModel() const { return mGroupModel; } + + /* Get a conversation by commhistory group ID, creating it if necessary. + * A telepathy channel will be established if none exists already. */ + Q_INVOKABLE ConversationChannel *getConversationById(int groupid); + /* Find commhistory group for the combination of local and remote UIDs, + * and return the associated conversation. Does not support multi-target + * groups currently. If the group doesn't exist, it will be created immediately. */ + Q_INVOKABLE ConversationChannel *getConversation(const QString &localUid, const QString &remoteUid, bool create = true); + + CommHistory::Group groupFromUid(const QString &localUid, const QString &remoteUid); + CommHistory::Group groupFromId(int groupid); + +private slots: + void groupDestroyed(QObject *obj); + +private: + CommHistory::GroupModel *mGroupModel; + QHash<int,ConversationChannel*> groups; + + void addGroup(int id, ConversationChannel *c); +}; + +#endif --- src/main.cpp +++ src/main.cpp @@ -43,19 +43,17 @@ #include <TelepathyQt4/Types> #include <TelepathyQt4/ClientRegistrar> +#include <CommHistory/GroupModel> + #include "src/windowmanager.h" #include "src/accountsmodel.h" #include "src/clienthandler.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 @@ -93,11 +91,7 @@ // Set up QML qmlRegisterType<AccountsModel>("org.nemomobile.qmlmessages", 1, 0, "AccountsModel"); - qmlRegisterUncreatableType<QmlChatModel>("org.nemomobile.qmlmessages", 1, 0, "ChatModel", "Cannot be created"); - qmlRegisterType<ConversationChannel>("org.nemomobile.qmlmessages", 1, 0, "ConversationChannel"); - - QmlGroupModel gm; - groupModel = &gm; + qmlRegisterUncreatableType<ConversationChannel>("org.nemomobile.qmlmessages", 1, 0, "ConversationChannel", ""); WindowManager *wm = WindowManager::instance(); if (showWindow) --- src/qmlchatmodel.cpp +++ src/qmlchatmodel.cpp @@ -1,60 +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 "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 @@ -1,62 +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 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 @@ -1,71 +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 "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 @@ -1,51 +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 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 - --- src/windowmanager.cpp +++ src/windowmanager.cpp @@ -30,18 +30,20 @@ #include "windowmanager.h" #include "clienthandler.h" -#include "qmlgroupmodel.h" +#include "groupmanager.h" #include "dbusadaptor.h" +#include "conversationchannel.h" #include <QDeclarativeView> #include <QDeclarativeContext> +#include <QDeclarativeItem> #include <QDBusConnection> #ifdef HAS_BOOSTER #include <applauncherd/MDeclarativeCache> #endif -Q_GLOBAL_STATIC(WindowManager, wmInstance) +#include <CommHistory/GroupModel> -extern QmlGroupModel *groupModel; +Q_GLOBAL_STATIC(WindowManager, wmInstance) WindowManager *WindowManager::instance() { @@ -49,7 +51,7 @@ } WindowManager::WindowManager(QObject *parent) - : QObject(parent) + : QObject(parent), mCurrentGroup(0) { new DBusAdaptor(this); QDBusConnection::sessionBus().registerService("org.nemomobile.qmlmessages"); @@ -65,7 +67,7 @@ #endif } -void WindowManager::showGroupsWindow() +void WindowManager::ensureWindow() { if (!mWindow) { #ifdef HAS_BOOSTER @@ -75,22 +77,71 @@ #endif QDeclarativeView *w = mWindow.data(); + mCurrentGroup = 0; // mWindow is a QWeakPointer, so it'll be cleared on delete w->setAttribute(Qt::WA_DeleteOnClose); w->setWindowTitle(tr("Messages")); - w->rootContext()->setContextProperty("clientHandler", QVariant::fromValue<QObject*>(ClientHandler::instance())); - w->rootContext()->setContextProperty("groupModel", QVariant::fromValue<QObject*>(groupModel)); + w->rootContext()->setContextProperty("windowManager", + QVariant::fromValue<QObject*>(this)); + w->rootContext()->setContextProperty("groupManager", + QVariant::fromValue<QObject*>(GroupManager::instance())); + w->rootContext()->setContextProperty("groupModel", + QVariant::fromValue<QObject*>(GroupManager::instance()->groupModel())); w->setSource(QUrl("qrc:qml/qmlmessages/main.qml")); w->setAttribute(Qt::WA_OpaquePaintEvent); w->setAttribute(Qt::WA_NoSystemBackground); w->viewport()->setAttribute(Qt::WA_OpaquePaintEvent); w->viewport()->setAttribute(Qt::WA_NoSystemBackground); } +} + +void WindowManager::showGroupsWindow() +{ + ensureWindow(); + + QGraphicsObject *root = mWindow.data()->rootObject(); + if (!root) + qFatal("No root object in window"); + bool ok = root->metaObject()->invokeMethod(root, "showGroupsList"); + if (!ok) + qWarning() << Q_FUNC_INFO << "showGroupsList call failed"; + + mWindow.data()->showFullScreen(); + mWindow.data()->activateWindow(); + mWindow.data()->raise(); +} + +void WindowManager::showConversation(const QString &localUid, const QString &remoteUid, unsigned type) +{ + Q_UNUSED(type); + ensureWindow(); + + qDebug() << Q_FUNC_INFO << localUid << remoteUid << type; + ConversationChannel *group = GroupManager::instance()->getConversation(localUid, remoteUid); + if (!group) { + qWarning() << Q_FUNC_INFO << "could not create group"; + return; + } - // XXX set page for existing windows? + QGraphicsObject *root = mWindow.data()->rootObject(); + if (!root) + qFatal("No root object in window"); + bool ok = root->metaObject()->invokeMethod(root, "showConversation", Q_ARG(QVariant, QVariant::fromValue<QObject*>(group))); + if (!ok) + qWarning() << Q_FUNC_INFO << "showConversation call failed"; mWindow.data()->showFullScreen(); mWindow.data()->activateWindow(); + mWindow.data()->raise(); +} + +void WindowManager::updateCurrentGroup(ConversationChannel *g) +{ + if (g == mCurrentGroup) + return; + + mCurrentGroup = g; + emit currentGroupChanged(g); } --- src/windowmanager.h +++ src/windowmanager.h @@ -35,6 +35,7 @@ #include <QWeakPointer> class QDeclarativeView; +class ConversationChannel; /* Right now, WindowManager is just responsible for creating/showing the * single window we manage when requested via DBus or application launch. @@ -51,11 +52,24 @@ explicit WindowManager(QObject *parent = 0); virtual ~WindowManager(); + Q_PROPERTY(ConversationChannel* currentGroup READ currentGroup WRITE updateCurrentGroup NOTIFY currentGroupChanged) + ConversationChannel *currentGroup() const { return mCurrentGroup; } + public slots: void showGroupsWindow(); + void showConversation(const QString &localUid, const QString &remoteUid, unsigned type); + +private slots: + void updateCurrentGroup(ConversationChannel *group); + +signals: + void currentGroupChanged(ConversationChannel *currentGroup); private: QWeakPointer<QDeclarativeView> mWindow; + ConversationChannel *mCurrentGroup; + + void ensureWindow(); }; #endif ++++++ qmlmessages.yaml --- qmlmessages.yaml +++ qmlmessages.yaml @@ -1,14 +1,12 @@ Name: qmlmessages Summary: Messaging application for nemo -Version: 0.0.3 +Version: 0.0.4 Release: 1 Group: Applications/System License: BSD URL: https://github.com/nemomobile/qmlmessages Sources: - "%{name}-%{version}.tar.bz2" -Patches: - - 0001-Use-pkgconfig-to-find-TelepathyQt4.patch Description: Messaging application using Qt Quick for Nemo Mobile. Configure: none @@ -45,6 +43,7 @@ Requires: - qt-components + - libcommhistory-declarative Files: - "%{_bindir}/qmlmessages" ++++++ deleted files: --- 0001-Use-pkgconfig-to-find-TelepathyQt4.patch
