1 /*
2     Kopete Contactlist Model
3 
4     Copyright (c) 2007      by Matt Rogers            <mattr@kde.org>
5 
6     Kopete    (c) 2002-2007 by the Kopete developers  <kopete-devel@kde.org>
7 
8     *************************************************************************
9     *                                                                       *
10     * This program is free software; you can redistribute it and/or modify  *
11     * it under the terms of the GNU General Public License as published by  *
12     * the Free Software Foundation; either version 2 of the License, or     *
13     * (at your option) any later version.                                   *
14     *                                                                       *
15     *************************************************************************
16 */
17 
18 #include "contactlistproxymodel.h"
19 
20 #include <QList>
21 #include <QTimer>
22 #include <QDebug>
23 #include <QStandardItem>
24 
25 #include <kcontacts/addressee.h>
26 
27 #include "kopetegroup.h"
28 #include "kopetemetacontact.h"
29 #include "kopetecontactlist.h"
30 #include "kopetecontact.h"
31 #include "kopeteappearancesettings.h"
32 #include "kopeteitembase.h"
33 
34 namespace Kopete {
35 namespace UI {
ContactListProxyModel(QObject * parent)36 ContactListProxyModel::ContactListProxyModel(QObject *parent)
37     : QSortFilterProxyModel(parent)
38     , rootRowCount(0)
39     , sortScheduled(false)
40 {
41     setDynamicSortFilter(true);
42     sort(0, Qt::AscendingOrder);
43     connect(Kopete::AppearanceSettings::self(), SIGNAL(configChanged()), this, SLOT(slotConfigChanged()));
44 
45     // Workaround Qt sorting bug
46     connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)),
47             this, SLOT(proxyRowsInserted(QModelIndex,int,int)));
48     connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)),
49             this, SLOT(proxyRowsRemoved(QModelIndex,int,int)));
50     connect(this, SIGNAL(modelReset()),
51             this, SLOT(proxyCheckSort()));
52     connect(this, SIGNAL(layoutChanged()),
53             this, SLOT(proxyCheckSort()));
54 }
55 
~ContactListProxyModel()56 ContactListProxyModel::~ContactListProxyModel()
57 {
58 }
59 
slotConfigChanged()60 void ContactListProxyModel::slotConfigChanged()
61 {
62     //kDebug(14001) << "config changed";
63     qDebug() << "config changed";
64     invalidate();
65 }
66 
lessThan(const QModelIndex & left,const QModelIndex & right) const67 bool ContactListProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
68 {
69     int leftType = left.data(Kopete::Items::TypeRole).toInt();
70     if (leftType != right.data(Kopete::Items::TypeRole).toInt()) {
71         return leftType == Kopete::Items::MetaContact;   // MetaContacts are always on top.
72     }
73     if (leftType == Kopete::Items::Group) {
74         switch (Kopete::AppearanceSettings::self()->contactListGroupSorting()) {
75         case Kopete::AppearanceSettings::EnumContactListGroupSorting::Manual:
76             return left.row() < right.row();
77         case Kopete::AppearanceSettings::EnumContactListGroupSorting::Name:
78         default:
79             QString leftName = left.data(Qt::DisplayRole).toString();
80             QString rightName = right.data(Qt::DisplayRole).toString();
81 
82             // Force the offline group to the bottom
83             QObject *groupObjectLeft = qVariantValue<QObject *>(sourceModel()->data(left, Kopete::Items::ObjectRole));
84             QObject *groupObjectRight = qVariantValue<QObject *>(sourceModel()->data(right, Kopete::Items::ObjectRole));
85 
86             if (groupObjectLeft == Kopete::Group::offline()) {
87                 return false;
88             } else if (groupObjectRight == Kopete::Group::offline()) {
89                 return true;
90             }
91             return QString::localeAwareCompare(leftName, rightName) < 0;
92         }
93     } else if (leftType == Kopete::Items::MetaContact) {
94         switch (Kopete::AppearanceSettings::self()->contactListMetaContactSorting()) {
95         case Kopete::AppearanceSettings::EnumContactListMetaContactSorting::Manual:
96             return left.row() < right.row();
97         case Kopete::AppearanceSettings::EnumContactListMetaContactSorting::Status:
98         {
99             int leftStatus = left.data(Kopete::Items::OnlineStatusRole).toInt();
100             int rightStatus = right.data(Kopete::Items::OnlineStatusRole).toInt();
101             if (leftStatus != rightStatus) {
102                 return !(leftStatus < rightStatus);
103             } else {
104                 QString leftName = left.data(Qt::DisplayRole).toString();
105                 QString rightName = right.data(Qt::DisplayRole).toString();
106                 return QString::localeAwareCompare(leftName, rightName) < 0;
107             }
108         }
109         case Kopete::AppearanceSettings::EnumContactListMetaContactSorting::Name:
110         default:
111             QString leftName = left.data(Qt::DisplayRole).toString();
112             QString rightName = right.data(Qt::DisplayRole).toString();
113             return QString::localeAwareCompare(leftName, rightName) < 0;
114         }
115     }
116 
117     return false;
118 }
119 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const120 bool ContactListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
121 {
122     QAbstractItemModel *model = sourceModel();
123     QModelIndex current = model->index(sourceRow, 0, sourceParent);
124     bool showEmpty = Kopete::AppearanceSettings::self()->showEmptyGroups();
125     bool showOffline = Kopete::AppearanceSettings::self()->showOfflineUsers();
126 
127     if (model->data(current, Kopete::Items::TypeRole) == Kopete::Items::Group) {
128         QObject *groupObject = qVariantValue<QObject *>(model->data(current, Kopete::Items::ObjectRole));
129         if (qobject_cast<Kopete::Group *>(groupObject) == Kopete::Group::topLevel()) {
130             return true;
131         }
132 
133         int connectedContactsCount = model->data(current, Kopete::Items::ConnectedCountRole).toInt();
134         int totalContactsCount = model->data(current, Kopete::Items::TotalCountRole).toInt();
135 
136         bool isOfflineGroup = (groupObject == Kopete::Group::offline());
137         if (!filterRegExp().isEmpty() && isOfflineGroup) {
138             return false;
139         }
140 
141         if (!filterRegExp().isEmpty()) {
142             // This shows or hides the contacts group folder if something was found.
143             // Walk through the group's metacontacts and see it the search result was found
144             // If it is found then we can show this group folder.
145             // This is fairly slow with > group 10000 contacts. Reasonable? (ginge)
146             for (int i = 0; i < model->rowCount(current); i++) {
147                 QModelIndex qmi = model->index(i, 0, current);
148 
149                 if (model->data(qmi, Kopete::Items::TypeRole) != Kopete::Items::MetaContact) {
150                     continue;
151                 }
152 
153                 QObject *mcObject = qVariantValue<QObject *>(model->data(qmi, Kopete::Items::ObjectRole));
154                 Kopete::MetaContact *mc = qobject_cast<Kopete::MetaContact *>(mcObject);
155 
156                 // Do a better search.
157                 if (searchContactInfo(mc, filterRegExp())) {
158                     qobject_cast<Kopete::Group *>(groupObject)->setExpanded(true);
159                     return true;
160                 }
161             }
162 
163             return false;
164         }
165 
166         if (!Kopete::AppearanceSettings::self()->showOfflineGrouped() && isOfflineGroup) {
167             return false;
168         }
169 
170         if (!showEmpty && totalContactsCount == 0 && !isOfflineGroup) {
171             return false;
172         }
173 
174         // do not display offline when viewing in grouped offline mode
175         if (showOffline && isOfflineGroup) {
176             return false;
177         }
178 
179         if (!showEmpty && !showOffline && connectedContactsCount == 0 && !isOfflineGroup) {
180             return false;
181         }
182 
183         return true;
184     }
185 
186     if (model->data(current, Kopete::Items::TypeRole) == Kopete::Items::MetaContact) {
187         if (!filterRegExp().isEmpty()) {
188             QObject *contactObject = qVariantValue<QObject *>(model->data(current, Kopete::Items::ObjectRole));
189             Kopete::MetaContact *mc = qobject_cast<Kopete::MetaContact *>(contactObject);
190 
191             // Do a better search
192             return searchContactInfo(mc, filterRegExp());
193         }
194 
195         // Get the MetaContacts parent group name
196         QObject *groupObject = qVariantValue<QObject *>(model->data(sourceParent, Kopete::Items::ObjectRole));
197 
198         if (Kopete::AppearanceSettings::self()->groupContactByGroup() && qobject_cast<Kopete::Group *>(groupObject) != 0) {
199             // If this contact's group is called Offline, and we are not globally
200             // showing offline all users show the offline group folder
201             if (groupObject == Kopete::Group::offline()) {
202                 return !showOffline;
203             }
204         }
205 
206         bool alwaysVisible = model->data(current, Kopete::Items::AlwaysVisible).toBool();
207         int mcStatus = model->data(current, Kopete::Items::OnlineStatusRole).toInt();
208         if (mcStatus <= OnlineStatus::Offline && !showOffline && !alwaysVisible) {
209             return false;
210         } else {
211             return true;
212         }
213     }
214 
215     return false;
216 }
217 
proxyRowsInserted(const QModelIndex & parent,int start,int end)218 void ContactListProxyModel::proxyRowsInserted(const QModelIndex &parent, int start, int end)
219 {
220     if (parent.isValid()) {
221         return;
222     }
223 
224     int count = (end - start) + 1;
225     if (rootRowCount <= 0 && count > 0 && !sortScheduled) {
226         sortScheduled = true;
227         QTimer::singleShot(0, this, SLOT(forceSort()));
228     }
229     rootRowCount += count;
230 }
231 
proxyRowsRemoved(const QModelIndex & parent,int start,int end)232 void ContactListProxyModel::proxyRowsRemoved(const QModelIndex &parent, int start, int end)
233 {
234     if (parent.isValid()) {
235         return;
236     }
237 
238     int count = (end - start) + 1;
239     rootRowCount -= count;
240 }
241 
proxyCheckSort()242 void ContactListProxyModel::proxyCheckSort()
243 {
244     int count = rowCount();
245     if (rootRowCount <= 0 && count > 0 && !sortScheduled) {
246         sortScheduled = true;
247         QTimer::singleShot(0, this, SLOT(forceSort()));
248     }
249     rootRowCount = count;
250 }
251 
forceSort()252 void ContactListProxyModel::forceSort()
253 {
254     if (!sortScheduled) {
255         return;
256     }
257 
258     sortScheduled = false;
259     sort(-1, Qt::AscendingOrder);
260     sort(0, Qt::AscendingOrder);
261 }
262 
263 // Better search. Now a search will look in the metacontact display name, the invidual account contact names and any email addresses.
searchContactInfo(Kopete::MetaContact * mc,QRegExp searchPattern) const264 bool ContactListProxyModel::searchContactInfo(Kopete::MetaContact *mc, QRegExp searchPattern) const
265 {
266     // Check the display name
267     if (mc->displayName().contains(searchPattern)) {
268         return true;
269     }
270 
271     // Check the address book
272     //DEPRECATED: KContacts::Addressee addressee = KContacts::StdAddressBook::self()->findByUid( mc->kabcId() );
273     KContacts::Addressee addressee = KContacts::Addressee();
274     if (!addressee.isEmpty()) {
275         QString emailAddr = addressee.fullEmail();
276 
277         if (emailAddr.contains(searchPattern)) {
278             return true;
279         }
280     }
281 
282     // Check alternative names
283     foreach (Kopete::Contact *c, mc->contacts()) {
284         // Search each metacontacts' contacts
285         if (c->contactId().contains(searchPattern)) {
286             return true;
287         }
288     }
289 
290     return false;
291 }
292 }
293 }
294