kmail

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