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