1 /*
2     Copyright (C) 2012 Aleix Pol <aleixpol@kde.org>
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 Street, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 #include "pinned-contacts-model.h"
20 #include "conversations-model.h"
21 #include "conversation.h"
22 
23 #include <TelepathyQt/Types>
24 #include <TelepathyQt/AvatarData>
25 #include <TelepathyQt/Presence>
26 #include <TelepathyQt/Account>
27 #include <TelepathyQt/PendingReady>
28 #include <TelepathyQt/ContactManager>
29 #include <TelepathyQt/PendingContacts>
30 
31 #include <KConfigGroup>
32 #include "debug.h"
33 
34 #include "KTp/presence.h"
35 #include "KTp/contact.h"
36 #include "KTp/persistent-contact.h"
37 
38 class PinnedContactsModelPrivate {
39 public:
PinnedContactsModelPrivate()40     PinnedContactsModelPrivate() {
41         conversations = nullptr;
42     }
43 
44     QList<KTp::PersistentContactPtr> m_pins;
45     ConversationsModel *conversations;
46 
pinsToString() const47     QStringList pinsToString() const {
48         QStringList ret;
49         Q_FOREACH(const KTp::PersistentContactPtr &p, m_pins) {
50             ret += p->accountId();
51             ret += p->contactId();
52         }
53         return ret;
54     }
55 };
56 
PinnedContactsModel(QObject * parent)57 PinnedContactsModel::PinnedContactsModel(QObject *parent)
58     : QAbstractListModel(parent)
59     , d(new PinnedContactsModelPrivate)
60 {
61     connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
62     connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
63 }
64 
~PinnedContactsModel()65 PinnedContactsModel::~PinnedContactsModel()
66 {
67     delete d;
68 }
69 
roleNames() const70 QHash<int, QByteArray> PinnedContactsModel::roleNames() const
71 {
72     QHash<int, QByteArray> roles = QAbstractListModel::roleNames();
73     roles[PresenceIconRole] = "presenceIcon";
74     roles[AvailabilityRole] = "available";
75     roles[ContactRole] = "contact";
76     roles[AccountRole] = "account";
77     roles[AlreadyChattingRole] = "alreadyChatting";
78     return roles;
79 }
80 
state() const81 QStringList PinnedContactsModel::state() const
82 {
83     return d->pinsToString();
84 }
85 
setState(const QStringList & pins)86 void PinnedContactsModel::setState(const QStringList &pins)
87 {
88     for (int i = 0; i < pins.count(); i += 2) {
89         appendContactPin(KTp::PersistentContact::create(pins[0], pins[1]));
90     }
91 }
92 
indexForContact(const KTp::ContactPtr & contact) const93 QModelIndex PinnedContactsModel::indexForContact(const KTp::ContactPtr &contact) const
94 {
95     for (int i = 0; i < d->m_pins.size() && contact; i++) {
96         if (d->m_pins[i]->contactId() == contact->id()) {
97             return index(i);
98         }
99     }
100     return QModelIndex();
101 }
102 
setPinning(const Tp::AccountPtr & account,const KTp::ContactPtr & contact,bool newState)103 void PinnedContactsModel::setPinning(const Tp::AccountPtr &account, const KTp::ContactPtr &contact, bool newState)
104 {
105     QModelIndex idx = indexForContact(contact);
106     bool found = idx.isValid();
107     if (newState && !found) {
108         KTp::PersistentContactPtr p = KTp::PersistentContact::create(account->uniqueIdentifier(), contact->id());
109         appendContactPin(p);
110     } else if (!newState && found) {
111         removeContactPin(d->m_pins[idx.row()]);
112     }
113 }
114 
data(const QModelIndex & index,int role) const115 QVariant PinnedContactsModel::data(const QModelIndex &index, int role) const
116 {
117     if (index.isValid()) {
118         KTp::PersistentContactPtr p = d->m_pins[index.row()];
119         switch(role) {
120         case Qt::DisplayRole:
121             if (p->contact()) {
122                 return p->contact()->alias();
123             }
124             break;
125         case PresenceIconRole:
126             if (p->contact()) {
127                 return KTp::Presence(p->contact()->presence()).icon();
128             } else {
129                 return KTp::Presence(Tp::Presence::offline()).icon();
130             }
131             break;
132         case AvailabilityRole:
133             if (!p->contact()) {
134                 return false;
135             } else {
136                 return p->contact()->presence().type() != Tp::ConnectionPresenceTypeOffline
137                         && p->contact()->presence().type() != Tp::ConnectionPresenceTypeError
138                         && p->contact()->presence().type() != Tp::ConnectionPresenceTypeUnset
139                         && p->contact()->presence().type() != Tp::ConnectionPresenceTypeUnknown;
140             }
141         case ContactRole:
142             return QVariant::fromValue<KTp::ContactPtr>(p->contact());
143         case AccountRole:
144             return QVariant::fromValue<Tp::AccountPtr>(p->account());
145         case AlreadyChattingRole: {
146             if (!p->contact() || !d->conversations) {
147                 return false;
148             }
149             bool found = false;
150             for (int i = 0; !found && i < d->conversations->rowCount(); i++) {
151                 QModelIndex idx = d->conversations->index(i, 0);
152                 KTp::ContactPtr contact = idx.data(ConversationsModel::ConversationRole).value<Conversation*>()->targetContact();
153                 found |= contact->id() == p->contact()->id();
154             }
155             return found;
156         }
157         case Qt::DecorationRole: {
158             QIcon icon;
159             if (p->contact()) {
160                 QString file = p->contact()->avatarData().fileName;
161                 if (!file.isEmpty()) {
162                     icon = QIcon::fromTheme(file);
163                 }
164             }
165             if (icon.isNull()) {
166                 icon = QIcon::fromTheme(QStringLiteral("im-user"));
167             }
168             return icon;
169         }
170         }
171     }
172     return QVariant();
173 }
174 
rowCount(const QModelIndex & parent) const175 int PinnedContactsModel::rowCount(const QModelIndex &parent) const
176 {
177     if (parent.isValid()) {
178         return 0;
179     }
180     return d->m_pins.count();
181 }
182 
removeContactPin(const KTp::PersistentContactPtr & pin)183 void PinnedContactsModel::removeContactPin(const KTp::PersistentContactPtr &pin)
184 {
185     int row = d->m_pins.indexOf(pin);
186     if (row >= 0) {
187         beginRemoveRows(QModelIndex(), row, row);
188         d->m_pins.removeAt(row);
189         endRemoveRows();
190 
191         Q_EMIT stateChanged();
192     } else
193         qWarning() << "trying to remove missing pin" << pin->contactId();
194 }
195 
appendContactPin(const KTp::PersistentContactPtr & pin)196 void PinnedContactsModel::appendContactPin(const KTp::PersistentContactPtr &pin)
197 {
198     auto samePin = [pin](const KTp::PersistentContactPtr &p) -> bool { return p->contactId() == pin->contactId(); };
199     if (std::find_if(d->m_pins.constBegin(), d->m_pins.constEnd(), samePin) != d->m_pins.constEnd()) {
200         return;
201     }
202 
203     int s = d->m_pins.size();
204     beginInsertRows(QModelIndex(), s, s);
205     d->m_pins += pin;
206     endInsertRows();
207 
208     if (pin->contact()) {
209         contactChanged(pin->contact());
210     }
211     connect(pin.data(), SIGNAL(contactChanged(KTp::ContactPtr)), SLOT(contactChanged(KTp::ContactPtr)));
212 
213     Q_EMIT stateChanged();
214 }
215 
contactChanged(const KTp::ContactPtr & contact)216 void PinnedContactsModel::contactChanged(const KTp::ContactPtr &contact)
217 {
218     if (contact) {
219         connect(contact.data(),
220                 SIGNAL(avatarDataChanged(Tp::AvatarData)),
221                 SLOT(contactDataChanged()));
222         connect(contact.data(),
223                 SIGNAL(aliasChanged(QString)),
224                 SLOT(contactDataChanged()));
225         connect(contact.data(),
226                 SIGNAL(presenceChanged(Tp::Presence)),
227                 SLOT(contactDataChanged()));
228     }
229 
230     QModelIndex index = indexForContact(contact);
231     Q_EMIT dataChanged(index, index);
232 }
233 
contactDataChanged()234 void PinnedContactsModel::contactDataChanged()
235 {
236     KTp::Contact *c = qobject_cast<KTp::Contact*>(sender());
237     QModelIndex index = indexForContact(KTp::ContactPtr(c));
238     Q_EMIT dataChanged(index, index);
239 }
240 
setConversationsModel(ConversationsModel * model)241 void PinnedContactsModel::setConversationsModel(ConversationsModel *model)
242 {
243     beginResetModel();
244     if (d->conversations) {
245         disconnect(d->conversations, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PinnedContactsModel::conversationsStateChanged);
246         disconnect(d->conversations, &QAbstractItemModel::rowsInserted, this, &PinnedContactsModel::conversationsStateChanged);
247     }
248 
249     d->conversations = model;
250     if (model) {
251         connect(d->conversations, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PinnedContactsModel::conversationsStateChanged);
252         connect(d->conversations, &QAbstractItemModel::rowsInserted, this, &PinnedContactsModel::conversationsStateChanged);
253     }
254     endResetModel();
255 }
256 
conversationsStateChanged(const QModelIndex & parent,int start,int end)257 void PinnedContactsModel::conversationsStateChanged(const QModelIndex &parent, int start, int end)
258 {
259     for (int i = start; i <= end; i++) {
260         QModelIndex idx = d->conversations->index(i, 0, parent);
261         Conversation *conv = idx.data(ConversationsModel::ConversationRole).value<Conversation*>();
262         QString contactId = conv->targetContact()->id();
263 
264         Q_FOREACH (const KTp::PersistentContactPtr &p, d->m_pins) {
265             if (p->contactId() == contactId) {
266                 QModelIndex contactIdx = indexForContact(p->contact());
267                 //We need to delay the dataChanged until the next event loop, when endRowsRemoved has been called
268                 QMetaObject::invokeMethod(this, "dataChanged", Qt::QueuedConnection, Q_ARG(QModelIndex, contactIdx), Q_ARG(QModelIndex, contactIdx));
269             }
270         }
271     }
272 }
273 
conversationsModel() const274 ConversationsModel* PinnedContactsModel::conversationsModel() const
275 {
276     return d->conversations;
277 }
278