kmail

kmedit.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmcomposewin.cpp
00003 // Author: Markus Wuebben <markus.wuebben@kde.org>
00004 // This code is published under the GPL.
00005 
00006 #include <config.h>
00007 
00008 #include "kmedit.h"
00009 #include "kmlineeditspell.h"
00010 
00011 #define REALLY_WANT_KMCOMPOSEWIN_H
00012 #include "kmcomposewin.h"
00013 #undef REALLY_WANT_KMCOMPOSEWIN_H
00014 #include "kmmsgdict.h"
00015 #include "kmfolder.h"
00016 #include "kmcommands.h"
00017 
00018 #include <maillistdrag.h>
00019 using KPIM::MailListDrag;
00020 
00021 #include <libkdepim/kfileio.h>
00022 #include <libemailfunctions/email.h>
00023 
00024 #include <kcursor.h>
00025 #include <kprocess.h>
00026 
00027 #include <kpopupmenu.h>
00028 #include <kdebug.h>
00029 #include <kmessagebox.h>
00030 #include <kurldrag.h>
00031 
00032 #include <ktempfile.h>
00033 #include <klocale.h>
00034 #include <kapplication.h>
00035 #include <kdirwatch.h>
00036 #include <kiconloader.h>
00037 
00038 #include "globalsettings.h"
00039 #include "replyphrases.h"
00040 
00041 #include <kspell.h>
00042 #include <kspelldlg.h>
00043 #include <spellingfilter.h>
00044 #include <ksyntaxhighlighter.h>
00045 
00046 #include <qregexp.h>
00047 #include <qbuffer.h>
00048 #include <qevent.h>
00049 
00050 #include <sys/stat.h>
00051 #include <sys/types.h>
00052 #include <stdlib.h>
00053 #include <unistd.h>
00054 #include <errno.h>
00055 #include <fcntl.h>
00056 #include <assert.h>
00057 
00058 
00059 void KMEdit::contentsDragEnterEvent(QDragEnterEvent *e)
00060 {
00061     if (e->provides(MailListDrag::format()))
00062         e->accept(true);
00063     else if (e->provides("image/png"))
00064         e->accept();
00065     else
00066         return KEdit::contentsDragEnterEvent(e);
00067 }
00068 
00069 void KMEdit::contentsDragMoveEvent(QDragMoveEvent *e)
00070 {
00071     if (e->provides(MailListDrag::format()))
00072         e->accept();
00073     else if (e->provides("image/png"))
00074         e->accept();
00075     else
00076         return KEdit::contentsDragMoveEvent(e);
00077 }
00078 
00079 void KMEdit::keyPressEvent( QKeyEvent* e )
00080 {
00081     if( e->key() == Key_Return ) {
00082         int line, col;
00083         getCursorPosition( &line, &col );
00084         QString lineText = text( line );
00085         // returns line with additional trailing space (bug in Qt?), cut it off
00086         lineText.truncate( lineText.length() - 1 );
00087         // special treatment of quoted lines only if the cursor is neither at
00088         // the begin nor at the end of the line
00089         if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) {
00090             bool isQuotedLine = false;
00091             uint bot = 0; // bot = begin of text after quote indicators
00092             while( bot < lineText.length() ) {
00093                 if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) {
00094                     isQuotedLine = true;
00095                     ++bot;
00096                 }
00097                 else if( lineText[bot].isSpace() ) {
00098                     ++bot;
00099                 }
00100                 else {
00101                     break;
00102                 }
00103             }
00104 
00105             KEdit::keyPressEvent( e );
00106 
00107             // duplicate quote indicators of the previous line before the new
00108             // line if the line actually contained text (apart from the quote
00109             // indicators) and the cursor is behind the quote indicators
00110             if( isQuotedLine
00111                 && ( bot != lineText.length() )
00112                 && ( col >= int( bot ) ) ) {
00113 
00114         // The cursor position might have changed unpredictably if there was selected
00115         // text which got replaced by a new line, so we query it again:
00116         getCursorPosition( &line, &col );
00117                 QString newLine = text( line );
00118                 // remove leading white space from the new line and instead
00119                 // add the quote indicators of the previous line
00120                 unsigned int leadingWhiteSpaceCount = 0;
00121                 while( ( leadingWhiteSpaceCount < newLine.length() )
00122                        && newLine[leadingWhiteSpaceCount].isSpace() ) {
00123                     ++leadingWhiteSpaceCount;
00124                 }
00125                 newLine = newLine.replace( 0, leadingWhiteSpaceCount,
00126                                            lineText.left( bot ) );
00127                 removeParagraph( line );
00128                 insertParagraph( newLine, line );
00129                 // place the cursor at the begin of the new line since
00130                 // we assume that the user split the quoted line in order
00131                 // to add a comment to the first part of the quoted line
00132                 setCursorPosition( line, 0 );
00133             }
00134         }
00135         else
00136             KEdit::keyPressEvent( e );
00137     }
00138     else
00139         KEdit::keyPressEvent( e );
00140 }
00141 
00142 void KMEdit::contentsDropEvent(QDropEvent *e)
00143 {
00144     if (e->provides(MailListDrag::format())) {
00145         // Decode the list of serial numbers stored as the drag data
00146         QByteArray serNums;
00147         MailListDrag::decode( e, serNums );
00148         QBuffer serNumBuffer(serNums);
00149         serNumBuffer.open(IO_ReadOnly);
00150         QDataStream serNumStream(&serNumBuffer);
00151         Q_UINT32 serNum;
00152         KMFolder *folder = 0;
00153         int idx;
00154         QPtrList<KMMsgBase> messageList;
00155         while (!serNumStream.atEnd()) {
00156             KMMsgBase *msgBase = 0;
00157             serNumStream >> serNum;
00158             KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
00159             if (folder)
00160                 msgBase = folder->getMsgBase(idx);
00161             if (msgBase)
00162                 messageList.append( msgBase );
00163         }
00164         serNumBuffer.close();
00165         uint identity = folder ? folder->identity() : 0;
00166         KMCommand *command =
00167             new KMForwardAttachedCommand(mComposer, messageList,
00168                                          identity, mComposer);
00169         command->start();
00170     }
00171     else if( e->provides("image/png") ) {
00172         emit attachPNGImageData(e->encodedData("image/png"));
00173     }
00174     else if( KURLDrag::canDecode( e ) ) {
00175         KURL::List urlList;
00176         if( KURLDrag::decode( e, urlList ) ) {
00177             KPopupMenu p;
00178             p.insertItem( i18n("Add as Text"), 0 );
00179             p.insertItem( i18n("Add as Attachment"), 1 );
00180             int id = p.exec( mapToGlobal( e->pos() ) );
00181             switch ( id) {
00182               case 0:
00183                 for ( KURL::List::Iterator it = urlList.begin();
00184                      it != urlList.end(); ++it ) {
00185                   insert( (*it).url() );
00186                 }
00187                 break;
00188               case 1:
00189                 for ( KURL::List::Iterator it = urlList.begin();
00190                      it != urlList.end(); ++it ) {
00191                   mComposer->addAttach( *it );
00192                 }
00193                 break;
00194             }
00195         }
00196         else if ( QTextDrag::canDecode( e ) ) {
00197           QString s;
00198           if ( QTextDrag::decode( e, s ) )
00199             insert( s );
00200         }
00201         else
00202           kdDebug(5006) << "KMEdit::contentsDropEvent, unable to add dropped object" << endl;
00203     }
00204     else if( e->provides("text/x-textsnippet") ) {
00205     emit insertSnippet();
00206     }
00207     else {
00208         KEdit::contentsDropEvent(e);
00209     }
00210 }
00211 
00212 KMEdit::KMEdit(QWidget *parent, KMComposeWin* composer,
00213                KSpellConfig* autoSpellConfig,
00214                const char *name)
00215   : KEdit( parent, name ),
00216     mComposer( composer ),
00217     mKSpellForDialog( 0 ),
00218     mSpeller( 0 ),
00219     mSpellConfig( autoSpellConfig ),
00220     mSpellingFilter( 0 ),
00221     mExtEditorTempFile( 0 ),
00222     mExtEditorTempFileWatcher( 0 ),
00223     mExtEditorProcess( 0 ),
00224     mUseExtEditor( false ),
00225     mWasModifiedBeforeSpellCheck( false ),
00226     mHighlighter( 0 ),
00227     mSpellLineEdit( false ),
00228     mPasteMode( QClipboard::Clipboard )
00229 {
00230   connect( this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()) );
00231   installEventFilter(this);
00232   KCursor::setAutoHideCursor( this, true, true );
00233   setOverwriteEnabled( true );
00234   createSpellers();
00235   connect( mSpellConfig, SIGNAL( configChanged() ),
00236            this, SLOT( createSpellers() ) );
00237   connect( mSpeller, SIGNAL( death() ),
00238            this, SLOT( spellerDied() ) );
00239 }
00240 
00241 void KMEdit::createSpellers()
00242 {
00243   delete mSpeller;
00244   mSpeller = new KMSpell( this, SLOT( spellerReady( KSpell * ) ), mSpellConfig );
00245 }
00246 
00247 void KMEdit::initializeAutoSpellChecking()
00248 {
00249   if ( mHighlighter )
00250     return; // already initialized
00251   QColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp
00252   QColor defaultColor2( 0x00, 0x70, 0x00 );
00253   QColor defaultColor3( 0x00, 0x60, 0x00 );
00254   QColor defaultForeground( kapp->palette().active().text() );
00255 
00256   QColor c = Qt::red;
00257   KConfigGroup readerConfig( KMKernel::config(), "Reader" );
00258   QColor col1;
00259   if ( !readerConfig.readBoolEntry(  "defaultColors", true ) )
00260       col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground );
00261   else
00262       col1 = defaultForeground;
00263   QColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 );
00264   QColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 );
00265   QColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 );
00266   QColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c );
00267   mHighlighter = new KMSyntaxHighter( this, /*active*/ true,
00268                                        /*autoEnabled*/ false,
00269                                        /*spellColor*/ misspelled,
00270                                        /*colorQuoting*/ true,
00271                                        col1, col2, col3, col4,
00272                                        mSpellConfig );
00273 
00274   connect( mHighlighter, SIGNAL(newSuggestions(const QString&, const QStringList&, unsigned int)),
00275            this, SLOT(addSuggestion(const QString&, const QStringList&, unsigned int)) );
00276 }
00277 
00278 
00279 QPopupMenu *KMEdit::createPopupMenu( const QPoint& pos )
00280 {
00281   enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll };
00282 
00283   QPopupMenu *menu = KEdit::createPopupMenu( pos );
00284   if ( !QApplication::clipboard()->image().isNull() ) {
00285     int id = menu->idAt(0);
00286     menu->setItemEnabled( id - IdPaste, true);
00287   }
00288 
00289   return menu;
00290 }
00291 
00292 void KMEdit::deleteAutoSpellChecking()
00293 { // because the highlighter doesn't support RichText, delete its instance.
00294   delete mHighlighter;
00295   mHighlighter =0;
00296 }
00297 
00298 void KMEdit::addSuggestion(const QString& text, const QStringList& lst, unsigned int )
00299 {
00300   mReplacements[text] = lst;
00301 }
00302 
00303 void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
00304 {
00305   if ( mHighlighter ) {
00306     mHighlighter->setActive(spellCheckingActive);
00307   }
00308 }
00309 
00310 
00311 KMEdit::~KMEdit()
00312 {
00313   removeEventFilter(this);
00314 
00315   if ( mSpeller ) {
00316     // The speller needs some time to clean up, so trigger cleanup and let it delete itself
00317     mSpeller->setAutoDelete( true );
00318     mSpeller->cleanUp();
00319     mSpeller = 0;
00320   }
00321 
00322   delete mKSpellForDialog;
00323   delete mHighlighter;
00324   mHighlighter = 0;
00325 }
00326 
00327 
00328 
00329 QString KMEdit::brokenText()
00330 {
00331   QString temp, line;
00332 
00333   int num_lines = numLines();
00334   for (int i = 0; i < num_lines; ++i)
00335   {
00336     int lastLine = 0;
00337     line = textLine(i);
00338     for (int j = 0; j < (int)line.length(); ++j)
00339     {
00340       if (lineOfChar(i, j) > lastLine)
00341       {
00342         lastLine = lineOfChar(i, j);
00343         temp += '\n';
00344       }
00345       temp += line[j];
00346     }
00347     if (i + 1 < num_lines) temp += '\n';
00348   }
00349 
00350   return temp;
00351 }
00352 
00353 
00354 unsigned int KMEdit::lineBreakColumn() const
00355 {
00356   unsigned int lineBreakColumn = 0;
00357   unsigned int numlines = numLines();
00358   while ( numlines-- ) {
00359     lineBreakColumn = QMAX( lineBreakColumn, textLine( numlines ).length() );
00360   }
00361   return lineBreakColumn;
00362 }
00363 
00364 KMSpell::KMSpell( QObject *receiver, const char *slot, KSpellConfig *spellConfig )
00365   : KSpell( 0, QString(), receiver, slot, spellConfig )
00366 {
00367 }
00368 
00369 KMSyntaxHighter::KMSyntaxHighter( QTextEdit *textEdit,
00370                                   bool spellCheckingActive,
00371                                   bool autoEnable,
00372                                   const QColor& spellColor,
00373                                   bool colorQuoting,
00374                                   const QColor& QuoteColor0,
00375                                   const QColor& QuoteColor1,
00376                                   const QColor& QuoteColor2,
00377                                   const QColor& QuoteColor3,
00378                                   KSpellConfig *spellConfig )
00379   : KDictSpellingHighlighter( textEdit, spellCheckingActive, autoEnable, spellColor, colorQuoting,
00380                               QuoteColor0, QuoteColor1, QuoteColor2, QuoteColor3, spellConfig )
00381 {
00382 }
00383 
00384 bool KMSyntaxHighter::isMisspelled( const QString &word )
00385 {
00386   if ( mIgnoredWords.contains( word ) ) {
00387     return false;
00388   }
00389   else {
00390     return KDictSpellingHighlighter::isMisspelled( word );
00391   }
00392 }
00393 
00394 void KMSyntaxHighter::ignoreWord( const QString &word )
00395 {
00396   mIgnoredWords << word;
00397 }
00398 
00399 QStringList KMSyntaxHighter::ignoredWords() const
00400 {
00401   return mIgnoredWords;
00402 }
00403 
00404 void KMEdit::spellerDied()
00405 {
00406   mSpeller = 0;
00407 }
00408 
00409 void KMEdit::spellerReady( KSpell *spell )
00410 {
00411   Q_ASSERT( mSpeller == spell );
00412 }
00413 
00414 bool KMEdit::eventFilter(QObject*o, QEvent* e)
00415 {
00416   if (o == this)
00417     KCursor::autoHideEventFilter(o, e);
00418 
00419   if (e->type() == QEvent::KeyPress)
00420   {
00421     QKeyEvent *k = (QKeyEvent*)e;
00422 
00423     if (mUseExtEditor) {
00424       if (k->key() == Key_Up)
00425       {
00426         emit focusUp();
00427         return true;
00428       }
00429 
00430       // ignore modifier keys (cf. bug 48841)
00431       if ( (k->key() == Key_Shift) || (k->key() == Key_Control) ||
00432            (k->key() == Key_Meta) || (k->key() == Key_Alt) )
00433         return true;
00434       if (mExtEditorTempFile) return true;
00435       QString sysLine = mExtEditor;
00436       mExtEditorTempFile = new KTempFile();
00437 
00438       mExtEditorTempFile->setAutoDelete(true);
00439 
00440       (*mExtEditorTempFile->textStream()) << text();
00441 
00442       mExtEditorTempFile->close();
00443       // replace %f in the system line
00444       sysLine.replace( "%f", mExtEditorTempFile->name() );
00445       mExtEditorProcess = new KProcess();
00446       mExtEditorProcess->setUseShell( true );
00447       sysLine += " ";
00448       while (!sysLine.isEmpty())
00449       {
00450         *mExtEditorProcess << sysLine.left(sysLine.find(" ")).local8Bit();
00451         sysLine.remove(0, sysLine.find(" ") + 1);
00452       }
00453       connect(mExtEditorProcess, SIGNAL(processExited(KProcess*)),
00454               SLOT(slotExternalEditorDone(KProcess*)));
00455       if (!mExtEditorProcess->start())
00456       {
00457         KMessageBox::error( topLevelWidget(),
00458                             i18n("Unable to start external editor.") );
00459         killExternalEditor();
00460       } else {
00461         mExtEditorTempFileWatcher = new KDirWatch( this, "mExtEditorTempFileWatcher" );
00462         connect( mExtEditorTempFileWatcher, SIGNAL(dirty(const QString&)),
00463                  SLOT(slotExternalEditorTempFileChanged(const QString&)) );
00464         mExtEditorTempFileWatcher->addFile( mExtEditorTempFile->name() );
00465       }
00466       return true;
00467     } else {
00468     // ---sven's Arrow key navigation start ---
00469     // Key Up in first line takes you to Subject line.
00470     if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0
00471       && lineOfChar(0, currentColumn()) == 0)
00472     {
00473       deselect();
00474       emit focusUp();
00475       return true;
00476     }
00477     // ---sven's Arrow key navigation end ---
00478 
00479     if (k->key() == Key_Backtab && k->state() == ShiftButton)
00480     {
00481       deselect();
00482       emit focusUp();
00483       return true;
00484     }
00485 
00486     }
00487   } else if ( e->type() == QEvent::ContextMenu ) {
00488     QContextMenuEvent *event = (QContextMenuEvent*) e;
00489 
00490     int para = 1, charPos, firstSpace, lastSpace;
00491 
00492     //Get the character at the position of the click
00493     charPos = charAt( viewportToContents(event->pos()), &para );
00494     QString paraText = text( para );
00495 
00496     if( !paraText.at(charPos).isSpace() )
00497     {
00498       //Get word right clicked on
00499       const QRegExp wordBoundary( "[\\s\\W]" );
00500       firstSpace = paraText.findRev( wordBoundary, charPos ) + 1;
00501       lastSpace = paraText.find( wordBoundary, charPos );
00502       if( lastSpace == -1 )
00503         lastSpace = paraText.length();
00504       QString word = paraText.mid( firstSpace, lastSpace - firstSpace );
00505       //Continue if this word was misspelled
00506       if( !word.isEmpty() && mReplacements.contains( word ) )
00507       {
00508         KPopupMenu p;
00509 
00510         //Add the suggestions to the popup menu
00511         QStringList reps = mReplacements[word];
00512         if( reps.count() > 0 )
00513         {
00514           int listPos = 0;
00515           for ( QStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) {
00516             p.insertItem( *it, listPos );
00517             listPos++;
00518           }
00519         }
00520         else
00521         {
00522           p.setItemEnabled( p.insertItem( i18n( "No Suggestions" ), -2 ), false );
00523         }
00524 
00525         int addToDictionaryId = -42;
00526         int ignoreId = -43;
00527         if ( mSpeller && mSpeller->status() == KSpell::Running ) {
00528           p.insertSeparator();
00529           addToDictionaryId = p.insertItem( i18n( "Add to Dictionary" ) );
00530           ignoreId = p.insertItem( i18n( "Ignore All" ) );
00531         }
00532 
00533         //Execute the popup inline
00534         const int id = p.exec( mapToGlobal( event->pos() ) );
00535 
00536         if ( id == ignoreId ) {
00537           mHighlighter->ignoreWord( word );
00538           mHighlighter->rehighlight();
00539         }
00540         if ( id == addToDictionaryId ) {
00541           mSpeller->addPersonal( word );
00542           mSpeller->writePersonalDictionary();
00543           if ( mHighlighter ) {
00544             // Wait a bit until reloading the highlighter, the mSpeller first needs to finish saving
00545             // the personal word list.
00546             QTimer::singleShot( 200, mHighlighter, SLOT( slotLocalSpellConfigChanged() ) );
00547           }
00548         }
00549         else if( id > -1 )
00550         {
00551           //Save the cursor position
00552           int parIdx = 1, txtIdx = 1;
00553           getCursorPosition(&parIdx, &txtIdx);
00554           setSelection(para, firstSpace, para, lastSpace);
00555           insert(mReplacements[word][id]);
00556           // Restore the cursor position; if the cursor was behind the
00557           // misspelled word then adjust the cursor position
00558           if ( para == parIdx && txtIdx >= lastSpace )
00559             txtIdx += mReplacements[word][id].length() - word.length();
00560           setCursorPosition(parIdx, txtIdx);
00561         }
00562 
00563         if ( id == addToDictionaryId || id == ignoreId ) {
00564           // No longer misspelled: Either added to dictionary or ignored
00565           mReplacements.remove( word );
00566         }
00567 
00568         //Cancel original event
00569         return true;
00570       }
00571     }
00572   } else if ( e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut ) {
00573     QFocusEvent *fe = static_cast<QFocusEvent*>(e);
00574     if(! (fe->reason() == QFocusEvent::ActiveWindow || fe->reason() == QFocusEvent::Popup) )
00575       emit focusChanged( fe->gotFocus() );
00576   }
00577 
00578   return KEdit::eventFilter(o, e);
00579 }
00580 
00581 
00582 int KMEdit::autoSpellChecking( bool on )
00583 {
00584   if ( textFormat() == Qt::RichText ) {
00585      // syntax highlighter doesn't support extended text properties
00586      if ( on )
00587        KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup."));
00588      return -1;
00589   }
00590   if ( mHighlighter ) {
00591     // don't autoEnable spell checking if the user turned spell checking off
00592     mHighlighter->setAutomatic( on );
00593     mHighlighter->setActive( on );
00594   }
00595   return 1;
00596 }
00597 
00598 
00599 void KMEdit::slotExternalEditorTempFileChanged( const QString & fileName ) {
00600   if ( !mExtEditorTempFile )
00601     return;
00602   if ( fileName != mExtEditorTempFile->name() )
00603     return;
00604   // read data back in from file
00605   setAutoUpdate(false);
00606   clear();
00607 
00608   insertLine(QString::fromLocal8Bit(KPIM::kFileToString( fileName, true, false )), -1);
00609   setAutoUpdate(true);
00610   repaint();
00611 }
00612 
00613 void KMEdit::slotExternalEditorDone( KProcess * proc ) {
00614   assert(proc == mExtEditorProcess);
00615   // make sure, we update even when KDirWatcher is too slow:
00616   slotExternalEditorTempFileChanged( mExtEditorTempFile->name() );
00617   killExternalEditor();
00618 }
00619 
00620 void KMEdit::killExternalEditor() {
00621   delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
00622   delete mExtEditorTempFile; mExtEditorTempFile = 0;
00623   delete mExtEditorProcess; mExtEditorProcess = 0;
00624 }
00625 
00626 
00627 bool KMEdit::checkExternalEditorFinished() {
00628   if ( !mExtEditorProcess )
00629     return true;
00630   switch ( KMessageBox::warningYesNoCancel( topLevelWidget(),
00631            i18n("The external editor is still running.\n"
00632                 "Abort the external editor or leave it open?"),
00633            i18n("External Editor"),
00634            i18n("Abort Editor"), i18n("Leave Editor Open") ) ) {
00635   case KMessageBox::Yes:
00636     killExternalEditor();
00637     return true;
00638   case KMessageBox::No:
00639     return true;
00640   default:
00641     return false;
00642   }
00643 }
00644 
00645 void KMEdit::spellcheck()
00646 {
00647   if ( mKSpellForDialog )
00648     return;
00649   mWasModifiedBeforeSpellCheck = isModified();
00650   mSpellLineEdit = !mSpellLineEdit;
00651 //  maybe for later, for now plaintext is given to KSpell
00652 //  if (textFormat() == Qt::RichText ) {
00653 //    kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl;
00654 //    mKSpellForDialog = new KSpell(this, i18n("Spellcheck - KMail"), this,
00655 //                    SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML);
00656 //  }
00657 //  else {
00658 
00659     // Don't use mSpellConfig here. Reason is that the spell dialog, KSpellDlg, uses its own
00660     // spell config, and therefore the two wouldn't be in sync.
00661     mKSpellForDialog = new KSpell( this, i18n("Spellcheck - KMail"), this,
00662                                    SLOT(slotSpellcheck2(KSpell*))/*, mSpellConfig*/ );
00663 //  }
00664 
00665   QStringList l = KSpellingHighlighter::personalWords();
00666   for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
00667       mKSpellForDialog->addPersonal( *it );
00668   }
00669   connect (mKSpellForDialog, SIGNAL( death()),
00670           this, SLOT (slotSpellDone()));
00671   connect (mKSpellForDialog, SIGNAL (misspelling (const QString &, const QStringList &, unsigned int)),
00672           this, SLOT (slotMisspelling (const QString &, const QStringList &, unsigned int)));
00673   connect (mKSpellForDialog, SIGNAL (corrected (const QString &, const QString &, unsigned int)),
00674           this, SLOT (slotCorrected (const QString &, const QString &, unsigned int)));
00675   connect (mKSpellForDialog, SIGNAL (done(const QString &)),
00676           this, SLOT (slotSpellResult (const QString&)));
00677 }
00678 
00679 void KMEdit::cut()
00680 {
00681   KEdit::cut();
00682   if ( textFormat() != Qt::RichText && mHighlighter )
00683     mHighlighter->restartBackgroundSpellCheck();
00684 }
00685 
00686 void KMEdit::clear()
00687 {
00688   KEdit::clear();
00689   if ( textFormat() != Qt::RichText && mHighlighter )
00690     mHighlighter->restartBackgroundSpellCheck();
00691 }
00692 
00693 void KMEdit::del()
00694 {
00695   KEdit::del();
00696   if ( textFormat() != Qt::RichText && mHighlighter )
00697     mHighlighter->restartBackgroundSpellCheck();
00698 }
00699 
00700 void KMEdit::paste()
00701 {
00702   mComposer->paste( mPasteMode );
00703 }
00704 
00705 // KMEdit indirectly inherits from QTextEdit, which has virtual paste() method,
00706 // but it controls whether it pastes clipboard or selection by an internal
00707 // flag that is not accessible in any way, so paste() being virtual is actually
00708 // useless, because reimplementations can't known where to paste from anyway.
00709 // Roll our own internal flag.
00710 void KMEdit::contentsMouseReleaseEvent( QMouseEvent * e )
00711 {
00712   if( e->button() != Qt::MidButton )
00713     return KEdit::contentsMouseReleaseEvent( e );
00714   mPasteMode = QClipboard::Selection;
00715   KEdit::contentsMouseReleaseEvent( e );
00716   mPasteMode = QClipboard::Clipboard;
00717 }
00718 
00719 void KMEdit::contentsMouseDoubleClickEvent( QMouseEvent *e )
00720 {
00721   bool handled = false;
00722   if ( e->button() == Qt::LeftButton ) {
00723 
00724     // Get the cursor position for the place where the user clicked to
00725     int paragraphPos;
00726     int charPos = charAt ( e->pos(), &paragraphPos );
00727     QString paraText = text( paragraphPos );
00728 
00729     // Now select the word under the cursor
00730     if ( charPos >= 0 && static_cast<unsigned int>( charPos ) <= paraText.length() ) {
00731 
00732       // Start the selection where the user clicked
00733       int start = charPos;
00734       unsigned int end = charPos;
00735 
00736       // Extend the selection to the left, until we reach a non-letter and non-digit char
00737       for (;;) {
00738         if ( ( start - 1 ) < 0 )
00739           break;
00740         QChar charToTheLeft = paraText.at( start - 1 );
00741         if ( charToTheLeft.isLetter() || charToTheLeft.isDigit() )
00742           start--;
00743         else
00744           break;
00745       }
00746 
00747       // Extend the selection to the left, until we reach a non-letter and non-digit char
00748       for (;;) {
00749         if ( ( end + 1 ) >= paraText.length() )
00750           break;
00751         QChar charToTheRight = paraText.at( end + 1 );
00752         if ( charToTheRight.isLetter() || charToTheRight.isDigit() )
00753           end++;
00754         else
00755           break;
00756       }
00757 
00758       setSelection( paragraphPos, start, paragraphPos, end + 1 );
00759       handled = true;
00760     }
00761   }
00762 
00763   if ( !handled )
00764     return KEdit::contentsMouseDoubleClickEvent( e );
00765 }
00766 
00767 void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos)
00768 {
00769     kdDebug(5006)<<"void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos) : "<<text <<endl;
00770     if( mSpellLineEdit )
00771         mComposer->sujectLineWidget()->spellCheckerMisspelling( text, lst, pos);
00772     else
00773         misspelling(text, lst, pos);
00774 }
00775 
00776 void KMEdit::slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos)
00777 {
00778     kdDebug(5006)<<"slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos) : "<<oldWord<<endl;
00779     if( mSpellLineEdit )
00780         mComposer->sujectLineWidget()->spellCheckerCorrected( oldWord, newWord, pos);
00781     else {
00782         unsigned int l = 0;
00783         unsigned int cnt = 0;
00784         bool _bold,_underline,_italic;
00785         QColor _color;
00786         QFont _font;
00787         posToRowCol (pos, l, cnt);
00788         setCursorPosition(l, cnt+1); // the new word will get the same markup now as the first character of the word
00789         _bold = bold();
00790         _underline = underline();
00791         _italic = italic();
00792         _color = color();
00793         _font = currentFont();
00794         corrected(oldWord, newWord, pos);
00795         setSelection (l, cnt, l, cnt+newWord.length());
00796         setBold(_bold);
00797         setItalic(_italic);
00798         setUnderline(_underline);
00799         setColor(_color);
00800         setCurrentFont(_font);
00801     }
00802 
00803 }
00804 
00805 void KMEdit::slotSpellcheck2(KSpell*)
00806 {
00807   // Make sure words ignored by the highlighter are ignored by KSpell as well
00808   if ( mHighlighter ) {
00809     for ( uint i = 0; i < mHighlighter->ignoredWords().size(); i++ )
00810       mKSpellForDialog->ignore( mHighlighter->ignoredWords()[i] );
00811   }
00812 
00813     if( !mSpellLineEdit)
00814     {
00815         spellcheck_start();
00816 
00817         QString quotePrefix;
00818         if(mComposer && mComposer->msg())
00819         {
00820             int languageNr = GlobalSettings::self()->replyCurrentLanguage();
00821             ReplyPhrases replyPhrases( QString::number(languageNr) );
00822             replyPhrases.readConfig();
00823 
00824             quotePrefix = mComposer->msg()->formatString(
00825                  replyPhrases.indentPrefix() );
00826         }
00827 
00828         kdDebug(5006) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl;
00829         QTextEdit plaintext;
00830         plaintext.setText(text());
00831         plaintext.setTextFormat(Qt::PlainText);
00832         mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls,
00833                                              SpellingFilter::FilterEmailAddresses);
00834 
00835         mKSpellForDialog->check(mSpellingFilter->filteredText());
00836     }
00837     else if( mComposer )
00838         mKSpellForDialog->check( mComposer->sujectLineWidget()->text());
00839 }
00840 
00841 void KMEdit::slotSpellResult(const QString &s)
00842 {
00843     if( !mSpellLineEdit)
00844         spellcheck_stop();
00845 
00846   int dlgResult = mKSpellForDialog->dlgResult();
00847   if ( dlgResult == KS_CANCEL )
00848   {
00849       if( mSpellLineEdit)
00850       {
00851           //stop spell check
00852           mSpellLineEdit = false;
00853           QString tmpText( s );
00854           tmpText =  tmpText.remove('\n');
00855 
00856           if( tmpText != mComposer->sujectLineWidget()->text() )
00857               mComposer->sujectLineWidget()->setText( tmpText );
00858       }
00859       else
00860       {
00861           setModified(true);
00862       }
00863   }
00864   mKSpellForDialog->cleanUp();
00865   KDictSpellingHighlighter::dictionaryChanged();
00866 
00867   emit spellcheck_done( dlgResult );
00868 }
00869 
00870 void KMEdit::slotSpellDone()
00871 {
00872   kdDebug(5006)<<" void KMEdit::slotSpellDone()\n";
00873   KSpell::spellStatus status = mKSpellForDialog->status();
00874   delete mKSpellForDialog;
00875   mKSpellForDialog = 0;
00876 
00877   kdDebug(5006) << "spelling: delete SpellingFilter" << endl;
00878   delete mSpellingFilter;
00879   mSpellingFilter = 0;
00880   mComposer->sujectLineWidget()->deselect();
00881   if (status == KSpell::Error)
00882   {
00883      KMessageBox::sorry( topLevelWidget(),
00884                          i18n("ISpell/Aspell could not be started. Please "
00885                               "make sure you have ISpell or Aspell properly "
00886                               "configured and in your PATH.") );
00887      emit spellcheck_done( KS_CANCEL );
00888   }
00889   else if (status == KSpell::Crashed)
00890   {
00891      spellcheck_stop();
00892      KMessageBox::sorry( topLevelWidget(),
00893                          i18n("ISpell/Aspell seems to have crashed.") );
00894      emit spellcheck_done( KS_CANCEL );
00895   }
00896   else
00897   {
00898       if( mSpellLineEdit )
00899           spellcheck();
00900       else if( !mComposer->subjectTextWasSpellChecked() && status == KSpell::FinishedNoMisspellingsEncountered )
00901           KMessageBox::information( topLevelWidget(),
00902                                     i18n("No misspellings encountered.") );
00903   }
00904 }
00905 
00906 void KMEdit::setCursorPositionFromStart( unsigned int pos ) {
00907   unsigned int l = 0;
00908   unsigned int c = 0;
00909   posToRowCol( pos, l, c );
00910   // kdDebug() << "Num lines: " << numLines() << endl;
00911   // kdDebug() << "Position " << pos << " converted to " << l << ":" << c << endl;
00912   setCursorPosition( l, c );
00913   ensureCursorVisible();
00914 }
00915 
00916 int KMEdit::indexOfCurrentLineStart( int paragraph, int index )
00917 {
00918   Q_ASSERT( paragraph >= 0 && paragraph < paragraphs() );
00919   Q_ASSERT( index >= 0 && ( index == 0 || index < paragraphLength( paragraph ) ) );
00920 
00921   const int startLine = lineOfChar( paragraph, index );
00922   Q_ASSERT( startLine >= 0 && startLine < linesOfParagraph( paragraph ) );
00923   for ( int curIndex = index; curIndex >= 0; curIndex-- ) {
00924     const int line = lineOfChar( paragraph, curIndex );
00925     if ( line != startLine ) {
00926       return curIndex + 1;
00927     }
00928   }
00929   return 0;
00930 }
00931 
00932 #include "kmedit.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys