00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "chatmessagepart.h"
00023
00024
00025
00026
00027 #include <ctime>
00028
00029
00030 #include <QtCore/QByteArray>
00031 #include <QtCore/QLatin1String>
00032 #include <QtCore/QList>
00033 #include <QtCore/QPointer>
00034 #include <QtCore/QRect>
00035 #include <QtCore/QRegExp>
00036 #include <QtCore/QTextCodec>
00037 #include <QtCore/QTextStream>
00038 #include <QtCore/QTimer>
00039 #include <QtCore/QBuffer>
00040 #include <QtGui/QClipboard>
00041 #include <QtGui/QCursor>
00042 #include <QtGui/QPixmap>
00043 #include <QtGui/QTextDocument>
00044 #include <QtGui/QScrollBar>
00045 #include <QMimeData>
00046 #include <QApplication>
00047
00048
00049 #include <dom/dom_doc.h>
00050 #include <dom/dom_text.h>
00051 #include <dom/dom_element.h>
00052 #include <dom/html_base.h>
00053 #include <dom/html_document.h>
00054 #include <dom/html_inline.h>
00055 #include <dom/html_form.h>
00056 #include <dom/dom2_events.h>
00057
00058
00059
00060 #include <kactioncollection.h>
00061 #include <kdebug.h>
00062 #include <kdeversion.h>
00063 #include <kfiledialog.h>
00064 #include <khtmlview.h>
00065 #include <klocale.h>
00066 #include <kmessagebox.h>
00067 #include <kmenu.h>
00068 #include <krun.h>
00069 #include <kstringhandler.h>
00070 #include <ktemporaryfile.h>
00071 #include <kio/copyjob.h>
00072 #include <kstandardaction.h>
00073 #include <kiconloader.h>
00074 #include <kcodecs.h>
00075 #include <kicon.h>
00076
00077
00078 #include "kopetecontact.h"
00079 #include "kopetecontactlist.h"
00080 #include "kopetechatwindow.h"
00081 #include "kopetechatsession.h"
00082 #include "kopetemetacontact.h"
00083 #include "kopetepluginmanager.h"
00084 #include "kopeteprotocol.h"
00085 #include "kopeteaccount.h"
00086 #include "kopeteglobal.h"
00087 #include "kopeteemoticons.h"
00088 #include "kopeteview.h"
00089 #include "kopetepicture.h"
00090 #include "kopeteappearancesettings.h"
00091 #include "kopetebehaviorsettings.h"
00092 #include "kopetechatwindowsettings.h"
00093 #include "kopetetransfermanager.h"
00094
00095 #include "kopetechatwindowstyle.h"
00096 #include "kopetechatwindowstylemanager.h"
00097
00098 class ToolTip;
00099
00100 class ChatMessagePart::Private
00101 {
00102 public:
00103 Private()
00104 : scrollPressed(false), manager(0),
00105 copyAction(0), saveAction(0), printAction(0),
00106 closeAction(0),copyURLAction(0), currentChatStyle(0),
00107 latestDirection(Kopete::Message::Inbound), latestType(Kopete::Message::TypeNormal),
00108 htmlEventListener(0)
00109 {}
00110
00111 ~Private()
00112 {
00113
00114
00115 }
00116
00117 bool bgOverride;
00118 bool fgOverride;
00119 bool rtfOverride;
00120
00121
00122 bool scrollPressed;
00123 Kopete::ChatSession *manager;
00124
00125 DOM::HTMLElement activeElement;
00126
00127 KAction *copyAction;
00128 KAction *saveAction;
00129 KAction *printAction;
00130 KAction *closeAction;
00131 KAction *copyURLAction;
00132
00133
00134 ChatWindowStyle *currentChatStyle;
00135 QPointer<Kopete::Contact> latestContact;
00136 Kopete::Message::MessageDirection latestDirection;
00137 Kopete::Message::MessageType latestType;
00138
00139
00140 QList<Kopete::Message> allMessages;
00141
00142
00143 QPointer<HTMLEventListener> htmlEventListener;
00144 };
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202 ChatMessagePart::ChatMessagePart( Kopete::ChatSession *mgr, QWidget *parent )
00203 : KHTMLPart( parent ), d( new Private )
00204 {
00205 d->manager = mgr;
00206
00207 d->currentChatStyle = ChatWindowStyleManager::self()->getStyleFromPool(
00208 KopeteChatWindowSettings::self()->styleName() );
00209
00210 kDebug(14000) << d->currentChatStyle->getStyleName();
00211
00212
00213 setJScriptEnabled( false ) ;
00214 setJavaEnabled( false );
00215 setPluginsEnabled( false );
00216 setMetaRefreshEnabled( false );
00217 setOnlyLocalReferences( true );
00218
00219
00220 writeTemplate();
00221
00222
00223 view()->setAcceptDrops(false);
00224
00225 connect( Kopete::AppearanceSettings::self(), SIGNAL(messageOverridesChanged()),
00226 this, SLOT( slotAppearanceChanged() ) );
00227 connect( KopeteChatWindowSettings::self(), SIGNAL(chatwindowAppearanceChanged()),
00228 this, SLOT( slotRefreshView() ) );
00229 connect( KopeteChatWindowSettings::self(), SIGNAL(styleChanged(const QString &)),
00230 this, SLOT( setStyle(const QString &) ) );
00231 connect( KopeteChatWindowSettings::self(), SIGNAL(styleVariantChanged(const QString &)),
00232 this, SLOT( setStyleVariant(const QString &) ) );
00233
00234
00235 connect( d->manager, SIGNAL(displayNameChanged()), this, SLOT(slotUpdateHeaderDisplayName()) );
00236 connect( d->manager, SIGNAL(photoChanged()), this, SLOT(slotUpdateHeaderPhoto()) );
00237
00238 connect( d->manager, SIGNAL(messageStateChanged(uint, Kopete::Message::MessageState)),
00239 this, SLOT(messageStateChanged(uint, Kopete::Message::MessageState)) );
00240
00241 connect ( browserExtension(), SIGNAL( openUrlRequestDelayed( const KUrl &, const KParts::OpenUrlArguments &, const KParts::BrowserArguments & ) ),
00242 this, SLOT( slotOpenURLRequest( const KUrl &, const KParts::OpenUrlArguments &, const KParts::BrowserArguments & ) ) );
00243
00244 connect( this, SIGNAL(popupMenu(const QString &, const QPoint &)),
00245 this, SLOT(slotRightClick(const QString &, const QPoint &)) );
00246 connect( view()->verticalScrollBar(), SIGNAL(sliderMoved(int)),
00247 this, SLOT(slotScrollingTo(int)) );
00248
00249 connect( Kopete::TransferManager::transferManager(), SIGNAL(askIncomingDone(unsigned int)),
00250 this, SLOT(slotFileTransferIncomingDone(unsigned int)) );
00251
00252
00253 d->copyAction = KStandardAction::copy( this, SLOT(copy()), actionCollection() );
00254 d->saveAction = KStandardAction::saveAs( this, SLOT(save()), actionCollection() );
00255 d->printAction = KStandardAction::print( this, SLOT(print()),actionCollection() );
00256 d->closeAction = KStandardAction::close( this, SLOT(slotCloseView()),actionCollection() );
00257 d->copyURLAction = new KAction( KIcon("edit-copy"), i18n( "Copy Link Address" ), actionCollection() );
00258 actionCollection()->addAction( "editcopy", d->copyURLAction );
00259 connect( d->copyURLAction, SIGNAL( triggered(bool) ), this, SLOT( slotCopyURL() ) );
00260
00261
00262 readOverrides();
00263 }
00264
00265 ChatMessagePart::~ChatMessagePart()
00266 {
00267 kDebug(14000) ;
00268
00269
00270 QList<Kopete::Message>::ConstIterator it, itEnd = d->allMessages.constEnd();
00271 for ( it = d->allMessages.constBegin(); it != itEnd; ++it )
00272 {
00273 if ( (*it).type() == Kopete::Message::TypeFileTransferRequest && !(*it).fileTransferDisabled() )
00274 {
00275 Kopete::TransferManager::transferManager()->cancelIncomingTransfer( (*it).id() );
00276 }
00277 }
00278
00279
00280 delete d;
00281 }
00282
00283 void ChatMessagePart::slotScrollingTo( int y )
00284 {
00285 int scrolledTo = y + view()->visibleHeight();
00286 d->scrollPressed = scrolledTo < ( view()->contentsHeight() - 10 );
00287 }
00288
00289 void ChatMessagePart::save()
00290 {
00291 const KUrl dummyUrl;
00292 KFileDialog dlg( dummyUrl, QLatin1String( "text/html text/plain" ), view() );
00293 dlg.setCaption( i18n( "Save Conversation" ) );
00294 dlg.setOperationMode( KFileDialog::Saving );
00295
00296 if ( dlg.exec() != QDialog::Accepted )
00297 return;
00298
00299 KUrl saveURL = dlg.selectedUrl();
00300 KTemporaryFile *tempFile = new KTemporaryFile();
00301 tempFile->setAutoRemove(false);
00302 tempFile->open();
00303
00304 QTextStream stream ( tempFile );
00305 stream.setCodec(QTextCodec::codecForName("UTF-8"));
00306
00307 if ( dlg.currentFilter() == QLatin1String( "text/plain" ) )
00308 {
00309 QList<Kopete::Message>::ConstIterator it, itEnd = d->allMessages.constEnd();
00310 for(it = d->allMessages.constBegin(); it != itEnd; ++it)
00311 {
00312 Kopete::Message tempMessage = *it;
00313 stream << "[" << KGlobal::locale()->formatDateTime(tempMessage.timestamp()) << "] ";
00314 if( tempMessage.from() && tempMessage.from()->metaContact() )
00315 {
00316 stream << formatName(tempMessage.from()->metaContact()->displayName(), Qt::RichText);
00317 }
00318 stream << ": " << tempMessage.plainBody() << "\n";
00319 }
00320 }
00321 else
00322 {
00323 stream << htmlDocument().toString().string() << '\n';
00324 }
00325
00326 stream.flush();
00327 QString fileName = tempFile->fileName();
00328 delete tempFile;
00329
00330 KIO::CopyJob *moveJob = KIO::move( KUrl( fileName ), saveURL, KIO::HideProgressInfo );
00331
00332 if ( !moveJob )
00333 {
00334 KMessageBox::queuedMessageBox( view(), KMessageBox::Error,
00335 i18n("<qt>Could not open <b>%1</b> for writing.</qt>", saveURL.prettyUrl() ),
00336 i18n("Error While Saving") );
00337 }
00338 }
00339
00340 void ChatMessagePart::pageUp()
00341 {
00342 view()->scrollBy( 0, -view()->visibleHeight() );
00343 }
00344
00345 void ChatMessagePart::pageDown()
00346 {
00347 view()->scrollBy( 0, view()->visibleHeight() );
00348 }
00349
00350 void ChatMessagePart::slotOpenURLRequest(const KUrl &url, const KParts::OpenUrlArguments &, const KParts::BrowserArguments &)
00351 {
00352 kDebug(14000) << "url=" << url.url();
00353 if ( url.protocol() == QLatin1String("kopetemessage") )
00354 {
00355 Kopete::Contact *contact = d->manager->account()->contacts()[ url.host() ];
00356 if ( contact )
00357 contact->execute();
00358 }
00359 else
00360 {
00361 KRun *runner = new KRun( url, 0, 0, false );
00362 runner->setRunExecutables( false );
00363
00364 }
00365 }
00366
00367 void ChatMessagePart::slotFileTransferIncomingDone( unsigned int id )
00368 {
00369 QList<Kopete::Message>::Iterator it = d->allMessages.end();
00370 while ( it != d->allMessages.begin() )
00371 {
00372 --it;
00373 if ( (*it).id() == id )
00374 {
00375 (*it).setFileTransferDisabled( true );
00376 disableFileTransferButtons( id );
00377 break;
00378 }
00379 }
00380 }
00381
00382 void ChatMessagePart::readOverrides()
00383 {
00384 d->bgOverride = Kopete::AppearanceSettings::self()->chatBgOverride();
00385 d->fgOverride = Kopete::AppearanceSettings::self()->chatFgOverride();
00386 d->rtfOverride = Kopete::AppearanceSettings::self()->chatRtfOverride();
00387 }
00388
00389 void ChatMessagePart::setStyle( const QString &styleName )
00390 {
00391
00392 d->currentChatStyle = ChatWindowStyleManager::self()->getStyleFromPool(styleName);
00393
00394
00395
00396 QTimer::singleShot( 0, this, SLOT(changeStyle()) );
00397 }
00398
00399 void ChatMessagePart::setStyle( ChatWindowStyle *style )
00400 {
00401
00402 d->currentChatStyle = style;
00403
00404
00405
00406 QTimer::singleShot( 0, this, SLOT(changeStyle()) );
00407 }
00408
00409 void ChatMessagePart::setStyleVariant( const QString &variantPath )
00410 {
00411 DOM::HTMLElement variantNode = document().getElementById( QString("mainStyle") );
00412 if( !variantNode.isNull() )
00413 variantNode.setInnerText( QString("@import url(\"%1\");").arg( adjustStyleVariantForChatSession( variantPath) ) );
00414 }
00415
00416 void ChatMessagePart::messageStateChanged( uint messageId, Kopete::Message::MessageState state )
00417 {
00418 QList<Kopete::Message>::Iterator it = d->allMessages.end();
00419 while ( it != d->allMessages.begin() )
00420 {
00421 --it;
00422 if ( (*it).id() == messageId )
00423 {
00424 (*it).setState( state );
00425 changeMessageStateElement( messageId, state );
00426 break;
00427 }
00428 }
00429 }
00430
00431 void ChatMessagePart::slotAppearanceChanged()
00432 {
00433 readOverrides();
00434
00435 changeStyle();
00436 }
00437
00438 void ChatMessagePart::appendMessage( Kopete::Message &message, bool restoring )
00439 {
00440 message.setBackgroundOverride( d->bgOverride );
00441 message.setForegroundOverride( d->fgOverride );
00442 message.setRichTextOverride( d->rtfOverride );
00443
00444
00445
00446 if( !restoring )
00447 message.setHtmlBody( message.parsedBody() );
00448
00449 #ifdef STYLE_TIMETEST
00450 QTime beforeMessage = QTime::currentTime();
00451 #endif
00452
00453 QString formattedMessageHtml;
00454 bool isConsecutiveMessage = false;
00455 int bufferLen = Kopete::BehaviorSettings::self()->chatWindowBufferViewSize();
00456
00457
00458
00459 DOM::HTMLElement chatNode = htmlDocument().getElementById( "Chat" );
00460
00461 if( chatNode.isNull() )
00462 {
00463 kDebug(14000) << "WARNING: Chat Node was null !";
00464 return;
00465 }
00466
00467
00468
00469
00470
00471 if( KopeteChatWindowSettings::self()->groupConsecutiveMessages() )
00472 {
00473 isConsecutiveMessage = (message.direction() == d->latestDirection && !d->latestContact.isNull()
00474 && d->latestContact == message.from() && message.type() == d->latestType
00475 && message.type() != Kopete::Message::TypeFileTransferRequest );
00476 }
00477
00478
00479 if(message.type() == Kopete::Message::TypeAction)
00480 {
00481
00482 if( d->currentChatStyle->hasActionTemplate() )
00483 {
00484 switch(message.direction())
00485 {
00486 case Kopete::Message::Inbound:
00487 formattedMessageHtml = d->currentChatStyle->getActionIncomingHtml();
00488 break;
00489 case Kopete::Message::Outbound:
00490 formattedMessageHtml = d->currentChatStyle->getActionOutgoingHtml();
00491 break;
00492 default:
00493 break;
00494 }
00495 }
00496
00497 else
00498 {
00499 formattedMessageHtml = d->currentChatStyle->getStatusHtml();
00500 }
00501 }
00502 else if(message.type() == Kopete::Message::TypeFileTransferRequest)
00503 {
00504 formattedMessageHtml = d->currentChatStyle->getFileTransferIncomingHtml();
00505 }
00506 else
00507 {
00508 switch(message.direction())
00509 {
00510 case Kopete::Message::Inbound:
00511 {
00512 if(isConsecutiveMessage)
00513 {
00514 formattedMessageHtml = d->currentChatStyle->getNextIncomingHtml();
00515 }
00516 else
00517 {
00518 formattedMessageHtml = d->currentChatStyle->getIncomingHtml();
00519 }
00520 break;
00521 }
00522 case Kopete::Message::Outbound:
00523 {
00524 if(isConsecutiveMessage)
00525 {
00526 formattedMessageHtml = d->currentChatStyle->getNextOutgoingHtml();
00527 }
00528 else
00529 {
00530 formattedMessageHtml = d->currentChatStyle->getOutgoingHtml();
00531 }
00532 break;
00533 }
00534 case Kopete::Message::Internal:
00535 {
00536 formattedMessageHtml = d->currentChatStyle->getStatusHtml();
00537 break;
00538 }
00539 }
00540 }
00541
00542 formattedMessageHtml = formatStyleKeywords( formattedMessageHtml, message );
00543
00544
00545
00546 DOM::HTMLElement newMessageNode = document().createElement( QString("span") );
00547 newMessageNode.setInnerHTML( formattedMessageHtml );
00548
00549
00550 DOM::HTMLElement insertNode = document().getElementById( QString("insert") );
00551
00552 if( isConsecutiveMessage && !insertNode.isNull() )
00553 {
00554
00555 insertNode.parentNode().replaceChild(newMessageNode, insertNode);
00556 }
00557 else
00558 {
00559
00560 if( !insertNode.isNull() )
00561 insertNode.parentNode().removeChild(insertNode);
00562
00563 chatNode.appendChild(newMessageNode);
00564 }
00565
00566 if ( message.type() == Kopete::Message::TypeNormal )
00567 {
00568 if ( message.direction() == Kopete::Message::Outbound )
00569 changeMessageStateElement( message.id(), message.state() );
00570 }
00571 else if ( message.type() == Kopete::Message::TypeFileTransferRequest )
00572 {
00573 if ( message.fileTransferDisabled() )
00574 disableFileTransferButtons( message.id() );
00575 else
00576 addFileTransferButtonsEventListener( message.id() );
00577 }
00578
00579
00580
00581
00582 d->latestDirection = message.direction();
00583 d->latestType = message.type();
00584 d->latestContact = const_cast<Kopete::Contact*>(message.from());
00585
00586
00587 if(!restoring)
00588 d->allMessages.append(message);
00589
00590 while ( bufferLen>0 && d->allMessages.count() >= bufferLen )
00591 {
00592 d->allMessages.pop_front();
00593
00594
00595
00596 if( !KopeteChatWindowSettings::self()->groupConsecutiveMessages() )
00597 {
00598 chatNode.removeChild( chatNode.firstChild() );
00599 }
00600 }
00601
00602 if ( !d->scrollPressed )
00603 QTimer::singleShot( 1, this, SLOT( slotScrollView() ) );
00604
00605 #ifdef STYLE_TIMETEST
00606 kDebug(14000) << "Message time: " << beforeMessage.msecsTo( QTime::currentTime());
00607 #endif
00608 }
00609
00610 void ChatMessagePart::slotRefreshView()
00611 {
00612 DOM::HTMLElement kopeteNode = document().getElementById( QString("KopeteStyle") );
00613 if( !kopeteNode.isNull() )
00614 kopeteNode.setInnerText( styleHTML() );
00615
00616 DOM::HTMLBodyElement bodyElement = htmlDocument().body();
00617 bodyElement.setBgColor( Kopete::AppearanceSettings::self()->chatBackgroundColor().name() );
00618 }
00619
00620 void ChatMessagePart::keepScrolledDown()
00621 {
00622 if ( !d->scrollPressed )
00623 QTimer::singleShot( 1, this, SLOT( slotScrollView() ) );
00624 }
00625
00626 const QString ChatMessagePart::styleHTML() const
00627 {
00628 Kopete::AppearanceSettings *settings = Kopete::AppearanceSettings::self();
00629
00630 QString style = QString(
00631 "body{background-color:%1;font-family:%2;font-size:%3pt;color:%4}"
00632 "td{font-family:%5;font-size:%6pt;color:%7}"
00633 "input{font-family:%8;font-size:%9pt;color:%10}"
00634 "a{color:%11}a.visited{color:%12}"
00635 "a.KopeteDisplayName{text-decoration:none;color:inherit;}"
00636 "a.KopeteDisplayName:hover{text-decoration:underline;color:inherit}"
00637 ".KopeteLink{cursor:pointer;}.KopeteLink:hover{text-decoration:underline}"
00638 ".KopeteMessageBody > p:first-child{margin:0;padding:0;display:inline;}" )
00639 .arg( settings->chatBackgroundColor().name() )
00640 .arg( settings->chatFont().family() )
00641 .arg( settings->chatFont().pointSize() )
00642 .arg( settings->chatTextColor().name() )
00643 .arg( settings->chatFont().family() )
00644 .arg( settings->chatFont().pointSize() )
00645 .arg( settings->chatTextColor().name() )
00646 .arg( settings->chatFont().family() )
00647 .arg( settings->chatFont().pointSize() )
00648 .arg( settings->chatTextColor().name() )
00649 .arg( settings->chatLinkColor().name() )
00650 .arg( settings->chatLinkColor().name() );
00651
00652 return style;
00653 }
00654
00655 void ChatMessagePart::clear()
00656 {
00657
00658 writeTemplate();
00659
00660
00661 d->latestContact = 0;
00662
00663
00664 QList<Kopete::Message>::ConstIterator it, itEnd = d->allMessages.constEnd();
00665 for ( it = d->allMessages.constBegin(); it != itEnd; ++it )
00666 {
00667 if ( (*it).type() == Kopete::Message::TypeFileTransferRequest && !(*it).fileTransferDisabled() )
00668 {
00669 Kopete::TransferManager::transferManager()->cancelIncomingTransfer( (*it).id() );
00670 }
00671 }
00672
00673
00674 d->allMessages.clear();
00675 }
00676
00677 Kopete::Contact *ChatMessagePart::contactFromNode( const DOM::Node &n ) const
00678 {
00679 DOM::Node node = n;
00680 int i;
00681 QList<Kopete::Contact*> m;
00682
00683 if ( node.isNull() )
00684 return 0;
00685
00686 while ( !node.isNull() && ( node.nodeType() == DOM::Node::TEXT_NODE || ((DOM::HTMLElement)node).className() != "KopeteDisplayName" ) )
00687 node = node.parentNode();
00688
00689 DOM::HTMLElement element = node;
00690 if ( element.className() != "KopeteDisplayName" )
00691 return 0;
00692
00693 m = d->manager->members();
00694 if ( element.hasAttribute( "contactid" ) )
00695 {
00696 QString contactId = element.getAttribute( "contactid" ).string();
00697 for ( i =0; i != m.size(); i++ )
00698 if ( m.at(i)->contactId() == contactId )
00699 return m[i];
00700 }
00701 else
00702 {
00703 QString nick = element.innerText().string().trimmed();
00704 foreach ( Kopete::Contact *contact, m )
00705 {
00706 QString contactNick;
00707 if( contact->metaContact() && contact->metaContact() != Kopete::ContactList::self()->myself() )
00708 contactNick = contact->metaContact()->displayName();
00709 else
00710 contactNick = contact->nickName();
00711
00712 if ( contactNick == nick )
00713 return contact;
00714 }
00715 }
00716
00717 return 0;
00718 }
00719
00720 void ChatMessagePart::slotRightClick( const QString &, const QPoint &point )
00721 {
00722
00723 DOM::Node activeNode = nodeUnderMouse();
00724 while ( !activeNode.isNull() && activeNode.nodeType() != DOM::Node::ELEMENT_NODE )
00725 activeNode = activeNode.parentNode();
00726
00727
00728 d->activeElement = activeNode;
00729 if ( d->activeElement.isNull() )
00730 return;
00731
00732 KMenu *chatWindowPopup = 0L;
00733
00734 if ( Kopete::Contact *contact = contactFromNode( d->activeElement ) )
00735 {
00736 chatWindowPopup = contact->popupMenu( d->manager );
00737 connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup , SLOT( deleteLater() ) );
00738 }
00739 else
00740 {
00741 chatWindowPopup = new KMenu();
00742
00743 QAction *action;
00744 if ( d->activeElement.className() == QLatin1String("KopeteDisplayName") )
00745 {
00746 action = chatWindowPopup->addAction( i18n( "User Has Left" ) );
00747 action->setEnabled(false);
00748 chatWindowPopup->addSeparator();
00749 }
00750 else if ( d->activeElement.tagName().lower() == QLatin1String( "a" ) )
00751 {
00752 chatWindowPopup->addAction( d->copyURLAction );
00753 chatWindowPopup->addSeparator();
00754 }
00755
00756 d->copyAction->setEnabled( hasSelection() );
00757 chatWindowPopup->addAction( d->copyAction );
00758 chatWindowPopup->addAction( d->saveAction );
00759 chatWindowPopup->addAction( d->printAction );
00760 chatWindowPopup->addSeparator();
00761 chatWindowPopup->addAction( d->closeAction );
00762
00763 connect( chatWindowPopup, SIGNAL( aboutToHide() ), chatWindowPopup, SLOT( deleteLater() ) );
00764 chatWindowPopup->popup( point );
00765 }
00766
00767
00768 emit contextMenuEvent( textUnderMouse(), chatWindowPopup );
00769
00770 chatWindowPopup->popup( point );
00771 }
00772
00773 QString ChatMessagePart::textUnderMouse()
00774 {
00775 DOM::Node activeNode = nodeUnderMouse();
00776 if( activeNode.nodeType() != DOM::Node::TEXT_NODE )
00777 return QString();
00778
00779 DOM::Text textNode = activeNode;
00780 QString data = textNode.data().string();
00781
00782
00783 int mouseLeft = view()->mapFromGlobal( QCursor::pos() ).x(),
00784 nodeLeft = activeNode.getRect().x(),
00785 cPos = 0,
00786 dataLen = data.length();
00787
00788 QFontMetrics metrics( Kopete::AppearanceSettings::self()->chatFont() );
00789 QString buffer;
00790 while( cPos < dataLen && nodeLeft < mouseLeft )
00791 {
00792 QChar c = data[cPos++];
00793 if( c.isSpace() )
00794 buffer.truncate(0);
00795 else
00796 buffer += c;
00797
00798 nodeLeft += metrics.width(c);
00799 }
00800
00801 if( cPos < dataLen )
00802 {
00803 QChar c = data[cPos++];
00804 while( cPos < dataLen && !c.isSpace() )
00805 {
00806 buffer += c;
00807 c = data[cPos++];
00808 }
00809 }
00810
00811 return buffer;
00812 }
00813
00814 void ChatMessagePart::slotCopyURL()
00815 {
00816 DOM::HTMLAnchorElement a = d->activeElement;
00817 if ( !a.isNull() )
00818 {
00819 QApplication::clipboard()->setText( a.href().string(), QClipboard::Clipboard );
00820 QApplication::clipboard()->setText( a.href().string(), QClipboard::Selection );
00821 }
00822 }
00823
00824 void ChatMessagePart::slotScrollView()
00825 {
00826
00827
00828
00829 view()->scrollBy( 0, view()->contentsHeight() );
00830 }
00831
00832 void ChatMessagePart::copy(bool justselection )
00833 {
00834
00835
00836
00837
00838
00839
00840 QString htmltext = selectedTextAsHTML();
00841 QString text = selectedText();
00842
00843
00844
00845
00846
00847 if(text.isEmpty())
00848 return;
00849
00850 disconnect( QApplication::clipboard(), SIGNAL( selectionChanged()), this, SLOT( slotClearSelection()));
00851
00852 #ifndef QT_NO_MIMECLIPBOARD
00853 if(!justselection)
00854 {
00855 QMimeData *mimeData = new QMimeData();
00856 mimeData->setText(text);
00857
00858 if(!htmltext.isEmpty()) {
00859 htmltext.replace( QChar( 0xa0 ), ' ' );
00860 mimeData->setHtml(htmltext);
00861 }
00862
00863 QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
00864 }
00865 QApplication::clipboard()->setText( text, QClipboard::Selection );
00866 #else
00867 if(!justselection)
00868 QApplication::clipboard()->setText( text, QClipboard::Clipboard );
00869 QApplication::clipboard()->setText( text, QClipboard::Selection );
00870 #endif
00871 connect( QApplication::clipboard(), SIGNAL( selectionChanged()), SLOT( slotClearSelection()));
00872
00873 }
00874
00875 void ChatMessagePart::print()
00876 {
00877 view()->print();
00878 }
00879
00880 void ChatMessagePart::khtmlDrawContentsEvent( khtml::DrawContentsEvent * event)
00881 {
00882 KHTMLPart::khtmlDrawContentsEvent(event);
00883
00884 }
00885 void ChatMessagePart::slotCloseView( bool force )
00886 {
00887 d->manager->view()->closeView( force );
00888 }
00889
00890 void ChatMessagePart::emitTooltipEvent( const QString &textUnderMouse, QString &toolTip )
00891 {
00892 emit tooltipEvent( textUnderMouse, toolTip );
00893 }
00894
00895
00896 QString ChatMessagePart::formatStyleKeywords( const QString &sourceHTML, const Kopete::Message &_message )
00897 {
00898 Kopete::Message message=_message;
00899 QString resultHTML = sourceHTML;
00900 QString nick, contactId, service, protocolIcon, nickLink;
00901
00902 if( message.from() )
00903 {
00904 nick = formatName(message.from(), Qt::RichText);
00905 contactId = message.from()->contactId();
00906
00907
00908
00909
00910
00911
00912 QString iconName = QLatin1String("kopete");
00913 service = QLatin1String("Kopete");
00914 if(message.from()->protocol() && !message.from()->protocol()->displayName().isNull())
00915 {
00916 service = message.from()->protocol()->displayName();
00917 iconName = message.from()->protocol()->pluginIcon();
00918 }
00919
00920 protocolIcon = KIconLoader::global()->iconPath( iconName, KIconLoader::Small );
00921
00922 nickLink=QString("<a href=\"kopetemessage://%1/?protocolId=%2&accountId=%3\" class=\"KopeteDisplayName\">")
00923 .arg( Qt::escape(message.from()->contactId()).replace('"',"""),
00924 Qt::escape(message.from()->protocol()->pluginId()).replace('"',"""),
00925 Qt::escape(message.from()->account()->accountId() ).replace('"',"""));
00926 }
00927 else
00928 {
00929 nickLink="<a>";
00930 }
00931
00932
00933
00934 resultHTML.replace( QLatin1String("%sender%"), nickLink+nick+"</a>" );
00935
00936 if ( Kopete::BehaviorSettings::showDates() )
00937 resultHTML.replace( QLatin1String("%time%"), KGlobal::locale()->formatDateTime(message.timestamp(), KLocale::ShortDate, true) );
00938 else
00939 resultHTML.replace( QLatin1String("%time%"), KGlobal::locale()->formatTime(message.timestamp().time(), true) );
00940
00941 resultHTML.replace( QLatin1String("%senderScreenName%"), nickLink+Qt::escape(contactId)+"</a>" );
00942
00943 resultHTML.replace( QLatin1String("%service%"), Qt::escape(service) );
00944
00945 resultHTML.replace( QLatin1String("%senderStatusIcon%"), Qt::escape(protocolIcon).replace('"',""") );
00946
00947
00948 QRegExp timeRegExp("%time\\{([^}]*)\\}%");
00949 int pos=0;
00950 while( (pos=timeRegExp.indexIn(resultHTML , pos) ) != -1 )
00951 {
00952 QString timeKeyword = formatTime( timeRegExp.cap(1), message.timestamp() );
00953 resultHTML.replace( pos , timeRegExp.cap(0).length() , timeKeyword );
00954 }
00955
00956
00957
00958
00959
00960 QString bgColor = QLatin1String("inherit");
00961 if( message.importance() == Kopete::Message::Highlight && Kopete::BehaviorSettings::self()->highlightEnabled() )
00962 {
00963 bgColor = Kopete::AppearanceSettings::self()->highlightBackgroundColor().name();
00964 }
00965
00966 QRegExp textBackgroundRegExp("%textbackgroundcolor\\{([^}]*)\\}%");
00967 int textPos=0;
00968 while( (textPos=textBackgroundRegExp.indexIn(resultHTML, textPos) ) != -1 )
00969 {
00970 resultHTML.replace( textPos , textBackgroundRegExp.cap(0).length() , bgColor );
00971 }
00972
00973
00974 if( message.from() )
00975 {
00976 QString photoPath = photoForContact( message.from() );
00977 if( photoPath.isEmpty() )
00978 {
00979 if(message.direction() == Kopete::Message::Inbound)
00980 photoPath = d->currentChatStyle->getStyleBaseHref() + QLatin1String("Incoming/buddy_icon.png");
00981 else if(message.direction() == Kopete::Message::Outbound)
00982 photoPath = d->currentChatStyle->getStyleBaseHref() + QLatin1String("Outgoing/buddy_icon.png");
00983 }
00984 resultHTML.replace(QLatin1String("%userIconPath%"), photoPath);
00985 }
00986
00987
00988
00989 if( message.type() == Kopete::Message::TypeAction && !d->currentChatStyle->hasActionTemplate() )
00990 {
00991 kDebug(14000) << "Map Action message to Status template. ";
00992
00993 QString boldNick = QString("%1<b>%2</b></a> ").arg(nickLink,nick);
00994 QString newBody = boldNick + message.parsedBody();
00995 message.setHtmlBody(newBody );
00996 }
00997
00998
00999 resultHTML.replace( QLatin1String("%messageDirection%"), message.isRightToLeft() ? "rtl" : "ltr" );
01000
01001
01002
01003 static const char* const nameColors[] =
01004 {
01005 "red", "blue" , "gray", "magenta", "violet", "#808000", "yellowgreen",
01006 "darkred", "darkgreen", "darksalmon", "darkcyan", "#B07D2B",
01007 "mediumpurple", "peru", "olivedrab", "#B01712", "darkorange", "slateblue",
01008 "slategray", "goldenrod", "orangered", "tomato", "#1E90FF", "steelblue",
01009 "deeppink", "saddlebrown", "coral", "royalblue"
01010 };
01011
01012 static const int nameColorsLen = sizeof(nameColors) / sizeof(nameColors[0]) - 1;
01013
01014 int hash = 0;
01015 for( int f = 0; f < contactId.length(); ++f )
01016 hash += contactId[f].unicode() * f;
01017 const QString colorName = nameColors[ hash % nameColorsLen ];
01018 QString lightColorName;
01019 kDebug(14000) << "Hash " << hash << " has color " << colorName;
01020 QRegExp senderColorRegExp("%senderColor(?:\\{([^}]*)\\})?%");
01021 textPos=0;
01022 while( (textPos=senderColorRegExp.indexIn(resultHTML, textPos) ) != -1 )
01023 {
01024 int light=100;
01025 bool doLight=false;
01026 if(senderColorRegExp.numCaptures()>=1)
01027 {
01028 light=senderColorRegExp.cap(1).toUInt(&doLight);
01029 }
01030
01031
01032 if ( doLight && lightColorName.isNull() )
01033 lightColorName = QColor( colorName ).light( light ).name();
01034
01035 resultHTML.replace( textPos , senderColorRegExp.cap(0).length(),
01036 doLight ? lightColorName : colorName );
01037 }
01038
01039 if ( message.type() == Kopete::Message::TypeFileTransferRequest )
01040 {
01041 QString fileIcon;
01042 if ( !message.filePreview().isNull() )
01043 {
01044 QByteArray tempArray;
01045 QBuffer tempBuffer( &tempArray );
01046 tempBuffer.open( QIODevice::WriteOnly );
01047 if( message.filePreview().save( &tempBuffer, "PNG" ) )
01048 fileIcon = QString( "data:image/png;base64," ) + tempArray.toBase64();
01049 }
01050
01051 if ( fileIcon.isEmpty() )
01052 {
01053 QString iconName = KMimeType::iconNameForUrl( message.fileName() );
01054 fileIcon = KIconLoader::global()->iconPath( iconName, -KIconLoader::SizeMedium );
01055 }
01056
01057 resultHTML.replace( QLatin1String("%fileName%"), Qt::escape( message.fileName() ).replace('"',""") );
01058 resultHTML.replace( QLatin1String("%fileSize%"), KGlobal::locale()->formatByteSize( message.fileSize() / 8 ).replace('"',""") );
01059 resultHTML.replace( QLatin1String("%fileIconPath%"), fileIcon );
01060
01061 resultHTML.replace( QLatin1String("%saveFileHandlerId%"), QString( "ftSV%1" ).arg( message.id() ) );
01062 resultHTML.replace( QLatin1String("%saveFileAsHandlerId%"), QString( "ftSA%1" ).arg( message.id() ) );
01063 resultHTML.replace( QLatin1String("%cancelRequestHandlerId%"), QString( "ftCC%1" ).arg( message.id() ) );
01064 }
01065
01066 if ( message.type() == Kopete::Message::TypeNormal && message.direction() == Kopete::Message::Outbound )
01067 resultHTML.replace( QLatin1String( "%stateElementId%" ), QString( "msST%1" ).arg( message.id() ) );
01068
01069
01070 resultHTML.replace( QLatin1String("%message%"), formatMessageBody(message) );
01071
01072
01073
01074 return resultHTML;
01075 }
01076
01077
01078 QString ChatMessagePart::formatStyleKeywords( const QString &sourceHTML )
01079 {
01080 QString resultHTML = sourceHTML;
01081
01082
01083 if( !d->manager->members().isEmpty() && d->manager->myself() )
01084 {
01085 QString sourceName, destinationName;
01086
01087 Kopete::Contact *remoteContact = d->manager->members().first();
01088
01089
01090 sourceName = d->manager->myself()->nickName();
01091 if( remoteContact->metaContact() )
01092 destinationName = remoteContact->metaContact()->displayName();
01093 else
01094 destinationName = remoteContact->nickName();
01095
01096
01097 resultHTML.replace( QLatin1String("%chatName%"), QString("<span id=\"KopeteHeaderChatNameInternal\">%1</span>").arg( formatName(d->manager->displayName(), Qt::RichText) ) );
01098
01099 resultHTML.replace( QLatin1String("%sourceName%"), formatName(sourceName, Qt::RichText) );
01100
01101 resultHTML.replace( QLatin1String("%destinationName%"), formatName(destinationName, Qt::RichText) );
01102
01103 resultHTML.replace( QLatin1String("%timeOpened%"), KGlobal::locale()->formatDateTime( QDateTime::currentDateTime(), KLocale::ShortDate, true ) );
01104
01105
01106 QRegExp timeRegExp("%timeOpened\\{([^}]*)\\}%");
01107 int pos=0;
01108 while( (pos=timeRegExp.indexIn(resultHTML, pos) ) != -1 )
01109 {
01110 QString timeKeyword = formatTime( timeRegExp.cap(1), QDateTime::currentDateTime() );
01111 resultHTML.replace( pos , timeRegExp.cap(0).length() , timeKeyword );
01112 }
01113
01114 QString photoIncoming = photoForContact( remoteContact );
01115 QString photoOutgoing = photoForContact( d->manager->myself() );
01116 if( photoIncoming.isEmpty() )
01117 {
01118 photoIncoming = d->currentChatStyle->getStyleBaseHref() + QLatin1String("Incoming/buddy_icon.png");
01119 }
01120
01121 if( photoOutgoing.isEmpty() )
01122 {
01123 photoOutgoing = d->currentChatStyle->getStyleBaseHref() + QLatin1String("Outgoing/buddy_icon.png");
01124 }
01125
01126 resultHTML.replace( QLatin1String("%incomingIconPath%"), photoIncoming );
01127 resultHTML.replace( QLatin1String("%outgoingIconPath%"), photoOutgoing );
01128 }
01129
01130 return resultHTML;
01131 }
01132
01133 QString ChatMessagePart::formatTime(const QString &timeFormat, const QDateTime &dateTime)
01134 {
01135 char buffer[256];
01136
01137 time_t timeT;
01138 struct tm *loctime;
01139
01140 timeT = dateTime.toTime_t();
01141
01142 loctime = localtime (&timeT);
01143 strftime (buffer, 256, timeFormat.toAscii(), loctime);
01144
01145 return QString(buffer);
01146 }
01147
01148 QString ChatMessagePart::formatName(const