1 /*
2 kopetecontact.cpp - Kopete Contact
3
4 Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett <duncan@kde.org>
5 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
6 Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org>
7
8 Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
9
10 *************************************************************************
11 * *
12 * This library is free software; you can redistribute it and/or *
13 * modify it under the terms of the GNU Lesser General Public *
14 * License as published by the Free Software Foundation; either *
15 * version 2 of the License, or (at your option) any later version. *
16 * *
17 *************************************************************************
18 */
19
20 #include "kopetecontact.h"
21
22 #include <QDialog>
23 #include <QIcon>
24 #include <QMenu>
25 #include <QLocale>
26 #include <QVariant>
27 #include <QWidget>
28
29 #include <kabcpersistence.h>
30 #include <kmessagebox.h>
31 #include <kmessagebox_queued.h>
32 #include <ktreewidgetsearchline.h>
33
34 #include "kopetecontactlist.h"
35 #include "kopeteglobal.h"
36 #include "kopeteuiglobal.h"
37 #include "kopeteprotocol.h"
38 #include "kopeteaccount.h"
39 #include "kopetestdaction.h"
40 #include "kopetechatsession.h"
41 #include "kopeteview.h"
42 #include "kopetemetacontact.h"
43 #include "kopeteappearancesettings.h"
44 #include "kopetebehaviorsettings.h"
45 #include "metacontactselectorwidget.h"
46 #include "kopeteemoticons.h"
47 #include "kopetestatusmessage.h"
48 #include "kopeteinfodialog.h"
49 #include "kopetedeletecontacttask.h"
50
51 //For the moving to another metacontact dialog
52 #include <QVBoxLayout>
53 #include <QCheckBox>
54 #include <QDialogButtonBox>
55 #include <QPushButton>
56
57 namespace Kopete {
58 class Contact::Private
59 {
60 public:
61 bool fileCapable;
62
63 OnlineStatus onlineStatus;
64 Account *account;
65
66 MetaContact *metaContact;
67
68 QString contactId;
69 QString icon;
70
71 QTime idleTimer;
72 unsigned long int idleTime;
73
74 Kopete::StatusMessage statusMessage;
75 KToggleAction *toggleAlwaysVisibleAction;
76
77 Kopete::Contact::NameType preferredNameType;
78 QString oldName;
79 };
80
81 /* static */
nameTypeFromString(const QString & nameType)82 Kopete::Contact::NameType Kopete::Contact::nameTypeFromString(const QString &nameType)
83 {
84 if (nameType == QLatin1String("nickName")) {
85 return Kopete::Contact::NickName;
86 } else if (nameType == QLatin1String("customName")) {
87 return Kopete::Contact::CustomName;
88 } else if (nameType == QLatin1String("formattedName")) {
89 return Kopete::Contact::FormattedName;
90 } else if (nameType == QLatin1String("contactId")) {
91 return Kopete::Contact::ContactId;
92 } else { // fallback to custom name
93 return Kopete::Contact::CustomName;
94 }
95 }
96
97 /* static */
nameTypeToString(Kopete::Contact::NameType nameType)98 const QString Kopete::Contact::nameTypeToString(Kopete::Contact::NameType nameType)
99 {
100 switch (nameType) {
101 case Kopete::Contact::NickName:
102 return QStringLiteral("nickName");
103 case Kopete::Contact::FormattedName:
104 return QStringLiteral("formattedName");
105 case Kopete::Contact::ContactId:
106 return QStringLiteral("contactId");
107 case Kopete::Contact::CustomName:
108 default: // fallback to custom name
109 return QStringLiteral("customName");
110 }
111 }
112
Contact(Account * account,const QString & contactId,MetaContact * parent,const QString & icon)113 Contact::Contact(Account *account, const QString &contactId, MetaContact *parent, const QString &icon)
114 : ContactListElement(parent)
115 , d(new Private())
116 {
117 //qCDebug(LIBKOPETE_LOG) << "Creating contact with id " << contactId;
118
119 d->contactId = contactId;
120 d->metaContact = parent;
121 connect(d->metaContact, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)));
122
123 d->fileCapable = false;
124 d->account = account;
125 d->idleTime = 0;
126 d->icon = icon;
127 d->preferredNameType = Kopete::Contact::CustomName;
128 d->oldName = displayName();
129
130 connect(this, SIGNAL(propertyChanged(Kopete::PropertyContainer*,QString,QVariant,QVariant)),
131 this, SLOT(slotPropertyChanged(Kopete::PropertyContainer*,QString,QVariant,QVariant)));
132
133 bool duplicate = false;
134 // If can happend that a MetaContact may be used without a account
135 // (ex: for unit tests or chat window style preview)
136 if (account) {
137 // Don't register myself contacts because otherwise we can't have own contact in contact list.
138 if (d->metaContact != Kopete::ContactList::self()->myself()) {
139 duplicate = !account->registerContact(this);
140 }
141
142 connect(account, SIGNAL(isConnectedChanged()), SLOT(slotAccountIsConnectedChanged()));
143 }
144
145 // Need to check this because myself() may have no parent
146 // Maybe too the metaContact doesn't have a valid protocol()
147 // (ex: for unit tests or chat window style preview)
148
149 // if alreadyRegistered is true (which mean that this is duplicate contact) we will not add
150 // parent and the contact will die out on next Kopete restart.
151 if (!duplicate && parent && protocol()) {
152 parent->addContact(this);
153 }
154 }
155
~Contact()156 Contact::~Contact()
157 {
158 //qCDebug(LIBKOPETE_LOG) ;
159 emit(contactDestroyed(this));
160 delete d;
161 }
162
onlineStatus() const163 OnlineStatus Contact::onlineStatus() const
164 {
165 if (this == account()->myself() || account()->isConnected()) {
166 return d->onlineStatus;
167 } else {
168 return protocol()->accountOfflineStatus();
169 }
170 }
171
setOnlineStatus(const OnlineStatus & status)172 void Contact::setOnlineStatus(const OnlineStatus &status)
173 {
174 if (status == d->onlineStatus) {
175 return;
176 }
177
178 const bool oldCanAcceptFiles = canAcceptFiles();
179 OnlineStatus oldStatus = d->onlineStatus;
180 d->onlineStatus = status;
181
182 Kopete::Global::Properties *globalProps = Kopete::Global::Properties::self();
183
184 // Contact changed from Offline to another known online status
185 if (oldStatus.status() == OnlineStatus::Offline
186 && status.status() != OnlineStatus::Unknown
187 && status.status() != OnlineStatus::Offline) {
188 if (!hasProperty(globalProps->onlineSince().key())) {
189 setProperty(globalProps->onlineSince(), QDateTime::currentDateTime());
190 }
191 // qCDebug(LIBKOPETE_LOG) << "REMOVING lastSeen property for " << nickName();
192 removeProperty(globalProps->lastSeen());
193 } else if (oldStatus.status() != OnlineStatus::Offline
194 && oldStatus.status() != OnlineStatus::Unknown
195 && status.status() == OnlineStatus::Offline) { // Contact went back offline
196 removeProperty(globalProps->onlineSince());
197 // qCDebug(LIBKOPETE_LOG) << "SETTING lastSeen property for " << nickName();
198 setProperty(globalProps->lastSeen(), QDateTime::currentDateTime());
199 }
200
201 if (this == account()->myself() || account()->isConnected()) {
202 emit onlineStatusChanged(this, status, oldStatus);
203 }
204
205 if (oldCanAcceptFiles != canAcceptFiles()) {
206 emit canAcceptFilesChanged();
207 }
208 }
209
statusMessage() const210 Kopete::StatusMessage Contact::statusMessage() const
211 {
212 return d->statusMessage;
213 }
214
setStatusMessage(const Kopete::StatusMessage & statusMessage)215 void Contact::setStatusMessage(const Kopete::StatusMessage &statusMessage)
216 {
217 bool emitUpdate = true;
218
219 if (d->statusMessage.title() == statusMessage.title() && d->statusMessage.message() == statusMessage.message()) {
220 emitUpdate = false;
221 }
222
223 d->statusMessage = statusMessage;
224
225 qCDebug(LIBKOPETE_LOG) << "Setting up the status title property with this: " << statusMessage.title();
226 if (!statusMessage.title().isEmpty()) {
227 setProperty(Kopete::Global::Properties::self()->statusTitle(), statusMessage.title());
228 } else {
229 removeProperty(Kopete::Global::Properties::self()->statusTitle());
230 }
231
232 qCDebug(LIBKOPETE_LOG) << "Setting up the status message property with this: " << statusMessage.message();
233 if (!statusMessage.message().isEmpty()) {
234 setProperty(Kopete::Global::Properties::self()->statusMessage(), statusMessage.message());
235 } else {
236 removeProperty(Kopete::Global::Properties::self()->statusMessage());
237 }
238
239 if (emitUpdate) {
240 emit statusMessageChanged(this);
241 }
242 }
243
slotAccountIsConnectedChanged()244 void Contact::slotAccountIsConnectedChanged()
245 {
246 if (this == account()->myself()) {
247 return;
248 }
249
250 if (account()->isConnected()) {
251 emit onlineStatusChanged(this, d->onlineStatus, protocol()->accountOfflineStatus());
252 } else {
253 emit onlineStatusChanged(this, protocol()->accountOfflineStatus(), d->onlineStatus);
254 }
255 }
256
sendFile(const QUrl &,const QString &,uint)257 void Contact::sendFile(const QUrl &, const QString &, uint)
258 {
259 qCWarning(LIBKOPETE_LOG) << "Plugin "
260 << protocol()->pluginId() << " has enabled file sending, "
261 << "but didn't implement it!" << endl;
262 }
263
slotAddContact()264 void Contact::slotAddContact()
265 {
266 if (metaContact()) {
267 metaContact()->setTemporary(false);
268 ContactList::self()->addMetaContact(metaContact());
269 }
270 }
271
popupMenu(ChatSession *)272 QMenu *Contact::popupMenu(ChatSession *)
273 {
274 return popupMenu();
275 }
276
popupMenu()277 QMenu *Contact::popupMenu()
278 {
279 QMenu *menu = new QMenu();
280
281 QString titleText;
282 const QString nick = displayName();
283 if (nick == contactId()) {
284 titleText = QStringLiteral("%1 (%2)").arg(contactId(), onlineStatus().description());
285 } else {
286 titleText = QStringLiteral("%1 <%2> (%3)").arg(nick, contactId(), onlineStatus().description());
287 }
288 menu->addSection(titleText);
289
290 if (metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId()) {
291 QAction *actionAddContact = new QAction(QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("&Add to Your Contact List"), menu);
292 connect(actionAddContact, SIGNAL(triggered(bool)), this, SLOT(slotAddContact()));
293
294 menu->addAction(actionAddContact);
295 menu->addSeparator();
296 }
297
298 // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
299 const bool reach = account()->isConnected() && isReachable();
300 const bool myself = (this == account()->myself());
301
302 QAction *actionSendMessage = KopeteStdAction::sendMessage(this, SLOT(sendMessage()), menu);
303 actionSendMessage->setEnabled(reach && !myself);
304 menu->addAction(actionSendMessage);
305
306 QAction *actionChat = KopeteStdAction::chat(this, SLOT(startChat()), menu);
307 actionChat->setEnabled(reach && !myself);
308 menu->addAction(actionChat);
309
310 QAction *actionSendFile = KopeteStdAction::sendFile(this, SLOT(sendFile()), menu);
311 actionSendFile->setEnabled(reach && d->fileCapable && !myself);
312 menu->addAction(actionSendFile);
313
314 // Protocol specific options will go below this separator
315 // through the use of the customContextMenuActions() function
316
317 // Get the custom actions from the protocols ( pure virtual function )
318 QList<QAction *> *customActions = customContextMenuActions();
319 if (customActions && !customActions->isEmpty()) {
320 menu->addSeparator();
321 QList<QAction *>::iterator it, itEnd = customActions->end();
322 for (it = customActions->begin(); it != itEnd; ++it) {
323 menu->addAction((*it));
324 }
325 }
326 delete customActions;
327
328 menu->addSeparator();
329
330 if (metaContact() && !metaContact()->isTemporary()) {
331 QAction *changeMetaContact = KopeteStdAction::changeMetaContact(this, SLOT(changeMetaContact()), menu);
332 menu->addAction(changeMetaContact);
333
334 d->toggleAlwaysVisibleAction = new KToggleAction(i18n("Visible when offline"), menu);
335 d->toggleAlwaysVisibleAction->setChecked(property(Kopete::Global::Properties::self()->isAlwaysVisible()).value().toBool());
336 menu->addAction(d->toggleAlwaysVisibleAction);
337 connect(d->toggleAlwaysVisibleAction, SIGNAL(toggled(bool)), this, SLOT(toggleAlwaysVisible()));
338 }
339
340 menu->addAction(KopeteStdAction::contactInfo(this, SLOT(slotUserInfo()), menu));
341
342 #if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11
343 if (account()->isBlocked(d->contactId)) {
344 KopeteStdAction::unblockContact(this, SLOT(slotUnblock()), menu, "actionUnblockContact")->plug(menu);
345 } else {
346 KopeteStdAction::blockContact(this, SLOT(slotBlock()), menu, "actionBlockContact")->plug(menu);
347 }
348 #endif
349
350 if (metaContact() && !metaContact()->isTemporary()) {
351 menu->addAction(KopeteStdAction::deleteContact(this, SLOT(slotDelete()), menu));
352 }
353
354 return menu;
355 }
356
toggleAlwaysVisible()357 void Contact::toggleAlwaysVisible()
358 {
359 bool alwaysVisible = property(Kopete::Global::Properties::self()->isAlwaysVisible()).value().toBool();
360 setProperty(Kopete::Global::Properties::self()->isAlwaysVisible(), !alwaysVisible);
361 d->toggleAlwaysVisibleAction->setChecked(!alwaysVisible);
362 }
363
changeMetaContact()364 void Contact::changeMetaContact()
365 {
366 QPointer <QDialog> moveDialog = new QDialog(Kopete::UI::Global::mainWidget());
367 moveDialog->setWindowTitle(i18n("Move Contact"));
368 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
369 QWidget *mainWidget = new QWidget(Kopete::UI::Global::mainWidget());
370 QVBoxLayout *mainLayout = new QVBoxLayout;
371 moveDialog->setLayout(mainLayout);
372 mainLayout->addWidget(mainWidget);
373 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
374 okButton->setDefault(true);
375 okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
376 moveDialog->connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
377 moveDialog->connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
378 //PORTING SCRIPT: WARNING mainLayout->addWidget(buttonBox) must be last item in layout. Please move it.
379 mainLayout->addWidget(buttonBox);
380 buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
381
382 QWidget *w = new QWidget(moveDialog);
383 QVBoxLayout *wVBoxLayout = new QVBoxLayout(w);
384 wVBoxLayout->setMargin(0);
385 //TODO PORT QT5 wVBoxLayout->setSpacing( QDialog::spacingHint() );
386 Kopete::UI::MetaContactSelectorWidget *selector = new Kopete::UI::MetaContactSelectorWidget(w);
387 selector->setLabelMessage(i18n("Select the meta contact to which you want to move this contact:"));
388 // exclude this metacontact as a target metacontact for the move
389 selector->excludeMetaContact(metaContact());
390 QCheckBox *chkCreateNew = new QCheckBox(i18n("Create a new metacontact for this contact"), w);
391 wVBoxLayout->addWidget(chkCreateNew);
392 chkCreateNew->setWhatsThis(i18n("If you select this option, a new metacontact will be created in the top-level group "
393 "with the name of this contact and the contact will be moved to it."));
394 QObject::connect(chkCreateNew, SIGNAL(toggled(bool)), selector, SLOT(setDisabled(bool)));
395
396 mainLayout->addWidget(w);
397 if (moveDialog->exec() == QDialog::Accepted) {
398 Kopete::MetaContact *mc = selector->metaContact();
399 if (chkCreateNew->isChecked()) {
400 mc = new Kopete::MetaContact();
401
402 if (metaContact()) { // Add new metaContact to old groups so we don't move it to Top Level group
403 foreach (Kopete::Group *group, metaContact()->groups()) {
404 mc->addToGroup(group);
405 }
406 }
407
408 Kopete::ContactList::self()->addMetaContact(mc);
409 }
410 if (mc) {
411 setMetaContact(mc);
412 }
413 }
414
415 if (moveDialog) {
416 moveDialog->deleteLater();
417 }
418 }
419
slotMetaContactDestroyed(QObject * mc)420 void Contact::slotMetaContactDestroyed(QObject *mc)
421 {
422 if (mc != d->metaContact) {
423 return;
424 }
425
426 d->metaContact = 0;
427 }
428
setMetaContact(MetaContact * m)429 void Contact::setMetaContact(MetaContact *m)
430 {
431 MetaContact *old = d->metaContact;
432 if (old == m) { //that make no sens
433 return;
434 }
435
436 if (old) {
437 old->removeContact(this);
438 disconnect(old, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)));
439
440 if (old->contacts().isEmpty()) {
441 //remove the old metacontact. (this delete the MC)
442 ContactList::self()->removeMetaContact(old);
443 } else {
444 d->metaContact = m; //i am forced to do that now if i want the next line works
445 //remove cached data for this protocol which will not be removed since we disconnected
446 protocol()->serialize(old);
447 }
448 }
449
450 d->metaContact = m;
451 setParent(m);
452
453 if (m) {
454 m->addContact(this);
455 connect(m, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)));
456 // it is necessary to call this write here, because MetaContact::addContact() does not differentiate
457 // between adding completely new contacts (which should be written to kabc) and restoring upon restart
458 // (where no write is needed).
459 KABCPersistence::self()->write(m);
460 }
461 sync();
462 }
463
serialize(QMap<QString,QString> &,QMap<QString,QString> &)464 void Contact::serialize(QMap<QString, QString> & /*serializedData*/, QMap<QString, QString> & /* addressBookData */)
465 {
466 }
467
isReachable()468 bool Contact::isReachable()
469 {
470 // The default implementation returns false when offline and true
471 // otherwise. Subclass if you need more control over the process.
472 return onlineStatus().status() != OnlineStatus::Offline;
473 }
474
startChat()475 void Contact::startChat()
476 {
477 KopeteView *v = manager(CanCreate)->view(true, QStringLiteral("kopete_chatwindow"));
478 if (v) {
479 v->raise(true);
480 }
481 }
482
sendMessage()483 void Contact::sendMessage()
484 {
485 KopeteView *v = manager(CanCreate)->view(true, QStringLiteral("kopete_emailwindow"));
486 if (v) {
487 v->raise(true);
488 }
489 }
490
execute()491 void Contact::execute()
492 {
493 // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
494 if (account()->isConnected() && isReachable()) {
495 KopeteView *v = manager(CanCreate)->view(true, Kopete::BehaviorSettings::self()->viewPlugin());
496 if (v) {
497 v->raise(true);
498 }
499 } else {
500 KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), KMessageBox::Sorry,
501 i18n("This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
502 "until this user comes online."), i18n("User is Not Reachable"));
503 }
504 }
505
slotDelete()506 void Contact::slotDelete()
507 {
508 if (KMessageBox::warningContinueCancel(Kopete::UI::Global::mainWidget(),
509 i18n("Are you sure you want to remove the contact '%1' from your contact list?",
510 d->contactId), i18n("Remove Contact"), KGuiItem(i18n("Remove"), QLatin1String("list-remove-user")), KStandardGuiItem::cancel(),
511 QStringLiteral("askRemoveContact"), KMessageBox::Notify)
512 == KMessageBox::Continue) {
513 Kopete::DeleteContactTask *deleteTask = new Kopete::DeleteContactTask(this);
514 deleteTask->start();
515 }
516 }
517
deleteContact()518 void Contact::deleteContact()
519 {
520 // Default implementation simply deletes the contact
521 deleteLater();
522 }
523
metaContact() const524 MetaContact *Contact::metaContact() const
525 {
526 return d->metaContact;
527 }
528
contactId() const529 QString Contact::contactId() const
530 {
531 return d->contactId;
532 }
533
protocol() const534 Protocol *Contact::protocol() const
535 {
536 return d->account ? d->account->protocol() : 0L;
537 }
538
account() const539 Account *Contact::account() const
540 {
541 return d->account;
542 }
543
sync(unsigned int)544 void Contact::sync(unsigned int)
545 {
546 /* Default implementation does nothing */
547 }
548
icon() const549 QString &Contact::icon() const
550 {
551 return d->icon;
552 }
553
setIcon(const QString & icon)554 void Contact::setIcon(const QString &icon)
555 {
556 d->icon = icon;
557 }
558
customContextMenuActions()559 QList<QAction *> *Contact::customContextMenuActions()
560 {
561 return nullptr;
562 }
563
customContextMenuActions(ChatSession *)564 QList<QAction *> *Contact::customContextMenuActions(ChatSession * /* manager */)
565 {
566 return customContextMenuActions();
567 }
568
isOnline() const569 bool Contact::isOnline() const
570 {
571 return onlineStatus().isDefinitelyOnline();
572 }
573
isFileCapable() const574 bool Contact::isFileCapable() const
575 {
576 return d->fileCapable;
577 }
578
setFileCapable(bool filecap)579 void Contact::setFileCapable(bool filecap)
580 {
581 if (d->fileCapable != filecap) {
582 d->fileCapable = filecap;
583 emit canAcceptFilesChanged();
584 }
585 }
586
canAcceptFiles() const587 bool Contact::canAcceptFiles() const
588 {
589 return isOnline() && d->fileCapable;
590 }
591
idleTime() const592 unsigned long int Contact::idleTime() const
593 {
594 if (d->idleTime == 0) {
595 return 0;
596 }
597
598 return d->idleTime+(d->idleTimer.elapsed()/1000);
599 }
600
setIdleTime(unsigned long int t)601 void Contact::setIdleTime(unsigned long int t)
602 {
603 bool idleChanged = false;
604 if (d->idleTime != t) {
605 idleChanged = true;
606 }
607 d->idleTime = t;
608 if (t > 0) {
609 d->idleTimer.start();
610 }
611 //FIXME: if t == 0, idleTime() will now return garbage
612 // else
613 // d->idleTimer.stop();
614 if (idleChanged) {
615 emit idleStateChanged(this);
616 }
617 }
618
toolTip() const619 QString Contact::toolTip() const
620 {
621 Kopete::Property p;
622 QString tip;
623 const QStringList shownProps = Kopete::AppearanceSettings::self()->toolTipContents();
624
625 // --------------------------------------------------------------------------
626 // Fixed part of tooltip
627
628 QString iconName;
629 if (this == account()->myself()) {
630 iconName = QStringLiteral("kopete-account-icon:%1:%2")
631 .arg(QString(QUrl::toPercentEncoding(protocol()->pluginId())),
632 QString(QUrl::toPercentEncoding(account()->accountId())));
633 } else {
634 iconName = QStringLiteral("kopete-contact-icon:%1:%2:%3")
635 .arg(QString(QUrl::toPercentEncoding(protocol()->pluginId())),
636 QString(QUrl::toPercentEncoding(account()->accountId())),
637 QString(QUrl::toPercentEncoding(contactId())));
638 }
639
640 QString nick = displayName();
641 if (nick == contactId()) {
642 tip = i18nc("@label:textbox %3 is contact-display-name, %1 is its status",
643 "<b><nobr>%3</nobr></b><br /><img src=\"%2\"> %1",
644 Kopete::Message::escape(onlineStatus().description()), iconName,
645 Kopete::Message::escape(d->contactId));
646 } else {
647 tip = i18nc("@label:textbox %4 is contact-display-name, %3 is contact-id, %1 is its status",
648 "<nobr><b>%4</b> (%3)</nobr><br /><img src=\"%2\"> %1",
649 Kopete::Message::escape(onlineStatus().description()), iconName,
650 Kopete::Message::escape(contactId()),
651 Kopete::Emoticons::parseEmoticons(Kopete::Message::escape(nick)));
652 }
653
654 // --------------------------------------------------------------------------
655 // Configurable part of tooltip
656
657 // FIXME: It shouldn't use QString to identity the properties. Instead it should use PropertyTmpl::key()
658 for (QStringList::ConstIterator it = shownProps.constBegin(); it != shownProps.constEnd(); ++it) {
659 if ((*it) == Kopete::Global::Properties::self()->fullName().key()) {
660 const QString name = formattedName();
661 if (!name.isEmpty()) {
662 tip += i18nc("@label:textbox formatted name",
663 "<br /><b>Full Name:</b> <nobr>%1</nobr>", name.toHtmlEscaped());
664 }
665 } else if ((*it) == Kopete::Global::Properties::self()->idleTime().key()) {
666 const QString time = formattedIdleTime();
667 if (!time.isEmpty()) {
668 tip += i18nc("@label:textbox formatted idle time",
669 "<br /><b>Idle:</b> <nobr>%1</nobr>", time);
670 }
671 } else if ((*it) == QLatin1String("homePage")) {
672 const QString url = property(*it).value().toString();
673 if (!url.isEmpty()) {
674 tip += i18nc("@label:textbox formatted url",
675 "<br /><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>",
676 QString(QUrl::toPercentEncoding(url)), Kopete::Message::escape(url.toHtmlEscaped()));
677 }
678 } else if ((*it) == Kopete::Global::Properties::self()->statusTitle().key()) {
679 const QString statusTitle = property(*it).value().toString();
680 if (!statusTitle.isEmpty()) {
681 tip += i18nc("@label:textbox formatted status title",
682 "<br /><b>Status Title:</b> %1", Kopete::Emoticons::parseEmoticons(Kopete::Message::escape(statusTitle)));
683 }
684 } else if ((*it) == Kopete::Global::Properties::self()->statusMessage().key()) {
685 const QString statusmsg = property(*it).value().toString();
686 if (!statusmsg.isEmpty()) {
687 tip += i18nc("@label:textbox formatted status message",
688 "<br /><b>Status Message:</b> %1", Kopete::Emoticons::parseEmoticons(Kopete::Message::escape(statusmsg)));
689 }
690 } else {
691 p = property(*it);
692 if (!p.isNull()) {
693 QVariant val = p.value();
694 QString valueText;
695
696 switch (val.type()) {
697 case QMetaType::QDateTime:
698 valueText = QLocale().toString(val.toDateTime());
699 valueText = Kopete::Message::escape(valueText);
700 break;
701 case QMetaType::QDate:
702 valueText = QLocale().toString(val.toDate());
703 valueText = Kopete::Message::escape(valueText);
704 break;
705 case QMetaType::QTime:
706 valueText = QLocale().toString(val.toTime());
707 valueText = Kopete::Message::escape(valueText);
708 break;
709 default:
710 if (p.isRichText()) {
711 valueText = val.toString();
712 } else {
713 valueText = Kopete::Message::escape(val.toString());
714 }
715 }
716
717 if (valueText.size() > 1000) {
718 valueText.truncate(997);
719 valueText += QLatin1String("...");
720 }
721
722 tip += i18nc("@label:textbox property label %2 is name, %1 is value",
723 "<br /><nobr><b>%2:</b></nobr> %1",
724 valueText, p.tmpl().label().toHtmlEscaped());
725 }
726 }
727 }
728
729 return tip;
730 }
731
formattedName() const732 QString Kopete::Contact::formattedName() const
733 {
734 if (hasProperty(Kopete::Global::Properties::self()->fullName().key())) {
735 return property(Kopete::Global::Properties::self()->fullName()).value().toString();
736 }
737
738 QString ret;
739 Kopete::Property first, last;
740
741 first = property(Kopete::Global::Properties::self()->firstName());
742 last = property(Kopete::Global::Properties::self()->lastName());
743 if (!first.isNull()) {
744 if (!last.isNull()) { // contact has both first and last name
745 ret = i18nc("firstName lastName", "%2 %1",
746 last.value().toString(),
747 first.value().toString());
748 } else { // only first name set
749 ret = first.value().toString();
750 }
751 } else if (!last.isNull()) { // only last name set
752 ret = last.value().toString();
753 }
754
755 return ret;
756 }
757
formattedIdleTime() const758 QString Kopete::Contact::formattedIdleTime() const
759 {
760 QString ret;
761 unsigned long int leftTime = idleTime();
762
763 if (leftTime > 0) { // FIXME: duplicated from code in kopetecontact listview.cpp
764 unsigned long int days, hours, mins, secs;
765
766 days = leftTime / (60*60*24);
767 leftTime = leftTime % (60*60*24);
768 hours = leftTime / (60*60);
769 leftTime = leftTime % (60*60);
770 mins = leftTime / 60;
771 secs = leftTime % 60;
772
773 if (days != 0) {
774 ret = i18nc("<days>d <hours>h <minutes>m <seconds>s",
775 "%4d %3h %2m %1s",
776 secs,
777 mins,
778 hours,
779 days);
780 } else if (hours != 0) {
781 ret = i18nc("<hours>h <minutes>m <seconds>s", "%3h %2m %1s",
782 secs,
783 mins,
784 hours);
785 } else {
786 // xgettext: no-c-format
787 ret = i18nc("<minutes>m <seconds>s", "%2m %1s",
788 secs,
789 mins);
790 }
791 }
792 return ret;
793 }
794
slotBlock()795 void Kopete::Contact::slotBlock()
796 {
797 account()->block(d->contactId);
798 }
799
slotUnblock()800 void Kopete::Contact::slotUnblock()
801 {
802 account()->unblock(d->contactId);
803 }
804
setNickName(const QString & name)805 void Kopete::Contact::setNickName(const QString &name)
806 {
807 setProperty(Kopete::Global::Properties::self()->nickName(), name);
808 }
809
nickName() const810 QString Kopete::Contact::nickName() const
811 {
812 const QString nick = property(Kopete::Global::Properties::self()->nickName()).value().toString();
813 if (!nick.isEmpty()) {
814 return nick;
815 }
816
817 return contactId();
818 }
819
setCustomName(const QString & name)820 void Kopete::Contact::setCustomName(const QString &name)
821 {
822 setProperty(Kopete::Global::Properties::self()->customName(), name);
823 }
824
customName() const825 QString Kopete::Contact::customName() const
826 {
827 const QString name = property(Kopete::Global::Properties::self()->customName()).value().toString();
828 if (!name.isEmpty()) {
829 return name;
830 }
831 return nickName();
832 }
833
setPhoto(const QString & photoPath)834 void Kopete::Contact::setPhoto(const QString &photoPath)
835 {
836 setProperty(Kopete::Global::Properties::self()->photo(), photoPath);
837 }
838
slotPropertyChanged(Kopete::PropertyContainer *,const QString & key,const QVariant &,const QVariant &)839 void Kopete::Contact::slotPropertyChanged(Kopete::PropertyContainer *, const QString &key, const QVariant &, const QVariant &)
840 {
841 if (key != Kopete::Global::Properties::self()->customName().key()
842 && key != Kopete::Global::Properties::self()->fullName().key()
843 && key != Kopete::Global::Properties::self()->firstName().key()
844 && key != Kopete::Global::Properties::self()->lastName().key()
845 && key != Kopete::Global::Properties::self()->nickName().key()) {
846 return;
847 }
848
849 const QString oldName = d->oldName;
850 const QString newName = displayName();
851 if (oldName != newName) {
852 d->oldName = newName;
853 emit displayNameChanged(oldName, newName);
854 }
855 }
856
setPreferredNameType(Kopete::Contact::NameType preferredNameType)857 void Kopete::Contact::setPreferredNameType(Kopete::Contact::NameType preferredNameType)
858 {
859 if (d->preferredNameType != preferredNameType) {
860 const QString oldName = displayName();
861 d->preferredNameType = preferredNameType;
862 const QString newName = displayName();
863 if (oldName != newName) {
864 d->oldName = newName;
865 emit displayNameChanged(oldName, newName);
866 }
867 }
868 }
869
preferredNameType() const870 Kopete::Contact::NameType Kopete::Contact::preferredNameType() const
871 {
872 return d->preferredNameType;
873 }
874
displayName() const875 QString Kopete::Contact::displayName() const
876 {
877 QString name;
878 switch (d->preferredNameType) {
879 case NickName:
880 name = nickName();
881 break;
882 case FormattedName:
883 name = formattedName();
884 break;
885 case ContactId:
886 name = contactId();
887 break;
888 case CustomName:
889 default: // fallback to custom name
890 name = customName();
891 break;
892 }
893 if (name.isEmpty()) {
894 return contactId();
895 } else {
896 return name;
897 }
898 }
899 } //END namespace Kopete
900