make webinterface translatable. there are around 20 short strings, all with context...
[kdenetwork.git] / kopete / libkopete / kopetecontact.cpp
blob2f0aa93ac3ee0eefdf87dc09609c9d212e68a974
1 /*
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 *************************************************************************
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 *************************************************************************
20 #include "kopetecontact.h"
22 #include <QApplication>
23 #include <QTextDocument>
24 #include <QTimer>
26 #include <KDebug>
28 #include <kdeversion.h>
29 #include <kinputdialog.h>
31 #include <kabcpersistence.h>
32 #include <kdialog.h>
33 #include <klocale.h>
34 #include <kicon.h>
35 #include <kmenu.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
58 #include <qlabel.h>
59 #include <qimage.h>
60 #include <qmime.h>
61 #include <kvbox.h>
62 #include <k3listview.h>
63 #include <qcheckbox.h>
66 namespace Kopete {
68 class Contact::Private
70 public:
71 bool fileCapable;
73 OnlineStatus onlineStatus;
74 Account *account;
76 MetaContact *metaContact;
78 QString contactId;
79 QString icon;
81 QTime idleTimer;
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 )
91 d = new Private;
93 //kDebug( 14010 ) << "Creating contact with id " << contactId;
95 d->contactId = contactId;
96 d->metaContact = parent;
97 d->fileCapable = false;
98 d->account = account;
99 d->idleTime = 0;
100 d->icon = icon;
102 // If can happend that a MetaContact may be used without a account
103 // (ex: for unit tests or chat window style preview)
104 if ( account )
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 );
122 Contact::~Contact()
124 //kDebug(14010) ;
125 emit( contactDestroyed( this ) );
126 delete d;
131 OnlineStatus Contact::onlineStatus() const
133 if ( this == account()->myself() || account()->isConnected() )
134 return d->onlineStatus;
135 else
136 return protocol()->accountOfflineStatus();
139 void Contact::setOnlineStatus( const OnlineStatus &status )
141 if( status == d->onlineStatus )
142 return;
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() );
184 else
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() );
190 else
191 removeProperty( Kopete::Global::Properties::self()->statusMessage() );
194 void Contact::slotAccountIsConnectedChanged()
196 if ( this == account()->myself() )
197 return;
199 if ( account()->isConnected() )
200 emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() );
201 else
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()
215 if( metaContact() )
217 metaContact()->setTemporary( false );
218 ContactList::self()->addMetaContact( metaContact() );
222 KMenu* Contact::popupMenu( ChatSession *manager )
224 KMenu *menu = new KMenu();
226 QString titleText;
227 QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
228 if( nick.isEmpty() )
229 titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
230 else
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 );
286 else
287 KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu, "actionBlockContact" )->plug( menu );
288 #endif
290 if( metaContact() && !metaContact()->isTemporary() )
291 menu->addAction( KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), 0, "actionDeleteContact" ) );
293 return menu;
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);
324 if( 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
337 return;
339 if( old )
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);
350 else
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 );
358 d->metaContact = m;
360 if( m )
362 m->addContact( this );
363 setParent( m );
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 * ) ) );
371 sync();
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") );
390 if(v)
391 v->raise(true);
394 void Contact::sendMessage()
396 KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") );
397 if(v)
398 v->raise(true);
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() );
407 if(v)
408 v->raise(true);
410 else
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);
427 deleteTask->start();
431 void Contact::deleteContact()
433 // Default implementation simply deletes the contact
434 deleteLater();
438 MetaContact * Contact::metaContact() const
440 return d->metaContact;
443 QString Contact::contactId() const
445 return d->contactId;
448 Protocol * Contact::protocol() const
450 return d->account ? d->account->protocol() : 0L;
453 Account * Contact::account() const
455 return d->account;
460 void Contact::sync(unsigned int)
462 /* Default implementation does nothing */
465 QString& Contact::icon() const
467 return d->icon;
470 void Contact::setIcon( const QString& icon )
472 d->icon = icon;
473 return;
476 QList<KAction *> *Contact::customContextMenuActions()
478 return 0L;
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
510 if(d->idleTime==0)
511 return 0;
513 return d->idleTime+(d->idleTimer.elapsed()/1000);
516 void Contact::setIdleTime( unsigned long int t )
518 bool idleChanged = false;
519 if(d->idleTime != t)
520 idleChanged = true;
521 d->idleTime=t;
522 if(t > 0)
523 d->idleTimer.start();
524 //FIXME: if t == 0, idleTime() will now return garbage
525 // else
526 // d->idleTimer.stop();
527 if(idleChanged)
528 emit idleStateChanged(this);
531 QString Contact::toolTip() const
533 Kopete::Property p;
534 QString tip;
535 QStringList shownProps = Kopete::AppearanceSettings::self()->toolTipContents();
537 // --------------------------------------------------------------------------
538 // Fixed part of tooltip
540 QString iconName;
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() )) );
548 else
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\">&nbsp;%1",
562 Kopete::Message::escape( onlineStatus().description() ), iconName,
563 Kopete::Message::escape( d->contactId ) );
565 else
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\">&nbsp;%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();
583 if(!name.isEmpty())
585 tip += i18nc("@label:textbox formatted name",
586 "<br /><b>Full Name:</b>&nbsp;<nobr>%1</nobr>", Qt::escape(name));
589 else if ((*it) == QString::fromLatin1("FormattedIdleTime"))
591 QString time = formattedIdleTime();
592 if(!time.isEmpty())
594 tip += i18nc("@label:textbox formatted idle time",
595 "<br /><b>Idle:</b>&nbsp;<nobr>%1</nobr>", time);
598 else if ((*it) == QString::fromLatin1("homePage"))
600 QString url = property(*it).value().toString();
601 if(!url.isEmpty())
603 tip += i18nc("@label:textbox formatted url",
604 "<br /><b>Home Page:</b>&nbsp;<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&nbsp;Title:</b>&nbsp;%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&nbsp;Message:</b>&nbsp;%1", Kopete::Emoticons::self()->theme().parseEmoticons( Kopete::Message::escape(statusmsg) ) );
626 else
628 p = property(*it);
629 if(!p.isNull())
631 QVariant val = p.value();
632 QString valueText;
634 switch(val.type())
636 case QVariant::DateTime:
637 valueText = KGlobal::locale()->formatDateTime(val.toDateTime());
638 valueText = Kopete::Message::escape( valueText );
639 break;
640 case QVariant::Date:
641 valueText = KGlobal::locale()->formatDate(val.toDate());
642 valueText = Kopete::Message::escape( valueText );
643 break;
644 case QVariant::Time:
645 valueText = KGlobal::locale()->formatTime(val.toTime());
646 valueText = Kopete::Message::escape( valueText );
647 break;
648 default:
649 if( p.isRichText() )
651 valueText = val.toString();
653 else
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>&nbsp;%1",
661 valueText, Qt::escape(p.tmpl().label()) );
666 return tip;
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();
674 QString ret;
675 Kopete::Property first, last;
677 first = property( Kopete::Global::Properties::self()->firstName() );
678 last = property( Kopete::Global::Properties::self()->lastName() );
679 if(!first.isNull())
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();
697 return ret;
700 QString Kopete::Contact::formattedIdleTime() const
702 QString ret;
703 unsigned long int leftTime = idleTime();
705 if ( leftTime > 0 )
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;
716 if ( days != 0 )
718 ret = i18nc( "<days>d <hours>h <minutes>m <seconds>s",
719 "%4d %3h %2m %1s" ,
720 secs ,
721 mins ,
722 hours ,
723 days );
725 else if ( hours != 0 )
727 ret = i18nc( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ,
728 secs ,
729 mins ,
730 hours );
732 else
734 // xgettext: no-c-format
735 ret = i18nc( "<minutes>m <seconds>s", "%2m %1s" ,
736 secs ,
737 mins );
740 return ret;
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() )
763 return nick;
765 return contactId();
768 void Kopete::Contact::setPhoto(const QString &photoPath)
770 setProperty( Kopete::Global::Properties::self()->photo(), photoPath );
774 } //END namespace Kopete
776 #include "kopetecontact.moc"