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>
18 using 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 
58 void 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 
68 void 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 
78 void 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 
141 void 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 
211 KMEdit::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 
240 void KMEdit::createSpellers()
241 {
242  delete mSpeller;
243  mSpeller = new KMSpell( this, TQ_SLOT( spellerReady( KSpell * ) ), mSpellConfig );
244 }
245 
246 void 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( kapp->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 
278 TQPopupMenu *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 
291 void KMEdit::deleteAutoSpellChecking()
292 { // because the highlighter doesn't support RichText, delete its instance.
293  delete mHighlighter;
294  mHighlighter =0;
295 }
296 
297 void KMEdit::addSuggestion(const TQString& text, const TQStringList& lst, unsigned int )
298 {
299  mReplacements[text] = lst;
300 }
301 
302 void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
303 {
304  if ( mHighlighter ) {
305  mHighlighter->setActive(spellCheckingActive);
306  }
307 }
308 
309 
310 KMEdit::~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 
328 TQString 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 
353 unsigned 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 
363 KMSpell::KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig )
364  : KSpell( 0, TQString(), receiver, slot, spellConfig )
365 {
366 }
367 
368 KMSyntaxHighter::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 
383 bool 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 
393 void KMSyntaxHighter::ignoreWord( const TQString &word )
394 {
395  mIgnoredWords << word;
396 }
397 
398 TQStringList KMSyntaxHighter::ignoredWords() const
399 {
400  return mIgnoredWords;
401 }
402 
403 void KMEdit::spellerDied()
404 {
405  mSpeller = 0;
406 }
407 
408 void KMEdit::spellerReady( KSpell *spell )
409 {
410  Q_ASSERT( mSpeller == spell );
411 }
412 
413 bool 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 
581 int 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 
598 void 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 
612 void 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 
619 void KMEdit::killExternalEditor() {
620  delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
621  delete mExtEditorTempFile; mExtEditorTempFile = 0;
622  delete mExtEditorProcess; mExtEditorProcess = 0;
623 }
624 
625 
626 bool 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 
644 void 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 
678 void KMEdit::cut()
679 {
680  KEdit::cut();
681  if ( textFormat() != TQt::RichText && mHighlighter )
682  mHighlighter->restartBackgroundSpellCheck();
683 }
684 
685 void KMEdit::clear()
686 {
687  KEdit::clear();
688  if ( textFormat() != TQt::RichText && mHighlighter )
689  mHighlighter->restartBackgroundSpellCheck();
690 }
691 
692 void KMEdit::del()
693 {
694  KEdit::del();
695  if ( textFormat() != TQt::RichText && mHighlighter )
696  mHighlighter->restartBackgroundSpellCheck();
697 }
698 
699 void 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.
709 void 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 
718 void 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 
766 void 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 
775 void 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 
804 void 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 
840 void 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 
869 void 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 
905 void 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 
915 int 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