1 /*
2 oscarcontact.cpp - Oscar Protocol Plugin
3
4 Copyright (c) 2002 by Tom Linsky <twl6@po.cwru.edu>
5 Kopete (c) 2002-2008 by the Kopete developers <kopete-devel@kde.org>
6
7 *************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 *************************************************************************
15 */
16
17 #include "oscarcontact.h"
18
19 #include <time.h>
20
21 #include <qapplication.h>
22 #include <qtextcodec.h>
23 #include <qtimer.h>
24 #include <QCryptographicHash>
25 #include <QDomDocument>
26 #include <QDomNodeList>
27 #include <QTextDocument>
28 #include <QTextCharFormat>
29 #include <QTextBlock>
30
31 #include <QAction>
32 #include <kdebug.h>
33 #include <KLocalizedString>
34 #include <krandom.h>
35 #include <kcodecs.h>
36 #include <kmessagebox.h>
37
38 #include <qinputdialog.h>
39
40 #include <qfiledialog.h>
41
42 #include "kopeteaccount.h"
43 #include "kopetechatsessionmanager.h"
44 #include "kopetemetacontact.h"
45 #include "kopetecontactlist.h"
46 #include "kopetegroup.h"
47 #include "kopeteuiglobal.h"
48 #include <kopeteglobal.h>
49 #include "kopetetransfermanager.h"
50 #include "kopeteavatarmanager.h"
51
52 #include "oscaraccount.h"
53 #include "client.h"
54 #include "contactmanager.h"
55 #include "oscarutils.h"
56 #include "oscarprotocol.h"
57 #include "oscarencodingselectiondialog.h"
58 #include "oscarstatusmanager.h"
59 #include "filetransferhandler.h"
60
61 #include <assert.h>
62
OscarContact(Kopete::Account * account,const QString & name,Kopete::MetaContact * parent,const QString & icon)63 OscarContact::OscarContact( Kopete::Account* account, const QString& name,
64 Kopete::MetaContact* parent, const QString& icon )
65 : Kopete::Contact( account, name, parent, icon )
66 {
67 mAccount = static_cast<OscarAccount*>(account);
68 mName = name;
69 mMsgManager = nullptr;
70 m_buddyIconDirty = false;
71 m_oesd = 0;
72
73 setFileCapable( true );
74
75 QObject::connect( mAccount->engine(), SIGNAL(haveIconForContact(QString,QByteArray)),
76 this, SLOT(haveIcon(QString,QByteArray)) );
77 QObject::connect( mAccount->engine(), SIGNAL(iconServerConnected()),
78 this, SLOT(requestBuddyIcon()) );
79 QObject::connect( mAccount->engine(), SIGNAL(receivedAwayMessage(QString,QString)),
80 this, SLOT(receivedStatusMessage(QString,QString)) );
81 QObject::connect( mAccount->engine(), SIGNAL(messageAck(QString,uint)),
82 this, SLOT(messageAck(QString,uint)) );
83 QObject::connect( mAccount->engine(), SIGNAL(messageError(QString,uint)),
84 this, SLOT(messageError(QString,uint)) );
85 }
86
~OscarContact()87 OscarContact::~OscarContact()
88 {
89 }
90
serialize(QMap<QString,QString> & serializedData,QMap<QString,QString> &)91 void OscarContact::serialize(QMap<QString, QString> &serializedData,
92 QMap<QString, QString> &/*addressBookData*/)
93 {
94 serializedData["ssi_name"] = m_ssiItem.name();
95 serializedData["ssi_type"] = QString::number( m_ssiItem.type() );
96 serializedData["ssi_gid"] = QString::number( m_ssiItem.gid() );
97 serializedData["ssi_bid"] = QString::number( m_ssiItem.bid() );
98 serializedData["ssi_alias"] = m_ssiItem.alias();
99 serializedData["ssi_waitingAuth"] = m_ssiItem.waitingAuth() ? QString::fromLatin1( "true" ) : QString::fromLatin1( "false" );
100 serializedData["ssi_metaInfoId"] = m_ssiItem.metaInfoId().toHex();
101 }
102
isOnServer() const103 bool OscarContact::isOnServer() const
104 {
105 ContactManager* serverList = mAccount->engine()->ssiManager();
106 OContact ssi = serverList->findContact( Oscar::normalize( contactId() ) );
107
108 return ( ssi && ssi.type() != 0xFFFF );
109 }
110
setSSIItem(const OContact & ssiItem)111 void OscarContact::setSSIItem( const OContact& ssiItem )
112 {
113 setCustomName( ssiItem.alias() );
114 m_ssiItem = ssiItem;
115 }
116
ssiItem() const117 OContact OscarContact::ssiItem() const
118 {
119 return m_ssiItem;
120 }
121
manager(CanCreateFlags canCreate)122 Kopete::ChatSession* OscarContact::manager( CanCreateFlags canCreate )
123 {
124 if ( !mMsgManager && canCreate )
125 {
126 /*kDebug(14190) <<
127 "Creating new ChatSession for contact '" << displayName() << "'" << endl;*/
128
129 QList<Kopete::Contact*> theContact;
130 theContact.append(this);
131
132 mMsgManager = Kopete::ChatSessionManager::self()->create(account()->myself(), theContact, protocol());
133
134 // This is for when the user types a message and presses send
135 connect(mMsgManager, SIGNAL(messageSent(Kopete::Message&,Kopete::ChatSession*)),
136 this, SLOT(slotSendMsg(Kopete::Message&,Kopete::ChatSession*)) );
137
138 // For when the message manager is destroyed
139 connect(mMsgManager, SIGNAL(destroyed()),
140 this, SLOT(chatSessionDestroyed()) );
141
142 connect(mMsgManager, SIGNAL(myselfTyping(bool)),
143 this, SLOT(slotTyping(bool)) );
144 }
145 return mMsgManager;
146 }
147
deleteContact()148 void OscarContact::deleteContact()
149 {
150 mAccount->engine()->removeContact( contactId() );
151 deleteLater();
152 }
153
chatSessionDestroyed()154 void OscarContact::chatSessionDestroyed()
155 {
156 mMsgManager = nullptr;
157 }
158
159 // Called when the metacontact owning this contact has changed groups
sync(unsigned int flags)160 void OscarContact::sync(unsigned int flags)
161 {
162 /*
163 * If the contact has changed groups, then we update the server
164 * adding the group if it doesn't exist, changing the ssi item
165 * contained in the client and updating the contact's ssi item
166 * Otherwise, we don't do much
167 */
168
169 if( !metaContact() || metaContact()->isTemporary() )
170 return;
171
172 if ( (flags & Kopete::Contact::MovedBetweenGroup) == Kopete::Contact::MovedBetweenGroup )
173 {
174
175 kDebug(OSCAR_GEN_DEBUG) << "Moving a contact between groups";
176 ContactManager* ssiManager = mAccount->engine()->ssiManager();
177
178 OContact oldGroup = ssiManager->findGroup( m_ssiItem.gid() );
179 Kopete::Group* newGroup = metaContact()->groups().first();
180 QString newGroupName = newGroup->displayName();
181 if ( newGroup->type() == Kopete::Group::TopLevel )
182 newGroupName = "Buddies";
183 if ( newGroupName == oldGroup.name() )
184 return; //we didn't really move
185
186 if ( m_ssiItem.isValid() )
187 mAccount->changeContactGroupInSSI( contactId(), newGroupName, true );
188 else
189 mAccount->addContactToSSI( contactId(), newGroupName, true );
190 }
191
192 if ( flags & Kopete::Contact::DisplayNameChanged && mAccount->engine() )
193 {
194 kDebug(OSCAR_GEN_DEBUG) << "Changing contact alias";
195 mAccount->engine()->changeContactAlias( contactId(), metaContact()->displayName() );
196 }
197 }
198
userInfoUpdated(const QString & contact,const UserDetails & details)199 void OscarContact::userInfoUpdated( const QString& contact, const UserDetails& details )
200 {
201 Q_UNUSED( contact );
202
203 if ( details.buddyIconHash().size() > 0 && details.buddyIconHash() != m_details.buddyIconHash() )
204 {
205 OscarProtocol *p = static_cast<OscarProtocol*>(protocol());
206 QString photoPath = property( Kopete::Global::Properties::self()->photo() ).value().toString();
207 if ( property( p->buddyIconHash ).value().toByteArray() != details.buddyIconHash() || QFileInfo(photoPath).size() == 0 )
208 {
209 m_buddyIconDirty = true;
210
211 if ( !mAccount->engine()->hasIconConnection() )
212 {
213 mAccount->engine()->connectToIconServer();
214 }
215 else
216 {
217 int time = ( KRandom::random() % 10 ) * 1000;
218 kDebug(OSCAR_GEN_DEBUG) << "updating buddy icon in "
219 << time/1000 << " seconds" << endl;
220 QTimer::singleShot( time, this, SLOT(requestBuddyIcon()) );
221 }
222 }
223 }
224
225 setProperty( Kopete::Global::Properties::self()->onlineSince(), details.onlineSinceTime() );
226 setIdleTime( details.idleTime() );
227 m_warningLevel = details.warningLevel();
228 m_details.merge( details );
229
230 setFileCapable( m_details.hasCap( CAP_SENDFILE ) );
231
232 QStringList capList;
233 // Append client name and version in case we found one
234 //if ( m_details.userClass() & 0x0080 /* WIRELESS */ )
235 // capList << i18n( "Mobile AIM Client" );
236 //else
237 //{
238 // if ( !m_details.clientName().isEmpty() )
239 // {
240 // capList << i18nc( "Translators: client name and version",
241 // "%1", m_details.clientName() );
242 // }
243 //}
244
245 // and now for some general informative capabilities
246 if ( m_details.hasCap( CAP_BUDDYICON ) )
247 capList << i18n( "Buddy icons" );
248 if ( m_details.hasCap( CAP_UTF8 ) )
249 capList << i18n( "UTF-8" );
250 if ( m_details.hasCap( CAP_RTFMSGS ) )
251 capList << i18n( "Rich text messages" );
252 if ( m_details.hasCap( CAP_CHAT ) )
253 capList << i18n( "Group chat" );
254 if ( m_details.hasCap( CAP_VOICE ) )
255 capList << i18n( "Voice chat" );
256 if ( m_details.hasCap( CAP_IMIMAGE ) )
257 capList << i18n( "DirectIM/IMImage" );
258 if ( m_details.hasCap( CAP_SENDBUDDYLIST ) )
259 capList << i18n( "Send buddy list" );
260 if ( m_details.hasCap( CAP_SENDFILE ) )
261 capList << i18n( "File transfers" );
262 if ( m_details.hasCap( CAP_GAMES ) || m_details.hasCap( CAP_GAMES2 ) )
263 capList << i18n( "Games" );
264
265 m_clientFeatures = capList.join( ", " );
266 setProperty( static_cast<OscarProtocol*>(protocol())->clientFeatures, m_clientFeatures );
267
268 setProperty( static_cast<OscarProtocol*>(protocol())->memberSince, details.memberSinceTime() );
269 setProperty( static_cast<OscarProtocol*>(protocol())->client, details.clientName() );
270 setProperty( static_cast<OscarProtocol*>(protocol())->protocolVersion, QString::number(details.dcProtoVersion()) );
271 }
272
startedTyping()273 void OscarContact::startedTyping()
274 {
275 Kopete::ChatSession* cs = manager();
276 // We want the user to know if someone is typing a message
277 // but there is no chat session for this contact
278 cs->receivedTypingMsg( this, true );
279 }
280
stoppedTyping()281 void OscarContact::stoppedTyping()
282 {
283 if ( mMsgManager )
284 mMsgManager->receivedTypingMsg( this, false );
285 }
286
slotTyping(bool typing)287 void OscarContact::slotTyping( bool typing )
288 {
289 if ( this != account()->myself() )
290 account()->engine()->sendTyping( contactId(), typing );
291 }
292
messageAck(const QString & contact,uint messageId)293 void OscarContact::messageAck( const QString& contact, uint messageId )
294 {
295 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
296 return;
297
298 Kopete::ChatSession* chatSession = manager();
299 if ( chatSession )
300 chatSession->receivedMessageState( messageId, Kopete::Message::StateSent );
301 }
302
messageError(const QString & contact,uint messageId)303 void OscarContact::messageError( const QString& contact, uint messageId )
304 {
305 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
306 return;
307
308 Kopete::ChatSession* chatSession = manager();
309 if ( chatSession )
310 chatSession->receivedMessageState( messageId, Kopete::Message::StateError );
311 }
312
contactCodec() const313 QTextCodec* OscarContact::contactCodec() const
314 {
315 if ( hasProperty( "contactEncoding" ) )
316 {
317 QTextCodec* codec = QTextCodec::codecForMib( property( "contactEncoding" ).value().toInt() );
318
319 if ( codec )
320 return codec;
321 else
322 return QTextCodec::codecForMib( 4 );
323 }
324 else
325 return mAccount->defaultCodec();
326 }
327
hasCap(int capNumber) const328 bool OscarContact::hasCap( int capNumber ) const
329 {
330 return m_details.hasCap( capNumber );
331 }
332
setPresenceTarget(const Oscar::Presence & presence)333 void OscarContact::setPresenceTarget( const Oscar::Presence &presence )
334 {
335 OscarProtocol* p = static_cast<OscarProtocol *>(protocol());
336 setOnlineStatus( p->statusManager()->onlineStatusOf( presence ) );
337 }
338
setEncoding(int mib)339 void OscarContact::setEncoding( int mib )
340 {
341 OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
342 if ( mib != 0 )
343 {
344 kDebug(OSCAR_GEN_DEBUG) << "setting encoding mib to " << mib << endl;
345 setProperty( p->contactEncoding, m_oesd->selectedEncoding() );
346 }
347 else
348 {
349 kDebug(OSCAR_GEN_DEBUG) << "setting encoding to default" << endl;
350 removeProperty( p->contactEncoding );
351 }
352 }
353
354 //here's where a filetransfer usually begins
355 //could be called by a QAction or our dcop code or something
sendFile(const QUrl & sourceURL,const QString & altFileName,uint fileSize)356 void OscarContact::sendFile( const QUrl &sourceURL, const QString &altFileName, uint fileSize )
357 {
358 kDebug(OSCAR_GEN_DEBUG) << "file: '" << sourceURL
359 << "' '" << altFileName << "' size " << fileSize << endl;
360 QStringList files;
361
362 //If the file location is null, then get it from a file open dialog
363 if( !sourceURL.isValid() ) {
364 files = QFileDialog::getOpenFileNames( nullptr, i18n( "Kopete File Transfer" ), QString() , "*");
365 } else {
366 //FIXME KF5 files << sourceURL.path(KUrl::RemoveTrailingSlash);
367 }
368
369 if( files.isEmpty() )
370 {
371 kDebug(OSCAR_GEN_DEBUG) << "files empty, assuming cancel";
372 return;
373 }
374 kDebug(OSCAR_GEN_DEBUG) << "files: '" << files << "' ";
375
376 FileTransferHandler *ftHandler = mAccount->engine()->createFileTransfer( mName, files );
377
378 Kopete::TransferManager *transferManager = Kopete::TransferManager::transferManager();
379 Kopete::Transfer *transfer = transferManager->addTransfer( this, files, ftHandler->totalSize(), mName, Kopete::FileTransferInfo::Outgoing);
380
381 connect( transfer, SIGNAL(transferCanceled()), ftHandler, SLOT(cancel()) );
382
383 connect( ftHandler, SIGNAL(transferCancelled()), transfer, SLOT(slotCancelled()) );
384 connect( ftHandler, SIGNAL(transferError(int,QString)), transfer, SLOT(slotError(int,QString)) );
385 connect( ftHandler, SIGNAL(transferProcessed(uint)), transfer, SLOT(slotProcessed(uint)) );
386 connect( ftHandler, SIGNAL(transferFinished()), transfer, SLOT(slotComplete()) );
387 connect( ftHandler, SIGNAL(transferNextFile(QString,QString)),
388 transfer, SLOT(slotNextFile(QString,QString)) );
389
390 ftHandler->send();
391 }
392
setAwayMessage(const QString & message)393 void OscarContact::setAwayMessage( const QString &message )
394 {
395 kDebug(OSCAR_AIM_DEBUG) <<
396 "Called for '" << contactId() << "', away msg='" << message << "'" << endl;
397
398 if ( !message.isEmpty() )
399 setProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage, filterAwayMessage( message ) );
400 else
401 removeProperty( static_cast<OscarProtocol*>( protocol() )->statusMessage );
402
403 emit statusMessageChanged( this );
404 }
405
changeContactEncoding()406 void OscarContact::changeContactEncoding()
407 {
408 if ( m_oesd )
409 return;
410
411 OscarProtocol* p = static_cast<OscarProtocol*>( protocol() );
412 m_oesd = new OscarEncodingSelectionDialog( Kopete::UI::Global::mainWidget(), property(p->contactEncoding).value().toInt() );
413 connect( m_oesd, SIGNAL(closing(int)), this, SLOT(changeEncodingDialogClosed(int)) );
414 m_oesd->show();
415 }
416
requestAuthorization()417 void OscarContact::requestAuthorization()
418 {
419 QString info = i18n("The user %1 requires authorization before being added to a contact list. "
420 "Do you want to send an authorization request?\n\nReason for requesting authorization:",
421 displayName() );
422
423 QString reason = QInputDialog::getText( nullptr, i18n("Request Authorization"), info, QLineEdit::Normal,
424 i18n("Please authorize me so I can add you to my contact list") );
425 if ( !reason.isNull() )
426 mAccount->engine()->requestAuth( contactId(), reason );
427 }
428
slotSendMsg(Kopete::Message & message,Kopete::ChatSession *)429 void OscarContact::slotSendMsg(Kopete::Message& message, Kopete::ChatSession *)
430 {
431 if (message.plainBody().isEmpty()) // no text, do nothing
432 return;
433 //okay, now we need to change the message.escapedBody from real HTML to aimhtml.
434 //looking right now for docs on that "format".
435 //looks like everything except for alignment codes comes in the format of spans
436
437 //font-style:italic -> <i>
438 //font-weight:600 -> <b> (anything > 400 should be <b>, 400 is not bold)
439 //text-decoration:underline -> <u>
440 //font-family: -> <font face="">
441 //font-size:xxpt -> <font ptsize=xx>
442
443 QTextDocument doc;
444 doc.setHtml( message.escapedBody() );
445
446 QString rtfText = QString( "<HTML><BODY dir=\"%1\">" ).arg( message.isRightToLeft() ? "rtl" : "ltr" );
447
448 bool hasFontTag = false;
449 QTextCharFormat defaultCharFormat;
450 for ( QTextBlock it = doc.begin(); it != doc.end(); it = it.next() )
451 {
452 QTextBlockFormat blockFormat = it.blockFormat();
453
454 // Plain text message has p tags without margin attributes and Qt's topMargin()
455 // returns default margins so we will end up with line break before text.
456 if ( message.format() != Qt::PlainText || it.blockNumber() != 0 )
457 rtfText += brMargin( blockFormat.topMargin(), defaultCharFormat.fontPointSize() );
458
459 bool lastFragmentHasLineSeparator = false;
460 for ( QTextBlock::iterator it2 = it.begin(); !(it2.atEnd()); ++it2 )
461 {
462 QTextFragment currentFragment = it2.fragment();
463 if ( currentFragment.isValid() )
464 {
465 QTextCharFormat format = currentFragment.charFormat();
466 if ( format.fontFamily() != defaultCharFormat.fontFamily() ||
467 format.foreground() != defaultCharFormat.foreground() ||
468 oscarFontSize(format.fontPointSize()) != oscarFontSize(defaultCharFormat.fontPointSize()) )
469 {
470 if ( hasFontTag )
471 {
472 rtfText += "</FONT>";
473 hasFontTag = false;
474 }
475
476 QString fontTag;
477 if ( !format.fontFamily().isEmpty() )
478 fontTag += QString( " FACE=\"%1\"" ).arg( format.fontFamily() );
479 if ( format.fontPointSize() > 0 )
480 fontTag += QString( " SIZE=%1" ).arg( oscarFontSize( format.fontPointSize() ) );
481 if ( format.foreground().style() != Qt::NoBrush )
482 fontTag += QString( " COLOR=%1" ).arg( format.foreground().color().name() );
483 if ( format.background().style() != Qt::NoBrush )
484 fontTag += QString( " BACK=%1" ).arg( format.background().color().name() );
485
486 if ( !fontTag.isEmpty() )
487 {
488 rtfText += QString("<FONT%1>").arg( fontTag );
489 hasFontTag = true;
490 }
491 }
492
493 if ( format.font().bold() != defaultCharFormat.font().bold() )
494 rtfText += ( format.font().bold() ) ? "<B>" : "</B>";
495 if ( format.fontItalic() != defaultCharFormat.fontItalic() )
496 rtfText += ( format.hasProperty(QTextFormat::FontItalic) ) ? "<I>" : "</I>";
497 if ( format.fontUnderline() != defaultCharFormat.fontUnderline() )
498 rtfText += ( format.hasProperty(QTextFormat::FontUnderline) ) ? "<U>" : "</U>";
499
500 QString text = currentFragment.text();
501 lastFragmentHasLineSeparator = text.endsWith( QChar::LineSeparator );
502 rtfText += text.toHtmlEscaped();
503 defaultCharFormat = format;
504 }
505 }
506 rtfText += brMargin( blockFormat.bottomMargin(), defaultCharFormat.fontPointSize(), !lastFragmentHasLineSeparator );
507 }
508
509 rtfText.replace( QChar::LineSeparator, "<BR>" );
510
511 if ( rtfText.endsWith( "<BR>" ) )
512 rtfText.chop(4);
513
514 if ( hasFontTag )
515 rtfText += "</FONT>";
516 if ( defaultCharFormat.font().bold() )
517 rtfText += "</B>";
518 if ( defaultCharFormat.hasProperty( QTextFormat::FontItalic ) )
519 rtfText += "</I>";
520 if ( defaultCharFormat.hasProperty( QTextFormat::FontUnderline ) )
521 rtfText += "</U>";
522
523 rtfText += "</BODY></HTML>";
524
525 kDebug(OSCAR_GEN_DEBUG) << "sending: " << rtfText;
526
527 // TODO: Need to check for message size?
528
529 Oscar::Message msg;
530 // Allow UCS2 because official AIM client doesn't sets the CAP_UTF8 anymore!
531 bool allowUCS2 = !isOnline() || !(m_details.userClass() & Oscar::CLASS_ICQ) || m_details.hasCap( CAP_UTF8 );
532 msg.setText( Oscar::Message::encodingForText( rtfText, allowUCS2 ), rtfText, contactCodec() );
533
534 msg.setId( message.id() );
535 msg.setReceiver(mName);
536 msg.setSender( mAccount->accountId() );
537 msg.setTimestamp(message.timestamp());
538 msg.setChannel(0x01);
539
540 mAccount->engine()->sendMessage(msg);
541
542 message.setState( Kopete::Message::StateSending );
543 // Show the message we just sent in the chat window
544 manager(Kopete::Contact::CanCreate)->appendMessage(message);
545 manager(Kopete::Contact::CanCreate)->messageSucceeded();
546 }
547
changeEncodingDialogClosed(int result)548 void OscarContact::changeEncodingDialogClosed( int result )
549 {
550 if ( result == QDialog::Accepted )
551 setEncoding( m_oesd->selectedEncoding() );
552
553 if ( m_oesd )
554 {
555 m_oesd->deleteLater();
556 m_oesd = nullptr;
557 }
558 }
559
requestBuddyIcon()560 void OscarContact::requestBuddyIcon()
561 {
562 if ( m_buddyIconDirty && m_details.buddyIconHash().size() > 0 )
563 {
564 account()->engine()->requestBuddyIcon( contactId(), m_details.buddyIconHash(),
565 m_details.iconType(), m_details.iconCheckSumType() );
566 }
567 }
568
haveIcon(const QString & user,QByteArray icon)569 void OscarContact::haveIcon( const QString& user, QByteArray icon )
570 {
571 if ( Oscar::normalize( user ) != Oscar::normalize( contactId() ) )
572 return;
573
574 kDebug(OSCAR_GEN_DEBUG) << "Updating icon for " << contactId();
575
576 QByteArray buddyIconHash = QCryptographicHash::hash( icon, QCryptographicHash::Md5 );
577 if ( memcmp( buddyIconHash, m_details.buddyIconHash().data(), 16 ) == 0 )
578 {
579 QImage img;
580 img.loadFromData(icon);
581 Kopete::AvatarManager::AvatarEntry entry;
582 entry.name = contactId();
583 entry.category = Kopete::AvatarManager::Contact;
584 entry.contact = this;
585 entry.image = img;
586 entry = Kopete::AvatarManager::self()->add(entry);
587
588 if (!entry.dataPath.isNull())
589 {
590 removeProperty( Kopete::Global::Properties::self()->photo() );
591 setProperty( Kopete::Global::Properties::self()->photo(), entry.dataPath );
592 setProperty( static_cast<OscarProtocol*>(protocol())->buddyIconHash, m_details.buddyIconHash() );
593 }
594
595 m_buddyIconDirty = false;
596 }
597 else
598 {
599 kDebug(14153) << "Buddy icon hash does not match!";
600 removeProperty( static_cast<OscarProtocol*>(protocol())->buddyIconHash );
601 removeProperty( Kopete::Global::Properties::self()->photo() );
602 }
603 }
604
receivedStatusMessage(const QString & contact,const QString & message)605 void OscarContact::receivedStatusMessage( const QString& contact, const QString& message )
606 {
607 if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
608 return;
609
610 setAwayMessage( message );
611 }
612
filterAwayMessage(const QString & message) const613 QString OscarContact::filterAwayMessage( const QString &message ) const
614 {
615 QString filteredMessage = message;
616 filteredMessage.replace(
617 QRegExp(QString::fromLatin1("<[hH][tT][mM][lL].*>(.*)</[hH][tT][mM][lL]>")),
618 QString::fromLatin1("\\1"));
619 filteredMessage.replace(
620 QRegExp(QString::fromLatin1("<[bB][oO][dD][yY].*>(.*)</[bB][oO][dD][yY]>")),
621 QString::fromLatin1("\\1") );
622 QRegExp fontRemover( QString::fromLatin1("<[fF][oO][nN][tT].*>(.*)</[fF][oO][nN][tT]>") );
623 fontRemover.setMinimal(true);
624 while ( filteredMessage.indexOf( fontRemover ) != -1 )
625 filteredMessage.replace( fontRemover, QString::fromLatin1("\\1") );
626 return filteredMessage;
627 }
628
oscarFontSize(int size) const629 int OscarContact::oscarFontSize( int size ) const
630 {
631 if ( size <= 0 )
632 return 0;
633 else if ( 1 <= size && size <= 9 )
634 return 1;
635 else if ( 10 <= size && size <= 11 )
636 return 2;
637 else if ( 12 <= size && size <= 13 )
638 return 3;
639 else if ( 14 <= size && size <= 16 )
640 return 4;
641 else if ( 17 <= size && size <= 22 )
642 return 5;
643 else if ( 23 <= size && size <= 29 )
644 return 6;
645 else
646 return 7;
647 }
648
brMargin(int margin,int fontPointSize,bool forceBr) const649 QString OscarContact::brMargin( int margin, int fontPointSize, bool forceBr ) const
650 {
651 int brHeight = ( fontPointSize == 0 ) ? 12 : fontPointSize;
652 int brCount = margin / brHeight;
653
654 if ( brCount <= 0 )
655 return ( forceBr ) ? "<BR>" : "";
656
657 QString s;
658 while ( brCount-- > 0 )
659 s += "<BR>";
660
661 return s;
662 }
663
664