kmail

kmedit.cpp
1// kmcomposewin.cpp
2// Author: Markus Wuebben <markus.wuebben@kde.org>
3// This code is published under the GPL.
4
5#include <config.h>
6
7#include "kmedit.h"
8#include "kmlineeditspell.h"
9
10#define REALLY_WANT_KMCOMPOSEWIN_H
11#include "kmcomposewin.h"
12#undef REALLY_WANT_KMCOMPOSEWIN_H
13#include "kmmsgdict.h"
14#include "kmfolder.h"
15#include "kmcommands.h"
16
17#include <maillistdrag.h>
18using KPIM::MailListDrag;
19
20#include <libtdepim/tdefileio.h>
21#include <libemailfunctions/email.h>
22
23#include <kcursor.h>
24#include <tdeprocess.h>
25
26#include <tdepopupmenu.h>
27#include <kdebug.h>
28#include <tdemessagebox.h>
29#include <kurldrag.h>
30
31#include <tdetempfile.h>
32#include <tdelocale.h>
33#include <tdeapplication.h>
34#include <kdirwatch.h>
35#include <kiconloader.h>
36
37#include "globalsettings.h"
38#include "replyphrases.h"
39
40#include <tdespell.h>
41#include <tdespelldlg.h>
42#include <spellingfilter.h>
43#include <ksyntaxhighlighter.h>
44
45#include <tqregexp.h>
46#include <tqbuffer.h>
47#include <tqevent.h>
48
49#include <sys/stat.h>
50#include <sys/types.h>
51#include <stdlib.h>
52#include <unistd.h>
53#include <errno.h>
54#include <fcntl.h>
55#include <assert.h>
56
57
58void KMEdit::contentsDragEnterEvent(TQDragEnterEvent *e)
59{
60 if (e->provides(MailListDrag::format()))
61 e->accept(true);
62 else if (e->provides("image/png"))
63 e->accept();
64 else
65 return KEdit::contentsDragEnterEvent(e);
66}
67
68void KMEdit::contentsDragMoveEvent(TQDragMoveEvent *e)
69{
70 if (e->provides(MailListDrag::format()))
71 e->accept();
72 else if (e->provides("image/png"))
73 e->accept();
74 else
75 return KEdit::contentsDragMoveEvent(e);
76}
77
78void KMEdit::keyPressEvent( TQKeyEvent* e )
79{
80 if( e->key() == Key_Return ) {
81 int line, col;
82 getCursorPosition( &line, &col );
83 TQString lineText = text( line );
84 // returns line with additional trailing space (bug in TQt?), cut it off
85 lineText.truncate( lineText.length() - 1 );
86 // special treatment of quoted lines only if the cursor is neither at
87 // the begin nor at the end of the line
88 if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) {
89 bool isQuotedLine = false;
90 uint bot = 0; // bot = begin of text after quote indicators
91 while( bot < lineText.length() ) {
92 if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) {
93 isQuotedLine = true;
94 ++bot;
95 }
96 else if( lineText[bot].isSpace() ) {
97 ++bot;
98 }
99 else {
100 break;
101 }
102 }
103
104 KEdit::keyPressEvent( e );
105
106 // duplicate quote indicators of the previous line before the new
107 // line if the line actually contained text (apart from the quote
108 // indicators) and the cursor is behind the quote indicators
109 if( isQuotedLine
110 && ( bot != lineText.length() )
111 && ( col >= int( bot ) ) ) {
112
113 // The cursor position might have changed unpredictably if there was selected
114 // text which got replaced by a new line, so we query it again:
115 getCursorPosition( &line, &col );
116 TQString newLine = text( line );
117 // remove leading white space from the new line and instead
118 // add the quote indicators of the previous line
119 unsigned int leadingWhiteSpaceCount = 0;
120 while( ( leadingWhiteSpaceCount < newLine.length() )
121 && newLine[leadingWhiteSpaceCount].isSpace() ) {
122 ++leadingWhiteSpaceCount;
123 }
124 newLine = newLine.replace( 0, leadingWhiteSpaceCount,
125 lineText.left( bot ) );
126 removeParagraph( line );
127 insertParagraph( newLine, line );
128 // place the cursor at the begin of the new line since
129 // we assume that the user split the quoted line in order
130 // to add a comment to the first part of the quoted line
131 setCursorPosition( line, 0 );
132 }
133 }
134 else
135 KEdit::keyPressEvent( e );
136 }
137 else
138 KEdit::keyPressEvent( e );
139}
140
141void KMEdit::contentsDropEvent(TQDropEvent *e)
142{
143 if (e->provides(MailListDrag::format())) {
144 // Decode the list of serial numbers stored as the drag data
145 TQByteArray serNums;
146 MailListDrag::decode( e, serNums );
147 TQBuffer serNumBuffer(serNums);
148 serNumBuffer.open(IO_ReadOnly);
149 TQDataStream serNumStream(&serNumBuffer);
150 TQ_UINT32 serNum;
151 KMFolder *folder = 0;
152 int idx;
153 TQPtrList<KMMsgBase> messageList;
154 while (!serNumStream.atEnd()) {
155 KMMsgBase *msgBase = 0;
156 serNumStream >> serNum;
157 KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
158 if (folder)
159 msgBase = folder->getMsgBase(idx);
160 if (msgBase)
161 messageList.append( msgBase );
162 }
163 serNumBuffer.close();
164 uint identity = folder ? folder->identity() : 0;
165 KMCommand *command =
166 new KMForwardAttachedCommand(mComposer, messageList,
167 identity, mComposer);
168 command->start();
169 }
170 else if( e->provides("image/png") ) {
171 emit attachPNGImageData(e->encodedData("image/png"));
172 }
173 else if( KURLDrag::canDecode( e ) ) {
174 KURL::List urlList;
175 if( KURLDrag::decode( e, urlList ) ) {
176 TDEPopupMenu p;
177 p.insertItem( i18n("Add as Text"), 0 );
178 p.insertItem( i18n("Add as Attachment"), 1 );
179 int id = p.exec( mapToGlobal( e->pos() ) );
180 switch ( id) {
181 case 0:
182 for ( KURL::List::Iterator it = urlList.begin();
183 it != urlList.end(); ++it ) {
184 insert( (*it).url() );
185 }
186 break;
187 case 1:
188 for ( KURL::List::Iterator it = urlList.begin();
189 it != urlList.end(); ++it ) {
190 mComposer->addAttach( *it );
191 }
192 break;
193 }
194 }
195 else if ( TQTextDrag::canDecode( e ) ) {
196 TQString s;
197 if ( TQTextDrag::decode( e, s ) )
198 insert( s );
199 }
200 else
201 kdDebug(5006) << "KMEdit::contentsDropEvent, unable to add dropped object" << endl;
202 }
203 else if( e->provides("text/x-textsnippet") ) {
204 emit insertSnippet();
205 }
206 else {
207 KEdit::contentsDropEvent(e);
208 }
209}
210
211KMEdit::KMEdit(TQWidget *parent, KMComposeWin* composer,
212 KSpellConfig* autoSpellConfig,
213 const char *name)
214 : KEdit( parent, name ),
215 mComposer( composer ),
216 mKSpellForDialog( 0 ),
217 mSpeller( 0 ),
218 mSpellConfig( autoSpellConfig ),
219 mSpellingFilter( 0 ),
220 mExtEditorTempFile( 0 ),
221 mExtEditorTempFileWatcher( 0 ),
222 mExtEditorProcess( 0 ),
223 mUseExtEditor( false ),
224 mWasModifiedBeforeSpellCheck( false ),
225 mHighlighter( 0 ),
226 mSpellLineEdit( false ),
227 mPasteMode( TQClipboard::Clipboard )
228{
229 connect( this, TQ_SIGNAL(selectionChanged()), this, TQ_SLOT(slotSelectionChanged()) );
230 installEventFilter(this);
231 KCursor::setAutoHideCursor( this, true, true );
232 setOverwriteEnabled( true );
233 createSpellers();
234 connect( mSpellConfig, TQ_SIGNAL( configChanged() ),
235 this, TQ_SLOT( createSpellers() ) );
236 connect( mSpeller, TQ_SIGNAL( death() ),
237 this, TQ_SLOT( spellerDied() ) );
238}
239
240void KMEdit::createSpellers()
241{
242 delete mSpeller;
243 mSpeller = new KMSpell( this, TQ_SLOT( spellerReady( KSpell * ) ), mSpellConfig );
244}
245
246void KMEdit::initializeAutoSpellChecking()
247{
248 if ( mHighlighter )
249 return; // already initialized
250 TQColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp
251 TQColor defaultColor2( 0x00, 0x70, 0x00 );
252 TQColor defaultColor3( 0x00, 0x60, 0x00 );
253 TQColor defaultForeground( tdeApp->palette().active().text() );
254
255 TQColor c = TQt::red;
256 TDEConfigGroup readerConfig( KMKernel::config(), "Reader" );
257 TQColor col1;
258 if ( !readerConfig.readBoolEntry( "defaultColors", true ) )
259 col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground );
260 else
261 col1 = defaultForeground;
262 TQColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 );
263 TQColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 );
264 TQColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 );
265 TQColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c );
266 mHighlighter = new KMSyntaxHighter( this, /*active*/ true,
267 /*autoEnabled*/ false,
268 /*spellColor*/ misspelled,
269 /*colorQuoting*/ true,
270 col1, col2, col3, col4,
271 mSpellConfig );
272
273 connect( mHighlighter, TQ_SIGNAL(newSuggestions(const TQString&, const TQStringList&, unsigned int)),
274 this, TQ_SLOT(addSuggestion(const TQString&, const TQStringList&, unsigned int)) );
275}
276
277
278TQPopupMenu *KMEdit::createPopupMenu( const TQPoint& pos )
279{
280 enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll };
281
282 TQPopupMenu *menu = KEdit::createPopupMenu( pos );
283 if ( !TQApplication::clipboard()->image().isNull() ) {
284 int id = menu->idAt(0);
285 menu->setItemEnabled( id - IdPaste, true);
286 }
287
288 return menu;
289}
290
291void KMEdit::deleteAutoSpellChecking()
292{ // because the highlighter doesn't support RichText, delete its instance.
293 delete mHighlighter;
294 mHighlighter =0;
295}
296
297void KMEdit::addSuggestion(const TQString& text, const TQStringList& lst, unsigned int )
298{
299 mReplacements[text] = lst;
300}
301
302void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
303{
304 if ( mHighlighter ) {
305 mHighlighter->setActive(spellCheckingActive);
306 }
307}
308
309
310KMEdit::~KMEdit()
311{
312 removeEventFilter(this);
313
314 if ( mSpeller ) {
315 // The speller needs some time to clean up, so trigger cleanup and let it delete itself
316 mSpeller->setAutoDelete( true );
317 mSpeller->cleanUp();
318 mSpeller = 0;
319 }
320
321 delete mKSpellForDialog;
322 delete mHighlighter;
323 mHighlighter = 0;
324}
325
326
327
328TQString KMEdit::brokenText()
329{
330 TQString temp, line;
331
332 int num_lines = numLines();
333 for (int i = 0; i < num_lines; ++i)
334 {
335 int lastLine = 0;
336 line = textLine(i);
337 for (int j = 0; j < (int)line.length(); ++j)
338 {
339 if (lineOfChar(i, j) > lastLine)
340 {
341 lastLine = lineOfChar(i, j);
342 temp += '\n';
343 }
344 temp += line[j];
345 }
346 if (i + 1 < num_lines) temp += '\n';
347 }
348
349 return temp;
350}
351
352
353unsigned int KMEdit::lineBreakColumn() const
354{
355 unsigned int lineBreakColumn = 0;
356 unsigned int numlines = numLines();
357 while ( numlines-- ) {
358 lineBreakColumn = TQMAX( lineBreakColumn, textLine( numlines ).length() );
359 }
360 return lineBreakColumn;
361}
362
363KMSpell::KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig )
364 : KSpell( 0, TQString(), receiver, slot, spellConfig )
365{
366}
367
368KMSyntaxHighter::KMSyntaxHighter( TQTextEdit *textEdit,
369 bool spellCheckingActive,
370 bool autoEnable,
371 const TQColor& spellColor,
372 bool colorQuoting,
373 const TQColor& QuoteColor0,
374 const TQColor& QuoteColor1,
375 const TQColor& QuoteColor2,
376 const TQColor& QuoteColor3,
377 KSpellConfig *spellConfig )
378 : KDictSpellingHighlighter( textEdit, spellCheckingActive, autoEnable, spellColor, colorQuoting,
379 QuoteColor0, QuoteColor1, QuoteColor2, QuoteColor3, spellConfig )
380{
381}
382
383bool KMSyntaxHighter::isMisspelled( const TQString &word )
384{
385 if ( mIgnoredWords.contains( word ) ) {
386 return false;
387 }
388 else {
389 return KDictSpellingHighlighter::isMisspelled( word );
390 }
391}
392
393void KMSyntaxHighter::ignoreWord( const TQString &word )
394{
395 mIgnoredWords << word;
396}
397
398TQStringList KMSyntaxHighter::ignoredWords() const
399{
400 return mIgnoredWords;
401}
402
403void KMEdit::spellerDied()
404{
405 mSpeller = 0;
406}
407
408void KMEdit::spellerReady( KSpell *spell )
409{
410 Q_ASSERT( mSpeller == spell );
411}
412
413bool KMEdit::eventFilter(TQObject*o, TQEvent* e)
414{
415 if (o == this)
416 KCursor::autoHideEventFilter(o, e);
417
418 if (e->type() == TQEvent::KeyPress)
419 {
420 TQKeyEvent *k = (TQKeyEvent*)e;
421
422 if (mUseExtEditor) {
423 if (k->key() == Key_Up)
424 {
425 emit focusUp();
426 return true;
427 }
428
429 // ignore modifier keys (cf. bug 48841)
430 if ( (k->key() == Key_Shift) || (k->key() == Key_Control) ||
431 (k->key() == Key_Meta) || (k->key() == Key_Alt) )
432 return true;
433 if (mExtEditorTempFile) return true;
434 TQString sysLine = mExtEditor;
435 mExtEditorTempFile = new KTempFile();
436
437 mExtEditorTempFile->setAutoDelete(true);
438
439 (*mExtEditorTempFile->textStream()) << text();
440
441 mExtEditorTempFile->close();
442 // replace %f in the system line
443 sysLine.replace( "%f", mExtEditorTempFile->name() );
444 mExtEditorProcess = new TDEProcess();
445 mExtEditorProcess->setUseShell( true );
446 sysLine += " ";
447 while (!sysLine.isEmpty())
448 {
449 *mExtEditorProcess << sysLine.left(sysLine.find(" ")).local8Bit();
450 sysLine.remove(0, sysLine.find(" ") + 1);
451 }
452 connect(mExtEditorProcess, TQ_SIGNAL(processExited(TDEProcess*)),
453 TQ_SLOT(slotExternalEditorDone(TDEProcess*)));
454 if (!mExtEditorProcess->start())
455 {
456 KMessageBox::error( topLevelWidget(),
457 i18n("Unable to start external editor.") );
458 killExternalEditor();
459 } else {
460 mExtEditorTempFileWatcher = new KDirWatch( this, "mExtEditorTempFileWatcher" );
461 connect( mExtEditorTempFileWatcher, TQ_SIGNAL(dirty(const TQString&)),
462 TQ_SLOT(slotExternalEditorTempFileChanged(const TQString&)) );
463 mExtEditorTempFileWatcher->addFile( mExtEditorTempFile->name() );
464 }
465 return true;
466 } else {
467 // ---sven's Arrow key navigation start ---
468 // Key Up in first line takes you to Subject line.
469 if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0
470 && lineOfChar(0, currentColumn()) == 0)
471 {
472 deselect();
473 emit focusUp();
474 return true;
475 }
476 // ---sven's Arrow key navigation end ---
477
478 if (k->key() == Key_Backtab && k->state() == ShiftButton)
479 {
480 deselect();
481 emit focusUp();
482 return true;
483 }
484
485 }
486 } else if ( e->type() == TQEvent::ContextMenu ) {
487 TQContextMenuEvent *event = (TQContextMenuEvent*) e;
488
489 int para = 1, charPos, firstSpace, lastSpace;
490
491 //Get the character at the position of the click
492 charPos = charAt( viewportToContents(event->pos()), &para );
493 TQString paraText = text( para );
494
495 if( !paraText.at(charPos).isSpace() )
496 {
497 //Get word right clicked on
498 const TQRegExp wordBoundary( "[\\s\\W]" );
499 firstSpace = paraText.findRev( wordBoundary, charPos ) + 1;
500 lastSpace = paraText.find( wordBoundary, charPos );
501 if( lastSpace == -1 )
502 lastSpace = paraText.length();
503 TQString word = paraText.mid( firstSpace, lastSpace - firstSpace );
504 //Continue if this word was misspelled
505 if( !word.isEmpty() && mReplacements.contains( word ) )
506 {
507 TDEPopupMenu p;
508
509 //Add the suggestions to the popup menu
510 TQStringList reps = mReplacements[word];
511 if( reps.count() > 0 )
512 {
513 int listPos = 0;
514 for ( TQStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) {
515 p.insertItem( *it, listPos );
516 listPos++;
517 }
518 }
519 else
520 {
521 p.setItemEnabled( p.insertItem( i18n( "No Suggestions" ), -2 ), false );
522 }
523
524 int addToDictionaryId = -42;
525 int ignoreId = -43;
526 if ( mSpeller && mSpeller->status() == KSpell::Running ) {
527 p.insertSeparator();
528 addToDictionaryId = p.insertItem( i18n( "Add to Dictionary" ) );
529 ignoreId = p.insertItem( i18n( "Ignore All" ) );
530 }
531
532 //Execute the popup inline
533 const int id = p.exec( mapToGlobal( event->pos() ) );
534
535 if ( id == ignoreId ) {
536 mHighlighter->ignoreWord( word );
537 mHighlighter->rehighlight();
538 }
539 if ( id == addToDictionaryId ) {
540 mSpeller->addPersonal( word );
541 mSpeller->writePersonalDictionary();
542 if ( mHighlighter ) {
543 // Wait a bit until reloading the highlighter, the mSpeller first needs to finish saving
544 // the personal word list.
545 TQTimer::singleShot( 200, mHighlighter, TQ_SLOT( slotLocalSpellConfigChanged() ) );
546 }
547 }
548 else if( id > -1 )
549 {
550 //Save the cursor position
551 int parIdx = 1, txtIdx = 1;
552 getCursorPosition(&parIdx, &txtIdx);
553 setSelection(para, firstSpace, para, lastSpace);
554 insert(mReplacements[word][id]);
555 // Restore the cursor position; if the cursor was behind the
556 // misspelled word then adjust the cursor position
557 if ( para == parIdx && txtIdx >= lastSpace )
558 txtIdx += mReplacements[word][id].length() - word.length();
559 setCursorPosition(parIdx, txtIdx);
560 }
561
562 if ( id == addToDictionaryId || id == ignoreId ) {
563 // No longer misspelled: Either added to dictionary or ignored
564 mReplacements.remove( word );
565 }
566
567 //Cancel original event
568 return true;
569 }
570 }
571 } else if ( e->type() == TQEvent::FocusIn || e->type() == TQEvent::FocusOut ) {
572 TQFocusEvent *fe = static_cast<TQFocusEvent*>(e);
573 if(! (fe->reason() == TQFocusEvent::ActiveWindow || fe->reason() == TQFocusEvent::Popup) )
574 emit focusChanged( fe->gotFocus() );
575 }
576
577 return KEdit::eventFilter(o, e);
578}
579
580
581int KMEdit::autoSpellChecking( bool on )
582{
583 if ( textFormat() == TQt::RichText ) {
584 // syntax highlighter doesn't support extended text properties
585 if ( on )
586 KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup."));
587 return -1;
588 }
589 if ( mHighlighter ) {
590 // don't autoEnable spell checking if the user turned spell checking off
591 mHighlighter->setAutomatic( on );
592 mHighlighter->setActive( on );
593 }
594 return 1;
595}
596
597
598void KMEdit::slotExternalEditorTempFileChanged( const TQString & fileName ) {
599 if ( !mExtEditorTempFile )
600 return;
601 if ( fileName != mExtEditorTempFile->name() )
602 return;
603 // read data back in from file
604 setAutoUpdate(false);
605 clear();
606
607 insertLine(TQString::fromLocal8Bit(KPIM::kFileToString( fileName, true, false )), -1);
608 setAutoUpdate(true);
609 repaint();
610}
611
612void KMEdit::slotExternalEditorDone( TDEProcess * proc ) {
613 assert(proc == mExtEditorProcess);
614 // make sure, we update even when KDirWatcher is too slow:
615 slotExternalEditorTempFileChanged( mExtEditorTempFile->name() );
616 killExternalEditor();
617}
618
619void KMEdit::killExternalEditor() {
620 delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
621 delete mExtEditorTempFile; mExtEditorTempFile = 0;
622 delete mExtEditorProcess; mExtEditorProcess = 0;
623}
624
625
626bool KMEdit::checkExternalEditorFinished() {
627 if ( !mExtEditorProcess )
628 return true;
629 switch ( KMessageBox::warningYesNoCancel( topLevelWidget(),
630 i18n("The external editor is still running.\n"
631 "Abort the external editor or leave it open?"),
632 i18n("External Editor"),
633 i18n("Abort Editor"), i18n("Leave Editor Open") ) ) {
634 case KMessageBox::Yes:
635 killExternalEditor();
636 return true;
637 case KMessageBox::No:
638 return true;
639 default:
640 return false;
641 }
642}
643
644void KMEdit::spellcheck()
645{
646 if ( mKSpellForDialog )
647 return;
648 mWasModifiedBeforeSpellCheck = isModified();
649 mSpellLineEdit = !mSpellLineEdit;
650// maybe for later, for now plaintext is given to KSpell
651// if (textFormat() == TQt::RichText ) {
652// kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl;
653// mKSpellForDialog = new KSpell(this, i18n("Spellcheck - KMail"), this,
654// TQ_SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML);
655// }
656// else {
657
658 // Don't use mSpellConfig here. Reason is that the spell dialog, KSpellDlg, uses its own
659 // spell config, and therefore the two wouldn't be in sync.
660 mKSpellForDialog = new KSpell( this, i18n("Spellcheck - KMail"), this,
661 TQ_SLOT(slotSpellcheck2(KSpell*))/*, mSpellConfig*/ );
662// }
663
664 TQStringList l = KSpellingHighlighter::personalWords();
665 for ( TQStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
666 mKSpellForDialog->addPersonal( *it );
667 }
668 connect (mKSpellForDialog, TQ_SIGNAL( death()),
669 this, TQ_SLOT (slotSpellDone()));
670 connect (mKSpellForDialog, TQ_SIGNAL (misspelling (const TQString &, const TQStringList &, unsigned int)),
671 this, TQ_SLOT (slotMisspelling (const TQString &, const TQStringList &, unsigned int)));
672 connect (mKSpellForDialog, TQ_SIGNAL (corrected (const TQString &, const TQString &, unsigned int)),
673 this, TQ_SLOT (slotCorrected (const TQString &, const TQString &, unsigned int)));
674 connect (mKSpellForDialog, TQ_SIGNAL (done(const TQString &)),
675 this, TQ_SLOT (slotSpellResult (const TQString&)));
676}
677
678void KMEdit::cut()
679{
680 KEdit::cut();
681 if ( textFormat() != TQt::RichText && mHighlighter )
682 mHighlighter->restartBackgroundSpellCheck();
683}
684
685void KMEdit::clear()
686{
687 KEdit::clear();
688 if ( textFormat() != TQt::RichText && mHighlighter )
689 mHighlighter->restartBackgroundSpellCheck();
690}
691
692void KMEdit::del()
693{
694 KEdit::del();
695 if ( textFormat() != TQt::RichText && mHighlighter )
696 mHighlighter->restartBackgroundSpellCheck();
697}
698
699void KMEdit::paste()
700{
701 mComposer->paste( mPasteMode );
702}
703
704// KMEdit indirectly inherits from TQTextEdit, which has virtual paste() method,
705// but it controls whether it pastes clipboard or selection by an internal
706// flag that is not accessible in any way, so paste() being virtual is actually
707// useless, because reimplementations can't known where to paste from anyway.
708// Roll our own internal flag.
709void KMEdit::contentsMouseReleaseEvent( TQMouseEvent * e )
710{
711 if( e->button() != TQt::MidButton )
712 return KEdit::contentsMouseReleaseEvent( e );
713 mPasteMode = TQClipboard::Selection;
714 KEdit::contentsMouseReleaseEvent( e );
715 mPasteMode = TQClipboard::Clipboard;
716}
717
718void KMEdit::contentsMouseDoubleClickEvent( TQMouseEvent *e )
719{
720 bool handled = false;
721 if ( e->button() == TQt::LeftButton ) {
722
723 // Get the cursor position for the place where the user clicked to
724 int paragraphPos;
725 int charPos = charAt ( e->pos(), &paragraphPos );
726 TQString paraText = text( paragraphPos );
727
728 // Now select the word under the cursor
729 if ( charPos >= 0 && static_cast<unsigned int>( charPos ) <= paraText.length() ) {
730
731 // Start the selection where the user clicked
732 int start = charPos;
733 unsigned int end = charPos;
734
735 // Extend the selection to the left, until we reach a non-letter and non-digit char
736 for (;;) {
737 if ( ( start - 1 ) < 0 )
738 break;
739 TQChar charToTheLeft = paraText.at( start - 1 );
740 if ( charToTheLeft.isLetter() || charToTheLeft.isDigit() )
741 start--;
742 else
743 break;
744 }
745
746 // Extend the selection to the left, until we reach a non-letter and non-digit char
747 for (;;) {
748 if ( ( end + 1 ) >= paraText.length() )
749 break;
750 TQChar charToTheRight = paraText.at( end + 1 );
751 if ( charToTheRight.isLetter() || charToTheRight.isDigit() )
752 end++;
753 else
754 break;
755 }
756
757 setSelection( paragraphPos, start, paragraphPos, end + 1 );
758 handled = true;
759 }
760 }
761
762 if ( !handled )
763 return KEdit::contentsMouseDoubleClickEvent( e );
764}
765
766void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos)
767{
768 kdDebug(5006)<<"void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos) : "<<text <<endl;
769 if( mSpellLineEdit )
770 mComposer->sujectLineWidget()->spellCheckerMisspelling( text, lst, pos);
771 else
772 misspelling(text, lst, pos);
773}
774
775void KMEdit::slotCorrected (const TQString &oldWord, const TQString &newWord, unsigned int pos)
776{
777 kdDebug(5006)<<"slotCorrected (const TQString &oldWord, const TQString &newWord, unsigned int pos) : "<<oldWord<<endl;
778 if( mSpellLineEdit )
779 mComposer->sujectLineWidget()->spellCheckerCorrected( oldWord, newWord, pos);
780 else {
781 unsigned int l = 0;
782 unsigned int cnt = 0;
783 bool _bold,_underline,_italic;
784 TQColor _color;
785 TQFont _font;
786 posToRowCol (pos, l, cnt);
787 setCursorPosition(l, cnt+1); // the new word will get the same markup now as the first character of the word
788 _bold = bold();
789 _underline = underline();
790 _italic = italic();
791 _color = color();
792 _font = currentFont();
793 corrected(oldWord, newWord, pos);
794 setSelection (l, cnt, l, cnt+newWord.length());
795 setBold(_bold);
796 setItalic(_italic);
797 setUnderline(_underline);
798 setColor(_color);
799 setCurrentFont(_font);
800 }
801
802}
803
804void KMEdit::slotSpellcheck2(KSpell*)
805{
806 // Make sure words ignored by the highlighter are ignored by KSpell as well
807 if ( mHighlighter ) {
808 for ( uint i = 0; i < mHighlighter->ignoredWords().size(); i++ )
809 mKSpellForDialog->ignore( mHighlighter->ignoredWords()[i] );
810 }
811
812 if( !mSpellLineEdit)
813 {
814 spellcheck_start();
815
816 TQString quotePrefix;
817 if(mComposer && mComposer->msg())
818 {
819 int languageNr = GlobalSettings::self()->replyCurrentLanguage();
820 ReplyPhrases replyPhrases( TQString::number(languageNr) );
821 replyPhrases.readConfig();
822
823 quotePrefix = mComposer->msg()->formatString(
824 replyPhrases.indentPrefix() );
825 }
826
827 kdDebug(5006) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl;
828 TQTextEdit plaintext;
829 plaintext.setText(text());
830 plaintext.setTextFormat(TQt::PlainText);
831 mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls,
832 SpellingFilter::FilterEmailAddresses);
833
834 mKSpellForDialog->check(mSpellingFilter->filteredText());
835 }
836 else if( mComposer )
837 mKSpellForDialog->check( mComposer->sujectLineWidget()->text());
838}
839
840void KMEdit::slotSpellResult(const TQString &s)
841{
842 if( !mSpellLineEdit)
843 spellcheck_stop();
844
845 int dlgResult = mKSpellForDialog->dlgResult();
846 if ( dlgResult == KS_CANCEL )
847 {
848 if( mSpellLineEdit)
849 {
850 //stop spell check
851 mSpellLineEdit = false;
852 TQString tmpText( s );
853 tmpText = tmpText.remove('\n');
854
855 if( tmpText != mComposer->sujectLineWidget()->text() )
856 mComposer->sujectLineWidget()->setText( tmpText );
857 }
858 else
859 {
860 setModified(true);
861 }
862 }
863 mKSpellForDialog->cleanUp();
864 KDictSpellingHighlighter::dictionaryChanged();
865
866 emit spellcheck_done( dlgResult );
867}
868
869void KMEdit::slotSpellDone()
870{
871 kdDebug(5006)<<" void KMEdit::slotSpellDone()\n";
872 KSpell::spellStatus status = mKSpellForDialog->status();
873 delete mKSpellForDialog;
874 mKSpellForDialog = 0;
875
876 kdDebug(5006) << "spelling: delete SpellingFilter" << endl;
877 delete mSpellingFilter;
878 mSpellingFilter = 0;
879 mComposer->sujectLineWidget()->deselect();
880 if (status == KSpell::Error)
881 {
882 KMessageBox::sorry( topLevelWidget(),
883 i18n("ISpell/Aspell could not be started. Please "
884 "make sure you have ISpell or Aspell properly "
885 "configured and in your PATH.") );
886 emit spellcheck_done( KS_CANCEL );
887 }
888 else if (status == KSpell::Crashed)
889 {
890 spellcheck_stop();
891 KMessageBox::sorry( topLevelWidget(),
892 i18n("ISpell/Aspell seems to have crashed.") );
893 emit spellcheck_done( KS_CANCEL );
894 }
895 else
896 {
897 if( mSpellLineEdit )
898 spellcheck();
899 else if( !mComposer->subjectTextWasSpellChecked() && status == KSpell::FinishedNoMisspellingsEncountered )
900 KMessageBox::information( topLevelWidget(),
901 i18n("No misspellings encountered.") );
902 }
903}
904
905void KMEdit::setCursorPositionFromStart( unsigned int pos ) {
906 unsigned int l = 0;
907 unsigned int c = 0;
908 posToRowCol( pos, l, c );
909 // kdDebug() << "Num lines: " << numLines() << endl;
910 // kdDebug() << "Position " << pos << " converted to " << l << ":" << c << endl;
911 setCursorPosition( l, c );
912 ensureCursorVisible();
913}
914
915int KMEdit::indexOfCurrentLineStart( int paragraph, int index )
916{
917 Q_ASSERT( paragraph >= 0 && paragraph < paragraphs() );
918 Q_ASSERT( index >= 0 && ( index == 0 || index < paragraphLength( paragraph ) ) );
919
920 const int startLine = lineOfChar( paragraph, index );
921 Q_ASSERT( startLine >= 0 && startLine < linesOfParagraph( paragraph ) );
922 for ( int curIndex = index; curIndex >= 0; curIndex-- ) {
923 const int line = lineOfChar( paragraph, curIndex );
924 if ( line != startLine ) {
925 return curIndex + 1;
926 }
927 }
928 return 0;
929}
930
931#include "kmedit.moc"
Mail folder.
Definition: kmfolder.h:69
const KMMsgBase * getMsgBase(int idx) const
Provides access to the basic message fields that are also stored in the index.
Definition: kmfolder.cpp:360
void getLocation(unsigned long key, KMFolder **retFolder, int *retIndex) const
Returns the folder the message represented by the serial number key is in and the index in that folde...
Definition: kmmsgdict.cpp:319
static const KMMsgDict * instance()
Access the globally unique MessageDict.
Definition: kmmsgdict.cpp:167
Reimplemented to make writePersonalDictionary() public, which we call everytime after adding a word t...
Definition: kmedit.h:30
Reimplemented to add support for ignored words.
Definition: kmedit.h:41
virtual bool isMisspelled(const TQString &word)
Reimplemented.
Definition: kmedit.cpp:383