/* * 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 <tdelocale.h> #include <tdemessagebox.h> #include <tdefiledialog.h> #include <tdeaction.h> #include <tdeapplication.h> #include <kstandarddirs.h> #include <tdeio/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, TQ_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, TQ_SLOT ( slotCheckVCard () ) ); } else { // this contact is the myself instance connect ( account()->myself (), TQ_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, TQ_SLOT ( slotCheckVCard () ) ); connect ( account()->myself (), TQ_SIGNAL ( onlineStatusChanged ( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, TQ_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()->onlineStatus().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<TDEAction> *JabberContact::customContextMenuActions () { TQPtrList<TDEAction> *actionCollection = new TQPtrList<TDEAction>(); TDEActionMenu *actionAuthorization = new TDEActionMenu ( i18n ("Authorization"), "connect_established", this, "jabber_authorization"); TDEAction *resendAuthAction, *requestAuthAction, *removeAuthAction; resendAuthAction = new TDEAction (i18n ("(Re)send Authorization To"), "mail-forward", 0, this, TQ_SLOT (slotSendAuth ()), actionAuthorization, "actionSendAuth"); resendAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::To || mRosterItem.subscription().type() == XMPP::Subscription::None ); actionAuthorization->insert(resendAuthAction); requestAuthAction = new TDEAction (i18n ("(Re)request Authorization From"), "mail-reply-sender", 0, this, TQ_SLOT (slotRequestAuth ()), actionAuthorization, "actionRequestAuth"); requestAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::From || mRosterItem.subscription().type() == XMPP::Subscription::None ); actionAuthorization->insert(requestAuthAction); removeAuthAction = new TDEAction (i18n ("Remove Authorization From"), "mail_delete", 0, this, TQ_SLOT (slotRemoveAuth ()), actionAuthorization, "actionRemoveAuth"); removeAuthAction->setEnabled( mRosterItem.subscription().type() == XMPP::Subscription::Both || mRosterItem.subscription().type() == XMPP::Subscription::From ); actionAuthorization->insert(removeAuthAction); TDEActionMenu *actionSetAvailability = new TDEActionMenu (i18n ("Set Availability"), "kopeteavailable", this, "jabber_online"); actionSetAvailability->insert(new TDEAction (i18n ("Online"), protocol()->JabberKOSOnline.iconFor(this), 0, this, TQ_SLOT (slotStatusOnline ()), actionSetAvailability, "actionOnline")); actionSetAvailability->insert(new TDEAction (i18n ("Free to Chat"), protocol()->JabberKOSChatty.iconFor(this), 0, this, TQ_SLOT (slotStatusChatty ()), actionSetAvailability, "actionChatty")); actionSetAvailability->insert(new TDEAction (i18n ("Away"), protocol()->JabberKOSAway.iconFor(this), 0, this, TQ_SLOT (slotStatusAway ()), actionSetAvailability, "actionAway")); actionSetAvailability->insert(new TDEAction (i18n ("Extended Away"), protocol()->JabberKOSXA.iconFor(this), 0, this, TQ_SLOT (slotStatusXA ()), actionSetAvailability, "actionXA")); actionSetAvailability->insert(new TDEAction (i18n ("Do Not Disturb"), protocol()->JabberKOSDND.iconFor(this), 0, this, TQ_SLOT (slotStatusDND ()), actionSetAvailability, "actionDND")); actionSetAvailability->insert(new TDEAction (i18n ("Invisible"), protocol()->JabberKOSInvisible.iconFor(this), 0, this, TQ_SLOT (slotStatusInvisible ()), actionSetAvailability, "actionInvisible")); TDEActionMenu *actionSelectResource = new TDEActionMenu (i18n ("Select Resource"), "connect_no", this, "actionSelectResource"); // if the contact is online, display the resources we have for it, // otherwise disable the menu if (onlineStatus ().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 TDEAction( ( *it ), "button_ok", 0, this, TQ_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.find(*it) ).iconFor ( account () )); actionSelectResource->insert ( new TDEAction( ( *it ), iconSet, 0, this, TQ_SLOT( slotSelectResource() ), actionSelectResource, TQString::number( i ).latin1() ) ); } i++; } } actionCollection->append( actionAuthorization ); actionCollection->append( actionSetAvailability ); actionCollection->append( actionSelectResource ); #ifdef SUPPORT_JINGLE TDEAction *actionVoiceCall = new TDEAction (i18n ("Voice call"), "voicecall", 0, this, TQ_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>" ).arg(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>") .arg(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.").arg(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()->onlineStatus().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, TQ_SIGNAL(finished()),this, TQ_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::currentDateTime().addDays ( -2 ); else cacheDate = TQDateTime::fromString ( cacheDateString.value().toString (), TQt::ISODate ); kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Cached vCard data for " << contactId () << " from " << cacheDate.toString () << endl; if ( !mVCardUpdateInProgress && ( cacheDate.addDays ( 1 ) < TQDateTime::currentDateTime () ) ) { 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, TQ_SLOT ( slotGetTimedVCard () ) ); } } void JabberContact::slotGetTimedVCard () { mVCardUpdateInProgress = false; // check if we are still connected - eventually we lost our connection in the meantime if ( !account()->myself()->onlineStatus().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, TQ_SIGNAL(finished()),this, TQ_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, TQ_SIGNAL ( finished () ), this, TQ_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::currentDateTime().toString ( TQt::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 &newStatus, const Kopete::OnlineStatus &oldStatus ) { /* * 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 ( onlineStatus().isDefinitelyOnline () ) { // Kopete already deals with lastSeen if the contact is online return; } if ( ( oldStatus.status () == Kopete::OnlineStatus::Connecting ) && newStatus.isDefinitelyOnline () ) { kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Scheduling request for last activity for " << mRosterItem.jid().bare () << endl; TQTimer::singleShot ( account()->client()->getPenaltyTime () * 1000, this, TQ_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 ( onlineStatus().isDefinitelyOnline () ) { // Kopete already deals with setting lastSeen if the contact is online return; } if ( account()->myself()->onlineStatus().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, TQ_SIGNAL ( finished () ), this, TQ_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::currentDateTime().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, TQ_SIGNAL (finished ()), this, TQ_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, TQImage::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, TQImage::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.find ( 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, TQ_SIGNAL ( destroyed ( TQObject * ) ), this, TQ_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, TQ_SIGNAL ( destroyed ( TQObject * ) ), this, TQ_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, TQ_SIGNAL(timeout()) , this , TQ_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 TDEAction *>( 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 TDEAction *>(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::Status status ) { if ( !account()->isConnected () ) { account()->errorConnectFirst (); return; } XMPP::Status newStatus = status; // honour our priority if(newStatus.isAvailable()) newStatus.setPriority ( account()->configGroup()->readNumEntry ( "Priority", 5 ) ); XMPP::JT_Presence * task = new XMPP::JT_Presence ( account()->client()->rootTask () ); task->pres ( bestAddress (), newStatus); task->go ( true ); } void JabberContact::slotStatusOnline () { XMPP::Status status; status.setShow(""); sendPresence ( status ); } void JabberContact::slotStatusChatty () { XMPP::Status status; status.setShow ("chat"); sendPresence ( status ); } void JabberContact::slotStatusAway () { XMPP::Status status; status.setShow ("away"); sendPresence ( status ); } void JabberContact::slotStatusXA () { XMPP::Status status; status.setShow ("xa"); sendPresence ( status ); } void JabberContact::slotStatusDND () { XMPP::Status status; status.setShow ("dnd"); sendPresence ( status ); } void JabberContact::slotStatusInvisible () { XMPP::Status 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 *parentAccount=account(); Kopete::OnlineStatus status=onlineStatus(); 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( parentAccount , ri , tr_type ); if(!Kopete::AccountManager::self()->registerAccount( transport )) return; transport->myself()->setOnlineStatus( status ); //push back the online status return; } } #include "jabbercontact.moc"