2
kopetecontact.cpp - Kopete Contact
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>
8
Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
10
*************************************************************************
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. *
17
*************************************************************************
20
#include "kopetecontact.h"
22
#include <QApplication>
23
#include <QTextDocument>
28
#include <kdeversion.h>
29
#include <kinputdialog.h>
31
#include <kabcpersistence.h>
36
#include <kmessagebox.h>
37
#include <k3listviewsearchline.h>
39
#include "kopetecontactlist.h"
40
#include "kopeteglobal.h"
41
#include "kopeteuiglobal.h"
42
#include "kopeteprotocol.h"
43
#include "kopeteaccount.h"
44
#include "kopetestdaction.h"
45
#include "kopetechatsession.h"
46
#include "kopeteview.h"
47
#include "kopetemetacontact.h"
48
#include "kopeteappearancesettings.h"
49
#include "kopetebehaviorsettings.h"
50
#include "metacontactselectorwidget.h"
51
#include "kopeteemoticons.h"
52
#include "kopetestatusmessage.h"
53
#include "kopeteinfodialog.h"
54
#include "kopetedeletecontacttask.h"
56
//For the moving to another metacontact dialog
61
#include <k3listview.h>
62
#include <qcheckbox.h>
67
class Contact::Private
72
OnlineStatus onlineStatus;
75
MetaContact *metaContact;
81
unsigned long int idleTime;
83
Kopete::StatusMessage statusMessage;
84
KToggleAction* toggleAlwaysVisibleAction;
87
Contact::Contact( Account *account, const QString &contactId,
88
MetaContact *parent, const QString &icon )
89
: ContactListElement( parent ), d(new Private())
91
//kDebug( 14010 ) << "Creating contact with id " << contactId;
93
d->contactId = contactId;
94
d->metaContact = parent;
95
connect( d->metaContact, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)) );
97
d->fileCapable = false;
102
bool duplicate = false;
103
// If can happend that a MetaContact may be used without a account
104
// (ex: for unit tests or chat window style preview)
107
// Don't register myself contacts because otherwise we can't have own contact in contact list.
108
if ( d->metaContact != Kopete::ContactList::self()->myself() )
109
duplicate = !account->registerContact( this );
111
connect( account, SIGNAL(isConnectedChanged()), SLOT(slotAccountIsConnectedChanged()) );
114
// Need to check this because myself() may have no parent
115
// Maybe too the metaContact doesn't have a valid protocol()
116
// (ex: for unit tests or chat window style preview)
118
// if alreadyRegistered is true (which mean that this is duplicate contact) we will not add
119
// parent and the contact will die out on next Kopete restart.
120
if( !duplicate && parent && protocol() )
121
parent->addContact( this );
127
emit( contactDestroyed( this ) );
133
OnlineStatus Contact::onlineStatus() const
135
if ( this == account()->myself() || account()->isConnected() )
136
return d->onlineStatus;
138
return protocol()->accountOfflineStatus();
141
void Contact::setOnlineStatus( const OnlineStatus &status )
143
if( status == d->onlineStatus )
146
const bool oldCanAcceptFiles = canAcceptFiles();
147
OnlineStatus oldStatus = d->onlineStatus;
148
d->onlineStatus = status;
150
Kopete::Global::Properties *globalProps = Kopete::Global::Properties::self();
152
// Contact changed from Offline to another known online status
153
if( oldStatus.status() == OnlineStatus::Offline &&
154
status.status() != OnlineStatus::Unknown &&
155
status.status() != OnlineStatus::Offline )
157
if ( !hasProperty( globalProps->onlineSince().key() ) )
158
setProperty( globalProps->onlineSince(), QDateTime::currentDateTime() );
159
// kDebug(14010) << "REMOVING lastSeen property for " << nickName();
160
removeProperty( globalProps->lastSeen() );
162
else if( oldStatus.status() != OnlineStatus::Offline &&
163
oldStatus.status() != OnlineStatus::Unknown &&
164
status.status() == OnlineStatus::Offline ) // Contact went back offline
166
removeProperty( globalProps->onlineSince() );
167
// kDebug(14010) << "SETTING lastSeen property for " << nickName();
168
setProperty( globalProps->lastSeen(), QDateTime::currentDateTime() );
171
if ( this == account()->myself() || account()->isConnected() )
172
emit onlineStatusChanged( this, status, oldStatus );
174
if ( oldCanAcceptFiles != canAcceptFiles() )
175
emit canAcceptFilesChanged();
178
Kopete::StatusMessage Contact::statusMessage() const
180
return d->statusMessage;
183
void Contact::setStatusMessage( const Kopete::StatusMessage &statusMessage )
185
bool emitUpdate = true;
187
if ( d->statusMessage.title() == statusMessage.title() && d->statusMessage.message() == statusMessage.message() )
190
d->statusMessage = statusMessage;
192
kDebug(14010) << "Setting up the status title property with this: " << statusMessage.title();
193
if( !statusMessage.title().isEmpty() )
194
setProperty( Kopete::Global::Properties::self()->statusTitle(), statusMessage.title() );
196
removeProperty( Kopete::Global::Properties::self()->statusTitle() );
198
kDebug(14010) << "Setting up the status message property with this: " << statusMessage.message();
199
if( !statusMessage.message().isEmpty() )
200
setProperty( Kopete::Global::Properties::self()->statusMessage(), statusMessage.message() );
202
removeProperty( Kopete::Global::Properties::self()->statusMessage() );
205
emit statusMessageChanged( this );
208
void Contact::slotAccountIsConnectedChanged()
210
if ( this == account()->myself() )
213
if ( account()->isConnected() )
214
emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() );
216
emit onlineStatusChanged( this, protocol()->accountOfflineStatus(), d->onlineStatus );
220
void Contact::sendFile( const KUrl &, const QString &, uint )
222
kWarning( 14010 ) << "Plugin "
223
<< protocol()->pluginId() << " has enabled file sending, "
224
<< "but didn't implement it!" << endl;
227
void Contact::slotAddContact()
231
metaContact()->setTemporary( false );
232
ContactList::self()->addMetaContact( metaContact() );
236
KMenu* Contact::popupMenu( ChatSession *manager )
238
KMenu *menu = new KMenu();
241
const QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
243
titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
245
titleText = QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick, contactId(), onlineStatus().description() );
246
menu->addTitle( titleText );
248
if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() )
250
KAction *actionAddContact = new KAction( KIcon("list-add-user"), i18n( "&Add to Your Contact List" ), menu );
251
connect( actionAddContact, SIGNAL(triggered(bool)), this, SLOT(slotAddContact()) );
253
menu->addAction(actionAddContact);
254
menu->addSeparator();
257
// FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
258
const bool reach = account()->isConnected() && isReachable();
259
const bool myself = (this == account()->myself());
261
KAction *actionSendMessage = KopeteStdAction::sendMessage( this, SLOT(sendMessage()), menu );
262
actionSendMessage->setEnabled( reach && !myself );
263
menu->addAction( actionSendMessage );
265
KAction *actionChat = KopeteStdAction::chat( this, SLOT(startChat()), menu );
266
actionChat->setEnabled( reach && !myself );
267
menu->addAction( actionChat );
269
KAction *actionSendFile = KopeteStdAction::sendFile( this, SLOT(sendFile()), menu );
270
actionSendFile->setEnabled( reach && d->fileCapable && !myself );
271
menu->addAction( actionSendFile );
273
// Protocol specific options will go below this separator
274
// through the use of the customContextMenuActions() function
276
// Get the custom actions from the protocols ( pure virtual function )
277
QList<KAction*> *customActions = customContextMenuActions();
278
if( customActions && !customActions->isEmpty() )
280
menu->addSeparator();
281
QList<KAction*>::iterator it, itEnd = customActions->end();
282
for( it = customActions->begin(); it != itEnd; ++it )
283
menu->addAction( (*it) );
285
delete customActions;
287
menu->addSeparator();
289
if( metaContact() && !metaContact()->isTemporary() )
291
KAction* changeMetaContact = KopeteStdAction::changeMetaContact( this, SLOT(changeMetaContact()), menu );
292
menu->addAction( changeMetaContact );
294
d->toggleAlwaysVisibleAction = new KToggleAction( i18n( "Visible when offline" ), menu );
295
d->toggleAlwaysVisibleAction->setChecked( property( Kopete::Global::Properties::self()->isAlwaysVisible() ).value().toBool() );
296
menu->addAction( d->toggleAlwaysVisibleAction );
297
connect( d->toggleAlwaysVisibleAction, SIGNAL(toggled(bool)), this, SLOT(toggleAlwaysVisible()) );
300
menu->addAction( KopeteStdAction::contactInfo( this, SLOT(slotUserInfo()), menu ) );
302
#if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11
303
if ( account()->isBlocked( d->contactId ) )
304
KopeteStdAction::unblockContact( this, SLOT(slotUnblock()), menu, "actionUnblockContact" )->plug( menu );
306
KopeteStdAction::blockContact( this, SLOT(slotBlock()), menu, "actionBlockContact" )->plug( menu );
309
if( metaContact() && !metaContact()->isTemporary() )
310
menu->addAction( KopeteStdAction::deleteContact( this, SLOT(slotDelete()), menu ) );
315
void Contact::toggleAlwaysVisible()
317
bool alwaysVisible = property( Kopete::Global::Properties::self()->isAlwaysVisible() ).value().toBool();
318
setProperty( Kopete::Global::Properties::self()->isAlwaysVisible(), !alwaysVisible );
319
d->toggleAlwaysVisibleAction->setChecked( !alwaysVisible );
322
void Contact::changeMetaContact()
324
QPointer <KDialog> moveDialog = new KDialog( Kopete::UI::Global::mainWidget() );
325
moveDialog->setCaption( i18n( "Move Contact" ) );
326
moveDialog->setButtons( KDialog::Ok | KDialog::Cancel );
327
moveDialog->setDefaultButton( KDialog::Ok );
328
moveDialog->showButtonSeparator( true );
330
KVBox *w = new KVBox( moveDialog );
331
w->setSpacing( KDialog::spacingHint() );
332
Kopete::UI::MetaContactSelectorWidget *selector = new Kopete::UI::MetaContactSelectorWidget(w);
333
selector->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" ));
334
// exclude this metacontact as a target metacontact for the move
335
selector->excludeMetaContact( metaContact() );
336
QCheckBox *chkCreateNew = new QCheckBox( i18n( "Create a new metacontact for this contact" ), w );
337
chkCreateNew ->setWhatsThis( i18n( "If you select this option, a new metacontact will be created in the top-level group "
338
"with the name of this contact and the contact will be moved to it." ) );
339
QObject::connect( chkCreateNew , SIGNAL(toggled(bool)) , selector , SLOT (setDisabled(bool)) ) ;
341
moveDialog->setMainWidget(w);
342
if( moveDialog->exec() == QDialog::Accepted )
344
Kopete::MetaContact *mc = selector->metaContact();
345
if(chkCreateNew->isChecked())
347
mc=new Kopete::MetaContact();
350
{ // Add new metaContact to old groups so we don't move it to Top Level group
351
foreach ( Kopete::Group* group, metaContact()->groups() )
352
mc->addToGroup( group );
355
Kopete::ContactList::self()->addMetaContact(mc);
359
setMetaContact( mc );
364
moveDialog->deleteLater();
367
void Contact::slotMetaContactDestroyed( QObject* mc )
369
if (mc != d->metaContact)
375
void Contact::setMetaContact( MetaContact *m )
377
MetaContact *old = d->metaContact;
378
if(old==m) //that make no sens
383
old->removeContact( this );
384
disconnect( old, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)) );
386
if(old->contacts().isEmpty())
388
//remove the old metacontact. (this delete the MC)
389
ContactList::self()->removeMetaContact(old);
393
d->metaContact = m; //i am forced to do that now if i want the next line works
394
//remove cached data for this protocol which will not be removed since we disconnected
395
protocol()->serialize( old );
404
m->addContact( this );
405
connect( m, SIGNAL(destroyed(QObject*)), this, SLOT(slotMetaContactDestroyed(QObject*)) );
406
// it is necessary to call this write here, because MetaContact::addContact() does not differentiate
407
// between adding completely new contacts (which should be written to kabc) and restoring upon restart
408
// (where no write is needed).
409
KABCPersistence::self()->write( m );
414
void Contact::serialize( QMap<QString, QString> &/*serializedData*/,
415
QMap<QString, QString> & /* addressBookData */ )
419
bool Contact::isReachable()
421
// The default implementation returns false when offline and true
422
// otherwise. Subclass if you need more control over the process.
423
return onlineStatus().status() != OnlineStatus::Offline;
427
void Contact::startChat()
429
KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_chatwindow") );
434
void Contact::sendMessage()
436
KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") );
441
void Contact::execute()
443
// FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
444
if ( account()->isConnected() && isReachable() )
446
KopeteView *v=manager( CanCreate )->view(true, Kopete::BehaviorSettings::self()->viewPlugin() );
452
KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry,
453
i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
454
"until this user comes online." ), i18n( "User is Not Reachable" ) );
458
void Contact::slotDelete()
460
if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
461
i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ,
462
d->contactId ), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("list-remove-user") ), KStandardGuiItem::cancel(),
463
QString::fromLatin1("askRemoveContact"), KMessageBox::Notify | KMessageBox::Dangerous )
464
== KMessageBox::Continue )
466
Kopete::DeleteContactTask *deleteTask = new Kopete::DeleteContactTask(this);
471
void Contact::deleteContact()
473
// Default implementation simply deletes the contact
478
MetaContact * Contact::metaContact() const
480
return d->metaContact;
483
QString Contact::contactId() const
488
Protocol * Contact::protocol() const
490
return d->account ? d->account->protocol() : 0L;
493
Account * Contact::account() const
500
void Contact::sync(unsigned int)
502
/* Default implementation does nothing */
505
QString& Contact::icon() const
510
void Contact::setIcon( const QString& icon )
516
QList<KAction *> *Contact::customContextMenuActions()
521
bool Contact::isOnline() const
523
return onlineStatus().isDefinitelyOnline();
526
bool Contact::isFileCapable() const
528
return d->fileCapable;
531
void Contact::setFileCapable( bool filecap )
533
if ( d->fileCapable != filecap )
535
d->fileCapable = filecap;
536
emit canAcceptFilesChanged();
540
bool Contact::canAcceptFiles() const
542
return isOnline() && d->fileCapable;
545
unsigned long int Contact::idleTime() const
550
return d->idleTime+(d->idleTimer.elapsed()/1000);
553
void Contact::setIdleTime( unsigned long int t )
555
bool idleChanged = false;
560
d->idleTimer.start();
561
//FIXME: if t == 0, idleTime() will now return garbage
563
// d->idleTimer.stop();
565
emit idleStateChanged(this);
568
QString Contact::toolTip() const
572
const QStringList shownProps = Kopete::AppearanceSettings::self()->toolTipContents();
574
// --------------------------------------------------------------------------
575
// Fixed part of tooltip
578
if ( this == account()->myself() )
580
iconName = QString::fromLatin1("kopete-account-icon:%1:%2")
581
.arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
582
QString(QUrl::toPercentEncoding( account()->accountId() )) );
587
iconName = QString::fromLatin1("kopete-contact-icon:%1:%2:%3")
588
.arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
589
QString(QUrl::toPercentEncoding( account()->accountId() )),
590
QString(QUrl::toPercentEncoding( contactId() )) );
593
// TODO: the nickname should be a configurable properties, like others. -Olivier
594
QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
595
if ( nick.isEmpty() )
597
tip = i18nc( "@label:textbox %3 is contact-display-name, %1 is its status",
598
"<b><nobr>%3</nobr></b><br /><img src=\"%2\"> %1",
599
Kopete::Message::escape( onlineStatus().description() ), iconName,
600
Kopete::Message::escape( d->contactId ) );
604
tip = i18nc( "@label:textbox %4 is contact-display-name, %3 is contact-id, %1 is its status",
605
"<nobr><b>%4</b> (%3)</nobr><br /><img src=\"%2\"> %1",
606
Kopete::Message::escape( onlineStatus().description() ), iconName,
607
Kopete::Message::escape( contactId() ),
608
Kopete::Emoticons::parseEmoticons( Kopete::Message::escape( nick ) ) );
611
// --------------------------------------------------------------------------
612
// Configurable part of tooltip
614
// FIXME: It shouldn't use QString to identity the properties. Instead it should use PropertyTmpl::key()
615
for(QStringList::ConstIterator it=shownProps.constBegin(); it!=shownProps.constEnd(); ++it)
617
if((*it) == Kopete::Global::Properties::self()->fullName().key())
619
const QString name = formattedName();
622
tip += i18nc("@label:textbox formatted name",
623
"<br /><b>Full Name:</b> <nobr>%1</nobr>", Qt::escape(name));
626
else if ((*it) == Kopete::Global::Properties::self()->idleTime().key())
628
const QString time = formattedIdleTime();
631
tip += i18nc("@label:textbox formatted idle time",
632
"<br /><b>Idle:</b> <nobr>%1</nobr>", time);
635
else if ((*it) == QString::fromLatin1("homePage"))
637
const QString url = property(*it).value().toString();
640
tip += i18nc("@label:textbox formatted url",
641
"<br /><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>",
642
QString(QUrl::toPercentEncoding( url )), Kopete::Message::escape( Qt::escape(url) ) );
645
else if ((*it) == Kopete::Global::Properties::self()->statusTitle().key() )
647
const QString statusTitle = property(*it).value().toString();
648
if(!statusTitle.isEmpty())
650
tip += i18nc("@label:textbox formatted status title",
651
"<br /><b>Status Title:</b> %1", Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(statusTitle) ) );
654
else if ((*it) == Kopete::Global::Properties::self()->statusMessage().key() )
656
const QString statusmsg = property(*it).value().toString();
657
if(!statusmsg.isEmpty())
659
tip += i18nc("@label:textbox formatted status message",
660
"<br /><b>Status Message:</b> %1", Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(statusmsg) ) );
668
QVariant val = p.value();
673
case QVariant::DateTime:
674
valueText = KGlobal::locale()->formatDateTime(val.toDateTime());
675
valueText = Kopete::Message::escape( valueText );
678
valueText = KGlobal::locale()->formatDate(val.toDate());
679
valueText = Kopete::Message::escape( valueText );
682
valueText = KGlobal::locale()->formatTime(val.toTime());
683
valueText = Kopete::Message::escape( valueText );
688
valueText = val.toString();
692
valueText = Kopete::Message::escape( val.toString() );
696
if (valueText.size() > 1000) {
697
valueText.truncate(997);
701
tip += i18nc("@label:textbox property label %2 is name, %1 is value",
702
"<br /><nobr><b>%2:</b></nobr> %1",
703
valueText, Qt::escape(p.tmpl().label()) );
711
QString Kopete::Contact::formattedName() const
713
if( hasProperty( Kopete::Global::Properties::self()->fullName().key() ) )
714
return property( Kopete::Global::Properties::self()->fullName() ).value().toString();
717
Kopete::Property first, last;
719
first = property( Kopete::Global::Properties::self()->firstName() );
720
last = property( Kopete::Global::Properties::self()->lastName() );
723
if(!last.isNull()) // contact has both first and last name
725
ret = i18nc("firstName lastName", "%2 %1",
726
last.value().toString(),
727
first.value().toString());
729
else // only first name set
731
ret = first.value().toString();
734
else if(!last.isNull()) // only last name set
736
ret = last.value().toString();
742
QString Kopete::Contact::formattedIdleTime() const
745
unsigned long int leftTime = idleTime();
748
{ // FIXME: duplicated from code in kopetecontact listview.cpp
749
unsigned long int days, hours, mins, secs;
751
days = leftTime / ( 60*60*24 );
752
leftTime = leftTime % ( 60*60*24 );
753
hours = leftTime / ( 60*60 );
754
leftTime = leftTime % ( 60*60 );
755
mins = leftTime / 60;
756
secs = leftTime % 60;
760
ret = i18nc( "<days>d <hours>h <minutes>m <seconds>s",
767
else if ( hours != 0 )
769
ret = i18nc( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ,
776
// xgettext: no-c-format
777
ret = i18nc( "<minutes>m <seconds>s", "%2m %1s" ,
785
void Kopete::Contact::slotBlock()
787
account()->block( d->contactId );
790
void Kopete::Contact::slotUnblock()
792
account()->unblock( d->contactId );
795
void Kopete::Contact::setNickName( const QString &name )
797
const QString oldNickName = nickName();
798
setProperty( Kopete::Global::Properties::self()->nickName(), name );
801
QString Kopete::Contact::nickName() const
803
const QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
804
if( !nick.isEmpty() )
810
void Kopete::Contact::setPhoto(const QString &photoPath)
812
setProperty( Kopete::Global::Properties::self()->photo(), photoPath );
816
} //END namespace Kopete
818
#include "kopetecontact.moc"