You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdenetwork/kopete/protocols/jabber/jabbercontact.cpp

1329 lines
43 KiB

/*
* jabbercontact.cpp - Regular Kopete Jabber protocol contact
*
* Copyright (c) 2002-2004 by Till Gerken <till@tantalo.net>
* Copyright (c) 2006 by Olivier Goffart <ogoffart at kde.org>
*
* Kopete (c) by the Kopete developers <kopete-devel@kde.org>
*
* *************************************************************************
* * *
* * This program is free software; you can redistribute it and/or modify *
* * it under the terms of the GNU General Public License as published by *
* * the Free Software Foundation; either version 2 of the License, or *
* * (at your option) any later version. *
* * *
* *************************************************************************
*/
#include "jabbercontact.h"
#include "xmpp_tasks.h"
#include "im.h"
#include <tqtimer.h>
#include <tqdatetime.h>
#include <tqstylesheet.h>
#include <tqimage.h>
#include <tqregexp.h>
#include <tqbuffer.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kfiledialog.h>
#include <kaction.h>
#include <kapplication.h>
#include <kstandarddirs.h>
#include <kio/netaccess.h>
#include <kinputdialog.h>
#include <kopeteview.h>
#include "kopetecontactlist.h"
#include "kopetegroup.h"
#include "kopeteuiglobal.h"
#include "kopetechatsessionmanager.h"
#include "kopeteaccountmanager.h"
#include "jabberprotocol.h"
#include "jabberaccount.h"
#include "jabberclient.h"
#include "jabberchatsession.h"
#include "jabberresource.h"
#include "jabberresourcepool.h"
#include "jabberfiletransfer.h"
#include "jabbertransport.h"
#include "dlgjabbervcard.h"
#ifdef SUPPORT_JINGLE
// #include "jinglesessionmanager.h"
// #include "jinglevoicesession.h"
#include "jinglevoicesessiondialog.h"
#endif
/**
* JabberContact constructor
*/
JabberContact::JabberContact (const XMPP::RosterItem &rosterItem, Kopete::Account *_account, Kopete::MetaContact * mc, const TQString &legacyId)
: JabberBaseContact ( rosterItem, _account, mc, legacyId) , mDiscoDone(false), m_syncTimer(0L)
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is created - " << this << endl;
// this contact is able to transfer files
setFileCapable ( true );
/*
* Catch when we're going online for the first time to
* update our properties from a vCard. (properties are
* not available during startup, so we need to read
* them later - this also serves as a random update
* feature)
* Note: The only time account->myself() could be a
* NULL pointer is if this contact here is the myself()
* instance itself. Since in that case we wouldn't
* get updates at all, we need to treat that as a
* special case.
*/
mVCardUpdateInProgress = false;
if ( !account()->myself () )
{
// this contact is a regular contact
connect ( this,
TQT_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ),
this, TQT_SLOT ( slotCheckVCard () ) );
}
else
{
// this contact is the myself instance
connect ( account()->myself (),
TQT_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ),
this, TQT_SLOT ( slotCheckVCard () ) );
connect ( account()->myself (),
TQT_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ),
this, TQT_SLOT ( slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) );
/*
* Trigger update once if we're already connected for contacts
* that are being added while we are online.
*/
if ( account()->myself()->onlinetqStatus().isDefinitelyOnline() )
{
slotGetTimedVCard ();
}
}
mRequestOfflineEvent = false;
mRequestDisplayedEvent = false;
mRequestDeliveredEvent = false;
mRequestComposingEvent = false;
mRequestGoneEvent = false;
}
JabberContact::~JabberContact()
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << contactId() << " is destroyed - " << this << endl;
}
TQPtrList<KAction> *JabberContact::customContextMenuActions ()
{
TQPtrList<KAction> *actionCollection = new TQPtrList<KAction>();
KActionMenu *actionAuthorization = new KActionMenu ( i18n ("Authorization"), "connect_established", this, "jabber_authorization");
KAction *resendAuthAction, *requestAuthAction, *removeAuthAction;
resendAuthAction = new KAction (i18n ("(Re)send Authorization To"), "mail_forward", 0,
this, TQT_SLOT (slotSendAuth ()), actionAuthorization, "actionSendAuth");
resendAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::To || mRosterItem.subscription().type() == XMPP::Subscription::None );
actionAuthorization->insert(resendAuthAction);
requestAuthAction = new KAction (i18n ("(Re)request Authorization From"), "mail_reply", 0,
this, TQT_SLOT (slotRequestAuth ()), actionAuthorization, "actionRequestAuth");
requestAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::From || mRosterItem.subscription().type() == XMPP::Subscription::None );
actionAuthorization->insert(requestAuthAction);
removeAuthAction = new KAction (i18n ("Remove Authorization From"), "mail_delete", 0,
this, TQT_SLOT (slotRemoveAuth ()), actionAuthorization, "actionRemoveAuth");
removeAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From );
actionAuthorization->insert(removeAuthAction);
KActionMenu *actionSetAvailability = new KActionMenu (i18n ("Set Availability"), "kopeteavailable", this, "jabber_online");
actionSetAvailability->insert(new KAction (i18n ("Online"), protocol()->JabberKOSOnline.iconFor(this),
0, this, TQT_SLOT (slotStatusOnline ()), actionSetAvailability, "actionOnline"));
actionSetAvailability->insert(new KAction (i18n ("Free to Chat"), protocol()->JabberKOSChatty.iconFor(this),
0, this, TQT_SLOT (slotStatusChatty ()), actionSetAvailability, "actionChatty"));
actionSetAvailability->insert(new KAction (i18n ("Away"), protocol()->JabberKOSAway.iconFor(this),
0, this, TQT_SLOT (slotStatusAway ()), actionSetAvailability, "actionAway"));
actionSetAvailability->insert(new KAction (i18n ("Extended Away"), protocol()->JabberKOSXA.iconFor(this),
0, this, TQT_SLOT (slotStatusXA ()), actionSetAvailability, "actionXA"));
actionSetAvailability->insert(new KAction (i18n ("Do Not Disturb"), protocol()->JabberKOSDND.iconFor(this),
0, this, TQT_SLOT (slotStatusDND ()), actionSetAvailability, "actionDND"));
actionSetAvailability->insert(new KAction (i18n ("Invisible"), protocol()->JabberKOSInvisible.iconFor(this),
0, this, TQT_SLOT (slotStatusInvisible ()), actionSetAvailability, "actionInvisible"));
KActionMenu *actionSelectResource = new KActionMenu (i18n ("Select Resource"), "connect_no", this, "actionSelectResource");
// if the contact is online, display the resources we have for it,
// otherwise disable the menu
if (onlinetqStatus ().status () == Kopete::OnlineStatus::Offline)
{
actionSelectResource->setEnabled ( false );
}
else
{
TQStringList items;
XMPP::ResourceList availableResources;
int activeItem = 0, i = 1;
const XMPP::Resource lockedResource = account()->resourcePool()->lockedResource ( mRosterItem.jid () );
// put default resource first
items.append (i18n ("Automatic (best/default resource)"));
account()->resourcePool()->findResources ( mRosterItem.jid (), availableResources );
XMPP::ResourceList::const_iterator resourcesEnd = availableResources.end ();
for ( XMPP::ResourceList::const_iterator it = availableResources.begin(); it != resourcesEnd; ++it, i++)
{
items.append ( (*it).name() );
if ( (*it).name() == lockedResource.name() )
activeItem = i;
}
// now go through the string list and add the resources with their icons
i = 0;
TQStringList::const_iterator itemsEnd = items.end ();
for(TQStringList::const_iterator it = items.begin(); it != itemsEnd; ++it)
{
if( i == activeItem )
{
actionSelectResource->insert ( new KAction( ( *it ), "button_ok", 0, this, TQT_SLOT( slotSelectResource() ),
actionSelectResource, TQString::number( i ).latin1() ) );
}
else
{
/*
* Select icon, using bestResource() without lock for the automatic entry
* and the resources' respective status icons for the rest.
*/
TQIconSet iconSet ( !i ?
protocol()->resourceToKOS ( account()->resourcePool()->bestResource ( mRosterItem.jid(), false ) ).iconFor ( account () ) : protocol()->resourceToKOS ( *availableResources.tqfind(*it) ).iconFor ( account () ));
actionSelectResource->insert ( new KAction( ( *it ), iconSet, 0, this, TQT_SLOT( slotSelectResource() ),
actionSelectResource, TQString::number( i ).latin1() ) );
}
i++;
}
}
actionCollection->append( actionAuthorization );
actionCollection->append( actionSetAvailability );
actionCollection->append( actionSelectResource );
#ifdef SUPPORT_JINGLE
KAction *actionVoiceCall = new KAction (i18n ("Voice call"), "voicecall", 0, this, TQT_SLOT (voiceCall ()), this, "jabber_voicecall");
actionVoiceCall->setEnabled( false );
actionCollection->append( actionVoiceCall );
// Check if the current contact support Voice calls, also honour lock by default.
JabberResource *bestResource = account()->resourcePool()->bestJabberResource( mRosterItem.jid() );
if( bestResource && bestResource->features().canVoice() )
{
actionVoiceCall->setEnabled( true );
}
#endif
return actionCollection;
}
void JabberContact::handleIncomingMessage (const XMPP::Message & message)
{
TQString viewPlugin;
Kopete::Message *newMessage = 0L;
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Received Message Type:" << message.type () << endl;
// fetch message manager
JabberChatSession *mManager = manager ( message.from().resource (), Kopete::Contact::CanCreate );
// evaluate notifications
if ( message.type () != "error" )
{
if (!message.invite().isEmpty())
{
TQString room=message.invite();
TQString originalBody=message.body().isEmpty() ? TQString() :
i18n( "The original message is : <i>\" %1 \"</i><br>" ).tqarg(TQStyleSheet::escape(message.body()));
TQString mes=i18n("<qt><i>%1</i> invited you to join the conference <b>%2</b><br>%3<br>"
"If you want to accept and join, just <b>enter your nickname</b> and press ok<br>"
"If you want to decline, press cancel</qt>")
.tqarg(message.from().full(), room , originalBody);
bool ok=false;
TQString futureNewNickName = KInputDialog::getText( i18n( "Invited to a conference - Jabber Plugin" ),
mes, TQString() , &ok , (mManager ? dynamic_cast<TQWidget*>(mManager->view(false)) : 0) );
if ( !ok || !account()->isConnected() || futureNewNickName.isEmpty() )
return;
XMPP::Jid roomjid(room);
account()->client()->joinGroupChat( roomjid.host() , roomjid.user() , futureNewNickName );
return;
}
else if (message.body().isEmpty())
// Then here could be event notifications
{
if (message.containsEvent ( XMPP::CancelEvent ) )
mManager->receivedTypingMsg ( this, false );
else if (message.containsEvent ( XMPP::ComposingEvent ) )
mManager->receivedTypingMsg ( this, true );
else if (message.containsEvent ( XMPP::DisplayedEvent ) )
mManager->receivedEventNotification ( i18n("Message has been displayed") );
else if (message.containsEvent ( XMPP::DeliveredEvent ) )
mManager->receivedEventNotification ( i18n("Message has been delivered") );
else if (message.containsEvent ( XMPP::OfflineEvent ) )
{
mManager->receivedEventNotification( i18n("Message stored on the server, contact offline") );
}
else if (message.containsEvent ( XMPP::GoneEvent ) )
{
if(mManager->view( Kopete::Contact::CannotCreate ))
{ //show an internal message if the user has not already closed his window
Kopete::Message m=Kopete::Message ( this, mManager->members(),
i18n("%1 has ended their participation in the chat session.").tqarg(metaContact()->displayName()),
Kopete::Message::Internal );
m.setImportance(Kopete::Message::Low);
mManager->view()->appendMessage ( m ); //use KopeteView::AppendMessage to bypass notifications
}
}
}
else
// Then here could be event notification requests
{
mRequestComposingEvent = message.containsEvent ( XMPP::ComposingEvent );
mRequestOfflineEvent = message.containsEvent ( XMPP::OfflineEvent );
mRequestDeliveredEvent = message.containsEvent ( XMPP::DeliveredEvent );
mRequestDisplayedEvent = message.containsEvent ( XMPP::DisplayedEvent);
mRequestGoneEvent= message.containsEvent ( XMPP::GoneEvent);
}
}
/**
* Don't display empty messages, these were most likely just carrying
* event notifications or other payload.
*/
if ( message.body().isEmpty () && message.urlList().isEmpty () && message.xHTMLBody().isEmpty() && !message.xencrypted() )
return;
// determine message type
if (message.type () == "chat")
viewPlugin = "kopete_chatwindow";
else
viewPlugin = "kopete_emailwindow";
Kopete::ContactPtrList contactList;
contactList.append ( account()->myself () );
// check for errors
if ( message.type () == "error" )
{
newMessage = new Kopete::Message( message.timeStamp (), this, contactList,
i18n("Your message could not be delivered: \"%1\", Reason: \"%2\"").
arg ( message.body () ).arg ( message.error().text ),
message.subject(), Kopete::Message::Inbound, Kopete::Message::PlainText, viewPlugin );
}
else
{
// store message id for outgoing notifications
mLastReceivedMessageId = message.id ();
// retrieve and reformat body
TQString body = message.body ();
TQString xHTMLBody;
if( !message.xencrypted().isEmpty () )
{
body = TQString ("-----BEGIN PGP MESSAGE-----\n\n") + message.xencrypted () + TQString ("\n-----END PGP MESSAGE-----\n");
}
else
{
xHTMLBody = message.xHTMLBody ();
}
// convert XMPP::Message into Kopete::Message
if (!xHTMLBody.isEmpty()) {
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a xHTML message" << endl;
newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, xHTMLBody,
message.subject (), Kopete::Message::Inbound,
Kopete::Message::RichText, viewPlugin );
}
else if ( !body.isEmpty () )
{
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Received a plain text message" << endl;
newMessage = new Kopete::Message ( message.timeStamp (), this, contactList, body,
message.subject (), Kopete::Message::Inbound,
Kopete::Message::PlainText, viewPlugin );
}
}
// append message to (eventually new) manager and preselect the originating resource
if ( newMessage )
{
mManager->appendMessage ( *newMessage, message.from().resource () );
delete newMessage;
}
// append URLs as separate messages
if ( !message.urlList().isEmpty () )
{
/*
* We need to copy it here because Iris returns a copy
* and we can't work with a returned copy in a for() loop.
*/
XMPP::UrlList urlList = message.urlList();
for ( XMPP::UrlList::const_iterator it = urlList.begin (); it != urlList.end (); ++it )
{
TQString description = (*it).desc().isEmpty() ? (*it).url() : TQStyleSheet::escape ( (*it).desc() );
TQString url = (*it).url ();
newMessage = new Kopete::Message ( message.timeStamp (), this, contactList,
TQString ( "<a href=\"%1\">%2</a>" ).arg ( url, description ),
message.subject (), Kopete::Message::Inbound,
Kopete::Message::RichText, viewPlugin );
mManager->appendMessage ( *newMessage, message.from().resource () );
delete newMessage;
}
}
}
void JabberContact::slotCheckVCard ()
{
TQDateTime cacheDate;
Kopete::ContactProperty cacheDateString = property ( protocol()->propVCardCacheTimeStamp );
// don't do anything while we are offline
if ( !account()->myself()->onlinetqStatus().isDefinitelyOnline () )
{
return;
}
if(!mDiscoDone)
{
if(transport()) //no need to disco if this is a legacy contact
mDiscoDone = true;
else if(!rosterItem().jid().node().isEmpty())
mDiscoDone = true; //contact with an @ are not transport for sure
else
{
mDiscoDone = true; //or it will happen twice, we don't want that.
//disco to see if it's not a transport
XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask());
TQObject::connect(jt, TQT_SIGNAL(finished()),this, TQT_SLOT(slotDiscoFinished()));
jt->get(rosterItem().jid(), TQString());
jt->go(true);
}
}
// avoid warning if key does not exist in configuration file
if ( cacheDateString.isNull () )
cacheDate = TQDateTime::tqcurrentDateTime().addDays ( -2 );
else
cacheDate = TQDateTime::fromString ( cacheDateString.value().toString (), Qt::ISODate );
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Cached vCard data for " << contactId () << " from " << cacheDate.toString () << endl;
if ( !mVCardUpdateInProgress && ( cacheDate.addDays ( 1 ) < TQDateTime::tqcurrentDateTime () ) )
{
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling update." << endl;
mVCardUpdateInProgress = true;
// current data is older than 24 hours, request a new one
TQTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, TQT_SLOT ( slotGetTimedVCard () ) );
}
}
void JabberContact::slotGetTimedVCard ()
{
mVCardUpdateInProgress = false;
// check if we are still connected - eventually we lost our connection in the meantime
if ( !account()->myself()->onlinetqStatus().isDefinitelyOnline () )
{
// we are not connected, discard this update
return;
}
if(!mDiscoDone)
{
if(transport()) //no need to disco if this is a legacy contact
mDiscoDone = true;
else if(!rosterItem().jid().node().isEmpty())
mDiscoDone = true; //contact with an @ are not transport for sure
else
{
//disco to see if it's not a transport
XMPP::JT_DiscoInfo *jt = new XMPP::JT_DiscoInfo(account()->client()->rootTask());
TQObject::connect(jt, TQT_SIGNAL(finished()),this, TQT_SLOT(slotDiscoFinished()));
jt->get(rosterItem().jid(), TQString());
jt->go(true);
}
}
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting vCard for " << contactId () << " from update timer." << endl;
mVCardUpdateInProgress = true;
// request vCard
XMPP::JT_VCard *task = new XMPP::JT_VCard ( account()->client()->rootTask () );
// signal to ourselves when the vCard data arrived
TQObject::connect ( task, TQT_SIGNAL ( finished () ), this, TQT_SLOT ( slotGotVCard () ) );
task->get ( mRosterItem.jid () );
task->go ( true );
}
void JabberContact::slotGotVCard ()
{
XMPP::JT_VCard * vCard = (XMPP::JT_VCard *) sender ();
// update timestamp of last vCard retrieval
if ( metaContact() && !metaContact()->isTemporary () )
{
setProperty ( protocol()->propVCardCacheTimeStamp, TQDateTime::tqcurrentDateTime().toString ( Qt::ISODate ) );
}
mVCardUpdateInProgress = false;
if ( !vCard->success() )
{
/*
* A vCard for the user does not exist or the
* request was unsuccessful or incomplete.
* The timestamp was already updated when
* requesting the vCard, so it's safe to
* just do nothing here.
*/
return;
}
setPropertiesFromVCard ( vCard->vcard () );
}
void JabberContact::slotCheckLastActivity ( Kopete::Contact *, const Kopete::OnlineStatus &newtqStatus, const Kopete::OnlineStatus &oldtqStatus )
{
/*
* Checking the last activity only makes sense if a contact is offline.
* So, this check should only be done in the following cases:
* - Kopete goes online for the first time and this contact is offline, or
* - Kopete is already online and this contact went offline.
*
* Since Kopete already takes care of maintaining the lastSeen property
* if the contact changes its state while we are online, we don't need
* to query its activity after we are already connected.
*/
if ( onlinetqStatus().isDefinitelyOnline () )
{
// Kopete already deals with lastSeen if the contact is online
return;
}
if ( ( oldtqStatus.status () == Kopete::OnlineStatus::Connecting ) && newtqStatus.isDefinitelyOnline () )
{
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling request for last activity for " << mRosterItem.jid().bare () << endl;
TQTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, TQT_SLOT ( slotGetTimedLastActivity () ) );
}
}
void JabberContact::slotGetTimedLastActivity ()
{
/*
* We have been called from @ref slotCheckLastActivity.
* We could have lost our connection in the meantime,
* so make sure we are online. Additionally, the contact
* itself could have gone online, so make sure it is
* still offline. (otherwise the last seen property is
* maintained by Kopete)
*/
if ( onlinetqStatus().isDefinitelyOnline () )
{
// Kopete already deals with setting lastSeen if the contact is online
return;
}
if ( account()->myself()->onlinetqStatus().isDefinitelyOnline () )
{
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Requesting last activity from timer for " << mRosterItem.jid().bare () << endl;
XMPP::JT_GetLastActivity *task = new XMPP::JT_GetLastActivity ( account()->client()->rootTask () );
TQObject::connect ( task, TQT_SIGNAL ( finished () ), this, TQT_SLOT ( slotGotLastActivity () ) );
task->get ( mRosterItem.jid () );
task->go ( true );
}
}
void JabberContact::slotGotLastActivity ()
{
XMPP::JT_GetLastActivity *task = (XMPP::JT_GetLastActivity *) sender ();
if ( task->success () )
{
setProperty ( protocol()->propLastSeen, TQDateTime(TQDateTime::tqcurrentDateTime().addSecs ( -task->seconds () )));
if( !task->message().isEmpty() )
{
setProperty( protocol()->propAwayMessage, task->message() );
}
}
}
void JabberContact::slotSendVCard()
{
XMPP::VCard vCard;
XMPP::VCard::AddressList addressList;
XMPP::VCard::EmailList emailList;
XMPP::VCard::PhoneList phoneList;
if ( !account()->isConnected () )
{
account()->errorConnectFirst ();
return;
}
// General information
vCard.setNickName (property(protocol()->propNickName).value().toString());
vCard.setFullName (property(protocol()->propFullName).value().toString());
vCard.setJid (property(protocol()->propJid).value().toString());
vCard.setBdayStr (property(protocol()->propBirthday).value().toString());
vCard.setTimezone (property(protocol()->propTimezone).value().toString());
vCard.setUrl (property(protocol()->propHomepage).value().toString());
// home address tab
XMPP::VCard::Address homeAddress;
homeAddress.home = true;
homeAddress.street = property(protocol()->propHomeStreet).value().toString();
homeAddress.extaddr = property(protocol()->propHomeExtAddr).value().toString();
homeAddress.pobox = property(protocol()->propHomePOBox).value().toString();
homeAddress.locality = property(protocol()->propHomeCity).value().toString();
homeAddress.pcode = property(protocol()->propHomePostalCode).value().toString();
homeAddress.country = property(protocol()->propHomeCountry).value().toString();
// work address tab
XMPP::VCard::Address workAddress;
workAddress.work = true;
workAddress.street = property(protocol()->propWorkStreet).value().toString();
workAddress.extaddr = property(protocol()->propWorkExtAddr).value().toString();
workAddress.pobox = property(protocol()->propWorkPOBox).value().toString();
workAddress.locality = property(protocol()->propWorkCity).value().toString();
workAddress.pcode = property(protocol()->propWorkPostalCode).value().toString();
workAddress.country = property(protocol()->propWorkCountry).value().toString();
addressList.append(homeAddress);
addressList.append(workAddress);
vCard.setAddressList(addressList);
// home email
XMPP::VCard::Email homeEmail;
homeEmail.home = true;
homeEmail.userid = property(protocol()->propEmailAddress).value().toString();
// work email
XMPP::VCard::Email workEmail;
workEmail.work = true;
workEmail.userid = property(protocol()->propWorkEmailAddress).value().toString();
emailList.append(homeEmail);
emailList.append(workEmail);
vCard.setEmailList(emailList);
// work information tab
XMPP::VCard::Org org;
org.name = property(protocol()->propCompanyName).value().toString();
org.unit = TQStringList::split(",", property(protocol()->propCompanyDepartement).value().toString());
vCard.setOrg(org);
vCard.setTitle (property(protocol()->propCompanyPosition).value().toString());
vCard.setRole (property(protocol()->propCompanyRole).value().toString());
// phone numbers tab
XMPP::VCard::Phone phoneHome;
phoneHome.home = true;
phoneHome.number = property(protocol()->propPrivatePhone).value().toString();
XMPP::VCard::Phone phoneWork;
phoneWork.work = true;
phoneWork.number = property(protocol()->propWorkPhone).value().toString();
XMPP::VCard::Phone phoneFax;
phoneFax.fax = true;
phoneFax.number = property(protocol()->propPhoneFax).value().toString();
XMPP::VCard::Phone phoneCell;
phoneCell.cell = true;
phoneCell.number = property(protocol()->propPrivateMobilePhone).value().toString();
phoneList.append(phoneHome);
phoneList.append(phoneWork);
phoneList.append(phoneFax);
phoneList.append(phoneCell);
vCard.setPhoneList(phoneList);
// about tab
vCard.setDesc(property(protocol()->propAbout).value().toString());
// Set contact photo as a binary value (if he has set a photo)
if( hasProperty( protocol()->propPhoto.key() ) )
{
TQString photoPath = property( protocol()->propPhoto ).value().toString();
TQImage image( photoPath );
TQByteArray ba;
TQBuffer buffer( ba );
buffer.open( IO_WriteOnly );
image.save( &buffer, "PNG" );
vCard.setPhoto( ba );
}
vCard.setVersion("3.0");
vCard.setProdId("Kopete");
XMPP::JT_VCard *task = new XMPP::JT_VCard (account()->client()->rootTask ());
// signal to ourselves when the vCard data arrived
TQObject::connect (task, TQT_SIGNAL (finished ()), this, TQT_SLOT (slotSentVCard ()));
task->set (vCard);
task->go (true);
}
void JabberContact::setPhoto( const TQString &photoPath )
{
TQImage contactPhoto(photoPath);
TQString newPhotoPath = photoPath;
if(contactPhoto.width() > 96 || contactPhoto.height() > 96)
{
// Save image to a new location if the image isn't the correct format.
TQString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) );
// Scale and crop the picture.
contactPhoto = contactPhoto.smoothScale( 96, 96, TQ_ScaleMin );
// crop image if not square
if(contactPhoto.width() < contactPhoto.height())
contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 96, 96);
else if (contactPhoto.width() > contactPhoto.height())
contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 96, 96);
// Use the cropped/scaled image now.
if(!contactPhoto.save(newLocation, "PNG"))
newPhotoPath = TQString();
else
newPhotoPath = newLocation;
}
else if (contactPhoto.width() < 32 || contactPhoto.height() < 32)
{
// Save image to a new location if the image isn't the correct format.
TQString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) );
// Scale and crop the picture.
contactPhoto = contactPhoto.smoothScale( 32, 32, TQ_ScaleMin );
// crop image if not square
if(contactPhoto.width() < contactPhoto.height())
contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, 32, 32);
else if (contactPhoto.width() > contactPhoto.height())
contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, 32, 32);
// Use the cropped/scaled image now.
if(!contactPhoto.save(newLocation, "PNG"))
newPhotoPath = TQString();
else
newPhotoPath = newLocation;
}
else if (contactPhoto.width() != contactPhoto.height())
{
// Save image to a new location if the image isn't the correct format.
TQString newLocation( locateLocal( "appdata", "jabberphotos/"+ KURL(photoPath).fileName().lower() ) );
if(contactPhoto.width() < contactPhoto.height())
contactPhoto = contactPhoto.copy((contactPhoto.width()-contactPhoto.height())/2, 0, contactPhoto.height(), contactPhoto.height());
else if (contactPhoto.width() > contactPhoto.height())
contactPhoto = contactPhoto.copy(0, (contactPhoto.height()-contactPhoto.width())/2, contactPhoto.height(), contactPhoto.height());
// Use the cropped/scaled image now.
if(!contactPhoto.save(newLocation, "PNG"))
newPhotoPath = TQString();
else
newPhotoPath = newLocation;
}
setProperty( protocol()->propPhoto, newPhotoPath );
}
void JabberContact::slotSentVCard ()
{
}
void JabberContact::slotChatSessionDeleted ( TQObject *sender )
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Message manager deleted, collecting the pieces..." << endl;
JabberChatSession *manager = static_cast<JabberChatSession *>(sender);
mManagers.remove ( mManagers.tqfind ( manager ) );
}
JabberChatSession *JabberContact::manager ( Kopete::ContactPtrList chatMembers, Kopete::Contact::CanCreateFlags canCreate )
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl;
Kopete::ChatSession *_manager = Kopete::ChatSessionManager::self()->findChatSession ( account()->myself(), chatMembers, protocol() );
JabberChatSession *manager = dynamic_cast<JabberChatSession*>( _manager );
/*
* If we didn't find a message manager for this contact,
* instantiate a new one if we are allowed to. (otherwise return 0)
*/
if ( !manager && canCreate )
{
XMPP::Jid jid = rosterItem().jid();
/*
* If we have no hardwired JID, set any eventually
* locked resource as preselected resource.
* If there is no locked resource, the resource field
* will stay empty.
*/
if ( jid.resource().isEmpty () )
jid.setResource ( account()->resourcePool()->lockedResource ( jid ).name () );
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found, creating a new one with resource '" << jid.resource () << "'" << endl;
manager = new JabberChatSession ( protocol(), static_cast<JabberBaseContact *>(account()->myself()), chatMembers, jid.resource () );
connect ( manager, TQT_SIGNAL ( destroyed ( TQObject * ) ), this, TQT_SLOT ( slotChatSessionDeleted ( TQObject * ) ) );
mManagers.append ( manager );
}
return manager;
}
Kopete::ChatSession *JabberContact::manager ( Kopete::Contact::CanCreateFlags canCreate )
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << endl;
Kopete::ContactPtrList chatMembers;
chatMembers.append ( this );
return manager ( chatMembers, canCreate );
}
JabberChatSession *JabberContact::manager ( const TQString &resource, Kopete::Contact::CanCreateFlags canCreate )
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "called, canCreate: " << canCreate << ", Resource: '" << resource << "'" << endl;
/*
* First of all, see if we already have a manager matching
* the requested resource or if there are any managers with
* an empty resource.
*/
if ( !resource.isEmpty () )
{
for ( JabberChatSession *mManager = mManagers.first (); mManager; mManager = mManagers.next () )
{
if ( mManager->resource().isEmpty () || ( mManager->resource () == resource ) )
{
// we found a matching manager, return this one
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found an existing message manager for this resource." << endl;
return mManager;
}
}
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No manager found for this resource, creating a new one." << endl;
/*
* If we have come this far, we were either supposed to create
* a manager with a preselected resource but have found
* no available manager. (not even one with an empty resource)
* This means, we will have to create a new one with a
* preselected resource.
*/
Kopete::ContactPtrList chatmembers;
chatmembers.append ( this );
JabberChatSession *manager = new JabberChatSession ( protocol(),
static_cast<JabberBaseContact *>(account()->myself()),
chatmembers, resource );
connect ( manager, TQT_SIGNAL ( destroyed ( TQObject * ) ), this, TQT_SLOT ( slotChatSessionDeleted ( TQObject * ) ) );
mManagers.append ( manager );
return manager;
}
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Resource is empty, grabbing first available manager." << endl;
/*
* The resource is empty, so just return first available manager.
*/
return dynamic_cast<JabberChatSession *>( manager ( canCreate ) );
}
void JabberContact::deleteContact ()
{
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing user " << contactId () << endl;
if ( !account()->isConnected () )
{
account()->errorConnectFirst ();
return;
}
/*
* Follow the recommendation of
* JEP-0162: Best Practices for Roster and Subscription Management
* http://www.jabber.org/jeps/jep-0162.html#removal
*/
bool remove_from_roster=false;
if( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From )
{
int result = KMessageBox::questionYesNoCancel (Kopete::UI::Global::mainWidget(),
i18n ( "Do you also want to remove the authorization from user %1 to see your status?" ).
arg ( mRosterItem.jid().bare () ), i18n ("Notification"),
KStdGuiItem::del (), i18n("Keep"), "JabberRemoveAuthorizationOnDelete" );
if(result == KMessageBox::Yes )
remove_from_roster = true;
else if( result == KMessageBox::Cancel)
return;
}
else if( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::To )
remove_from_roster = true;
if( remove_from_roster )
{
XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () );
rosterTask->remove ( mRosterItem.jid () );
rosterTask->go ( true );
}
else
{
sendSubscription("unsubscribe");
XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () );
rosterTask->set ( mRosterItem.jid (), TQString() , TQStringList() );
rosterTask->go (true);
}
}
void JabberContact::sync ( unsigned int )
{
// if we are offline or this is a temporary contact or we should not synch, don't bother
if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () || metaContact() == Kopete::ContactList::self()->myself() )
return;
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << contactId () /*<< " - " <<kdBacktrace()*/ << endl;
if(!m_syncTimer)
{
m_syncTimer=new TQTimer(this);
connect(m_syncTimer, TQT_SIGNAL(timeout()) , this , TQT_SLOT(slotDelayedSync()));
}
m_syncTimer->start(2*1000,true);
/*
the sync operation is delayed, because when we are doing a move to group operation,
kopete first add the contact to the group, then removes it.
Theses two operations should anyway be done in only one pass.
if there is two jabber contact in one metacontact, this may result in an infinite change of
groups between theses two contacts, and the server is being flooded.
*/
}
void JabberContact::slotDelayedSync( )
{
m_syncTimer->deleteLater();
m_syncTimer=0L;
// if we are offline or this is a temporary contact or we should not synch, don't bother
if ( dontSync () || !account()->isConnected () || metaContact()->isTemporary () )
return;
bool changed=metaContact()->displayName() != mRosterItem.name();
TQStringList groups;
Kopete::GroupList groupList = metaContact ()->groups ();
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing contact " << contactId () << endl;
for ( Kopete::Group * g = groupList.first (); g; g = groupList.next () )
{
if ( g->type () != Kopete::Group::TopLevel )
groups += g->displayName ();
}
if(mRosterItem.groups() != groups)
{
changed=true;
mRosterItem.setGroups ( groups );
}
if(!changed)
{
kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "contact has not changed, abort sync" << endl;
return;
}
XMPP::JT_Roster * rosterTask = new XMPP::JT_Roster ( account()->client()->rootTask () );
rosterTask->set ( mRosterItem.jid (), metaContact()->displayName (), mRosterItem.groups () );
rosterTask->go (true);
}
void JabberContact::sendFile ( const KURL &sourceURL, const TQString &/*fileName*/, uint /*fileSize*/ )
{
TQString filePath;
// if the file location is null, then get it from a file open dialog
if ( !sourceURL.isValid () )
filePath = KFileDialog::getOpenFileName( TQString() , "*", 0L, i18n ( "Kopete File Transfer" ) );
else
filePath = sourceURL.path(-1);
TQFile file ( filePath );
if ( file.exists () )
{
// send the file
new JabberFileTransfer ( account (), this, filePath );
}
}
void JabberContact::slotSendAuth ()
{
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)send auth " << contactId () << endl;
sendSubscription ("subscribed");
}
void JabberContact::slotRequestAuth ()
{
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "(Re)request auth " << contactId () << endl;
sendSubscription ("subscribe");
}
void JabberContact::slotRemoveAuth ()
{
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Remove auth " << contactId () << endl;
sendSubscription ("unsubscribed");
}
void JabberContact::sendSubscription ( const TQString& subType )
{
if ( !account()->isConnected () )
{
account()->errorConnectFirst ();
return;
}
XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () );
task->sub ( mRosterItem.jid().full (), subType );
task->go ( true );
}
void JabberContact::slotSelectResource ()
{
int currentItem = TQString ( static_cast<const KAction *>( sender() )->name () ).toUInt ();
/*
* Warn the user if there is already an active chat window.
* The resource selection will only apply for newly opened
* windows.
*/
if ( manager ( Kopete::Contact::CannotCreate ) != 0 )
{
KMessageBox::queuedMessageBox ( Kopete::UI::Global::mainWidget (),
KMessageBox::Information,
i18n ("You have preselected a resource for contact %1, "
"but you still have open chat windows for this contact. "
"The preselected resource will only apply to newly opened "
"chat windows.").arg ( contactId () ),
i18n ("Jabber Resource Selector") );
}
if (currentItem == 0)
{
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing active resource, trusting bestResource()." << endl;
account()->resourcePool()->removeLock ( rosterItem().jid() );
}
else
{
TQString selectedResource = static_cast<const KAction *>(sender())->text();
kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Moving to resource " << selectedResource << endl;
account()->resourcePool()->lockToResource ( rosterItem().jid() , XMPP::Resource ( selectedResource ) );
}
}
void JabberContact::sendPresence ( const XMPP::tqStatus status )
{
if ( !account()->isConnected () )
{
account()->errorConnectFirst ();
return;
}
XMPP::tqStatus newtqStatus = status;
// honour our priority
if(newtqStatus.isAvailable())
newtqStatus.setPriority ( account()->configGroup()->readNumEntry ( "Priority", 5 ) );
XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () );
task->pres ( bestAddress (), newtqStatus);
task->go ( true );
}
void JabberContact::slotStatusOnline ()
{
XMPP::tqStatus status;
status.setShow("");
sendPresence ( status );
}
void JabberContact::slotStatusChatty ()
{
XMPP::tqStatus status;
status.setShow ("chat");
sendPresence ( status );
}
void JabberContact::slotStatusAway ()
{
XMPP::tqStatus status;
status.setShow ("away");
sendPresence ( status );
}
void JabberContact::slotStatusXA ()
{
XMPP::tqStatus status;
status.setShow ("xa");
sendPresence ( status );
}
void JabberContact::slotStatusDND ()
{
XMPP::tqStatus status;
status.setShow ("dnd");
sendPresence ( status );
}
void JabberContact::slotStatusInvisible ()
{
XMPP::tqStatus status;
status.setIsAvailable( false );
sendPresence ( status );
}
bool JabberContact::isContactRequestingEvent( XMPP::MsgEvent event )
{
if ( event == OfflineEvent )
return mRequestOfflineEvent;
else if ( event == DeliveredEvent )
return mRequestDeliveredEvent;
else if ( event == DisplayedEvent )
return mRequestDisplayedEvent;
else if ( event == ComposingEvent )
return mRequestComposingEvent;
else if ( event == CancelEvent )
return mRequestComposingEvent;
else if ( event == GoneEvent )
return mRequestGoneEvent;
else
return false;
}
TQString JabberContact::lastReceivedMessageId () const
{
return mLastReceivedMessageId;
}
void JabberContact::voiceCall( )
{
#ifdef SUPPORT_JINGLE
Jid jid = mRosterItem.jid();
// It's honour lock by default.
JabberResource *bestResource = account()->resourcePool()->bestJabberResource( jid );
if( bestResource )
{
if( jid.resource().isEmpty() )
{
// If the jid resource is empty, get the JID from best resource for this contact.
jid = bestResource->jid();
}
// Check if the voice caller exist and the current resource support voice.
if( account()->voiceCaller() && bestResource->features().canVoice() )
{
JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog( jid, account()->voiceCaller() );
voiceDialog->show();
voiceDialog->start();
}
#if 0
if( account()->sessionManager() && bestResource->features().canVoice() )
{
JingleVoiceSession *session = static_cast<JingleVoiceSession*>(account()->sessionManager()->createSession("http://www.google.com/session/phone", jid));
JingleVoiceSessionDialog *voiceDialog = new JingleVoiceSessionDialog(session);
voiceDialog->show();
voiceDialog->start();
}
#endif
}
else
{
// Shouldn't never go there.
}
#endif
}
void JabberContact::slotDiscoFinished( )
{
mDiscoDone = true;
JT_DiscoInfo *jt = (JT_DiscoInfo *)sender();
bool is_transport=false;
TQString tr_type;
if ( jt->success() )
{
TQValueList<XMPP::DiscoItem::Identity> identities = jt->item().identities();
TQValueList<XMPP::DiscoItem::Identity>::Iterator it;
for ( it = identities.begin(); it != identities.end(); ++it )
{
XMPP::DiscoItem::Identity ident=*it;
if(ident.category == "gateway")
{
is_transport=true;
tr_type=ident.type;
//name=ident.name;
break; //(we currently only support gateway)
}
else if (ident.category == "service")
{
//The ApaSMSAgent is reporting itself as service (instead of gateway) which is broken.
//we anyway support it. See bug 127811
if(ident.type == "sms")
{
is_transport=true;
tr_type=ident.type;
}
}
}
}
if(is_transport && !transport())
{ //ok, we are not a contact, we are a transport....
XMPP::RosterItem ri = rosterItem();
Kopete::MetaContact *mc=metaContact();
JabberAccount *tqparentAccount=account();
Kopete::OnlineStatus status=onlinetqStatus();
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << ri.jid().full() << " is not a contact but a gateway - " << this << endl;
if( Kopete::AccountManager::self()->findAccount( protocol()->pluginId() , account()->accountId() + "/" + ri.jid().bare() ) )
{
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "oops, transport already exists, abort operation " << endl;
return;
}
delete this; //we are not a contact i said !
if(mc->contacts().count() == 0)
Kopete::ContactList::self()->removeMetaContact( mc );
//we need to create the transport when 'this' is already deleted, so transport->myself() will not conflict with it
JabberTransport *transport = new JabberTransport( tqparentAccount , ri , tr_type );
if(!Kopete::AccountManager::self()->registerAccount( transport ))
return;
transport->myself()->setOnlineStatus( status ); //push back the online status
return;
}
}
#include "jabbercontact.moc"