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 <tqregexp.h> 00047 #include <tqbuffer.h> 00048 #include <tqevent.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(TQDragEnterEvent *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(TQDragMoveEvent *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( TQKeyEvent* e ) 00080 { 00081 if( e->key() == Key_Return ) { 00082 int line, col; 00083 getCursorPosition( &line, &col ); 00084 TQString lineText = text( line ); 00085 // returns line with additional trailing space (bug in TQt?), 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 TQString 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(TQDropEvent *e) 00143 { 00144 if (e->provides(MailListDrag::format())) { 00145 // Decode the list of serial numbers stored as the drag data 00146 TQByteArray serNums; 00147 MailListDrag::decode( e, serNums ); 00148 TQBuffer serNumBuffer(serNums); 00149 serNumBuffer.open(IO_ReadOnly); 00150 TQDataStream serNumStream(&serNumBuffer); 00151 TQ_UINT32 serNum; 00152 KMFolder *folder = 0; 00153 int idx; 00154 TQPtrList<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 ( TQTextDrag::canDecode( e ) ) { 00197 TQString s; 00198 if ( TQTextDrag::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(TQWidget *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( TQClipboard::Clipboard ) 00229 { 00230 connect( this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotSelectionChanged()) ); 00231 installEventFilter(this); 00232 KCursor::setAutoHideCursor( this, true, true ); 00233 setOverwriteEnabled( true ); 00234 createSpellers(); 00235 connect( mSpellConfig, TQT_SIGNAL( configChanged() ), 00236 this, TQT_SLOT( createSpellers() ) ); 00237 connect( mSpeller, TQT_SIGNAL( death() ), 00238 this, TQT_SLOT( spellerDied() ) ); 00239 } 00240 00241 void KMEdit::createSpellers() 00242 { 00243 delete mSpeller; 00244 mSpeller = new KMSpell( TQT_TQOBJECT(this), TQT_SLOT( spellerReady( KSpell * ) ), mSpellConfig ); 00245 } 00246 00247 void KMEdit::initializeAutoSpellChecking() 00248 { 00249 if ( mHighlighter ) 00250 return; // already initialized 00251 TQColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp 00252 TQColor defaultColor2( 0x00, 0x70, 0x00 ); 00253 TQColor defaultColor3( 0x00, 0x60, 0x00 ); 00254 TQColor defaultForeground( kapp->palette().active().text() ); 00255 00256 TQColor c = TQt::red; 00257 KConfigGroup readerConfig( KMKernel::config(), "Reader" ); 00258 TQColor col1; 00259 if ( !readerConfig.readBoolEntry( "defaultColors", true ) ) 00260 col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground ); 00261 else 00262 col1 = defaultForeground; 00263 TQColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 ); 00264 TQColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 ); 00265 TQColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 ); 00266 TQColor 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, TQT_SIGNAL(newSuggestions(const TQString&, const TQStringList&, unsigned int)), 00275 this, TQT_SLOT(addSuggestion(const TQString&, const TQStringList&, unsigned int)) ); 00276 } 00277 00278 00279 TQPopupMenu *KMEdit::createPopupMenu( const TQPoint& pos ) 00280 { 00281 enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll }; 00282 00283 TQPopupMenu *menu = KEdit::createPopupMenu( pos ); 00284 if ( !TQApplication::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 TQString& text, const TQStringList& 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 TQString KMEdit::brokenText() 00330 { 00331 TQString 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 = TQMAX( lineBreakColumn, textLine( numlines ).length() ); 00360 } 00361 return lineBreakColumn; 00362 } 00363 00364 KMSpell::KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig ) 00365 : KSpell( 0, TQString(), receiver, slot, spellConfig ) 00366 { 00367 } 00368 00369 KMSyntaxHighter::KMSyntaxHighter( TQTextEdit *textEdit, 00370 bool spellCheckingActive, 00371 bool autoEnable, 00372 const TQColor& spellColor, 00373 bool colorQuoting, 00374 const TQColor& QuoteColor0, 00375 const TQColor& QuoteColor1, 00376 const TQColor& QuoteColor2, 00377 const TQColor& 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 TQString &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 TQString &word ) 00395 { 00396 mIgnoredWords << word; 00397 } 00398 00399 TQStringList 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(TQObject*o, TQEvent* e) 00415 { 00416 if (TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(this)) 00417 KCursor::autoHideEventFilter(o, e); 00418 00419 if (e->type() == TQEvent::KeyPress) 00420 { 00421 TQKeyEvent *k = (TQKeyEvent*)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 TQString 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, TQT_SIGNAL(processExited(KProcess*)), 00454 TQT_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( TQT_TQOBJECT(this), "mExtEditorTempFileWatcher" ); 00462 connect( mExtEditorTempFileWatcher, TQT_SIGNAL(dirty(const TQString&)), 00463 TQT_SLOT(slotExternalEditorTempFileChanged(const TQString&)) ); 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() == TQEvent::ContextMenu ) { 00488 TQContextMenuEvent *event = (TQContextMenuEvent*) 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()), ¶ ); 00494 TQString paraText = text( para ); 00495 00496 if( !paraText.at(charPos).isSpace() ) 00497 { 00498 //Get word right clicked on 00499 const TQRegExp 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 TQString 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 TQStringList reps = mReplacements[word]; 00512 if( reps.count() > 0 ) 00513 { 00514 int listPos = 0; 00515 for ( TQStringList::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 TQTimer::singleShot( 200, mHighlighter, TQT_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() == TQEvent::FocusIn || e->type() == TQEvent::FocusOut ) { 00573 TQFocusEvent *fe = TQT_TQFOCUSEVENT(e); 00574 if(! (fe->reason() == TQFocusEvent::ActiveWindow || fe->reason() == TQFocusEvent::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() == TQt::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 TQString & 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(TQString::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() == TQt::RichText ) { 00653 // kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl; 00654 // mKSpellForDialog = new KSpell(this, i18n("Spellcheck - KMail"), this, 00655 // TQT_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( TQT_TQWIDGET(this), i18n("Spellcheck - KMail"), TQT_TQOBJECT(this), 00662 TQT_SLOT(slotSpellcheck2(KSpell*))/*, mSpellConfig*/ ); 00663 // } 00664 00665 TQStringList l = KSpellingHighlighter::personalWords(); 00666 for ( TQStringList::Iterator it = l.begin(); it != l.end(); ++it ) { 00667 mKSpellForDialog->addPersonal( *it ); 00668 } 00669 connect (mKSpellForDialog, TQT_SIGNAL( death()), 00670 this, TQT_SLOT (slotSpellDone())); 00671 connect (mKSpellForDialog, TQT_SIGNAL (misspelling (const TQString &, const TQStringList &, unsigned int)), 00672 this, TQT_SLOT (slotMisspelling (const TQString &, const TQStringList &, unsigned int))); 00673 connect (mKSpellForDialog, TQT_SIGNAL (corrected (const TQString &, const TQString &, unsigned int)), 00674 this, TQT_SLOT (slotCorrected (const TQString &, const TQString &, unsigned int))); 00675 connect (mKSpellForDialog, TQT_SIGNAL (done(const TQString &)), 00676 this, TQT_SLOT (slotSpellResult (const TQString&))); 00677 } 00678 00679 void KMEdit::cut() 00680 { 00681 KEdit::cut(); 00682 if ( textFormat() != TQt::RichText && mHighlighter ) 00683 mHighlighter->restartBackgroundSpellCheck(); 00684 } 00685 00686 void KMEdit::clear() 00687 { 00688 KEdit::clear(); 00689 if ( textFormat() != TQt::RichText && mHighlighter ) 00690 mHighlighter->restartBackgroundSpellCheck(); 00691 } 00692 00693 void KMEdit::del() 00694 { 00695 KEdit::del(); 00696 if ( textFormat() != TQt::RichText && mHighlighter ) 00697 mHighlighter->restartBackgroundSpellCheck(); 00698 } 00699 00700 void KMEdit::paste() 00701 { 00702 mComposer->paste( mPasteMode ); 00703 } 00704 00705 // KMEdit indirectly inherits from TQTextEdit, 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( TQMouseEvent * e ) 00711 { 00712 if( e->button() != Qt::MidButton ) 00713 return KEdit::contentsMouseReleaseEvent( e ); 00714 mPasteMode = TQClipboard::Selection; 00715 KEdit::contentsMouseReleaseEvent( e ); 00716 mPasteMode = TQClipboard::Clipboard; 00717 } 00718 00719 void KMEdit::contentsMouseDoubleClickEvent( TQMouseEvent *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(), ¶graphPos ); 00727 TQString 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 TQChar 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 TQChar 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 TQString &text, const TQStringList &lst, unsigned int pos) 00768 { 00769 kdDebug(5006)<<"void KMEdit::slotMisspelling(const TQString &text, const TQStringList &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 TQString &oldWord, const TQString &newWord, unsigned int pos) 00777 { 00778 kdDebug(5006)<<"slotCorrected (const TQString &oldWord, const TQString &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 TQColor _color; 00786 TQFont _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 TQString quotePrefix; 00818 if(mComposer && mComposer->msg()) 00819 { 00820 int languageNr = GlobalSettings::self()->replyCurrentLanguage(); 00821 ReplyPhrases replyPhrases( TQString::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 TQTextEdit plaintext; 00830 plaintext.setText(text()); 00831 plaintext.setTextFormat(TQt::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 TQString &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 TQString 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"