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 @tiscalinet.be>
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.h>
23 #include <Q3StyleSheet>
27 #include <kdeversion.h>
28 #include <kinputdialog.h>
30 #include <kabcpersistence.h>
31 #include <kdialogbase.h>
34 #include <kmessagebox.h>
35 #include <klistviewsearchline.h>
37 #include "kopetecontactlist.h"
38 #include "kopeteglobal.h"
39 #include "kopeteuiglobal.h"
40 #include "kopeteprotocol.h"
41 #include "kopeteaccount.h"
42 #include "kopetestdaction.h"
43 #include "kopetechatsession.h"
44 #include "kopeteview.h"
45 #include "kopetemetacontact.h"
46 #include "kopeteprefs.h"
47 #include "metacontactselectorwidget.h"
49 //For the moving to another metacontact dialog
54 #include <klistview.h>
55 #include <qcheckbox.h>
60 struct Contact::Private
65 OnlineStatus onlineStatus
;
68 MetaContact
*metaContact
;
74 unsigned long int idleTime
;
76 Kopete::ContactProperty::Map properties
;
80 Contact::Contact( Account
*account
, const QString
&contactId
,
81 MetaContact
*parent
, const QString
&icon
)
86 //kdDebug( 14010 ) << k_funcinfo << "Creating contact with id " << contactId << endl;
88 d
->contactId
= contactId
;
89 d
->metaContact
= parent
;
90 d
->fileCapable
= false;
96 account
->registerContact( this );
98 // Need to check this because myself() may have no parent
101 connect( parent
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
102 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
104 parent
->addContact( this );
107 connect( account
, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) );
112 //kdDebug(14010) << k_funcinfo << endl;
113 emit( contactDestroyed( this ) );
119 OnlineStatus
Contact::onlineStatus() const
121 if ( this == account()->myself() || account()->isConnected() )
122 return d
->onlineStatus
;
124 return protocol()->accountOfflineStatus();
127 void Contact::setOnlineStatus( const OnlineStatus
&status
)
129 if( status
== d
->onlineStatus
)
132 OnlineStatus oldStatus
= d
->onlineStatus
;
133 d
->onlineStatus
= status
;
135 Kopete::Global::Properties
*globalProps
= Kopete::Global::Properties::self();
137 // Contact changed from Offline to another status
138 if( oldStatus
.status() == OnlineStatus::Offline
&&
139 status
.status() != OnlineStatus::Offline
)
141 setProperty( globalProps
->onlineSince(), QDateTime::currentDateTime() );
142 /*kdDebug(14010) << k_funcinfo << "REMOVING lastSeen property for " <<
143 d->displayName << endl;*/
144 removeProperty( globalProps
->lastSeen() );
146 else if( oldStatus
.status() != OnlineStatus::Offline
&&
147 oldStatus
.status() != OnlineStatus::Unknown
&&
148 status
.status() == OnlineStatus::Offline
) // Contact went back offline
150 removeProperty( globalProps
->onlineSince() );
151 /*kdDebug(14010) << k_funcinfo << "SETTING lastSeen property for " <<
152 d->displayName << endl;*/
153 setProperty( globalProps
->lastSeen(), QDateTime::currentDateTime() );
156 if ( this == account()->myself() || account()->isConnected() )
157 emit
onlineStatusChanged( this, status
, oldStatus
);
160 void Contact::slotAccountIsConnectedChanged()
162 if ( this == account()->myself() )
165 if ( account()->isConnected() )
166 emit
onlineStatusChanged( this, d
->onlineStatus
, protocol()->accountOfflineStatus() );
168 emit
onlineStatusChanged( this, protocol()->accountOfflineStatus(), d
->onlineStatus
);
172 void Contact::sendFile( const KURL
&, const QString
&, uint
)
174 kdWarning( 14010 ) << k_funcinfo
<< "Plugin "
175 << protocol()->pluginId() << " has enabled file sending, "
176 << "but didn't implement it!" << endl
;
179 void Contact::slotAddContact()
183 metaContact()->setTemporary( false );
184 ContactList::self()->addMetaContact( metaContact() );
188 KMenu
* Contact::popupMenu( ChatSession
*manager
)
190 KMenu
*menu
= new KMenu();
193 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
195 titleText
= QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
197 titleText
= QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick
, contactId(), onlineStatus().description() );
198 menu
->addTitle( titleText
);
203 if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() )
205 KAction
*actionAddContact
= new KAction( i18n( "&Add to Your Contact List" ), QString::fromLatin1( "bookmark_add" ),
206 0, this, SLOT( slotAddContact() ), menu
, "actionAddContact" );
207 actionAddContact
->plug( menu
);
208 menu
->addSeparator();
211 // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
212 bool reach
= account()->isConnected() && isReachable();
213 bool myself
= (this == account()->myself());
215 KAction
*actionSendMessage
= KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), menu
, "actionSendMessage" );
216 actionSendMessage
->setEnabled( reach
&& !myself
);
217 actionSendMessage
->plug( menu
);
219 KAction
*actionChat
= KopeteStdAction::chat( this, SLOT( startChat() ), menu
, "actionChat" );
220 actionChat
->setEnabled( reach
&& !myself
);
221 actionChat
->plug( menu
);
223 KAction
*actionSendFile
= KopeteStdAction::sendFile( this, SLOT( sendFile() ), menu
, "actionSendFile" );
224 actionSendFile
->setEnabled( reach
&& d
->fileCapable
&& !myself
);
225 actionSendFile
->plug( menu
);
227 // Protocol specific options will go below this separator
228 // through the use of the customContextMenuActions() function
230 // Get the custom actions from the protocols ( pure virtual function )
231 Q3PtrList
<KAction
> *customActions
= customContextMenuActions( manager
);
232 if( customActions
&& !customActions
->isEmpty() )
234 menu
->addSeparator();
236 for( KAction
*a
= customActions
->first(); a
; a
= customActions
->next() )
239 delete customActions
;
241 menu
->addSeparator();
243 if( metaContact() && !metaContact()->isTemporary() )
244 KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), menu
, "actionChangeMetaContact" )->plug( menu
);
246 KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), menu
, "actionUserInfo" )->plug( menu
);
248 #if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11
249 if ( account()->isBlocked( d
->contactId
) )
250 KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu
, "actionUnblockContact" )->plug( menu
);
252 KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu
, "actionBlockContact" )->plug( menu
);
255 if( metaContact() && !metaContact()->isTemporary() )
256 KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu
, "actionDeleteContact" )->plug( menu
);
261 void Contact::changeMetaContact()
263 KDialogBase
*moveDialog
= new KDialogBase( Kopete::UI::Global::mainWidget(), "moveDialog", true, i18n( "Move Contact" ),
264 KDialogBase::Ok
| KDialogBase::Cancel
, KDialogBase::Ok
, true );
266 KVBox
*w
= new KVBox( moveDialog
);
267 w
->setSpacing( KDialog::spacingHint() );
268 Kopete::UI::MetaContactSelectorWidget
*selector
= new Kopete::UI::MetaContactSelectorWidget(w
);
269 selector
->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" ));
270 // exclude this metacontact as a target metacontact for the move
271 selector
->excludeMetaContact( metaContact() );
272 QCheckBox
*chkCreateNew
= new QCheckBox( i18n( "Create a new metacontact for this contact" ), w
);
273 chkCreateNew
->setWhatsThis( i18n( "If you select this option, a new metacontact will be created in the top-level group "
274 "with the name of this contact and the contact will be moved to it." ) );
275 QObject::connect( chkCreateNew
, SIGNAL( toggled(bool) ) , selector
, SLOT ( setDisabled(bool) ) ) ;
277 moveDialog
->setMainWidget(w
);
278 if( moveDialog
->exec() == QDialog::Accepted
)
280 Kopete::MetaContact
*mc
= selector
->metaContact();
281 if(chkCreateNew
->isChecked())
283 mc
=new Kopete::MetaContact();
284 Kopete::ContactList::self()->addMetaContact(mc
);
288 setMetaContact( mc
);
292 moveDialog
->deleteLater();
295 void Contact::setMetaContact( MetaContact
*m
)
297 MetaContact
*old
= d
->metaContact
;
298 if(old
==m
) //that make no sens
303 int result
=KMessageBox::No
;
304 if( old
->isTemporary() )
305 result
=KMessageBox::Yes
;
306 else if( old
->contacts().count()==1 )
307 { //only one contact, including this one, that mean the contact will be empty efter the move
308 result
= KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget(), i18n( "You are moving the contact `%1' to the meta contact `%2'.\n"
309 "`%3' will be empty afterwards. Do you want to delete this contact?" )
310 .arg(contactId(), m
? m
->displayName() : QString::null
, old
->displayName())
311 , i18n( "Move Contact" ), KStdGuiItem::del(), i18n( "&Keep" ) , QString::fromLatin1("delete_old_contact_when_move") );
312 if(result
==KMessageBox::Cancel
)
315 old
->removeContact( this );
316 disconnect( old
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
317 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
319 if(result
==KMessageBox::Yes
)
321 //remove the old metacontact. (this delete the MC)
322 ContactList::self()->removeMetaContact(old
);
326 d
->metaContact
= m
; //i am forced to do that now if i want the next line works
327 //remove cached data for this protocol which will not be removed since we disconnected
328 protocol()->slotMetaContactAboutToSave( old
);
336 m
->addContact( this );
337 m
->insertChild( this );
338 // it is necessary to call this write here, because MetaContact::addContact() does not differentiate
339 // between adding completely new contacts (which should be written to kabc) and restoring upon restart
340 // (where no write is needed).
341 KABCPersistence::self()->write( m
);
342 connect( d
->metaContact
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
343 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
348 void Contact::serialize( QMap
<QString
, QString
> &/*serializedData*/,
349 QMap
<QString
, QString
> & /* addressBookData */ )
354 void Contact::serializeProperties(QMap
<QString
, QString
> &serializedData
)
357 Kopete::ContactProperty::Map::ConstIterator it
;// = d->properties.ConstIterator;
358 for (it
=d
->properties
.begin(); it
!= d
->properties
.end(); ++it
)
360 if (!it
.data().tmpl().persistent())
363 QVariant val
= it
.data().value();
364 QString key
= QString::fromLatin1("prop_%1_%2").arg(QString::fromLatin1(val
.typeName()), it
.key());
366 serializedData
[key
] = val
.toString();
369 } // end serializeProperties()
371 void Contact::deserializeProperties(
372 QMap
<QString
, QString
> &serializedData
)
374 QMap
<QString
, QString
>::ConstIterator it
;
375 for ( it
=serializedData
.begin(); it
!= serializedData
.end(); ++it
)
377 QString key
= it
.key();
379 if ( !key
.startsWith( QString::fromLatin1("prop_") ) ) // avoid parsing other serialized data
382 QStringList keyList
= QStringList::split( QChar('_'), key
, false );
383 if( keyList
.count() < 3 ) // invalid key, not enough parts in string "prop_X_Y"
386 key
= keyList
[2]; // overwrite key var with the real key name this property has
387 QString
type( keyList
[1] ); // needed for QVariant casting
389 QVariant
variant( it
.data() );
390 if( !variant
.cast(QVariant::nameToType(type
.toLatin1())) )
392 kdDebug(14010) << k_funcinfo
<<
393 "Casting QVariant to needed type FAILED" <<
394 "key=" << key
<< ", type=" << type
<< endl
;
398 Kopete::ContactPropertyTmpl tmpl
= Kopete::Global::Properties::self()->tmpl(key
);
401 kdDebug( 14010 ) << k_funcinfo
<< "no ContactPropertyTmpl defined for" \
402 " key " << key
<< ", cannot restore persistent property" << endl
;
406 setProperty(tmpl
, variant
);
411 bool Contact::isReachable()
413 // The default implementation returns false when offline and true
414 // otherwise. Subclass if you need more control over the process.
415 return onlineStatus().status() != OnlineStatus::Offline
;
419 void Contact::startChat()
421 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_chatwindow") );
426 void Contact::sendMessage()
428 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_emailwindow") );
433 void Contact::execute()
435 // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
436 if ( account()->isConnected() && isReachable() )
438 KopeteView
*v
=manager( CanCreate
)->view(true, KopetePrefs::prefs()->interfacePreference() );
444 KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry
,
445 i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
446 "until this user comes online." ), i18n( "User is Not Reachable" ) );
450 void Contact::slotDelete()
452 if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
453 i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ).
454 arg( d
->contactId
), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("editdelete") ),
455 QString::fromLatin1("askRemoveContact"), KMessageBox::Notify
| KMessageBox::Dangerous
)
456 == KMessageBox::Continue
)
462 void Contact::deleteContact()
464 // Default implementation simply deletes the contact
469 MetaContact
* Contact::metaContact() const
471 return d
->metaContact
;
474 QString
Contact::contactId() const
479 Protocol
* Contact::protocol() const
481 return d
->account
? d
->account
->protocol() : 0L;
484 Account
* Contact::account() const
491 void Contact::sync(unsigned int)
493 /* Default implementation does nothing */
496 QString
& Contact::icon() const
501 void Contact::setIcon( const QString
& icon
)
507 QList
<KAction
*> *Contact::customContextMenuActions()
512 QList
<KAction
*> *Contact::customContextMenuActions( ChatSession
* /* manager */ )
514 return customContextMenuActions();
518 bool Contact::isOnline() const
520 return onlineStatus().isDefinitelyOnline();
524 bool Contact::isFileCapable() const
526 return d
->fileCapable
;
529 void Contact::setFileCapable( bool filecap
)
531 d
->fileCapable
= filecap
;
535 bool Contact::canAcceptFiles() const
537 return isOnline() && d
->fileCapable
;
540 unsigned long int Contact::idleTime() const
545 return d
->idleTime
+(d
->idleTimer
.elapsed()/1000);
548 void Contact::setIdleTime( unsigned long int t
)
550 bool idleChanged
= false;
555 d
->idleTimer
.start();
556 //FIXME: if t == 0, idleTime() will now return garbage
558 // d->idleTimer.stop();
560 emit
idleStateChanged(this);
564 QStringList
Contact::properties() const
566 return d
->properties
.keys();
569 bool Contact::hasProperty(const QString
&key
) const
571 return d
->properties
.contains(key
);
574 const ContactProperty
&Contact::property(const QString
&key
) const
577 return d
->properties
[key
];
579 return Kopete::ContactProperty::null
;
582 const Kopete::ContactProperty
&Contact::property(
583 const Kopete::ContactPropertyTmpl
&tmpl
) const
585 if(hasProperty(tmpl
.key()))
586 return d
->properties
[tmpl
.key()];
588 return Kopete::ContactProperty::null
;
592 void Contact::setProperty(const Kopete::ContactPropertyTmpl
&tmpl
,
593 const QVariant
&value
)
595 if(tmpl
.isNull() || tmpl
.key().isEmpty())
597 kdDebug(14000) << k_funcinfo
<<
598 "No valid template for property passed!" << endl
;
602 if(value
.isNull() || value
.canCast(QVariant::String
) && value
.toString().isEmpty())
604 removeProperty(tmpl
);
608 QVariant oldValue
= property(tmpl
.key()).value();
610 if(oldValue
!= value
)
612 Kopete::ContactProperty
prop(tmpl
, value
);
613 d
->properties
.insert(tmpl
.key(), prop
, true);
615 emit
propertyChanged(this, tmpl
.key(), oldValue
, value
);
620 void Contact::removeProperty(const Kopete::ContactPropertyTmpl
&tmpl
)
622 if(!tmpl
.isNull() && !tmpl
.key().isEmpty())
625 QVariant oldValue
= property(tmpl
.key()).value();
626 d
->properties
.remove(tmpl
.key());
627 emit
propertyChanged(this, tmpl
.key(), oldValue
, QVariant());
632 QString
Contact::toolTip() const
634 Kopete::ContactProperty p
;
636 QStringList shownProps
= KopetePrefs::prefs()->toolTipContents();
638 // --------------------------------------------------------------------------
639 // Fixed part of tooltip
641 QString iconName
= QString::fromLatin1("kopete-contact-icon:%1:%2:%3")
642 .arg( KURL::encode_string( protocol()->pluginId() ),
643 KURL::encode_string( account()->accountId() ),
644 KURL::encode_string( contactId() ) );
646 // TODO: the nickname should be a configurable properties, like others. -Olivier
647 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
648 if ( nick
.isEmpty() )
650 tip
= i18n( "<b>DISPLAY NAME</b><br><img src=\"%2\"> CONTACT STATUS",
651 "<b><nobr>%3</nobr></b><br><img src=\"%2\"> %1" ).
652 arg( Kopete::Message::escape( onlineStatus().description() ), iconName
,
653 Kopete::Message::escape( d
->contactId
) );
657 tip
= i18n( "<b>DISPLAY NAME</b> (CONTACT ID)<br><img src=\"%2\"> CONTACT STATUS",
658 "<nobr><b>%4</b> (%3)</nobr><br><img src=\"%2\"> %1" ).
659 arg( Kopete::Message::escape( onlineStatus().description() ), iconName
,
660 Kopete::Message::escape( contactId() ),
661 Kopete::Message::escape( nick
) );
664 // --------------------------------------------------------------------------
665 // Configurable part of tooltip
667 for(QStringList::Iterator it
=shownProps
.begin(); it
!=shownProps
.end(); ++it
)
669 if((*it
) == QString::fromLatin1("FormattedName"))
671 QString name
= formattedName();
674 tip
+= i18n("<br><b>Full Name:</b> FORMATTED NAME",
675 "<br><b>Full Name:</b> <nobr>%1</nobr>").arg(Q3StyleSheet::escape(name
));
678 else if ((*it
) == QString::fromLatin1("FormattedIdleTime"))
680 QString time
= formattedIdleTime();
683 tip
+= i18n("<br><b>Idle:</b> FORMATTED IDLE TIME",
684 "<br><b>Idle:</b> <nobr>%1</nobr>").arg(time
);
687 else if ((*it
) == QString::fromLatin1("homePage"))
689 QString url
= property(*it
).value().toString();
692 tip
+= i18n("<br><b>Home Page:</b> FORMATTED URL",
693 "<br><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>").
694 arg( KURL::encode_string( url
), Kopete::Message::escape( Q3StyleSheet::escape(url
) ) );
697 else if ((*it
) == QString::fromLatin1("awayMessage"))
699 QString awaymsg
= property(*it
).value().toString();
700 if(!awaymsg
.isEmpty())
702 tip
+= i18n("<br><b>Away Message:</b> FORMATTED AWAY MESSAGE",
703 "<br><b>Away Message:</b> %1").arg ( Kopete::Message::escape(awaymsg
) );
711 QVariant val
= p
.value();
716 case QVariant::DateTime
:
717 valueText
= KGlobal::locale()->formatDateTime(val
.toDateTime());
718 valueText
= Kopete::Message::escape( valueText
);
721 valueText
= KGlobal::locale()->formatDate(val
.toDate());
722 valueText
= Kopete::Message::escape( valueText
);
725 valueText
= KGlobal::locale()->formatTime(val
.toTime());
726 valueText
= Kopete::Message::escape( valueText
);
731 valueText
= val
.toString();
735 valueText
= Kopete::Message::escape( val
.toString() );
739 tip
+= i18n("<br><b>PROPERTY LABEL:</b> PROPERTY VALUE",
740 "<br><nobr><b>%2:</b></nobr> %1").
741 arg( valueText
, Q3StyleSheet::escape(p
.tmpl().label()) );
749 QString
Kopete::Contact::formattedName() const
751 if( hasProperty(QString::fromLatin1("FormattedName")) )
752 return property(QString::fromLatin1("FormattedName")).value().toString();
755 Kopete::ContactProperty first
, last
;
757 first
= property(QString::fromLatin1("firstName"));
758 last
= property(QString::fromLatin1("lastName"));
761 if(!last
.isNull()) // contact has both first and last name
763 ret
= i18n("firstName lastName", "%2 %1")
764 .arg(last
.value().toString())
765 .arg(first
.value().toString());
767 else // only first name set
769 ret
= first
.value().toString();
772 else if(!last
.isNull()) // only last name set
774 ret
= last
.value().toString();
780 QString
Kopete::Contact::formattedIdleTime() const
783 unsigned long int leftTime
= idleTime();
786 { // FIXME: duplicated from code in kopetecontactlistview.cpp
787 unsigned long int days
, hours
, mins
, secs
;
789 days
= leftTime
/ ( 60*60*24 );
790 leftTime
= leftTime
% ( 60*60*24 );
791 hours
= leftTime
/ ( 60*60 );
792 leftTime
= leftTime
% ( 60*60 );
793 mins
= leftTime
/ 60;
794 secs
= leftTime
% 60;
798 ret
= i18n( "<days>d <hours>h <minutes>m <seconds>s",
805 else if ( hours
!= 0 )
807 ret
= i18n( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" )
814 ret
= i18n( "<minutes>m <seconds>s", "%2m %1s" )
823 void Kopete::Contact::slotBlock()
825 account()->block( d
->contactId
);
828 void Kopete::Contact::slotUnblock()
830 account()->unblock( d
->contactId
);
833 void Kopete::Contact::setNickName( const QString
&name
)
835 setProperty( Kopete::Global::Properties::self()->nickName(), name
);
838 QString
Kopete::Contact::nickName() const
840 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
841 if( !nick
.isEmpty() )
847 void Contact::virtual_hook( uint
, void * )
851 } //END namespace Kopete
854 #include "kopetecontact.moc"