summaryrefslogtreecommitdiffstats
path: root/kopete/protocols/jabber/jabberbasecontact.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/protocols/jabber/jabberbasecontact.cpp')
-rw-r--r--kopete/protocols/jabber/jabberbasecontact.cpp676
1 files changed, 676 insertions, 0 deletions
diff --git a/kopete/protocols/jabber/jabberbasecontact.cpp b/kopete/protocols/jabber/jabberbasecontact.cpp
new file mode 100644
index 00000000..56cc1de4
--- /dev/null
+++ b/kopete/protocols/jabber/jabberbasecontact.cpp
@@ -0,0 +1,676 @@
+ /*
+ * jabbercontact.cpp - Base class for the 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 <kdebug.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+#include <qtimer.h>
+#include <qimage.h>
+#include <qregexp.h>
+#include <kmessagebox.h>
+#include <kio/netaccess.h>
+
+
+#include <kopetegroup.h>
+#include <kopetecontactlist.h>
+
+#include "jabberbasecontact.h"
+
+#include "xmpp_tasks.h"
+
+#include "jabberprotocol.h"
+#include "jabberaccount.h"
+#include "jabberresource.h"
+#include "jabberresourcepool.h"
+#include "kopetemetacontact.h"
+#include "kopetemessage.h"
+#include "kopeteuiglobal.h"
+#include "jabbertransport.h"
+#include "dlgjabbervcard.h"
+
+
+/**
+ * JabberBaseContact constructor
+ */
+JabberBaseContact::JabberBaseContact (const XMPP::RosterItem &rosterItem, Kopete::Account *account, Kopete::MetaContact * mc, const QString &legacyId)
+ : Kopete::Contact (account, legacyId.isEmpty() ? rosterItem.jid().full() : legacyId , mc )
+{
+ setDontSync ( false );
+
+ JabberTransport *t=transport();
+ m_account= t ? t->account() : static_cast<JabberAccount *>(Kopete::Contact::account());
+
+
+ // take roster item and update display name
+ updateContact ( rosterItem );
+
+}
+
+
+JabberProtocol *JabberBaseContact::protocol ()
+{
+
+ return static_cast<JabberProtocol *>(Kopete::Contact::protocol ());
+}
+
+
+JabberTransport * JabberBaseContact::transport( )
+{
+ return dynamic_cast<JabberTransport*>(Kopete::Contact::account());
+}
+
+
+/* Return if we are reachable (defaults to true because
+ we can send on- and offline, only return false if the
+ account itself is offline, too) */
+bool JabberBaseContact::isReachable ()
+{
+ if (account()->isConnected())
+ return true;
+
+ return false;
+
+}
+
+void JabberBaseContact::updateContact ( const XMPP::RosterItem & item )
+{
+ kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing local copy of " << contactId() << " with information received from server. (name='" << item.name() << "' groups='" << item.groups() << "')"<< endl;
+
+ mRosterItem = item;
+
+ // if we don't have a meta contact yet, stop processing here
+ if ( !metaContact () )
+ return;
+
+ /*
+ * We received the information from the server, as such,
+ * don't attempt to synch while we update our local copy.
+ */
+ setDontSync ( true );
+
+ // The myself contact is not in the roster on server, ignore this code
+ // because the myself MetaContact displayname become the latest
+ // Jabber acccount jid.
+ if( metaContact() != Kopete::ContactList::self()->myself() )
+ {
+ // only update the alias if its not empty
+ if ( !item.name().isEmpty () && item.name() != item.jid().bare() )
+ {
+ QString newName = item.name ();
+ QString oldName = metaContact()->displayName();
+ Kopete::Contact *source=metaContact()->displayNameSourceContact();
+// kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "setting display name of " << contactId () << " to " << newName << endl;
+ metaContact()->setDisplayName ( newName );
+ //automatically set to custom source if the source is to this contact.
+ if( metaContact()->displayNameSource()==Kopete::MetaContact::SourceContact && newName != oldName && ( source == this || source == 0L ) )
+ metaContact()->setDisplayNameSource( Kopete::MetaContact::SourceCustom );
+ }
+ }
+
+ /*
+ * Set the contact's subscription status
+ */
+ switch ( item.subscription().type () )
+ {
+ case XMPP::Subscription::None:
+ setProperty ( protocol()->propSubscriptionStatus,
+ i18n ( "You cannot see each others' status." ) );
+ break;
+ case XMPP::Subscription::To:
+ setProperty ( protocol()->propSubscriptionStatus,
+ i18n ( "You can see this contact's status but they cannot see your status." ) );
+ break;
+ case XMPP::Subscription::From:
+ setProperty ( protocol()->propSubscriptionStatus,
+ i18n ( "This contact can see your status but you cannot see their status." ) );
+ break;
+ case XMPP::Subscription::Both:
+ setProperty ( protocol()->propSubscriptionStatus,
+ i18n ( "You can see each others' status." ) );
+ break;
+ }
+
+ if( !metaContact()->isTemporary() )
+ {
+ /*
+ * In this method, as opposed to KC::syncGroups(),
+ * the group list from the server is authoritative.
+ * As such, we need to find a list of all groups
+ * that the meta contact resides in but does not
+ * reside in on the server anymore, as well as all
+ * groups that the meta contact does not reside in,
+ * but resides in on the server.
+ * Then, we'll have to synchronize the KMC using
+ * that information.
+ */
+ Kopete::GroupList groupsToRemoveFrom, groupsToAddTo;
+
+ // find all groups our contact is in but that are not in the server side roster
+ for ( unsigned i = 0; i < metaContact()->groups().count (); i++ )
+ {
+ if ( item.groups().find ( metaContact()->groups().at(i)->displayName () ) == item.groups().end () )
+ groupsToRemoveFrom.append ( metaContact()->groups().at ( i ) );
+ }
+
+ // now find all groups that are in the server side roster but not in the local group
+ for ( unsigned i = 0; i < item.groups().count (); i++ )
+ {
+ bool found = false;
+ for ( unsigned j = 0; j < metaContact()->groups().count (); j++)
+ {
+ if ( metaContact()->groups().at(j)->displayName () == *item.groups().at(i) )
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if ( !found )
+ {
+ groupsToAddTo.append ( Kopete::ContactList::self()->findGroup ( *item.groups().at(i) ) );
+ }
+ }
+
+ /*
+ * Special case: if we don't add the contact to any group and the
+ * list of groups to remove from contains the top level group, we
+ * risk removing the contact from the visible contact list. In this
+ * case, we need to make sure at least the top level group stays.
+ */
+ if ( ( groupsToAddTo.count () == 0 ) && ( groupsToRemoveFrom.contains ( Kopete::Group::topLevel () ) ) )
+ {
+ groupsToRemoveFrom.remove ( Kopete::Group::topLevel () );
+ }
+
+ for ( Kopete::Group *group = groupsToRemoveFrom.first (); group; group = groupsToRemoveFrom.next () )
+ {
+ kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing " << contactId() << " from group " << group->displayName () << endl;
+ metaContact()->removeFromGroup ( group );
+ }
+
+ for ( Kopete::Group *group = groupsToAddTo.first (); group; group = groupsToAddTo.next () )
+ {
+ kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding " << contactId() << " to group " << group->displayName () << endl;
+ metaContact()->addToGroup ( group );
+ }
+ }
+
+ /*
+ * Enable updates for the server again.
+ */
+ setDontSync ( false );
+
+ //can't do it now because it's called from contructor at a point some virtual function are not available
+ QTimer::singleShot(0, this, SLOT(reevaluateStatus()));
+
+}
+
+void JabberBaseContact::updateResourceList ()
+{
+ /*
+ * Set available resources.
+ * This is a bit more complicated: We need to generate
+ * all images dynamically from the KOS icons and store
+ * them into the mime factory, then plug them into
+ * the richtext.
+ */
+ JabberResourcePool::ResourceList resourceList;
+ account()->resourcePool()->findResources ( rosterItem().jid() , resourceList );
+
+ if ( resourceList.isEmpty () )
+ {
+ removeProperty ( protocol()->propAvailableResources );
+ return;
+ }
+
+ QString resourceListStr = "<table cellspacing=\"0\">";
+
+ for ( JabberResourcePool::ResourceList::iterator it = resourceList.begin (); it != resourceList.end (); ++it )
+ {
+ // icon, resource name and priority
+ resourceListStr += QString ( "<tr><td><img src=\"kopete-onlinestatus-icon:%1\" /> <b>%2</b> (Priority: %3)</td></tr>" ).
+ arg ( protocol()->resourceToKOS((*it)->resource()).mimeSourceFor ( account () ),
+ (*it)->resource().name (), QString::number ( (*it)->resource().priority () ) );
+
+ // client name, version, OS
+ if ( !(*it)->clientName().isEmpty () )
+ {
+ resourceListStr += QString ( "<tr><td>%1: %2 (%3)</td></tr>" ).
+ arg ( i18n ( "Client" ), (*it)->clientName (), (*it)->clientSystem () );
+ }
+
+ // Supported features
+#if 0 //disabled because it's just an ugly and long list of incomprehensible namespaces to the user
+ QStringList supportedFeatures = (*it)->features().list();
+ QStringList::ConstIterator featuresIt, featuresItEnd = supportedFeatures.constEnd();
+ if( !supportedFeatures.empty() )
+ resourceListStr += QString( "<tr><td>Supported Features:" );
+ for( featuresIt = supportedFeatures.constBegin(); featuresIt != featuresItEnd; ++featuresIt )
+ {
+ XMPP::Features tempFeature(*featuresIt);
+ resourceListStr += QString("\n<br>");
+ if ( tempFeature.id() > XMPP::Features::FID_None )
+ resourceListStr += tempFeature.name() + QString(" (");
+ resourceListStr += *featuresIt;
+ if ( tempFeature.id() > Features::FID_None )
+ resourceListStr += QString(")");
+ }
+ if( !supportedFeatures.empty() )
+ resourceListStr += QString( "</td></tr>" );
+#endif
+
+ // resource timestamp
+ resourceListStr += QString ( "<tr><td>%1: %2</td></tr>" ).
+ arg ( i18n ( "Timestamp" ), KGlobal::locale()->formatDateTime ( (*it)->resource().status().timeStamp(), true, true ) );
+
+ // message, if any
+ if ( !(*it)->resource().status().status().stripWhiteSpace().isEmpty () )
+ {
+ resourceListStr += QString ( "<tr><td>%1: %2</td></tr>" ).
+ arg (
+ i18n ( "Message" ),
+ Kopete::Message::escape( (*it)->resource().status().status () )
+ );
+ }
+ }
+
+ resourceListStr += "</table>";
+
+ setProperty ( protocol()->propAvailableResources, resourceListStr );
+}
+
+void JabberBaseContact::reevaluateStatus ()
+{
+ kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining new status for " << contactId () << endl;
+
+ Kopete::OnlineStatus status;
+ XMPP::Resource resource = account()->resourcePool()->bestResource ( mRosterItem.jid () );
+
+ status = protocol()->resourceToKOS ( resource );
+
+
+ /* Add some icon to show the subscription */
+ if( ( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::From)
+ && inherits ( "JabberContact" ) && metaContact() != Kopete::ContactList::self()->myself() && account()->isConnected() )
+ {
+ status = Kopete::OnlineStatus(status.status() ,
+ status.weight() ,
+ protocol() ,
+ status.internalStatus() | 0x0100,
+ status.overlayIcons() + QStringList("status_unknown_overlay") , //FIXME: find better icon
+ status.description() );
+ }
+
+
+ updateResourceList ();
+
+ kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New status for " << contactId () << " is " << status.description () << endl;
+ setOnlineStatus ( status );
+
+ /*
+ * Set away message property.
+ * We just need to read it from the current resource.
+ */
+ if ( !resource.status ().status ().isEmpty () )
+ {
+ setProperty ( protocol()->propAwayMessage, resource.status().status () );
+ }
+ else
+ {
+ removeProperty ( protocol()->propAwayMessage );
+ }
+
+}
+
+QString JabberBaseContact::fullAddress ()
+{
+
+ XMPP::Jid jid = rosterItem().jid();
+
+ if ( jid.resource().isEmpty () )
+ {
+ jid.setResource ( account()->resourcePool()->bestResource ( jid ).name () );
+ }
+
+ return jid.full ();
+
+}
+
+XMPP::Jid JabberBaseContact::bestAddress ()
+{
+
+ // see if we are subscribed with a preselected resource
+ if ( !mRosterItem.jid().resource().isEmpty () )
+ {
+ // we have a preselected resource, so return our default full address
+ return mRosterItem.jid ();
+ }
+
+ // construct address out of user@host and current best resource
+ XMPP::Jid jid = mRosterItem.jid ();
+ jid.setResource ( account()->resourcePool()->bestResource( mRosterItem.jid() ).name () );
+
+ return jid;
+
+}
+
+void JabberBaseContact::setDontSync ( bool flag )
+{
+
+ mDontSync = flag;
+
+}
+
+bool JabberBaseContact::dontSync ()
+{
+
+ return mDontSync;
+
+}
+
+void JabberBaseContact::serialize (QMap < QString, QString > &serializedData, QMap < QString, QString > & /* addressBookData */ )
+{
+
+ // Contact id and display name are already set for us, only add the rest
+ serializedData["JID"] = mRosterItem.jid().full();
+
+ serializedData["groups"] = mRosterItem.groups ().join (QString::fromLatin1 (","));
+}
+
+void JabberBaseContact::slotUserInfo( )
+{
+ if ( !account()->isConnected () )
+ {
+ account()->errorConnectFirst ();
+ return;
+ }
+
+ // Update the vCard
+ //slotGetTimedVCard();
+
+ new dlgJabberVCard ( account(), this, Kopete::UI::Global::mainWidget () );
+}
+
+void JabberBaseContact::setPropertiesFromVCard ( const XMPP::VCard &vCard )
+{
+ kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Updating vCard for " << contactId () << endl;
+
+ // update vCard cache timestamp if this is not a temporary contact
+ if ( metaContact() && !metaContact()->isTemporary () )
+ {
+ setProperty ( protocol()->propVCardCacheTimeStamp, QDateTime::currentDateTime().toString ( Qt::ISODate ) );
+ }
+
+
+ /*
+ * Set the nickname property.
+ * but ignore it if we are in a groupchat, or it will clash with the normal nickname
+ */
+ if(inherits ( "JabberContact" ))
+ {
+ if ( !vCard.nickName().isEmpty () )
+ {
+ setProperty ( protocol()->propNickName, vCard.nickName () );
+ }
+ else
+ {
+ removeProperty ( protocol()->propNickName );
+ }
+ }
+
+ /**
+ * Kopete does not allow a modification of the "full name"
+ * property. However, some vCards specify only the full name,
+ * some specify only first and last name.
+ * Due to these inconsistencies, if first and last name don't
+ * exist, it is attempted to parse the full name.
+ */
+
+ // remove all properties first
+ removeProperty ( protocol()->propFirstName );
+ removeProperty ( protocol()->propLastName );
+ removeProperty ( protocol()->propFullName );
+
+ if ( !vCard.fullName().isEmpty () && vCard.givenName().isEmpty () && vCard.familyName().isEmpty () )
+ {
+ QString lastName = vCard.fullName().section ( ' ', 0, -1 );
+ QString firstName = vCard.fullName().left(vCard.fullName().length () - lastName.length ()).stripWhiteSpace ();
+
+ setProperty ( protocol()->propFirstName, firstName );
+ setProperty ( protocol()->propLastName, lastName );
+ }
+ else
+ {
+ if ( !vCard.givenName().isEmpty () )
+ setProperty ( protocol()->propFirstName, vCard.givenName () );
+
+ if ( !vCard.familyName().isEmpty () )
+ setProperty ( protocol()->propLastName, vCard.familyName () );
+ }
+ if( !vCard.fullName().isEmpty() )
+ setProperty ( protocol()->propFullName, vCard.fullName() );
+
+ /*
+ * Set the general information
+ */
+ removeProperty( protocol()->propJid );
+ removeProperty( protocol()->propBirthday );
+ removeProperty( protocol()->propTimezone );
+ removeProperty( protocol()->propHomepage );
+
+ setProperty( protocol()->propJid, vCard.jid() );
+
+ if( !vCard.bdayStr().isEmpty () )
+ setProperty( protocol()->propBirthday, vCard.bdayStr() );
+ if( !vCard.timezone().isEmpty () )
+ setProperty( protocol()->propTimezone, vCard.timezone() );
+ if( !vCard.url().isEmpty () )
+ setProperty( protocol()->propHomepage, vCard.url() );
+
+ /*
+ * Set the work information.
+ */
+ removeProperty( protocol()->propCompanyName );
+ removeProperty( protocol()->propCompanyDepartement );
+ removeProperty( protocol()->propCompanyPosition );
+ removeProperty( protocol()->propCompanyRole );
+
+ if( !vCard.org().name.isEmpty() )
+ setProperty( protocol()->propCompanyName, vCard.org().name );
+ if( !vCard.org().unit.join(",").isEmpty() )
+ setProperty( protocol()->propCompanyDepartement, vCard.org().unit.join(",")) ;
+ if( !vCard.title().isEmpty() )
+ setProperty( protocol()->propCompanyPosition, vCard.title() );
+ if( !vCard.role().isEmpty() )
+ setProperty( protocol()->propCompanyRole, vCard.role() );
+
+ /*
+ * Set the about information
+ */
+ removeProperty( protocol()->propAbout );
+
+ if( !vCard.desc().isEmpty() )
+ setProperty( protocol()->propAbout, vCard.desc() );
+
+
+ /*
+ * Set the work and home addresses information
+ */
+ removeProperty( protocol()->propWorkStreet );
+ removeProperty( protocol()->propWorkExtAddr );
+ removeProperty( protocol()->propWorkPOBox );
+ removeProperty( protocol()->propWorkCity );
+ removeProperty( protocol()->propWorkPostalCode );
+ removeProperty( protocol()->propWorkCountry );
+
+ removeProperty( protocol()->propHomeStreet );
+ removeProperty( protocol()->propHomeExtAddr );
+ removeProperty( protocol()->propHomePOBox );
+ removeProperty( protocol()->propHomeCity );
+ removeProperty( protocol()->propHomePostalCode );
+ removeProperty( protocol()->propHomeCountry );
+
+ for(XMPP::VCard::AddressList::const_iterator it = vCard.addressList().begin(); it != vCard.addressList().end(); it++)
+ {
+ XMPP::VCard::Address address = (*it);
+
+ if(address.work)
+ {
+ setProperty( protocol()->propWorkStreet, address.street );
+ setProperty( protocol()->propWorkExtAddr, address.extaddr );
+ setProperty( protocol()->propWorkPOBox, address.pobox );
+ setProperty( protocol()->propWorkCity, address.locality );
+ setProperty( protocol()->propWorkPostalCode, address.pcode );
+ setProperty( protocol()->propWorkCountry, address.country );
+ }
+ else
+ if(address.home)
+ {
+ setProperty( protocol()->propHomeStreet, address.street );
+ setProperty( protocol()->propHomeExtAddr, address.extaddr );
+ setProperty( protocol()->propHomePOBox, address.pobox );
+ setProperty( protocol()->propHomeCity, address.locality );
+ setProperty( protocol()->propHomePostalCode, address.pcode );
+ setProperty( protocol()->propHomeCountry, address.country );
+ }
+ }
+
+
+ /*
+ * Delete emails first, they might not be present
+ * in the vCard at all anymore.
+ */
+ removeProperty ( protocol()->propEmailAddress );
+ removeProperty ( protocol()->propWorkEmailAddress );
+
+ /*
+ * Set the home and work email information.
+ */
+ XMPP::VCard::EmailList::const_iterator emailEnd = vCard.emailList().end ();
+ for(XMPP::VCard::EmailList::const_iterator it = vCard.emailList().begin(); it != emailEnd; ++it)
+ {
+ XMPP::VCard::Email email = (*it);
+
+ if(email.work)
+ {
+ if( !email.userid.isEmpty() )
+ setProperty ( protocol()->propWorkEmailAddress, email.userid );
+ }
+ else
+ if(email.home)
+ {
+ if( !email.userid.isEmpty() )
+ setProperty ( protocol()->propEmailAddress, email.userid );
+ }
+ }
+
+ /*
+ * Delete phone number properties first as they might have
+ * been unset during an update and are not present in
+ * the vCard at all anymore.
+ */
+ removeProperty ( protocol()->propPrivatePhone );
+ removeProperty ( protocol()->propPrivateMobilePhone );
+ removeProperty ( protocol()->propWorkPhone );
+ removeProperty ( protocol()->propWorkMobilePhone );
+
+ /*
+ * Set phone numbers. Note that if a mobile phone number
+ * is specified, it's assigned to the private mobile
+ * phone number property. This might not be the desired
+ * behavior for all users.
+ */
+ XMPP::VCard::PhoneList::const_iterator phoneEnd = vCard.phoneList().end ();
+ for(XMPP::VCard::PhoneList::const_iterator it = vCard.phoneList().begin(); it != phoneEnd; ++it)
+ {
+ XMPP::VCard::Phone phone = (*it);
+
+ if(phone.work)
+ {
+ setProperty ( protocol()->propWorkPhone, phone.number );
+ }
+ else
+ if(phone.fax)
+ {
+ setProperty ( protocol()->propPhoneFax, phone.number);
+ }
+ else
+ if(phone.cell)
+ {
+ setProperty ( protocol()->propPrivateMobilePhone, phone.number );
+ }
+ else
+ if(phone.home)
+ {
+ setProperty ( protocol()->propPrivatePhone, phone.number );
+ }
+
+ }
+
+ /*
+ * Set photo/avatar property.
+ */
+ removeProperty( protocol()->propPhoto );
+
+ QImage contactPhoto;
+ QString fullJid = mRosterItem.jid().full();
+ QString finalPhotoPath = locateLocal("appdata", "jabberphotos/" + fullJid.replace(QRegExp("[./~]"),"-") +".png");
+
+ // photo() is a QByteArray
+ if ( !vCard.photo().isEmpty() )
+ {
+ kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact has a photo embedded into his vCard." << endl;
+
+ // QImage is used to save to disk in PNG later.
+ contactPhoto = QImage( vCard.photo() );
+ }
+ // Contact photo is a URI.
+ else if( !vCard.photoURI().isEmpty() )
+ {
+ QString tempPhotoPath = 0;
+
+ // Downalod photo from URI.
+ if( !KIO::NetAccess::download( vCard.photoURI(), tempPhotoPath, 0) )
+ {
+ KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, i18n( "Downloading of Jabber contact photo failed!" ) );
+ return;
+ }
+
+ kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact photo is a URI." << endl;
+
+ contactPhoto = QImage( tempPhotoPath );
+
+ KIO::NetAccess::removeTempFile( tempPhotoPath );
+ }
+
+ // Save the image to the disk, then set the property.
+ if( !contactPhoto.isNull() && contactPhoto.save(finalPhotoPath, "PNG") )
+ {
+ kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting photo for contact: " << fullJid << endl;
+ setProperty( protocol()->propPhoto, finalPhotoPath );
+ }
+
+}
+
+
+
+
+#include "jabberbasecontact.moc"
+
+// vim: set noet ts=4 sts=4 sw=4: