Show invite menu in wlm chat window immediately
[kdenetwork.git] / kopete / libkopete / kopetecontact.cpp
blob35d54e2441fcc7e9bda2361afda86340d8f4bc1e
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>
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
57 #include <qlabel.h>
58 #include <qimage.h>
59 #include <qmime.h>
60 #include <kvbox.h>
61 #include <k3listview.h>
62 #include <qcheckbox.h>
65 namespace Kopete {
67 class Contact::Private
69 public:
70 bool fileCapable;
72 OnlineStatus onlineStatus;
73 Account *account;
75 MetaContact *metaContact;
77 QString contactId;
78 QString icon;
80 QTime idleTimer;
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;
95 d->account = account;
96 d->idleTime = 0;
97 d->icon = icon;
99 // If can happend that a MetaContact may be used without a account
100 // (ex: for unit tests or chat window style preview)
101 if ( account )
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 );
119 Contact::~Contact()
121 //kDebug(14010) ;
122 emit( contactDestroyed( this ) );
123 delete d;
128 OnlineStatus Contact::onlineStatus() const
130 if ( this == account()->myself() || account()->isConnected() )
131 return d->onlineStatus;
132 else
133 return protocol()->accountOfflineStatus();
136 void Contact::setOnlineStatus( const OnlineStatus &status )
138 if( status == d->onlineStatus )
139 return;
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() );
185 else
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() );
191 else
192 removeProperty( Kopete::Global::Properties::self()->statusMessage() );
195 void Contact::slotAccountIsConnectedChanged()
197 if ( this == account()->myself() )
198 return;
200 if ( account()->isConnected() )
201 emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() );
202 else
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()
216 if( metaContact() )
218 metaContact()->setTemporary( false );
219 ContactList::self()->addMetaContact( metaContact() );
223 KMenu* Contact::popupMenu( ChatSession *manager )
225 KMenu *menu = new KMenu();
227 QString titleText;
228 QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
229 if( nick.isEmpty() )
230 titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
231 else
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 );
287 else
288 KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu, "actionBlockContact" )->plug( menu );
289 #endif
291 if( metaContact() && !metaContact()->isTemporary() )
292 menu->addAction( KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu ) );
294 return 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);
325 if( 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
338 return;
340 if( old )
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);
351 else
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 );
359 d->metaContact = m;
361 if( m )
363 m->addContact( this );
364 setParent( m );
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 * ) ) );
372 sync();
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") );
391 if(v)
392 v->raise(true);
395 void Contact::sendMessage()
397 KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") );
398 if(v)
399 v->raise(true);
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() );
408 if(v)
409 v->raise(true);
411 else
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);
428 deleteTask->start();
432 void Contact::deleteContact()
434 // Default implementation simply deletes the contact
435 deleteLater();
439 MetaContact * Contact::metaContact() const
441 return d->metaContact;
444 QString Contact::contactId() const
446 return d->contactId;
449 Protocol * Contact::protocol() const
451 return d->account ? d->account->protocol() : 0L;
454 Account * Contact::account() const
456 return d->account;
461 void Contact::sync(unsigned int)
463 /* Default implementation does nothing */
466 QString& Contact::icon() const
468 return d->icon;
471 void Contact::setIcon( const QString& icon )
473 d->icon = icon;
474 return;
477 QList<KAction *> *Contact::customContextMenuActions()
479 return 0L;
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
514 if(d->idleTime==0)
515 return 0;
517 return d->idleTime+(d->idleTimer.elapsed()/1000);
520 void Contact::setIdleTime( unsigned long int t )
522 bool idleChanged = false;
523 if(d->idleTime != t)
524 idleChanged = true;
525 d->idleTime=t;
526 if(t > 0)
527 d->idleTimer.start();
528 //FIXME: if t == 0, idleTime() will now return garbage
529 // else
530 // d->idleTimer.stop();
531 if(idleChanged)
532 emit idleStateChanged(this);
535 QString Contact::toolTip() const
537 Kopete::Property p;
538 QString tip;
539 QStringList shownProps = Kopete::AppearanceSettings::self()->toolTipContents();
541 // --------------------------------------------------------------------------
542 // Fixed part of tooltip
544 QString iconName;
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() )) );
552 else
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\">&nbsp;%1",
566 Kopete::Message::escape( onlineStatus().description() ), iconName,
567 Kopete::Message::escape( d->contactId ) );
569 else
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\">&nbsp;%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();
587 if(!name.isEmpty())
589 tip += i18nc("@label:textbox formatted name",
590 "<br /><b>Full Name:</b>&nbsp;<nobr>%1</nobr>", Qt::escape(name));
593 else if ((*it) == Kopete::Global::Properties::self()->idleTime().key())
595 QString time = formattedIdleTime();
596 if(!time.isEmpty())
598 tip += i18nc("@label:textbox formatted idle time",
599 "<br /><b>Idle:</b>&nbsp;<nobr>%1</nobr>", time);
602 else if ((*it) == QString::fromLatin1("homePage"))
604 QString url = property(*it).value().toString();
605 if(!url.isEmpty())
607 tip += i18nc("@label:textbox formatted url",
608 "<br /><b>Home Page:</b>&nbsp;<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&nbsp;Title:</b>&nbsp;%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&nbsp;Message:</b>&nbsp;%1", Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(statusmsg) ) );
630 else
632 p = property(*it);
633 if(!p.isNull())
635 QVariant val = p.value();
636 QString valueText;
638 switch(val.type())
640 case QVariant::DateTime:
641 valueText = KGlobal::locale()->formatDateTime(val.toDateTime());
642 valueText = Kopete::Message::escape( valueText );
643 break;
644 case QVariant::Date:
645 valueText = KGlobal::locale()->formatDate(val.toDate());
646 valueText = Kopete::Message::escape( valueText );
647 break;
648 case QVariant::Time:
649 valueText = KGlobal::locale()->formatTime(val.toTime());
650 valueText = Kopete::Message::escape( valueText );
651 break;
652 default:
653 if( p.isRichText() )
655 valueText = val.toString();
657 else
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>&nbsp;%1",
665 valueText, Qt::escape(p.tmpl().label()) );
670 return tip;
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();
678 QString ret;
679 Kopete::Property first, last;
681 first = property( Kopete::Global::Properties::self()->firstName() );
682 last = property( Kopete::Global::Properties::self()->lastName() );
683 if(!first.isNull())
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();
701 return ret;
704 QString Kopete::Contact::formattedIdleTime() const
706 QString ret;
707 unsigned long int leftTime = idleTime();
709 if ( leftTime > 0 )
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;
720 if ( days != 0 )
722 ret = i18nc( "<days>d <hours>h <minutes>m <seconds>s",
723 "%4d %3h %2m %1s" ,
724 secs ,
725 mins ,
726 hours ,
727 days );
729 else if ( hours != 0 )
731 ret = i18nc( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" ,
732 secs ,
733 mins ,
734 hours );
736 else
738 // xgettext: no-c-format
739 ret = i18nc( "<minutes>m <seconds>s", "%2m %1s" ,
740 secs ,
741 mins );
744 return ret;
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() )
767 return nick;
769 return contactId();
772 void Kopete::Contact::setPhoto(const QString &photoPath)
774 setProperty( Kopete::Global::Properties::self()->photo(), photoPath );
778 } //END namespace Kopete
780 #include "kopetecontact.moc"