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