/*************************************************************************** chatview.cpp - description ------------------- begin : Wed Jan 15 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "chatview.h" #include <qcolor.h> #include <qdatetime.h> #include <qpushbutton.h> #include <qregexp.h> #include <qstylesheet.h> #include <qtextbrowser.h> #include <qtextedit.h> #include <kdebug.h> #include <kglobal.h> #include <klocale.h> #include <krun.h> #include <ktextbrowser.h> #include <kurl.h> #include "../contact/contact.h" #include "../currentaccount.h" #include "../kmessdebug.h" #include "../emoticoncollection.h" #include "../emoticon.h" #include "../msnstring.h" #include "emoticonchooser.h" // The constructor ChatView::ChatView(QWidget *parent, const char *name ) : ChatViewInterface(parent,name), currentAccount_(0), doSendTypingMessages_(true), emoticonChooser_(0), initialized_(false), isEmpty_(true) { sendButton_->setEnabled( false ); chatBrowser_->setFocus(); // Make the chat browser and edit backgrounds white chatBrowser_->setPaletteBackgroundColor( QColor( 255, 255, 255 ) ); messageEdit_->setPaletteBackgroundColor( QColor( 255, 255, 255 ) ); messageEdit_->setPaletteForegroundColor( QColor( 0, 0, 0 ) ); // Connect the message edit so that if its displayed color changes, // it checked to match the user's chosen color. connect( messageEdit_, SIGNAL( currentColorChanged( const QColor & ) ), this, SLOT ( editorColorChanged( const QColor & ) ) ); } // The destructor ChatView::~ChatView() { } // Add the given text to the chat browser and scroll to the end void ChatView::addTextToBrowser( QString text ) { // Set the text to the browser window chatBrowser_->append( text ); isEmpty_ = false; scrollChatToBottom(); } // Delete the newline behind the message edit's cursor. void ChatView::deleteNewlineAtCursor() { int endpara, endindex, startpara, startindex; // Get the cursor's position in the message edit messageEdit_->getCursorPosition(&endpara, &endindex); // The position before the cursor will be one index behind the end, // unless the index is zero. if ( endindex == 0 ) { startpara = endpara - 1; startindex = messageEdit_->paragraphLength( startpara ); // Select the character behind the cursor messageEdit_->setSelection(startpara, startindex, endpara, endindex); // Delete the selection, which should be a newline. messageEdit_->del(); } } // The color in the text box changed. void ChatView::editorColorChanged(const QColor &color) { #ifdef KMESSTEST ASSERT( currentAccount_ != 0 ); #endif // Sometimes the text box color seems to spontaneously reset to black. // If this happened, set the color back to the user's color. if ( color.name() != currentAccount_->getFontColor() ) { #ifdef KMESSDEBUG_CHATVIEW kdDebug() << "ChatView - Restore the color to the account setting by calling 'updateEditorFont'." << endl; #endif updateEditorFont(); } } // The user pressed return in the message editor, so send the message void ChatView::enterPressed() { if(! messageEdit_->text().isEmpty()) { // Don't send any typing messages while preparing to send the message in the text box... doSendTypingMessages_ = false; // Pressing enter caused a newline to be entered under the cursor... remove it. deleteNewlineAtCursor(); // Send the message. sendMessage(); // Now messages can again be sent doSendTypingMessages_ = true; } } // Initialize the object bool ChatView::initialize() { if ( initialized_ ) { kdDebug() << "ChatView already initialized." << endl; return false; } if ( !initializeCurrentAccount() ) { return false; } if ( !initializeEmoticonChooser() ) { return false; } if ( !initializeEmoticons() ) { return false; } chatBrowser_->setTextFormat( RichText ); chatBrowser_->setText( "<qt>" ); initialized_ = true; return true; } // Initialize the current account bool ChatView::initializeCurrentAccount() { currentAccount_ = CurrentAccount::instance(); if ( currentAccount_ == 0 ) { kdDebug() << "ChatView - Couldn't get the instance of the current account." << endl; return false; } connect( currentAccount_, SIGNAL( changedFontSettings() ), this, SLOT ( updateEditorFont() ) ); updateEditorFont(); return true; } // Create the emoticons bool ChatView::initializeEmoticons() { emoticons_ = EmoticonCollection::instance(); if ( emoticons_ == 0 ) { kdDebug() << "ChatView - Unable to get instance of emoticon collection." << endl; return false; } return true; } // Initialize the emoticon chooser bool ChatView::initializeEmoticonChooser() { // Create the emoticon chooser emoticonChooser_ = new EmoticonChooser( this, "emoticonChooser" ); // Connect up the emoticon chooser // Connect the chooser's insertEmoticon signal connect( emoticonChooser_, SIGNAL( insertEmoticon( QString ) ), this, SLOT ( insertEmoticon( QString ) ) ); return true; } // Insert an emoticon into the message editor void ChatView::insertEmoticon( QString emoticonText ) { // Insert the text at the cursor. messageEdit_->insert( emoticonText ); // Delete the newline there deleteNewlineAtCursor(); } // Whether or not the message area is empty bool ChatView::isEmpty() const { return isEmpty_; } // The message text changed, so the user is typing void ChatView::messageTextChanged() { if ( doSendTypingMessages_ ) { // Disable or enable the send button depending on whether or not the message edit is empty if ( messageEdit_->text().isEmpty() ) { sendButton_->setEnabled( false ); } else { sendButton_->setEnabled( true ); // If the last character of the message is a newline, sending the typing signal will // cause the actual text message to be received twice, so check the last character... if ( messageEdit_->text().right(1) != "\n" ) { // If the typing timer isn't already going... if ( !userTypingTimer_.isActive() ) { emit userIsTyping(); userTypingTimer_.start( 4000, true ); } } } } } // The user clicked the new line button so insert a new line in the editor void ChatView::newLineClicked() { messageEdit_->insert("\n"); } // Do some effects characters (ie, bold, underline and italic specials) void ChatView::parseEffects(QString &text) const { int offset = 0, effectsNumber; bool startFree, endFree; QRegExp effectsSearch; QString effectsCharacter, markupString; QChar boundaryCharacter; for (effectsNumber = 0; effectsNumber < 3; effectsNumber++) { switch( effectsNumber ) { case 0: effectsCharacter = "_"; markupString = "u"; break; case 1: effectsCharacter = "\\*"; markupString = "b"; break; case 2: effectsCharacter = "/"; markupString = "i"; break; } effectsSearch.setPattern( effectsCharacter + "([a-zA-Z0-9]+)" + effectsCharacter ); offset = 0; while( offset >= 0 ) { offset = effectsSearch.search( text, offset ); if( offset >= 0 ) { startFree = endFree = false; if ( offset == 0 ) { startFree = true; } else { boundaryCharacter = text.at( offset - 1 ); if ( boundaryCharacter.isSpace() || boundaryCharacter.isPunct() ) startFree = true; } if ( ( offset + effectsSearch.matchedLength() ) == text.length() ) { endFree = true; } else { boundaryCharacter = text.at( offset + effectsSearch.matchedLength() ); if ( boundaryCharacter.isSpace() || boundaryCharacter.isPunct() ) endFree = true; } if ( startFree && endFree ) { text.replace( offset, 1, "<" + markupString + ">"); text.replace( offset + effectsSearch.matchedLength() + markupString.length(), 1, "</" + markupString + ">"); } } offset+= effectsSearch.matchedLength(); } } } // Replace any urls with a real clickable URL. void ChatView::parseUrls(QString &text) const { int wwwPosition; QString httpText; // First, replace all "www."s with "http://www." // This should work, but doesn't, so do it a cruder way // text = text.replace( QRegExp("(?!http://)www."), "http://www." ); wwwPosition = text.find("www."); while ( wwwPosition >= 0 ) { httpText = text.mid( wwwPosition - 7, 7 ); if ( httpText != "http://" && httpText != "ttps://" ) { text.insert( wwwPosition, "http://" ); } wwwPosition = text.find("www.", wwwPosition + 11); } // Then parse for common expressions. parseUrlsForTag( text, "https:" ); parseUrlsForTag( text, "http:" ); parseUrlsForTag( text, "ftp:" ); parseUrlsForTag( text, "mailto:" ); } // Parse the text for the given tag and insert a clickable URL for it. void ChatView::parseUrlsForTag(QString &text, QString tag) const { int newline, space, tagPosition, tagEndPosition; QString tagText, fullTag, fullEndTag; tagPosition = text.find( tag ); // While the tag exists in the text... while ( tagPosition >= 0 ) { // If the tag is found, find the end of the tag. // The end of the tag is at the first space or newling after the tag start. space = text.find( " ", tagPosition ); newline = text.find( "\n", tagPosition ); if ( space >= 0 ) { if ( newline >= 0 ) { if ( space < newline ) { tagEndPosition = space; } else { tagEndPosition = newline; } } else { tagEndPosition = space; } } else { if ( newline >= 0 ) { tagEndPosition = newline; } else { tagEndPosition = text.length(); } } // Check that the character at the tag position isn't // punctuation. if ( ( text.mid( tagEndPosition - 1, 1 ) == "." ) || ( text.mid( tagEndPosition - 1, 1 ) == "?" ) || ( text.mid( tagEndPosition - 1, 1 ) == "\"" ) ) { tagEndPosition = tagEndPosition - 1; } // Extract the text of the tag tagText = text.mid( tagPosition, tagEndPosition - tagPosition ); // Insert the end of the tag. fullEndTag = "</a>"; text.insert( tagEndPosition, fullEndTag ); // Insert the start of the tag. fullTag = "<a href=\"" + tagText + "\">"; text.insert( tagPosition, fullTag ); // Find the next occurence of "tag" in the text. tagPosition = text.find( tag, tagEndPosition + fullTag.length() + fullEndTag.length() ); } } // Scroll to the bottom of the chat browser void ChatView::scrollChatToBottom() { // Make sure the browser scrolls to the bottom. chatBrowser_->repaintContents( 0, 0, chatBrowser_->contentsWidth(), chatBrowser_->contentsHeight(), true); chatBrowser_->scrollToBottom(); } // The user clicked send, so send the message void ChatView::sendClicked() { sendMessage(); } // Send a message via the server void ChatView::sendMessage() { uint maxSendableMessageLength = 1400; QString text = messageEdit_->text(); if(! text.isEmpty()) { messageEdit_->clear(); // Since the message will be sent as UTF8, it's the UTF8 length we have to consider. // If the text is longer than the sendable amount, put the remainder back in the text edit. if ( text.utf8().length() > maxSendableMessageLength ) { // If so, then divide the text into the first part and a remainder. QCString remainder = text.utf8().right( text.utf8().length() - maxSendableMessageLength ); text = QString::fromUtf8( text.utf8().left( maxSendableMessageLength ) ); // Return the remainder to the message edit. messageEdit_->setText( QString::fromUtf8( remainder ) ); } // Replace "\n"s with "\r\n"s text = text.replace( QRegExp("\n"), "\r\n" ); // Ask the server to send the message to the contact(s) emit sendMessageToContact( text ); // Show the message in the browser window showMessage( currentAccount_->getHandle(), currentAccount_->getFriendlyName(), text, currentAccount_->getFont(), currentAccount_->getFontColor(), "#006A00" ); } } // Show a preformatted application message in the message browser. void ChatView::showAppMessage(QString html) { addTextToBrowser(html + "<br>"); } // Show the emoticon chooser at the given point. void ChatView::showEmoticonChooser(QPoint point) { // Popup the emoticon chooser at that position. emoticonChooser_->popup( point ); } // Add the given message to the message browser. void ChatView::showMessage(QString handle, QString name, QString text, QFont font, QString color, QString nameColor) { MsnString parsedName, parsedText; #ifdef KMESSTEST ASSERT( currentAccount_ != 0 ); #endif #ifdef KMESSDEBUG_CHATVIEW kdDebug() << "ChatView - Show message from " << name << endl; #endif QString time, formattedMessage, parsedMessage, fontBefore, fontAfter; // See if this is a contact's message and not the user if ( handle != currentAccount_->getHandle() ) { // If the user wants to replace the contact's font with a font of his choosing... if ( currentAccount_->getUseContactFont() ) { // Replace the given font with the user's stored contact font font = currentAccount_->getContactFont(); color = currentAccount_->getContactFontColor(); } } // Get the time time = KGlobal::locale()->formatTime( QTime::currentTime(), true ); parsedName.setText( name ); parsedText.setText( text ); // for now... // later all the parsing done here will be done in the MsnString class name = parsedName.getText(); text = parsedText.getText(); // Parse URLs in the text. parseUrls( text ); // Parse effects characters (_, *, /) if ( currentAccount_->getUseFontEffects() ) { parseEffects( text ); parseEffects( name ); } // Replace any "\r\n"s in the message with "<br>" so that carriage returns will show properly. text = text.replace( "\r\n", "<br>" ) .replace( '\r', "<br>" ) .replace( '\n', "<br>" ); // Create the font HTML for the message fontBefore = ""; if ( font.bold() ) fontBefore += "<b>"; if ( font.italic() ) fontBefore += "<i>"; if ( font.underline() ) fontBefore += "<u>"; fontBefore += "<font face=\"" + font.family() + "\" color=\"" + color + "\" >"; fontAfter = "</font>"; if ( font.underline() ) fontAfter += "</u>"; if ( font.italic() ) fontAfter += "</i>"; if ( font.bold() ) fontAfter += "</b>"; // Get the user's set message format, wrapped in the font and color of the user name parsedMessage = "<font color=\"" + nameColor + "\" >" + currentAccount_->getChatFormat() + "</font>"; // Replace returns in the format with breaks parsedMessage = parsedMessage.replace( QRegExp("\n"), "<br>" ); // Replace the variables in the format with their proper formatted values. parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "time" ) + "\\}"), time ); parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "name" ) + "\\}"), name ); parsedMessage = parsedMessage.replace( QRegExp("\\{" + i18n( "message" ) + "\\}"), fontBefore + text + fontAfter ); parsedMessage = parsedMessage.replace( QRegExp(" "), " " ); // Add the message to the browser addTextToBrowser( parsedMessage ); } // Show a specially formatted system message in the message browser. void ChatView::showSystemMessage(QString message) { QString html; // Put the system message in html form between some nice lines. html = "<hr size=2 color=red><font color=\"red\">"; html += message; html += "</font><hr size=2 color=red>"; addTextToBrowser( html ); } // Update the editor's font to match the account's font void ChatView::updateEditorFont() { #ifdef KMESSTEST ASSERT( currentAccount_ != 0 ); #endif QColor color; if ( currentAccount_ != 0 ) { // Change the color to the user's color color.setNamedColor( currentAccount_->getFontColor() ); messageEdit_->setColor( color ); messageEdit_->setPaletteForegroundColor( color ); messageEdit_->setFont( currentAccount_->getFont() ); messageEdit_->setCurrentFont( currentAccount_->getFont() ); messageEdit_->setPointSize( currentAccount_->getFont().pointSize() ); /* browserFont = account_->getFont(); messageBrowser_->setCurrentFont( browserFont ); */ } } // The user clicked a url in the chat browser void ChatView::urlClicked(const QString &url) { KURL theurl(url); if ( theurl.protocol() == "kmess" ) { QString method = theurl.host(); QString cookie = theurl.query().mid( 1 ); #ifdef KMESSDEBUG_CHATVIEW kdDebug() << "ChatWindowView::urlClick( url = " << url << " )" << endl; kdDebug() << "CWV: Method = " << method << " Cookie = " << cookie << endl; #endif emit appCommand( cookie, method ); } else { #ifdef KMESSDEBUG_CHATVIEW kdDebug() << "CWV: User clicked on url: " << url << endl; #endif new KRun(theurl); } } #include "chatview.moc"