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>
38 #include <kemoticons.h>
40 #include "kopetecontactlist.h"
41 #include "kopeteglobal.h"
42 #include "kopeteuiglobal.h"
43 #include "kopeteprotocol.h"
44 #include "kopeteaccount.h"
45 #include "kopetestdaction.h"
46 #include "kopetechatsession.h"
47 #include "kopeteview.h"
48 #include "kopetemetacontact.h"
49 #include "kopeteappearancesettings.h"
50 #include "kopetebehaviorsettings.h"
51 #include "metacontactselectorwidget.h"
52 #include "kopeteemoticons.h"
53 #include "kopetestatusmessage.h"
54 #include "kopeteinfodialog.h"
55 #include "kopetedeletecontacttask.h"
57 //For the moving to another metacontact dialog
62 #include <k3listview.h>
63 #include <qcheckbox.h>
68 class Contact::Private
73 OnlineStatus onlineStatus
;
76 MetaContact
*metaContact
;
82 unsigned long int idleTime
;
84 Kopete::StatusMessage statusMessage
;
87 Contact::Contact( Account
*account
, const QString
&contactId
,
88 MetaContact
*parent
, const QString
&icon
)
89 : ContactListElement( parent
)
93 //kDebug( 14010 ) << "Creating contact with id " << contactId;
95 d
->contactId
= contactId
;
96 d
->metaContact
= parent
;
97 d
->fileCapable
= false;
102 // If can happend that a MetaContact may be used without a account
103 // (ex: for unit tests or chat window style preview)
106 account
->registerContact( this );
107 connect( account
, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) );
110 // Need to check this because myself() may have no parent
111 // Maybe too the metaContact doesn't have a valid protocol()
112 // (ex: for unit tests or chat window style preview)
113 if( parent
&& protocol() )
115 connect( parent
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
116 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
118 parent
->addContact( this );
125 emit( contactDestroyed( this ) );
131 OnlineStatus
Contact::onlineStatus() const
133 if ( this == account()->myself() || account()->isConnected() )
134 return d
->onlineStatus
;
136 return protocol()->accountOfflineStatus();
139 void Contact::setOnlineStatus( const OnlineStatus
&status
)
141 if( status
== d
->onlineStatus
)
144 OnlineStatus oldStatus
= d
->onlineStatus
;
145 d
->onlineStatus
= status
;
147 Kopete::Global::Properties
*globalProps
= Kopete::Global::Properties::self();
149 // Contact changed from Offline to another status
150 if( oldStatus
.status() == OnlineStatus::Offline
&&
151 status
.status() != OnlineStatus::Offline
)
153 setProperty( globalProps
->onlineSince(), QDateTime::currentDateTime() );
154 /*kDebug(14010) << "REMOVING lastSeen property for " <<
155 d->displayName << endl;*/
156 removeProperty( globalProps
->lastSeen() );
158 else if( oldStatus
.status() != OnlineStatus::Offline
&&
159 oldStatus
.status() != OnlineStatus::Unknown
&&
160 status
.status() == OnlineStatus::Offline
) // Contact went back offline
162 removeProperty( globalProps
->onlineSince() );
163 /*kDebug(14010) << "SETTING lastSeen property for " <<
164 d->displayName << endl;*/
165 setProperty( globalProps
->lastSeen(), QDateTime::currentDateTime() );
168 if ( this == account()->myself() || account()->isConnected() )
169 emit
onlineStatusChanged( this, status
, oldStatus
);
172 Kopete::StatusMessage
Contact::statusMessage() const
174 return d
->statusMessage
;
177 void Contact::setStatusMessage( const Kopete::StatusMessage
&statusMessage
)
179 d
->statusMessage
= statusMessage
;
181 kDebug(14010) << "Setting up the status title property with this: " << statusMessage
.title();
182 if( !statusMessage
.title().isEmpty() )
183 setProperty( Kopete::Global::Properties::self()->statusTitle(), statusMessage
.title() );
185 removeProperty( Kopete::Global::Properties::self()->statusTitle() );
187 kDebug(14010) << "Setting up the status message property with this: " << statusMessage
.message();
188 if( !statusMessage
.message().isEmpty() )
189 setProperty( Kopete::Global::Properties::self()->statusMessage(), statusMessage
.message() );
191 removeProperty( Kopete::Global::Properties::self()->statusMessage() );
194 void Contact::slotAccountIsConnectedChanged()
196 if ( this == account()->myself() )
199 if ( account()->isConnected() )
200 emit
onlineStatusChanged( this, d
->onlineStatus
, protocol()->accountOfflineStatus() );
202 emit
onlineStatusChanged( this, protocol()->accountOfflineStatus(), d
->onlineStatus
);
206 void Contact::sendFile( const KUrl
&, const QString
&, uint
)
208 kWarning( 14010 ) << "Plugin "
209 << protocol()->pluginId() << " has enabled file sending, "
210 << "but didn't implement it!" << endl
;
213 void Contact::slotAddContact()
217 metaContact()->setTemporary( false );
218 ContactList::self()->addMetaContact( metaContact() );
222 KMenu
* Contact::popupMenu( ChatSession
*manager
)
224 KMenu
*menu
= new KMenu();
227 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
229 titleText
= QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
231 titleText
= QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick
, contactId(), onlineStatus().description() );
232 menu
->addTitle( titleText
);
234 if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() )
236 KAction
*actionAddContact
= new KAction( KIcon("add-user"), i18n( "&Add to Your Contact List" ), menu
);
237 connect( actionAddContact
, SIGNAL(triggered(bool)), this, SLOT( slotAddContact() ) );
239 menu
->addAction(actionAddContact
);
240 menu
->addSeparator();
243 // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
244 bool reach
= account()->isConnected() && isReachable();
245 bool myself
= (this == account()->myself());
247 KAction
*actionSendMessage
= KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), 0, "actionSendMessage" );
248 actionSendMessage
->setEnabled( reach
&& !myself
);
249 menu
->addAction( actionSendMessage
);
251 KAction
*actionChat
= KopeteStdAction::chat( this, SLOT( startChat() ), 0, "actionChat" );
252 actionChat
->setEnabled( reach
&& !myself
);
253 menu
->addAction( actionChat
);
255 KAction
*actionSendFile
= KopeteStdAction::sendFile( this, SLOT( sendFile() ), 0, "actionSendFile" );
256 actionSendFile
->setEnabled( reach
&& d
->fileCapable
&& !myself
);
257 menu
->addAction( actionSendFile
);
259 // Protocol specific options will go below this separator
260 // through the use of the customContextMenuActions() function
262 // Get the custom actions from the protocols ( pure virtual function )
263 QList
<KAction
*> *customActions
= customContextMenuActions( manager
);
264 if( customActions
&& !customActions
->isEmpty() )
266 menu
->addSeparator();
267 QList
<KAction
*>::iterator it
, itEnd
= customActions
->end();
268 for( it
= customActions
->begin(); it
!= itEnd
; ++it
)
269 menu
->addAction( (*it
) );
271 delete customActions
;
273 menu
->addSeparator();
275 if( metaContact() && !metaContact()->isTemporary() )
277 KAction
* changeMetaContact
= KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), 0, "actionChangeMetaContact" );
278 menu
->addAction( changeMetaContact
);
281 menu
->addAction( KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), 0, "actionUserInfo" ) );
283 #if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11
284 if ( account()->isBlocked( d
->contactId
) )
285 KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu
, "actionUnblockContact" )->plug( menu
);
287 KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu
, "actionBlockContact" )->plug( menu
);
290 if( metaContact() && !metaContact()->isTemporary() )
291 menu
->addAction( KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), 0, "actionDeleteContact" ) );
296 void Contact::changeMetaContact()
298 KDialog
*moveDialog
= new KDialog( Kopete::UI::Global::mainWidget() );
299 moveDialog
->setCaption( i18n( "Move Contact" ) );
300 moveDialog
->setButtons( KDialog::Ok
| KDialog::Cancel
);
301 moveDialog
->setDefaultButton( KDialog::Ok
);
302 moveDialog
->showButtonSeparator( true );
304 KVBox
*w
= new KVBox( moveDialog
);
305 w
->setSpacing( KDialog::spacingHint() );
306 Kopete::UI::MetaContactSelectorWidget
*selector
= new Kopete::UI::MetaContactSelectorWidget(w
);
307 selector
->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" ));
308 // exclude this metacontact as a target metacontact for the move
309 selector
->excludeMetaContact( metaContact() );
310 QCheckBox
*chkCreateNew
= new QCheckBox( i18n( "Create a new metacontact for this contact" ), w
);
311 chkCreateNew
->setWhatsThis( i18n( "If you select this option, a new metacontact will be created in the top-level group "
312 "with the name of this contact and the contact will be moved to it." ) );
313 QObject::connect( chkCreateNew
, SIGNAL( toggled(bool) ) , selector
, SLOT ( setDisabled(bool) ) ) ;
315 moveDialog
->setMainWidget(w
);
316 if( moveDialog
->exec() == QDialog::Accepted
)
318 Kopete::MetaContact
*mc
= selector
->metaContact();
319 if(chkCreateNew
->isChecked())
321 mc
=new Kopete::MetaContact();
322 Kopete::ContactList::self()->addMetaContact(mc
);
326 setMetaContact( mc
);
330 moveDialog
->deleteLater();
333 void Contact::setMetaContact( MetaContact
*m
)
335 MetaContact
*old
= d
->metaContact
;
336 if(old
==m
) //that make no sens
341 old
->removeContact( this );
342 disconnect( old
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
343 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
345 if(old
->contacts().isEmpty())
347 //remove the old metacontact. (this delete the MC)
348 ContactList::self()->removeMetaContact(old
);
352 d
->metaContact
= m
; //i am forced to do that now if i want the next line works
353 //remove cached data for this protocol which will not be removed since we disconnected
354 protocol()->slotMetaContactAboutToSave( old
);
362 m
->addContact( this );
364 // it is necessary to call this write here, because MetaContact::addContact() does not differentiate
365 // between adding completely new contacts (which should be written to kabc) and restoring upon restart
366 // (where no write is needed).
367 KABCPersistence::self()->write( m
);
368 connect( d
->metaContact
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
369 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
374 void Contact::serialize( QMap
<QString
, QString
> &/*serializedData*/,
375 QMap
<QString
, QString
> & /* addressBookData */ )
379 bool Contact::isReachable()
381 // The default implementation returns false when offline and true
382 // otherwise. Subclass if you need more control over the process.
383 return onlineStatus().status() != OnlineStatus::Offline
;
387 void Contact::startChat()
389 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_chatwindow") );
394 void Contact::sendMessage()
396 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_emailwindow") );
401 void Contact::execute()
403 // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
404 if ( account()->isConnected() && isReachable() )
406 KopeteView
*v
=manager( CanCreate
)->view(true, Kopete::BehaviorSettings::self()->viewPlugin() );
412 KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry
,
413 i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
414 "until this user comes online." ), i18n( "User is Not Reachable" ) );
418 void Contact::slotDelete()
420 if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
421 i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ,
422 d
->contactId
), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("list-remove-user") ), KStandardGuiItem::cancel(),
423 QString::fromLatin1("askRemoveContact"), KMessageBox::Notify
| KMessageBox::Dangerous
)
424 == KMessageBox::Continue
)
426 Kopete::DeleteContactTask
*deleteTask
= new Kopete::DeleteContactTask(this);
431 void Contact::deleteContact()
433 // Default implementation simply deletes the contact
438 MetaContact
* Contact::metaContact() const
440 return d
->metaContact
;
443 QString
Contact::contactId() const
448 Protocol
* Contact::protocol() const
450 return d
->account
? d
->account
->protocol() : 0L;
453 Account
* Contact::account() const
460 void Contact::sync(unsigned int)
462 /* Default implementation does nothing */
465 QString
& Contact::icon() const
470 void Contact::setIcon( const QString
& icon
)
476 QList
<KAction
*> *Contact::customContextMenuActions()
481 QList
<KAction
*> *Contact::customContextMenuActions( ChatSession
* /* manager */ )
483 return customContextMenuActions();
486 bool Contact::isOnline() const
488 return onlineStatus().isDefinitelyOnline();
492 bool Contact::isFileCapable() const
494 return d
->fileCapable
;
497 void Contact::setFileCapable( bool filecap
)
499 d
->fileCapable
= filecap
;
503 bool Contact::canAcceptFiles() const
505 return isOnline() && d
->fileCapable
;
508 unsigned long int Contact::idleTime() const
513 return d
->idleTime
+(d
->idleTimer
.elapsed()/1000);
516 void Contact::setIdleTime( unsigned long int t
)
518 bool idleChanged
= false;
523 d
->idleTimer
.start();
524 //FIXME: if t == 0, idleTime() will now return garbage
526 // d->idleTimer.stop();
528 emit
idleStateChanged(this);
531 QString
Contact::toolTip() const
535 QStringList shownProps
= Kopete::AppearanceSettings::self()->toolTipContents();
537 // --------------------------------------------------------------------------
538 // Fixed part of tooltip
541 if ( this == account()->myself() )
543 iconName
= QString::fromLatin1("kopete-account-icon:%1:%2")
544 .arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
545 QString(QUrl::toPercentEncoding( account()->accountId() )) );
550 iconName
= QString::fromLatin1("kopete-contact-icon:%1:%2:%3")
551 .arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
552 QString(QUrl::toPercentEncoding( account()->accountId() )),
553 QString(QUrl::toPercentEncoding( contactId() )) );
556 // TODO: the nickname should be a configurable properties, like others. -Olivier
557 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
558 if ( nick
.isEmpty() )
560 tip
= i18nc( "@label:textbox %3 is contact-display-name, %1 is its status",
561 "<b><nobr>%3</nobr></b><br /><img src=\"%2\"> %1",
562 Kopete::Message::escape( onlineStatus().description() ), iconName
,
563 Kopete::Message::escape( d
->contactId
) );
567 tip
= i18nc( "@label:textbox %4 is contact-display-name, %3 is contact-id, %1 is its status",
568 "<nobr><b>%4</b> (%3)</nobr><br /><img src=\"%2\"> %1",
569 Kopete::Message::escape( onlineStatus().description() ), iconName
,
570 Kopete::Message::escape( contactId() ),
571 Kopete::Emoticons::self()->theme().parseEmoticons( Kopete::Message::escape( nick
) ) );
574 // --------------------------------------------------------------------------
575 // Configurable part of tooltip
577 // FIXME: It shouldn't use QString to identity the properties. Instead it should use PropertyTmpl::key()
578 for(QStringList::Iterator it
=shownProps
.begin(); it
!=shownProps
.end(); ++it
)
580 if((*it
) == Kopete::Global::Properties::self()->fullName().key() )
582 QString name
= formattedName();
585 tip
+= i18nc("@label:textbox formatted name",
586 "<br /><b>Full Name:</b> <nobr>%1</nobr>", Qt::escape(name
));
589 else if ((*it
) == QString::fromLatin1("FormattedIdleTime"))
591 QString time
= formattedIdleTime();
594 tip
+= i18nc("@label:textbox formatted idle time",
595 "<br /><b>Idle:</b> <nobr>%1</nobr>", time
);
598 else if ((*it
) == QString::fromLatin1("homePage"))
600 QString url
= property(*it
).value().toString();
603 tip
+= i18nc("@label:textbox formatted url",
604 "<br /><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>",
605 QString(QUrl::toPercentEncoding( url
)), Kopete::Message::escape( Qt::escape(url
) ) );
608 else if ((*it
) == Kopete::Global::Properties::self()->statusTitle().key() )
610 QString statusTitle
= property(*it
).value().toString();
611 if(!statusTitle
.isEmpty())
613 tip
+= i18nc("@label:textbox formatted status title",
614 "<br /><b>Status Title:</b> %1", Kopete::Emoticons::self()->theme().parseEmoticons( Kopete::Message::escape(statusTitle
) ) );
617 else if ((*it
) == Kopete::Global::Properties::self()->statusMessage().key() )
619 QString statusmsg
= property(*it
).value().toString();
620 if(!statusmsg
.isEmpty())
622 tip
+= i18nc("@label:textbox formatted status message",
623 "<br /><b>Status Message:</b> %1", Kopete::Emoticons::self()->theme().parseEmoticons( Kopete::Message::escape(statusmsg
) ) );
631 QVariant val
= p
.value();
636 case QVariant::DateTime
:
637 valueText
= KGlobal::locale()->formatDateTime(val
.toDateTime());
638 valueText
= Kopete::Message::escape( valueText
);
641 valueText
= KGlobal::locale()->formatDate(val
.toDate());
642 valueText
= Kopete::Message::escape( valueText
);
645 valueText
= KGlobal::locale()->formatTime(val
.toTime());
646 valueText
= Kopete::Message::escape( valueText
);
651 valueText
= val
.toString();
655 valueText
= Kopete::Message::escape( val
.toString() );
659 tip
+= i18nc("@label:textbox property label %2 is name, %1 is value",
660 "<br /><nobr><b>%2:</b></nobr> %1",
661 valueText
, Qt::escape(p
.tmpl().label()) );
669 QString
Kopete::Contact::formattedName() const
671 if( hasProperty( Kopete::Global::Properties::self()->fullName().key() ) )
672 return property( Kopete::Global::Properties::self()->fullName() ).value().toString();
675 Kopete::Property first
, last
;
677 first
= property( Kopete::Global::Properties::self()->firstName() );
678 last
= property( Kopete::Global::Properties::self()->lastName() );
681 if(!last
.isNull()) // contact has both first and last name
683 ret
= i18nc("firstName lastName", "%2 %1",
684 last
.value().toString(),
685 first
.value().toString());
687 else // only first name set
689 ret
= first
.value().toString();
692 else if(!last
.isNull()) // only last name set
694 ret
= last
.value().toString();
700 QString
Kopete::Contact::formattedIdleTime() const
703 unsigned long int leftTime
= idleTime();
706 { // FIXME: duplicated from code in kopetecontact listview.cpp
707 unsigned long int days
, hours
, mins
, secs
;
709 days
= leftTime
/ ( 60*60*24 );
710 leftTime
= leftTime
% ( 60*60*24 );
711 hours
= leftTime
/ ( 60*60 );
712 leftTime
= leftTime
% ( 60*60 );
713 mins
= leftTime
/ 60;
714 secs
= leftTime
% 60;
718 ret
= i18nc( "<days>d <hours>h <minutes>m <seconds>s",
725 else if ( hours
!= 0 )
727 ret
= i18nc( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ,
734 // xgettext: no-c-format
735 ret
= i18nc( "<minutes>m <seconds>s", "%2m %1s" ,
743 void Kopete::Contact::slotBlock()
745 account()->block( d
->contactId
);
748 void Kopete::Contact::slotUnblock()
750 account()->unblock( d
->contactId
);
753 void Kopete::Contact::setNickName( const QString
&name
)
755 QString oldNickName
= nickName();
756 setProperty( Kopete::Global::Properties::self()->nickName(), name
);
759 QString
Kopete::Contact::nickName() const
761 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
762 if( !nick
.isEmpty() )
768 void Kopete::Contact::setPhoto(const QString
&photoPath
)
770 setProperty( Kopete::Global::Properties::self()->photo(), photoPath
);
774 } //END namespace Kopete
776 #include "kopetecontact.moc"