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
;
86 Contact::Contact( Account
*account
, const QString
&contactId
,
87 MetaContact
*parent
, const QString
&icon
)
88 : ContactListElement( parent
), d(new Private())
90 //kDebug( 14010 ) << "Creating contact with id " << contactId;
92 d
->contactId
= contactId
;
93 d
->metaContact
= parent
;
94 d
->fileCapable
= false;
99 // If can happend that a MetaContact may be used without a account
100 // (ex: for unit tests or chat window style preview)
103 account
->registerContact( this );
104 connect( account
, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) );
107 // Need to check this because myself() may have no parent
108 // Maybe too the metaContact doesn't have a valid protocol()
109 // (ex: for unit tests or chat window style preview)
110 if( parent
&& protocol() )
112 connect( parent
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
113 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
115 parent
->addContact( this );
122 emit( contactDestroyed( this ) );
128 OnlineStatus
Contact::onlineStatus() const
130 if ( this == account()->myself() || account()->isConnected() )
131 return d
->onlineStatus
;
133 return protocol()->accountOfflineStatus();
136 void Contact::setOnlineStatus( const OnlineStatus
&status
)
138 if( status
== d
->onlineStatus
)
141 bool oldCanAcceptFiles
= canAcceptFiles();
142 OnlineStatus oldStatus
= d
->onlineStatus
;
143 d
->onlineStatus
= status
;
145 Kopete::Global::Properties
*globalProps
= Kopete::Global::Properties::self();
147 // Contact changed from Offline to another status
148 if( oldStatus
.status() == OnlineStatus::Offline
&&
149 status
.status() != OnlineStatus::Offline
)
151 setProperty( globalProps
->onlineSince(), QDateTime::currentDateTime() );
152 /*kDebug(14010) << "REMOVING lastSeen property for " <<
153 d->displayName << endl;*/
154 removeProperty( globalProps
->lastSeen() );
156 else if( oldStatus
.status() != OnlineStatus::Offline
&&
157 oldStatus
.status() != OnlineStatus::Unknown
&&
158 status
.status() == OnlineStatus::Offline
) // Contact went back offline
160 removeProperty( globalProps
->onlineSince() );
161 /*kDebug(14010) << "SETTING lastSeen property for " <<
162 d->displayName << endl;*/
163 setProperty( globalProps
->lastSeen(), QDateTime::currentDateTime() );
166 if ( this == account()->myself() || account()->isConnected() )
167 emit
onlineStatusChanged( this, status
, oldStatus
);
169 if ( oldCanAcceptFiles
!= canAcceptFiles() )
170 emit
canAcceptFilesChanged();
173 Kopete::StatusMessage
Contact::statusMessage() const
175 return d
->statusMessage
;
178 void Contact::setStatusMessage( const Kopete::StatusMessage
&statusMessage
)
180 d
->statusMessage
= statusMessage
;
182 kDebug(14010) << "Setting up the status title property with this: " << statusMessage
.title();
183 if( !statusMessage
.title().isEmpty() )
184 setProperty( Kopete::Global::Properties::self()->statusTitle(), statusMessage
.title() );
186 removeProperty( Kopete::Global::Properties::self()->statusTitle() );
188 kDebug(14010) << "Setting up the status message property with this: " << statusMessage
.message();
189 if( !statusMessage
.message().isEmpty() )
190 setProperty( Kopete::Global::Properties::self()->statusMessage(), statusMessage
.message() );
192 removeProperty( Kopete::Global::Properties::self()->statusMessage() );
195 void Contact::slotAccountIsConnectedChanged()
197 if ( this == account()->myself() )
200 if ( account()->isConnected() )
201 emit
onlineStatusChanged( this, d
->onlineStatus
, protocol()->accountOfflineStatus() );
203 emit
onlineStatusChanged( this, protocol()->accountOfflineStatus(), d
->onlineStatus
);
207 void Contact::sendFile( const KUrl
&, const QString
&, uint
)
209 kWarning( 14010 ) << "Plugin "
210 << protocol()->pluginId() << " has enabled file sending, "
211 << "but didn't implement it!" << endl
;
214 void Contact::slotAddContact()
218 metaContact()->setTemporary( false );
219 ContactList::self()->addMetaContact( metaContact() );
223 KMenu
* Contact::popupMenu( ChatSession
*manager
)
225 KMenu
*menu
= new KMenu();
228 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
230 titleText
= QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
232 titleText
= QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick
, contactId(), onlineStatus().description() );
233 menu
->addTitle( titleText
);
235 if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() )
237 KAction
*actionAddContact
= new KAction( KIcon("add-user"), i18n( "&Add to Your Contact List" ), menu
);
238 connect( actionAddContact
, SIGNAL(triggered(bool)), this, SLOT( slotAddContact() ) );
240 menu
->addAction(actionAddContact
);
241 menu
->addSeparator();
244 // FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
245 bool reach
= account()->isConnected() && isReachable();
246 bool myself
= (this == account()->myself());
248 KAction
*actionSendMessage
= KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), menu
);
249 actionSendMessage
->setEnabled( reach
&& !myself
);
250 menu
->addAction( actionSendMessage
);
252 KAction
*actionChat
= KopeteStdAction::chat( this, SLOT( startChat() ), menu
);
253 actionChat
->setEnabled( reach
&& !myself
);
254 menu
->addAction( actionChat
);
256 KAction
*actionSendFile
= KopeteStdAction::sendFile( this, SLOT( sendFile() ), menu
);
257 actionSendFile
->setEnabled( reach
&& d
->fileCapable
&& !myself
);
258 menu
->addAction( actionSendFile
);
260 // Protocol specific options will go below this separator
261 // through the use of the customContextMenuActions() function
263 // Get the custom actions from the protocols ( pure virtual function )
264 QList
<KAction
*> *customActions
= customContextMenuActions( manager
);
265 if( customActions
&& !customActions
->isEmpty() )
267 menu
->addSeparator();
268 QList
<KAction
*>::iterator it
, itEnd
= customActions
->end();
269 for( it
= customActions
->begin(); it
!= itEnd
; ++it
)
270 menu
->addAction( (*it
) );
272 delete customActions
;
274 menu
->addSeparator();
276 if( metaContact() && !metaContact()->isTemporary() )
278 KAction
* changeMetaContact
= KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), menu
);
279 menu
->addAction( changeMetaContact
);
282 menu
->addAction( KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), menu
) );
284 #if 0 //this is not fully implemented yet (and doesn't work). disable for now - Olivier 2005-01-11
285 if ( account()->isBlocked( d
->contactId
) )
286 KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu
, "actionUnblockContact" )->plug( menu
);
288 KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu
, "actionBlockContact" )->plug( menu
);
291 if( metaContact() && !metaContact()->isTemporary() )
292 menu
->addAction( KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu
) );
297 void Contact::changeMetaContact()
299 KDialog
*moveDialog
= new KDialog( Kopete::UI::Global::mainWidget() );
300 moveDialog
->setCaption( i18n( "Move Contact" ) );
301 moveDialog
->setButtons( KDialog::Ok
| KDialog::Cancel
);
302 moveDialog
->setDefaultButton( KDialog::Ok
);
303 moveDialog
->showButtonSeparator( true );
305 KVBox
*w
= new KVBox( moveDialog
);
306 w
->setSpacing( KDialog::spacingHint() );
307 Kopete::UI::MetaContactSelectorWidget
*selector
= new Kopete::UI::MetaContactSelectorWidget(w
);
308 selector
->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" ));
309 // exclude this metacontact as a target metacontact for the move
310 selector
->excludeMetaContact( metaContact() );
311 QCheckBox
*chkCreateNew
= new QCheckBox( i18n( "Create a new metacontact for this contact" ), w
);
312 chkCreateNew
->setWhatsThis( i18n( "If you select this option, a new metacontact will be created in the top-level group "
313 "with the name of this contact and the contact will be moved to it." ) );
314 QObject::connect( chkCreateNew
, SIGNAL( toggled(bool) ) , selector
, SLOT ( setDisabled(bool) ) ) ;
316 moveDialog
->setMainWidget(w
);
317 if( moveDialog
->exec() == QDialog::Accepted
)
319 Kopete::MetaContact
*mc
= selector
->metaContact();
320 if(chkCreateNew
->isChecked())
322 mc
=new Kopete::MetaContact();
323 Kopete::ContactList::self()->addMetaContact(mc
);
327 setMetaContact( mc
);
331 moveDialog
->deleteLater();
334 void Contact::setMetaContact( MetaContact
*m
)
336 MetaContact
*old
= d
->metaContact
;
337 if(old
==m
) //that make no sens
342 old
->removeContact( this );
343 disconnect( old
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
344 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
346 if(old
->contacts().isEmpty())
348 //remove the old metacontact. (this delete the MC)
349 ContactList::self()->removeMetaContact(old
);
353 d
->metaContact
= m
; //i am forced to do that now if i want the next line works
354 //remove cached data for this protocol which will not be removed since we disconnected
355 protocol()->slotMetaContactAboutToSave( old
);
363 m
->addContact( this );
365 // it is necessary to call this write here, because MetaContact::addContact() does not differentiate
366 // between adding completely new contacts (which should be written to kabc) and restoring upon restart
367 // (where no write is needed).
368 KABCPersistence::self()->write( m
);
369 connect( d
->metaContact
, SIGNAL( aboutToSave( Kopete::MetaContact
* ) ),
370 protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact
* ) ) );
375 void Contact::serialize( QMap
<QString
, QString
> &/*serializedData*/,
376 QMap
<QString
, QString
> & /* addressBookData */ )
380 bool Contact::isReachable()
382 // The default implementation returns false when offline and true
383 // otherwise. Subclass if you need more control over the process.
384 return onlineStatus().status() != OnlineStatus::Offline
;
388 void Contact::startChat()
390 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_chatwindow") );
395 void Contact::sendMessage()
397 KopeteView
*v
=manager( CanCreate
)->view(true, QString::fromLatin1("kopete_emailwindow") );
402 void Contact::execute()
404 // FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
405 if ( account()->isConnected() && isReachable() )
407 KopeteView
*v
=manager( CanCreate
)->view(true, Kopete::BehaviorSettings::self()->viewPlugin() );
413 KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry
,
414 i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
415 "until this user comes online." ), i18n( "User is Not Reachable" ) );
419 void Contact::slotDelete()
421 if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
422 i18n( "Are you sure you want to remove the contact '%1' from your contact list?" ,
423 d
->contactId
), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("list-remove-user") ), KStandardGuiItem::cancel(),
424 QString::fromLatin1("askRemoveContact"), KMessageBox::Notify
| KMessageBox::Dangerous
)
425 == KMessageBox::Continue
)
427 Kopete::DeleteContactTask
*deleteTask
= new Kopete::DeleteContactTask(this);
432 void Contact::deleteContact()
434 // Default implementation simply deletes the contact
439 MetaContact
* Contact::metaContact() const
441 return d
->metaContact
;
444 QString
Contact::contactId() const
449 Protocol
* Contact::protocol() const
451 return d
->account
? d
->account
->protocol() : 0L;
454 Account
* Contact::account() const
461 void Contact::sync(unsigned int)
463 /* Default implementation does nothing */
466 QString
& Contact::icon() const
471 void Contact::setIcon( const QString
& icon
)
477 QList
<KAction
*> *Contact::customContextMenuActions()
482 QList
<KAction
*> *Contact::customContextMenuActions( ChatSession
* /* manager */ )
484 return customContextMenuActions();
487 bool Contact::isOnline() const
489 return onlineStatus().isDefinitelyOnline();
493 bool Contact::isFileCapable() const
495 return d
->fileCapable
;
498 void Contact::setFileCapable( bool filecap
)
500 if ( d
->fileCapable
!= filecap
)
502 d
->fileCapable
= filecap
;
503 emit
canAcceptFilesChanged();
507 bool Contact::canAcceptFiles() const
509 return isOnline() && d
->fileCapable
;
512 unsigned long int Contact::idleTime() const
517 return d
->idleTime
+(d
->idleTimer
.elapsed()/1000);
520 void Contact::setIdleTime( unsigned long int t
)
522 bool idleChanged
= false;
527 d
->idleTimer
.start();
528 //FIXME: if t == 0, idleTime() will now return garbage
530 // d->idleTimer.stop();
532 emit
idleStateChanged(this);
535 QString
Contact::toolTip() const
539 QStringList shownProps
= Kopete::AppearanceSettings::self()->toolTipContents();
541 // --------------------------------------------------------------------------
542 // Fixed part of tooltip
545 if ( this == account()->myself() )
547 iconName
= QString::fromLatin1("kopete-account-icon:%1:%2")
548 .arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
549 QString(QUrl::toPercentEncoding( account()->accountId() )) );
554 iconName
= QString::fromLatin1("kopete-contact-icon:%1:%2:%3")
555 .arg( QString(QUrl::toPercentEncoding( protocol()->pluginId() )),
556 QString(QUrl::toPercentEncoding( account()->accountId() )),
557 QString(QUrl::toPercentEncoding( contactId() )) );
560 // TODO: the nickname should be a configurable properties, like others. -Olivier
561 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
562 if ( nick
.isEmpty() )
564 tip
= i18nc( "@label:textbox %3 is contact-display-name, %1 is its status",
565 "<b><nobr>%3</nobr></b><br /><img src=\"%2\"> %1",
566 Kopete::Message::escape( onlineStatus().description() ), iconName
,
567 Kopete::Message::escape( d
->contactId
) );
571 tip
= i18nc( "@label:textbox %4 is contact-display-name, %3 is contact-id, %1 is its status",
572 "<nobr><b>%4</b> (%3)</nobr><br /><img src=\"%2\"> %1",
573 Kopete::Message::escape( onlineStatus().description() ), iconName
,
574 Kopete::Message::escape( contactId() ),
575 Kopete::Emoticons::parseEmoticons( Kopete::Message::escape( nick
) ) );
578 // --------------------------------------------------------------------------
579 // Configurable part of tooltip
581 // FIXME: It shouldn't use QString to identity the properties. Instead it should use PropertyTmpl::key()
582 for(QStringList::Iterator it
=shownProps
.begin(); it
!=shownProps
.end(); ++it
)
584 if((*it
) == Kopete::Global::Properties::self()->fullName().key())
586 QString name
= formattedName();
589 tip
+= i18nc("@label:textbox formatted name",
590 "<br /><b>Full Name:</b> <nobr>%1</nobr>", Qt::escape(name
));
593 else if ((*it
) == Kopete::Global::Properties::self()->idleTime().key())
595 QString time
= formattedIdleTime();
598 tip
+= i18nc("@label:textbox formatted idle time",
599 "<br /><b>Idle:</b> <nobr>%1</nobr>", time
);
602 else if ((*it
) == QString::fromLatin1("homePage"))
604 QString url
= property(*it
).value().toString();
607 tip
+= i18nc("@label:textbox formatted url",
608 "<br /><b>Home Page:</b> <a href=\"%1\"><nobr>%2</nobr></a>",
609 QString(QUrl::toPercentEncoding( url
)), Kopete::Message::escape( Qt::escape(url
) ) );
612 else if ((*it
) == Kopete::Global::Properties::self()->statusTitle().key() )
614 QString statusTitle
= property(*it
).value().toString();
615 if(!statusTitle
.isEmpty())
617 tip
+= i18nc("@label:textbox formatted status title",
618 "<br /><b>Status Title:</b> %1", Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(statusTitle
) ) );
621 else if ((*it
) == Kopete::Global::Properties::self()->statusMessage().key() )
623 QString statusmsg
= property(*it
).value().toString();
624 if(!statusmsg
.isEmpty())
626 tip
+= i18nc("@label:textbox formatted status message",
627 "<br /><b>Status Message:</b> %1", Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(statusmsg
) ) );
635 QVariant val
= p
.value();
640 case QVariant::DateTime
:
641 valueText
= KGlobal::locale()->formatDateTime(val
.toDateTime());
642 valueText
= Kopete::Message::escape( valueText
);
645 valueText
= KGlobal::locale()->formatDate(val
.toDate());
646 valueText
= Kopete::Message::escape( valueText
);
649 valueText
= KGlobal::locale()->formatTime(val
.toTime());
650 valueText
= Kopete::Message::escape( valueText
);
655 valueText
= val
.toString();
659 valueText
= Kopete::Message::escape( val
.toString() );
663 tip
+= i18nc("@label:textbox property label %2 is name, %1 is value",
664 "<br /><nobr><b>%2:</b></nobr> %1",
665 valueText
, Qt::escape(p
.tmpl().label()) );
673 QString
Kopete::Contact::formattedName() const
675 if( hasProperty( Kopete::Global::Properties::self()->fullName().key() ) )
676 return property( Kopete::Global::Properties::self()->fullName() ).value().toString();
679 Kopete::Property first
, last
;
681 first
= property( Kopete::Global::Properties::self()->firstName() );
682 last
= property( Kopete::Global::Properties::self()->lastName() );
685 if(!last
.isNull()) // contact has both first and last name
687 ret
= i18nc("firstName lastName", "%2 %1",
688 last
.value().toString(),
689 first
.value().toString());
691 else // only first name set
693 ret
= first
.value().toString();
696 else if(!last
.isNull()) // only last name set
698 ret
= last
.value().toString();
704 QString
Kopete::Contact::formattedIdleTime() const
707 unsigned long int leftTime
= idleTime();
710 { // FIXME: duplicated from code in kopetecontact listview.cpp
711 unsigned long int days
, hours
, mins
, secs
;
713 days
= leftTime
/ ( 60*60*24 );
714 leftTime
= leftTime
% ( 60*60*24 );
715 hours
= leftTime
/ ( 60*60 );
716 leftTime
= leftTime
% ( 60*60 );
717 mins
= leftTime
/ 60;
718 secs
= leftTime
% 60;
722 ret
= i18nc( "<days>d <hours>h <minutes>m <seconds>s",
729 else if ( hours
!= 0 )
731 ret
= i18nc( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ,
738 // xgettext: no-c-format
739 ret
= i18nc( "<minutes>m <seconds>s", "%2m %1s" ,
747 void Kopete::Contact::slotBlock()
749 account()->block( d
->contactId
);
752 void Kopete::Contact::slotUnblock()
754 account()->unblock( d
->contactId
);
757 void Kopete::Contact::setNickName( const QString
&name
)
759 QString oldNickName
= nickName();
760 setProperty( Kopete::Global::Properties::self()->nickName(), name
);
763 QString
Kopete::Contact::nickName() const
765 QString nick
= property( Kopete::Global::Properties::self()->nickName() ).value().toString();
766 if( !nick
.isEmpty() )
772 void Kopete::Contact::setPhoto(const QString
&photoPath
)
774 setProperty( Kopete::Global::Properties::self()->photo(), photoPath
);
778 } //END namespace Kopete
780 #include "kopetecontact.moc"