1 /*
2 * Copyright (C) 2013 David Edmundson <kde@davidedmundson.co.uk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 #include "text-channel-watcher-proxy-model.h"
20
21 #include <TelepathyQt/TextChannel>
22 #include <TelepathyQt/ChannelClassSpecList>
23 #include <TelepathyQt/MethodInvocationContext>
24 #include <TelepathyQt/Types>
25 #include <TelepathyQt/ReceivedMessage>
26
27 #include <KTp/types.h>
28 #include <KTp/contact.h>
29 #include <KTp/message.h>
30
31
channelClasses()32 inline Tp::ChannelClassSpecList channelClasses() {
33 return Tp::ChannelClassSpecList() << Tp::ChannelClassSpec::textChat();
34 }
35
36 class ChannelWatcher : public QObject, public Tp::RefCounted
37 {
38 Q_OBJECT
39 public:
40 ChannelWatcher(const QPersistentModelIndex &index, const Tp::TextChannelPtr &channel, QObject *parent=nullptr);
41 int unreadMessageCount() const;
42 QString lastMessage() const;
43 KTp::Message::MessageDirection lastMessageDirection() const;
44 QPersistentModelIndex modelIndex() const;
45 Q_SIGNALS:
46 void messagesChanged();
47 void invalidated();
48 private Q_SLOTS:
49 void onMessageReceived(const Tp::ReceivedMessage &message);
50 void onMessageSent(const Tp::Message &message);
51 private:
52 QPersistentModelIndex m_index;
53 Tp::TextChannelPtr m_channel;
54 QString m_lastMessage;
55 KTp::Message::MessageDirection m_lastMessageDirection;
56 };
57
58 typedef Tp::SharedPtr<ChannelWatcher> ChannelWatcherPtr;
59
ChannelWatcher(const QPersistentModelIndex & index,const Tp::TextChannelPtr & channel,QObject * parent)60 ChannelWatcher::ChannelWatcher(const QPersistentModelIndex &index, const Tp::TextChannelPtr &channel, QObject *parent):
61 QObject(parent),
62 m_index(index),
63 m_channel(channel),
64 m_lastMessageDirection(KTp::Message::LocalToRemote)
65 {
66 connect(channel.data(), SIGNAL(pendingMessageRemoved(Tp::ReceivedMessage)), SIGNAL(messagesChanged()));
67 connect(channel.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SIGNAL(invalidated()));
68
69 connect(channel.data(), SIGNAL(messageReceived(Tp::ReceivedMessage)), SLOT(onMessageReceived(Tp::ReceivedMessage)));
70 connect(channel.data(), SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)), SLOT(onMessageSent(Tp::Message)));
71
72 //trigger an update to the contact straight away
73 QTimer::singleShot(0, this, SIGNAL(messagesChanged()));
74 }
75
modelIndex() const76 QPersistentModelIndex ChannelWatcher::modelIndex() const
77 {
78 return m_index;
79 }
80
unreadMessageCount() const81 int ChannelWatcher::unreadMessageCount() const
82 {
83 return m_channel->messageQueue().size();
84 }
85
lastMessage() const86 QString ChannelWatcher::lastMessage() const
87 {
88 return m_lastMessage;
89 }
90
lastMessageDirection() const91 KTp::Message::MessageDirection ChannelWatcher::lastMessageDirection() const
92 {
93 return m_lastMessageDirection;
94 }
95
onMessageReceived(const Tp::ReceivedMessage & message)96 void ChannelWatcher::onMessageReceived(const Tp::ReceivedMessage &message)
97 {
98 if (!message.isDeliveryReport()) {
99 m_lastMessage = message.text();
100 m_lastMessageDirection = KTp::Message::RemoteToLocal;
101 Q_EMIT messagesChanged();
102 }
103 }
104
onMessageSent(const Tp::Message & message)105 void ChannelWatcher::onMessageSent(const Tp::Message &message)
106 {
107 m_lastMessage = message.text();
108 m_lastMessageDirection = KTp::Message::LocalToRemote;
109 Q_EMIT messagesChanged();
110 }
111
112 namespace KTp {
113
114 class TextChannelWatcherProxyModel::Private {
115 public:
116 QHash<KTp::ContactPtr, ChannelWatcherPtr> currentChannels;
117 };
118
119 } //namespace
120
121
122
123
TextChannelWatcherProxyModel(QObject * parent)124 KTp::TextChannelWatcherProxyModel::TextChannelWatcherProxyModel(QObject *parent) :
125 QIdentityProxyModel(parent),
126 Tp::AbstractClientObserver(channelClasses(), true),
127 d(new TextChannelWatcherProxyModel::Private)
128 {
129 }
130
~TextChannelWatcherProxyModel()131 KTp::TextChannelWatcherProxyModel::~TextChannelWatcherProxyModel()
132 {
133 delete d;
134 }
135
observeChannels(const Tp::MethodInvocationContextPtr<> & context,const Tp::AccountPtr & account,const Tp::ConnectionPtr & connection,const QList<Tp::ChannelPtr> & channels,const Tp::ChannelDispatchOperationPtr & dispatchOperation,const QList<Tp::ChannelRequestPtr> & requestsSatisfied,const Tp::AbstractClientObserver::ObserverInfo & observerInfo)136 void KTp::TextChannelWatcherProxyModel::observeChannels(const Tp::MethodInvocationContextPtr<> &context, const Tp::AccountPtr &account, const Tp::ConnectionPtr &connection, const QList<Tp::ChannelPtr> &channels, const Tp::ChannelDispatchOperationPtr &dispatchOperation, const QList<Tp::ChannelRequestPtr> &requestsSatisfied, const Tp::AbstractClientObserver::ObserverInfo &observerInfo)
137 {
138 Q_UNUSED(context)
139 Q_UNUSED(account)
140 Q_UNUSED(connection)
141 Q_UNUSED(dispatchOperation)
142 Q_UNUSED(requestsSatisfied)
143 Q_UNUSED(observerInfo)
144
145 if (!sourceModel()) {
146 return;
147 }
148
149 Q_FOREACH(const Tp::ChannelPtr & channel, channels) {
150 Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel);
151 if (textChannel) {
152 KTp::ContactPtr targetContact = KTp::ContactPtr::qObjectCast(textChannel->targetContact());
153
154 //skip group chats and situations where we don't have a single contact to mark messages for
155 if (targetContact.isNull()) {
156 continue;
157 }
158
159 //if it's not in our source model, ignore the channel
160 QModelIndexList matchedContacts = sourceModel()->match(QModelIndex(sourceModel()->index(0,0)), KTp::IdRole, QVariant::fromValue(targetContact->id()));
161 if (matchedContacts.size() !=1) {
162 continue;
163 }
164
165 QPersistentModelIndex contactIndex(matchedContacts[0]);
166
167 ChannelWatcherPtr watcher = ChannelWatcherPtr(new ChannelWatcher(contactIndex, textChannel));
168 d->currentChannels[targetContact] = watcher;
169
170 connect(watcher.data(), SIGNAL(messagesChanged()), SLOT(onChannelMessagesChanged()));
171 }
172 }
173 }
174
data(const QModelIndex & proxyIndex,int role) const175 QVariant KTp::TextChannelWatcherProxyModel::data(const QModelIndex &proxyIndex, int role) const
176 {
177 // if we're processing a person and either of those two roles,
178 // we propagate the data from sub contacts
179 if (proxyIndex.model()->rowCount(proxyIndex) > 0
180 && (role == KTp::ContactHasTextChannelRole || role == KTp::ContactUnreadMessageCountRole)) {
181 QVariant personData;
182
183 for (int i = 0; i < proxyIndex.model()->rowCount(proxyIndex); i++) {
184 QVariant data = proxyIndex.child(i, 0).data(role);
185 if (!data.isNull()) {
186 personData = data;
187 break;
188 }
189 }
190
191 return personData;
192 }
193
194 const QModelIndex sourceIndex = mapToSource(proxyIndex);
195
196 if (role == KTp::ContactHasTextChannelRole) {
197 KTp::ContactPtr contact = sourceIndex.data(KTp::ContactRole).value<KTp::ContactPtr>();
198 if (contact) {
199 if (d->currentChannels.contains(contact)) {
200 return true;
201 }
202 }
203 return QVariant();
204 }
205
206 if (role == KTp::ContactUnreadMessageCountRole) {
207 KTp::ContactPtr contact = sourceIndex.data(KTp::ContactRole).value<KTp::ContactPtr>();
208 if (contact) {
209 if (d->currentChannels.contains(contact)) {
210 return d->currentChannels[contact]->unreadMessageCount();
211 }
212 }
213 return QVariant();
214 }
215 if (role == KTp::ContactLastMessageRole) {
216 KTp::ContactPtr contact = sourceIndex.data(KTp::ContactRole).value<KTp::ContactPtr>();
217 if (contact) {
218 if (d->currentChannels.contains(contact)) {
219 return d->currentChannels[contact]->lastMessage();
220 }
221 }
222 return QString();
223 }
224 if (role == KTp::ContactLastMessageDirectionRole) {
225 KTp::ContactPtr contact = sourceIndex.data(KTp::ContactRole).value<KTp::ContactPtr>();
226 if (contact) {
227 if (d->currentChannels.contains(contact)) {
228 return d->currentChannels[contact]->lastMessageDirection();
229 }
230 }
231 return KTp::Message::LocalToRemote;
232 }
233
234 return sourceIndex.data(role);
235 }
236
onChannelMessagesChanged()237 void KTp::TextChannelWatcherProxyModel::onChannelMessagesChanged()
238 {
239 ChannelWatcher* watcher = qobject_cast<ChannelWatcher*>(sender());
240 Q_ASSERT(watcher);
241 const QModelIndex &index = mapFromSource(watcher->modelIndex());
242 dataChanged(index, index);
243 }
244
onChannelInvalidated()245 void KTp::TextChannelWatcherProxyModel::onChannelInvalidated()
246 {
247 ChannelWatcher* watcher = qobject_cast<ChannelWatcher*>(sender());
248 Q_ASSERT(watcher);
249 const QModelIndex &index = mapFromSource(watcher->modelIndex());
250 const KTp::ContactPtr &contact = index.data(KTp::ContactRole).value<KTp::ContactPtr>();
251
252 d->currentChannels.remove(contact);
253 dataChanged(index, index);
254 }
255
256 #include "text-channel-watcher-proxy-model.moc"
257 #include "moc_text-channel-watcher-proxy-model.cpp"
258