/*************************************************************************** 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 "../contact/contact.h" #include "../dialogs/addemoticondialog.h" #include "../model/contactlist.h" #include "../utils/kmessshared.h" #include "../currentaccount.h" #include "../accountsmanager.h" #include "../emoticonmanager.h" #include "../kmess.h" #include "../kmessapplication.h" #include "contactswidget.h" #include <IsfQt> #include <IsfInkCanvas> #include <QClipboard> #include <QDropEvent> #include <QEvent> #include <QRegExp> #include <QTemporaryFile> #include <QTextCodec> #include <QTextDocument> #include <QTextOption> #include <KAction> #include <KFileDialog> #include <KHTMLView> #include <KLocale> #include <KMenu> #include <KMessageBox> #include <KRun> #include <KStandardAction> // The constructor ChatView::ChatView( QWidget *parent ) : QWidget( parent ) // , Ui::ChatView() , contactsWidget_( 0 ) , currentAccount_( 0 ) , initialized_( false ) { // Insert a KHTMLPart in the placeholder chatMessageView_ = new ChatMessageView( this, this ); connect( chatMessageView_, SIGNAL( popupMenu(const QString&,const QPoint&) ), this, SLOT ( slotShowContextMenu(const QString&,const QPoint&) ) ); connect( chatMessageView_, SIGNAL( openUrlRequest(const KUrl&) ), this, SLOT( slotOpenURLRequest(const KUrl&) ) ); connect( this, SIGNAL( updateApplicationMessage(const QString&,const QString&) ), chatMessageView_, SLOT( updateApplicationMessage(const QString&,const QString&) ) ); // Create a layout to maximize the KHTMLPart QVBoxLayout *layout = new QVBoxLayout( this ); layout->setContentsMargins( 0, 6, 0, 0 ); layout->addWidget( chatMessageView_->view() ); // And to the messages view chatMessageView_->view()->setAcceptDrops( true ); chatMessageView_->view()->installEventFilter( this ); // Create the containers for this chat's current text and drawing messageEditContents_ = new QTextDocument(); inkDrawing_ = new Isf::Drawing(); // Adapt the new document for RTL is necessary if( QApplication::isRightToLeft() ) { QTextOption textOption; textOption.setTextDirection( Qt::RightToLeft ); messageEditContents_->setDefaultTextOption( textOption ); messageEditContents_->clear(); } } // The destructor ChatView::~ChatView() { // Delete the related widgets delete inkDrawing_; delete messageEditContents_; } // Parse and add the given XML string to the view void ChatView::addContents( const QString& xmlString ) { chatMessageView_->addXml( xmlString ); } // Copy the currently selected text void ChatView::editCopy() { // The view never has focus, only selection. if( ! chatMessageView_->hasSelection() ) { return; } kapp->clipboard()->setText( chatMessageView_->selectedText() ); // use selectedTextAsHTML() and setData() to copy HTML instead of text } // Event filter to detect special actions in the message editor; also detect those of the message view. They get filtered // here since the message view has a NoFocus policy, which prevents it from having keyboard shortcut events. bool ChatView::eventFilter( QObject *obj, QEvent *event ) { Q_UNUSED( obj ); switch( event->type() ) { case QEvent::DragEnter: case QEvent::DragMove: { QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>( event ); if( ! dragEvent ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Ignoring invalid drag event!"; #endif return false; } const QMimeData *mimeData = dragEvent->mimeData(); if( mimeData->hasUrls() || mimeData->hasImage() || mimeData->hasFormat( "application/kmess.list.item" ) ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Accepting drag event of mimetypes:" << dragEvent->mimeData()->formats(); #endif dragEvent->acceptProposedAction(); return true; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Ignoring invalid drop having mimetypes:" << dragEvent->mimeData()->formats(); #endif break; } case QEvent::Drop: { QDropEvent *dropEvent = static_cast<QDropEvent*>( event ); const QMimeData *data = dropEvent->mimeData(); // Process the drop only if it comes from out of our application. if( ! dropEvent || dropEvent->source() != 0 ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Ignoring invalid drop!"; #endif break; } const QMimeData *mimeData = dropEvent->mimeData(); if( mimeData->hasUrls() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Drag'n'dropped files:" << mimeData->urls(); #endif // Send the files to the contact startFileTransfer( mimeData->urls() ); dropEvent->acceptProposedAction(); return true; } if( mimeData->hasImage() ) { QImage image( qvariant_cast<QImage>( mimeData->imageData() ) ); if( image.isNull() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Drag'n'dropped an invalid image!"; #endif return true; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Drag'n'dropped a" << image.size() << "image"; #endif // Create a temporary file where the image will be stored. // TODO: The file is not destroyed, as it needs to be kept // available until the transfer is completed. Find a way // to remove it when KMess quits or as soon as the transfer // is done. QTemporaryFile file; file.setAutoRemove( false ); file.setFileTemplate( QDir::tempPath() + "/kmess.XXXXXX.jpg" ); if( ! file.open() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Cannot create temporary image file!"; #endif return true; } dropEvent->acceptProposedAction(); // Save the image and get its local URL image.save( &file, "JPG" ); file.close(); QUrl imageUrl( "file://" + file.fileName() ); // Send the image to the contact QList<QUrl> urlList; urlList.append( imageUrl ); startFileTransfer( urlList ); return true; } if( data->hasFormat( "application/kmess.list.item" ) && ! data->text().isEmpty() ) { // Invite the listed contacts, but check them a little QStringList handles( data->text().split( "; " ) ); foreach( const QString &handle, handles ) { // Don't try to invite random strings, nonexistent handles if( ! Account::isValidEmail( handle ) || currentAccount_->getContactByHandle( handle ) == 0 ) { handles.removeAll( handle ); } } // If any address has survived, it has gained itself the valhalla, an invitation to our chat if( ! handles.isEmpty() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Drag'n'dropped addresses:" << handles; #endif inviteContacts( handles ); dropEvent->acceptProposedAction(); return true; } } break; } // Ensure that the chat shows the same contents when resizing it case QEvent::Resize: { chatMessageView_->scrollChatToBottomGently(); break; } default: break; } return false; } // Return a pointer to this chat's Contacts Widget ContactsWidget *ChatView::getContactsWidget() const { return contactsWidget_; } // Return a pointer to the ink image drawn by the user in this chat Isf::Drawing *ChatView::getInkDrawing() const { return inkDrawing_; } // Return a pointer to the text typed by the user in this chat QTextDocument *ChatView::getMessageEditContents() const { return messageEditContents_; } // Get the text zoom factor int ChatView::getZoomFactor() { return chatMessageView_->zoomFactor(); } // Initialize the object bool ChatView::initialize() { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "initializing."; #endif if ( initialized_ ) { kmDebug() << "already initialized."; return false; } currentAccount_ = CurrentAccount::instance(); if ( currentAccount_ == 0 ) { kmDebug() << "Couldn't get the instance of the current account."; return false; } connect( currentAccount_, SIGNAL( changedFontSettings() ), chatMessageView_, SLOT ( updateChatStyle() ) ); connect( currentAccount_, SIGNAL( changedChatStyleSettings() ), chatMessageView_, SLOT ( updateChatStyle() ) ); connect( currentAccount_, SIGNAL( changedEmoticonSettings() ), chatMessageView_, SLOT ( updateChatStyle() ) ); // Create the widget contactsWidget_ = new ContactsWidget( 0 ); // Hide it by default contactsWidget_->hide(); // Update the chat view with the style settings // from the current account chatMessageView_->updateChatStyle(); initialized_ = true; return true; } // Whether or not the message area is empty bool ChatView::isEmpty() const { return chatMessageView_->isEmpty(); } /* Save the chat to the given file * * @param path The path to the file to save to * @param format Format of the file (xml, text, ...) * @param overwriteContents whether to append or overwrite the contents of the file * @param allowUserInteraction whether to allow dialog boxes to be sent if something succeeds or fails * @returns true if everything was saved correctly, false otherwise */ bool ChatView::saveChatToFile( const QString &path, Account::ChatExportFormat format, bool overwriteContents, bool allowUserInteraction ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "saving chat to '" << path << "'."; #endif // Find out if the file already exists QFile file( path ); bool isExistingFile = file.exists(); // Create and open the file. if( ! file.open( QIODevice::ReadWrite ) ) { if( ! allowUserInteraction ) { return false; } QFileInfo info( file ); kmWarning() << "File save failed! Could not open file" << path << "."; KMessageBox::sorry( chatMessageView_->view(), i18n( "Could not save chat log in directory '%1'.\n" "Make sure you have permission to write in " "the folder where logs are being saved.", info.absolutePath() ) ); return false; } // Add a bit more security to the log file, if the platform supports it file.setPermissions( QFile::ReadOwner | QFile::WriteOwner ); QString fileData; QString appendPoint; bool willAppend = ( isExistingFile && ! overwriteContents ); // Search for the style tag: we will append to this file only if the // tag exists and is identical to the current tag. // Text chat logs don't have any formatting nor tag, so this test must // be skipped. if( willAppend && format != Account::EXPORT_TEXT ) { fileData = file.read( 512 ); if( fileData.indexOf( chatMessageView_->getStyleTag() ) == -1 ) { kmWarning() << "Style Tag not found in data:" << fileData << ", writing to a new file"; // we return false, so the caller will write this data to a new file return false; } } QString chatHistory( chatMessageView_->getHistory( format, willAppend, appendPoint ) ); #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Writing to file:" << file.fileName(); kmDebug() << "Current file exists?" << isExistingFile << " allow overwriting?" << overwriteContents; kmDebug() << "Will append to current file?" << willAppend; #endif if( willAppend ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Appending to existing chat file..."; #endif const qint16 maxReadSize = 256; // Read the last bytes of the file file.seek( file.size() - maxReadSize ); fileData = file.read( maxReadSize ); int pos = fileData.lastIndexOf( appendPoint ); // The append point was not found! if( pos < 0 ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Append process failed, insertion string not found."; #endif return false; } // Seek the file to the insertion point file.seek( file.size() - maxReadSize + pos ); // Add to the chat history the current 'tail' of the file chatHistory += fileData.mid( pos ); // Insert the history and tail in the file, below } else { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Creating new chat file..."; #endif file.resize( 0 ); // Truncate it (99% useless precaution) } // Output the chat history to the file, with the right text encoding QTextStream textStream( &file ); // For plain text logs we use the locale codec if (format == Account::EXPORT_TEXT) { textStream.setCodec( QTextCodec::codecForLocale() ); } // For HTML logs (which are created with the UTF-8 charset) we use UTF-8 encoding else { textStream.setCodec( QTextCodec::codecForName("UTF-8") ); } textStream << chatHistory; textStream.flush(); file.close(); return true; } // Enable or disable the parts of the chat which allow user interaction void ChatView::setEnabled( bool isEnabled ) { // Set the state of the main UI elements contactsWidget_->setEnabled( isEnabled ); // The chat is being enabled back, replace some stuff which changes // at every connection if( isEnabled ) { // Re-read the current account pointer // because it likely is a different one currentAccount_ = CurrentAccount::instance(); } } // Replace the message browser's contents with the given XML string. void ChatView::setContents( const QString& xmlString ) { chatMessageView_->setXml( xmlString ); } // Set the zoom factor of the text void ChatView::setZoomFactor( int percentage ) { chatMessageView_->setFontScaleFactor( percentage ); } // Add the given message to the message browser. void ChatView::showMessage( const ChatMessage &message ) { // Send it to the message view to have it shown chatMessageView_->showMessage( message ); // Notify the contacts widget, so that it updates the right contact frame if( ! KMESS_NULL( contactsWidget_ ) ) { contactsWidget_->messageReceived( message.getContactHandle() ); } } // Show a dialog to save the chat. void ChatView::showSaveChatDialog() { QFileInfo targetFile; int overwriteAllowed; // Default name for the saved chat QString path( currentAccount_->getSaveChatPath() + "/kmess-chat.html" ); // Repeat until a file is chosen or the operation is cancelled do { // Show a dialog to get a filename from the user. path = KFileDialog::getSaveFileName( path, i18nc( "Chat log saving dialog, file type filter", "*.html *.htm|Web Page (*.html)\n" "*.txt|Plain Text Document (*.txt)\n" ) ); // Verify if the user has canceled the command if( path.isEmpty() ) { return; } // Default to accept the path name overwriteAllowed = KMessageBox::Ok; // Check if the file exists and if so, warn the user targetFile.setFile( path ); if( targetFile.exists() ) { overwriteAllowed = KMessageBox::warningContinueCancel( 0, i18n( "The file '%1' already exists.\nDo you want to overwrite it?", targetFile.fileName() ), i18n( "Overwrite File" ), KGuiItem( i18n( "Over&write" ) ) ); } } while( overwriteAllowed == KMessageBox::Cancel ); Account::ChatExportFormat format; if( targetFile.suffix() == "html" ) { format = Account::EXPORT_HTML; } else { format = Account::EXPORT_TEXT; } saveChatToFile( path, format, true ); } /** * Add a contact's email to the contact list */ 00606 void ChatView::slotAddContact() { QString email( chatViewClickedUrl_.url() ); if( ! chatViewClickedUrl_.isValid() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Not adding anything from invalid URL: " << email; #endif return; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Adding contact from URL: " << email; #endif // Strip the pseudo-protocol 'mail to' from the email address if( email.left( 7 ) == "mailto:" ) { email = email.mid( 7 ); } emit addContact( email ); // Reset the url chatViewClickedUrl_.clear(); } /** * Open a dialog to add a new custom emoticon seen in the chat */ 00640 void ChatView::slotAddNewEmoticon() { if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "emoticon" ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Ignoring request for invalid URL: " << chatViewClickedUrl_; #endif return; } /* * KMess' internal emoticon addition URLs are in the form * <code>kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPicturePath</code> */ QString path( chatViewClickedUrl_.path().mid( 1 ) ); // remove first / #ifdef KMESSTEST KMESS_ASSERT( ! path.isEmpty() ); #endif // Reset the url chatViewClickedUrl_.clear(); QString handle ( path.section( "/", 0, 0 ) ); // First parameter: contact handle QString shortcut( path.section( "/", 1, 1 ) ); // Second parameter: emoticon shortcut QString picture ( path.section( "/", 2 ) ); // Third parameter: emoticon picture path const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle ); if( contact == 0 ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Contact handle not found: " << handle; #endif return; } // URL-Decode the encoded strings picture = KUrl::fromPercentEncoding( picture.toAscii() ); shortcut = KUrl::fromPercentEncoding( shortcut.toUtf8() ); #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Showing Add Emoticon dialog - shortcut=" << shortcut << " picture=" << picture; #endif // Finally, show the dialog, preselecting the picture file and shortcut AddEmoticonDialog *addDialog = new AddEmoticonDialog( EmoticonManager::instance()->getTheme( true ), chatMessageView_->view() ); connect( addDialog, SIGNAL( addedEmoticon(QString) ), chatMessageView_, SLOT ( addedEmoticon(QString) ) ); addDialog->preSelect( picture, shortcut ); } // Clear the chat's contents void ChatView::slotClearChat() { chatMessageView_->clearView( false ); } // The user clicked the "copy address" or "copy email" option in the context menu void ChatView::slotCopyAddress() { QString url( chatViewClickedUrl_.url() ); if( ! chatViewClickedUrl_.isValid() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Not copying invalid URL: " << url; #endif return; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Copying URL: " << url; #endif // Strip the pseudo-protocol 'mail to' from email addresses if( url.left( 7 ) == "mailto:" ) { url = url.mid( 7 ); } kapp->clipboard()->setText( url ); // Reset the url chatViewClickedUrl_.clear(); } // The user clicked the "find text" option in the context menu void ChatView::slotFindChatText() { chatMessageView_->findText(); } /** * Add an emoticon from the chat to the contact's emoticon blacklist. * * The user can add a contact's emoticons into a black list: KMess will not display * blacklisted emoticons, and keep the shortcut as text. Useful for contacts * having annoying emoticons. */ 00757 void ChatView::slotIgnoreEmoticon() { if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "emoticon" ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Ignoring request for invalid URL: " << chatViewClickedUrl_; #endif return; } /* * KMess' internal emoticon URLs are in the form * <code>kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPicturePath</code> */ QString path( chatViewClickedUrl_.path().mid( 1 ) ); // remove first / #ifdef KMESSTEST KMESS_ASSERT( ! path.isEmpty() ); #endif // Reset the url chatViewClickedUrl_.clear(); QString handle ( path.section( "/", 0, 0 ) ); // First parameter: contact handle QString shortcut( path.section( "/", 1, 1 ) ); // Second parameter: emoticon shortcut ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle ); if( contact == 0 ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Contact handle not found: " << handle; #endif return; } // URL-Decode the encoded strings shortcut = KUrl::fromPercentEncoding( shortcut.toUtf8() ); // Add the emoticon to the contact's blacklist and update the chat if( contact->manageEmoticonBlackList( true, shortcut ) ) { chatMessageView_->removeCustomEmoticon( shortcut ); } } // Open a new url clicked in the khtml widget void ChatView::slotOpenURLRequest( const KUrl &url ) { chatViewClickedUrl_ = url; // Internal URLs form: kmess://call_type/parameters?more_parameters if( url.protocol() == "kmess" ) { // Application URLs form: kmess://application/responseType/contactHandle?cookieId if( url.host() == "application" ) { // Handle the applications input slotSendAppCommand(); } // Emoticon URLs form: kmess://emoticon/contactHandle/urlEncodedShortcut/urlEncodedPictureTag else if( url.host() == "emoticon" ) { // Add a new emoticon slotAddNewEmoticon(); } else if( url.host() == "accountconfig" ) { // Show the account config dialog AccountsManager::instance()->showAccountSettings( currentAccount_, window(), AccountSettingsDialog::PageChatStyle ); } } else if( url.protocol() == "file" ) { // Obtain the widget of the main KMess window, to correctly link it to the opened app KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp ); QWidget *mainWindow = kmessApp->getContactListWindow()->window(); // Execute the local file with the system's default association (KRun auto-deletes itself) new KRun( url, mainWindow, 0, true ); } else { // Just execute the link slotVisitAddress(); } } // Scroll the view forward or backward void ChatView::scrollTo( bool forward, bool fast ) { chatMessageView_->scrollChat( forward, fast ); } // Scroll the view down to the last line void ChatView::scrollToBottom( bool force ) { if( force ) { chatMessageView_->scrollChatToBottom(); } else { chatMessageView_->scrollChatToBottomGently(); } } // Change ink drawing object void ChatView::setInkDrawing( Isf::Drawing* newDrawing ) { delete inkDrawing_; inkDrawing_ = newDrawing; } /** * The user clicked a kmess internal link in the ChatMessageView */ 00890 void ChatView::slotSendAppCommand() { // Ignore non-internal links and non-application internal links if( ! chatViewClickedUrl_.isValid() || chatViewClickedUrl_.protocol() != "kmess" || chatViewClickedUrl_.host() != "application" ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Not sending invalid application link: " << chatViewClickedUrl_.url(); #endif return; } /* * KMess' internal application URLs are in the form * kmess://application/responseType/accountHandle?cookieId */ QString path ( chatViewClickedUrl_.path() .mid( 1 ) ); // remove first / QString query ( chatViewClickedUrl_.query().mid( 1 ) ); // Remove the ? QString method ( path.section( "/", 0, 0 ) ); // First parameter: response type QString contact( path.section( "/", 1, 1 ) ); // Second parameter: contact handle QString cookie ( query ); // Third parameter: transfer cookie ID #ifdef KMESSTEST KMESS_ASSERT( ! path.isEmpty() ); KMESS_ASSERT( ! query.isEmpty() ); #endif #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Sending application link - method=" << method << " contact=" << contact << " cookie=" << cookie; #endif emit appCommand( cookie, contact, method ); } // The user right clicked at the KHTMLPart to show a popup. void ChatView::slotShowContextMenu( const QString &clickedUrl, const QPoint &point ) { KAction *urlAction = 0; // Add items to this context menu KMenu *contextMenu = chatMessageView_->popupMenu(); // separate the "copy/find/select all" actions from the chat-specific ones below. contextMenu->addSeparator(); // Analyze incoming URL, if present if( ! clickedUrl.isEmpty() ) { KUrl url( clickedUrl ); #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Clicked URL: " << url.prettyUrl(); #endif if( url.protocol() == "kmess" && url.host() == "emoticon" ) { urlAction = new KAction( KIcon( "list-add" ), i18n("Add this &Emoticon..."), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotAddNewEmoticon()) ); contextMenu->addAction( urlAction ); urlAction = new KAction( KIcon( "list-remove" ), i18n("Hide this &Emoticon"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotIgnoreEmoticon()) ); contextMenu->addAction( urlAction ); } else if( url.protocol().left( 6 ) == "mailto" ) { urlAction = new KAction( KIcon( "mail" ), i18n("Send &Email"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotVisitAddress()) ); contextMenu->addAction( urlAction ); urlAction = new KAction( KIcon( "list-add-user" ), i18n("Add &Contact"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotAddContact()) ); contextMenu->addAction( urlAction ); urlAction = new KAction( KIcon( "copy" ), i18n("Copy E&mail Address"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotCopyAddress()) ); contextMenu->addAction( urlAction ); } else { urlAction = new KAction( KIcon( "document-open-remote" ), i18n("Visit &Link"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotVisitAddress()) ); contextMenu->addAction( urlAction ); urlAction = new KAction( KIcon( "edit-copy" ), i18n("Copy &Address"), this ); connect( urlAction, SIGNAL(triggered(bool)), this, SLOT(slotCopyAddress()) ); contextMenu->addAction( urlAction ); } chatViewClickedUrl_ = url; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Creating popup menu entries"; #endif // Create items KAction *clearChatAction = KStandardAction::clear( this, SLOT(slotClearChat()), 0 ); KAction *saveToFileAction = KStandardAction::save( this, SLOT(showSaveChatDialog()), 0 ); // Update the labels a bit though clearChatAction ->setText( i18n("C&lear Chat") ); saveToFileAction->setText( i18n("Save Chat to &File...") ); // Add a separator to divide the context-depending entries from the rest of the menu if( urlAction != 0 ) { contextMenu->addSeparator(); } contextMenu->addAction( clearChatAction ); contextMenu->addAction( saveToFileAction ); // Set items disabled, depending on the text selection if( chatMessageView_->hasSelection() || ! chatMessageView_->hasHistory() ) { saveToFileAction->setEnabled( false ); } // Only enable the clear chat action if it contains messages clearChatAction->setEnabled( ! isEmpty() ); // Show the menu contextMenu->exec( point ); delete contextMenu; delete clearChatAction; delete saveToFileAction; if ( urlAction != 0 ) { delete urlAction; } } // The user clicked the "visit address" or "send email" option in the context menu, or clicked a link in the ChatMessageView void ChatView::slotVisitAddress() { if( ! chatViewClickedUrl_.isValid() ) { #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Not opening invalid URL: " << chatViewClickedUrl_; #endif return; } #ifdef KMESSDEBUG_CHATVIEW kmDebug() << "Opening URL: " << chatViewClickedUrl_; #endif // Launch the browser for the given URL if( chatViewClickedUrl_.protocol() == "mailto" ) { currentAccount_->openMailAtCompose( chatViewClickedUrl_.url().mid( 7 ) ); } else { KMessShared::openBrowser( chatViewClickedUrl_ ); } // Reset the url chatViewClickedUrl_.clear(); } // Update the messages which contain custom emoticons void ChatView::updateCustomEmoticon( const QString &handle, const QString &code ) { #ifdef KMESSTEST KMESS_ASSERT( ! code.isEmpty() ); #endif // Check for empty replacements. if( code.isEmpty() ) { kmWarning() << "can't update custom emoticon, emoticon code not given (contact=" << handle << ")."; return; } // Get contact emoticon replacements. const ContactBase *contact = currentAccount_->getContactByHandle(handle); if(KMESS_NULL(contact)) return; // Get emoticon replacement, instruct chatMessageView to replace it. const QString &replacement = contact->getEmoticonReplacements()[code]; chatMessageView_->updateCustomEmoticon( code, replacement, handle ); } #include "chatview.moc"