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