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
