|
|
|
// -*- mode: C++; c-file-style: "gnu" -*-
|
|
|
|
// kmcomposewin.cpp
|
|
|
|
// Author: Markus Wuebben <markus.wuebben@kde.org>
|
|
|
|
// This code is published under the GPL.
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "kmedit.h"
|
|
|
|
#include "kmlineeditspell.h"
|
|
|
|
|
|
|
|
#define REALLY_WANT_KMCOMPOSEWIN_H
|
|
|
|
#include "kmcomposewin.h"
|
|
|
|
#undef REALLY_WANT_KMCOMPOSEWIN_H
|
|
|
|
#include "kmmsgdict.h"
|
|
|
|
#include "kmfolder.h"
|
|
|
|
#include "kmcommands.h"
|
|
|
|
|
|
|
|
#include <maillistdrag.h>
|
|
|
|
using KPIM::MailListDrag;
|
|
|
|
|
|
|
|
#include <libtdepim/kfileio.h>
|
|
|
|
#include <libemailfunctions/email.h>
|
|
|
|
|
|
|
|
#include <kcursor.h>
|
|
|
|
#include <kprocess.h>
|
|
|
|
|
|
|
|
#include <kpopupmenu.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <kmessagebox.h>
|
|
|
|
#include <kurldrag.h>
|
|
|
|
|
|
|
|
#include <ktempfile.h>
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kapplication.h>
|
|
|
|
#include <kdirwatch.h>
|
|
|
|
#include <kiconloader.h>
|
|
|
|
|
|
|
|
#include "globalsettings.h"
|
|
|
|
#include "replyphrases.h"
|
|
|
|
|
|
|
|
#include <kspell.h>
|
|
|
|
#include <kspelldlg.h>
|
|
|
|
#include <spellingfilter.h>
|
|
|
|
#include <ksyntaxhighlighter.h>
|
|
|
|
|
|
|
|
#include <tqregexp.h>
|
|
|
|
#include <tqbuffer.h>
|
|
|
|
#include <tqevent.h>
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
|
|
|
|
void KMEdit::contentsDragEnterEvent(TQDragEnterEvent *e)
|
|
|
|
{
|
|
|
|
if (e->provides(MailListDrag::format()))
|
|
|
|
e->accept(true);
|
|
|
|
else if (e->provides("image/png"))
|
|
|
|
e->accept();
|
|
|
|
else
|
|
|
|
return KEdit::contentsDragEnterEvent(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::contentsDragMoveEvent(TQDragMoveEvent *e)
|
|
|
|
{
|
|
|
|
if (e->provides(MailListDrag::format()))
|
|
|
|
e->accept();
|
|
|
|
else if (e->provides("image/png"))
|
|
|
|
e->accept();
|
|
|
|
else
|
|
|
|
return KEdit::contentsDragMoveEvent(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::keyPressEvent( TQKeyEvent* e )
|
|
|
|
{
|
|
|
|
if( e->key() == Key_Return ) {
|
|
|
|
int line, col;
|
|
|
|
getCursorPosition( &line, &col );
|
|
|
|
TQString lineText = text( line );
|
|
|
|
// returns line with additional trailing space (bug in TQt?), cut it off
|
|
|
|
lineText.truncate( lineText.length() - 1 );
|
|
|
|
// special treatment of quoted lines only if the cursor is neither at
|
|
|
|
// the begin nor at the end of the line
|
|
|
|
if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) {
|
|
|
|
bool isQuotedLine = false;
|
|
|
|
uint bot = 0; // bot = begin of text after quote indicators
|
|
|
|
while( bot < lineText.length() ) {
|
|
|
|
if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) {
|
|
|
|
isQuotedLine = true;
|
|
|
|
++bot;
|
|
|
|
}
|
|
|
|
else if( lineText[bot].isSpace() ) {
|
|
|
|
++bot;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
KEdit::keyPressEvent( e );
|
|
|
|
|
|
|
|
// duplicate quote indicators of the previous line before the new
|
|
|
|
// line if the line actually contained text (apart from the quote
|
|
|
|
// indicators) and the cursor is behind the quote indicators
|
|
|
|
if( isQuotedLine
|
|
|
|
&& ( bot != lineText.length() )
|
|
|
|
&& ( col >= int( bot ) ) ) {
|
|
|
|
|
|
|
|
// The cursor position might have changed unpredictably if there was selected
|
|
|
|
// text which got replaced by a new line, so we query it again:
|
|
|
|
getCursorPosition( &line, &col );
|
|
|
|
TQString newLine = text( line );
|
|
|
|
// remove leading white space from the new line and instead
|
|
|
|
// add the quote indicators of the previous line
|
|
|
|
unsigned int leadingWhiteSpaceCount = 0;
|
|
|
|
while( ( leadingWhiteSpaceCount < newLine.length() )
|
|
|
|
&& newLine[leadingWhiteSpaceCount].isSpace() ) {
|
|
|
|
++leadingWhiteSpaceCount;
|
|
|
|
}
|
|
|
|
newLine = newLine.replace( 0, leadingWhiteSpaceCount,
|
|
|
|
lineText.left( bot ) );
|
|
|
|
removeParagraph( line );
|
|
|
|
insertParagraph( newLine, line );
|
|
|
|
// place the cursor at the begin of the new line since
|
|
|
|
// we assume that the user split the quoted line in order
|
|
|
|
// to add a comment to the first part of the quoted line
|
|
|
|
setCursorPosition( line, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
KEdit::keyPressEvent( e );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
KEdit::keyPressEvent( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::contentsDropEvent(TQDropEvent *e)
|
|
|
|
{
|
|
|
|
if (e->provides(MailListDrag::format())) {
|
|
|
|
// Decode the list of serial numbers stored as the drag data
|
|
|
|
TQByteArray serNums;
|
|
|
|
MailListDrag::decode( e, serNums );
|
|
|
|
TQBuffer serNumBuffer(serNums);
|
|
|
|
serNumBuffer.open(IO_ReadOnly);
|
|
|
|
TQDataStream serNumStream(&serNumBuffer);
|
|
|
|
TQ_UINT32 serNum;
|
|
|
|
KMFolder *folder = 0;
|
|
|
|
int idx;
|
|
|
|
TQPtrList<KMMsgBase> messageList;
|
|
|
|
while (!serNumStream.atEnd()) {
|
|
|
|
KMMsgBase *msgBase = 0;
|
|
|
|
serNumStream >> serNum;
|
|
|
|
KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
|
|
|
|
if (folder)
|
|
|
|
msgBase = folder->getMsgBase(idx);
|
|
|
|
if (msgBase)
|
|
|
|
messageList.append( msgBase );
|
|
|
|
}
|
|
|
|
serNumBuffer.close();
|
|
|
|
uint identity = folder ? folder->identity() : 0;
|
|
|
|
KMCommand *command =
|
|
|
|
new KMForwardAttachedCommand(mComposer, messageList,
|
|
|
|
identity, mComposer);
|
|
|
|
command->start();
|
|
|
|
}
|
|
|
|
else if( e->provides("image/png") ) {
|
|
|
|
emit attachPNGImageData(e->encodedData("image/png"));
|
|
|
|
}
|
|
|
|
else if( KURLDrag::canDecode( e ) ) {
|
|
|
|
KURL::List urlList;
|
|
|
|
if( KURLDrag::decode( e, urlList ) ) {
|
|
|
|
KPopupMenu p;
|
|
|
|
p.insertItem( i18n("Add as Text"), 0 );
|
|
|
|
p.insertItem( i18n("Add as Attachment"), 1 );
|
|
|
|
int id = p.exec( mapToGlobal( e->pos() ) );
|
|
|
|
switch ( id) {
|
|
|
|
case 0:
|
|
|
|
for ( KURL::List::Iterator it = urlList.begin();
|
|
|
|
it != urlList.end(); ++it ) {
|
|
|
|
insert( (*it).url() );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
for ( KURL::List::Iterator it = urlList.begin();
|
|
|
|
it != urlList.end(); ++it ) {
|
|
|
|
mComposer->addAttach( *it );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if ( TQTextDrag::canDecode( e ) ) {
|
|
|
|
TQString s;
|
|
|
|
if ( TQTextDrag::decode( e, s ) )
|
|
|
|
insert( s );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
kdDebug(5006) << "KMEdit::contentsDropEvent, unable to add dropped object" << endl;
|
|
|
|
}
|
|
|
|
else if( e->provides("text/x-textsnippet") ) {
|
|
|
|
emit insertSnippet();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
KEdit::contentsDropEvent(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
KMEdit::KMEdit(TQWidget *parent, KMComposeWin* composer,
|
|
|
|
KSpellConfig* autoSpellConfig,
|
|
|
|
const char *name)
|
|
|
|
: KEdit( parent, name ),
|
|
|
|
mComposer( composer ),
|
|
|
|
mKSpellForDialog( 0 ),
|
|
|
|
mSpeller( 0 ),
|
|
|
|
mSpellConfig( autoSpellConfig ),
|
|
|
|
mSpellingFilter( 0 ),
|
|
|
|
mExtEditorTempFile( 0 ),
|
|
|
|
mExtEditorTempFileWatcher( 0 ),
|
|
|
|
mExtEditorProcess( 0 ),
|
|
|
|
mUseExtEditor( false ),
|
|
|
|
mWasModifiedBeforeSpellCheck( false ),
|
|
|
|
mHighlighter( 0 ),
|
|
|
|
mSpellLineEdit( false ),
|
|
|
|
mPasteMode( TQClipboard::Clipboard )
|
|
|
|
{
|
|
|
|
connect( this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotSelectionChanged()) );
|
|
|
|
installEventFilter(this);
|
|
|
|
KCursor::setAutoHideCursor( this, true, true );
|
|
|
|
setOverwriteEnabled( true );
|
|
|
|
createSpellers();
|
|
|
|
connect( mSpellConfig, TQT_SIGNAL( configChanged() ),
|
|
|
|
this, TQT_SLOT( createSpellers() ) );
|
|
|
|
connect( mSpeller, TQT_SIGNAL( death() ),
|
|
|
|
this, TQT_SLOT( spellerDied() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::createSpellers()
|
|
|
|
{
|
|
|
|
delete mSpeller;
|
|
|
|
mSpeller = new KMSpell( TQT_TQOBJECT(this), TQT_SLOT( spellerReady( KSpell * ) ), mSpellConfig );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::initializeAutoSpellChecking()
|
|
|
|
{
|
|
|
|
if ( mHighlighter )
|
|
|
|
return; // already initialized
|
|
|
|
TQColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp
|
|
|
|
TQColor defaultColor2( 0x00, 0x70, 0x00 );
|
|
|
|
TQColor defaultColor3( 0x00, 0x60, 0x00 );
|
|
|
|
TQColor defaultForeground( kapp->palette().active().text() );
|
|
|
|
|
|
|
|
TQColor c = TQt::red;
|
|
|
|
TDEConfigGroup readerConfig( KMKernel::config(), "Reader" );
|
|
|
|
TQColor col1;
|
|
|
|
if ( !readerConfig.readBoolEntry( "defaultColors", true ) )
|
|
|
|
col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground );
|
|
|
|
else
|
|
|
|
col1 = defaultForeground;
|
|
|
|
TQColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 );
|
|
|
|
TQColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 );
|
|
|
|
TQColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 );
|
|
|
|
TQColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c );
|
|
|
|
mHighlighter = new KMSyntaxHighter( this, /*active*/ true,
|
|
|
|
/*autoEnabled*/ false,
|
|
|
|
/*spellColor*/ misspelled,
|
|
|
|
/*colorQuoting*/ true,
|
|
|
|
col1, col2, col3, col4,
|
|
|
|
mSpellConfig );
|
|
|
|
|
|
|
|
connect( mHighlighter, TQT_SIGNAL(newSuggestions(const TQString&, const TQStringList&, unsigned int)),
|
|
|
|
this, TQT_SLOT(addSuggestion(const TQString&, const TQStringList&, unsigned int)) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TQPopupMenu *KMEdit::createPopupMenu( const TQPoint& pos )
|
|
|
|
{
|
|
|
|
enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll };
|
|
|
|
|
|
|
|
TQPopupMenu *menu = KEdit::createPopupMenu( pos );
|
|
|
|
if ( !TQApplication::clipboard()->image().isNull() ) {
|
|
|
|
int id = menu->idAt(0);
|
|
|
|
menu->setItemEnabled( id - IdPaste, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return menu;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::deleteAutoSpellChecking()
|
|
|
|
{ // because the highlighter doesn't support RichText, delete its instance.
|
|
|
|
delete mHighlighter;
|
|
|
|
mHighlighter =0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::addSuggestion(const TQString& text, const TQStringList& lst, unsigned int )
|
|
|
|
{
|
|
|
|
mReplacements[text] = lst;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
|
|
|
|
{
|
|
|
|
if ( mHighlighter ) {
|
|
|
|
mHighlighter->setActive(spellCheckingActive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KMEdit::~KMEdit()
|
|
|
|
{
|
|
|
|
removeEventFilter(this);
|
|
|
|
|
|
|
|
if ( mSpeller ) {
|
|
|
|
// The speller needs some time to clean up, so trigger cleanup and let it delete itself
|
|
|
|
mSpeller->setAutoDelete( true );
|
|
|
|
mSpeller->cleanUp();
|
|
|
|
mSpeller = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete mKSpellForDialog;
|
|
|
|
delete mHighlighter;
|
|
|
|
mHighlighter = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TQString KMEdit::brokenText()
|
|
|
|
{
|
|
|
|
TQString temp, line;
|
|
|
|
|
|
|
|
int num_lines = numLines();
|
|
|
|
for (int i = 0; i < num_lines; ++i)
|
|
|
|
{
|
|
|
|
int lastLine = 0;
|
|
|
|
line = textLine(i);
|
|
|
|
for (int j = 0; j < (int)line.length(); ++j)
|
|
|
|
{
|
|
|
|
if (lineOfChar(i, j) > lastLine)
|
|
|
|
{
|
|
|
|
lastLine = lineOfChar(i, j);
|
|
|
|
temp += '\n';
|
|
|
|
}
|
|
|
|
temp += line[j];
|
|
|
|
}
|
|
|
|
if (i + 1 < num_lines) temp += '\n';
|
|
|
|
}
|
|
|
|
|
|
|
|
return temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
unsigned int KMEdit::lineBreakColumn() const
|
|
|
|
{
|
|
|
|
unsigned int lineBreakColumn = 0;
|
|
|
|
unsigned int numlines = numLines();
|
|
|
|
while ( numlines-- ) {
|
|
|
|
lineBreakColumn = TQMAX( lineBreakColumn, textLine( numlines ).length() );
|
|
|
|
}
|
|
|
|
return lineBreakColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
KMSpell::KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig )
|
|
|
|
: KSpell( 0, TQString(), receiver, slot, spellConfig )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
KMSyntaxHighter::KMSyntaxHighter( TQTextEdit *textEdit,
|
|
|
|
bool spellCheckingActive,
|
|
|
|
bool autoEnable,
|
|
|
|
const TQColor& spellColor,
|
|
|
|
bool colorQuoting,
|
|
|
|
const TQColor& QuoteColor0,
|
|
|
|
const TQColor& QuoteColor1,
|
|
|
|
const TQColor& QuoteColor2,
|
|
|
|
const TQColor& QuoteColor3,
|
|
|
|
KSpellConfig *spellConfig )
|
|
|
|
: KDictSpellingHighlighter( textEdit, spellCheckingActive, autoEnable, spellColor, colorQuoting,
|
|
|
|
QuoteColor0, QuoteColor1, QuoteColor2, QuoteColor3, spellConfig )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KMSyntaxHighter::isMisspelled( const TQString &word )
|
|
|
|
{
|
|
|
|
if ( mIgnoredWords.contains( word ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return KDictSpellingHighlighter::isMisspelled( word );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMSyntaxHighter::ignoreWord( const TQString &word )
|
|
|
|
{
|
|
|
|
mIgnoredWords << word;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQStringList KMSyntaxHighter::ignoredWords() const
|
|
|
|
{
|
|
|
|
return mIgnoredWords;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::spellerDied()
|
|
|
|
{
|
|
|
|
mSpeller = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::spellerReady( KSpell *spell )
|
|
|
|
{
|
|
|
|
Q_ASSERT( mSpeller == spell );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool KMEdit::eventFilter(TQObject*o, TQEvent* e)
|
|
|
|
{
|
|
|
|
if (TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(this))
|
|
|
|
KCursor::autoHideEventFilter(o, e);
|
|
|
|
|
|
|
|
if (e->type() == TQEvent::KeyPress)
|
|
|
|
{
|
|
|
|
TQKeyEvent *k = (TQKeyEvent*)e;
|
|
|
|
|
|
|
|
if (mUseExtEditor) {
|
|
|
|
if (k->key() == Key_Up)
|
|
|
|
{
|
|
|
|
emit focusUp();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore modifier keys (cf. bug 48841)
|
|
|
|
if ( (k->key() == Key_Shift) || (k->key() == Key_Control) ||
|
|
|
|
(k->key() == Key_Meta) || (k->key() == Key_Alt) )
|
|
|
|
return true;
|
|
|
|
if (mExtEditorTempFile) return true;
|
|
|
|
TQString sysLine = mExtEditor;
|
|
|
|
mExtEditorTempFile = new KTempFile();
|
|
|
|
|
|
|
|
mExtEditorTempFile->setAutoDelete(true);
|
|
|
|
|
|
|
|
(*mExtEditorTempFile->textStream()) << text();
|
|
|
|
|
|
|
|
mExtEditorTempFile->close();
|
|
|
|
// replace %f in the system line
|
|
|
|
sysLine.replace( "%f", mExtEditorTempFile->name() );
|
|
|
|
mExtEditorProcess = new TDEProcess();
|
|
|
|
mExtEditorProcess->setUseShell( true );
|
|
|
|
sysLine += " ";
|
|
|
|
while (!sysLine.isEmpty())
|
|
|
|
{
|
|
|
|
*mExtEditorProcess << sysLine.left(sysLine.find(" ")).local8Bit();
|
|
|
|
sysLine.remove(0, sysLine.find(" ") + 1);
|
|
|
|
}
|
|
|
|
connect(mExtEditorProcess, TQT_SIGNAL(processExited(TDEProcess*)),
|
|
|
|
TQT_SLOT(slotExternalEditorDone(TDEProcess*)));
|
|
|
|
if (!mExtEditorProcess->start())
|
|
|
|
{
|
|
|
|
KMessageBox::error( topLevelWidget(),
|
|
|
|
i18n("Unable to start external editor.") );
|
|
|
|
killExternalEditor();
|
|
|
|
} else {
|
|
|
|
mExtEditorTempFileWatcher = new KDirWatch( TQT_TQOBJECT(this), "mExtEditorTempFileWatcher" );
|
|
|
|
connect( mExtEditorTempFileWatcher, TQT_SIGNAL(dirty(const TQString&)),
|
|
|
|
TQT_SLOT(slotExternalEditorTempFileChanged(const TQString&)) );
|
|
|
|
mExtEditorTempFileWatcher->addFile( mExtEditorTempFile->name() );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// ---sven's Arrow key navigation start ---
|
|
|
|
// Key Up in first line takes you to Subject line.
|
|
|
|
if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0
|
|
|
|
&& lineOfChar(0, currentColumn()) == 0)
|
|
|
|
{
|
|
|
|
deselect();
|
|
|
|
emit focusUp();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// ---sven's Arrow key navigation end ---
|
|
|
|
|
|
|
|
if (k->key() == Key_Backtab && k->state() == ShiftButton)
|
|
|
|
{
|
|
|
|
deselect();
|
|
|
|
emit focusUp();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
} else if ( e->type() == TQEvent::ContextMenu ) {
|
|
|
|
TQContextMenuEvent *event = (TQContextMenuEvent*) e;
|
|
|
|
|
|
|
|
int para = 1, charPos, firstSpace, lastSpace;
|
|
|
|
|
|
|
|
//Get the character at the position of the click
|
|
|
|
charPos = charAt( viewportToContents(event->pos()), ¶ );
|
|
|
|
TQString paraText = text( para );
|
|
|
|
|
|
|
|
if( !paraText.at(charPos).isSpace() )
|
|
|
|
{
|
|
|
|
//Get word right clicked on
|
|
|
|
const TQRegExp wordBoundary( "[\\s\\W]" );
|
|
|
|
firstSpace = paraText.findRev( wordBoundary, charPos ) + 1;
|
|
|
|
lastSpace = paraText.find( wordBoundary, charPos );
|
|
|
|
if( lastSpace == -1 )
|
|
|
|
lastSpace = paraText.length();
|
|
|
|
TQString word = paraText.mid( firstSpace, lastSpace - firstSpace );
|
|
|
|
//Continue if this word was misspelled
|
|
|
|
if( !word.isEmpty() && mReplacements.contains( word ) )
|
|
|
|
{
|
|
|
|
KPopupMenu p;
|
|
|
|
|
|
|
|
//Add the suggestions to the popup menu
|
|
|
|
TQStringList reps = mReplacements[word];
|
|
|
|
if( reps.count() > 0 )
|
|
|
|
{
|
|
|
|
int listPos = 0;
|
|
|
|
for ( TQStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) {
|
|
|
|
p.insertItem( *it, listPos );
|
|
|
|
listPos++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
p.setItemEnabled( p.insertItem( i18n( "No Suggestions" ), -2 ), false );
|
|
|
|
}
|
|
|
|
|
|
|
|
int addToDictionaryId = -42;
|
|
|
|
int ignoreId = -43;
|
|
|
|
if ( mSpeller && mSpeller->status() == KSpell::Running ) {
|
|
|
|
p.insertSeparator();
|
|
|
|
addToDictionaryId = p.insertItem( i18n( "Add to Dictionary" ) );
|
|
|
|
ignoreId = p.insertItem( i18n( "Ignore All" ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
//Execute the popup inline
|
|
|
|
const int id = p.exec( mapToGlobal( event->pos() ) );
|
|
|
|
|
|
|
|
if ( id == ignoreId ) {
|
|
|
|
mHighlighter->ignoreWord( word );
|
|
|
|
mHighlighter->rehighlight();
|
|
|
|
}
|
|
|
|
if ( id == addToDictionaryId ) {
|
|
|
|
mSpeller->addPersonal( word );
|
|
|
|
mSpeller->writePersonalDictionary();
|
|
|
|
if ( mHighlighter ) {
|
|
|
|
// Wait a bit until reloading the highlighter, the mSpeller first needs to finish saving
|
|
|
|
// the personal word list.
|
|
|
|
TQTimer::singleShot( 200, mHighlighter, TQT_SLOT( slotLocalSpellConfigChanged() ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( id > -1 )
|
|
|
|
{
|
|
|
|
//Save the cursor position
|
|
|
|
int parIdx = 1, txtIdx = 1;
|
|
|
|
getCursorPosition(&parIdx, &txtIdx);
|
|
|
|
setSelection(para, firstSpace, para, lastSpace);
|
|
|
|
insert(mReplacements[word][id]);
|
|
|
|
// Restore the cursor position; if the cursor was behind the
|
|
|
|
// misspelled word then adjust the cursor position
|
|
|
|
if ( para == parIdx && txtIdx >= lastSpace )
|
|
|
|
txtIdx += mReplacements[word][id].length() - word.length();
|
|
|
|
setCursorPosition(parIdx, txtIdx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( id == addToDictionaryId || id == ignoreId ) {
|
|
|
|
// No longer misspelled: Either added to dictionary or ignored
|
|
|
|
mReplacements.remove( word );
|
|
|
|
}
|
|
|
|
|
|
|
|
//Cancel original event
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if ( e->type() == TQEvent::FocusIn || e->type() == TQEvent::FocusOut ) {
|
|
|
|
TQFocusEvent *fe = TQT_TQFOCUSEVENT(e);
|
|
|
|
if(! (fe->reason() == TQFocusEvent::ActiveWindow || fe->reason() == TQFocusEvent::Popup) )
|
|
|
|
emit focusChanged( fe->gotFocus() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return KEdit::eventFilter(o, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int KMEdit::autoSpellChecking( bool on )
|
|
|
|
{
|
|
|
|
if ( textFormat() == TQt::RichText ) {
|
|
|
|
// syntax highlighter doesn't support extended text properties
|
|
|
|
if ( on )
|
|
|
|
KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup."));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if ( mHighlighter ) {
|
|
|
|
// don't autoEnable spell checking if the user turned spell checking off
|
|
|
|
mHighlighter->setAutomatic( on );
|
|
|
|
mHighlighter->setActive( on );
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void KMEdit::slotExternalEditorTempFileChanged( const TQString & fileName ) {
|
|
|
|
if ( !mExtEditorTempFile )
|
|
|
|
return;
|
|
|
|
if ( fileName != mExtEditorTempFile->name() )
|
|
|
|
return;
|
|
|
|
// read data back in from file
|
|
|
|
setAutoUpdate(false);
|
|
|
|
clear();
|
|
|
|
|
|
|
|
insertLine(TQString::fromLocal8Bit(KPIM::kFileToString( fileName, true, false )), -1);
|
|
|
|
setAutoUpdate(true);
|
|
|
|
repaint();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotExternalEditorDone( TDEProcess * proc ) {
|
|
|
|
assert(proc == mExtEditorProcess);
|
|
|
|
// make sure, we update even when KDirWatcher is too slow:
|
|
|
|
slotExternalEditorTempFileChanged( mExtEditorTempFile->name() );
|
|
|
|
killExternalEditor();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::killExternalEditor() {
|
|
|
|
delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
|
|
|
|
delete mExtEditorTempFile; mExtEditorTempFile = 0;
|
|
|
|
delete mExtEditorProcess; mExtEditorProcess = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool KMEdit::checkExternalEditorFinished() {
|
|
|
|
if ( !mExtEditorProcess )
|
|
|
|
return true;
|
|
|
|
switch ( KMessageBox::warningYesNoCancel( topLevelWidget(),
|
|
|
|
i18n("The external editor is still running.\n"
|
|
|
|
"Abort the external editor or leave it open?"),
|
|
|
|
i18n("External Editor"),
|
|
|
|
i18n("Abort Editor"), i18n("Leave Editor Open") ) ) {
|
|
|
|
case KMessageBox::Yes:
|
|
|
|
killExternalEditor();
|
|
|
|
return true;
|
|
|
|
case KMessageBox::No:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::spellcheck()
|
|
|
|
{
|
|
|
|
if ( mKSpellForDialog )
|
|
|
|
return;
|
|
|
|
mWasModifiedBeforeSpellCheck = isModified();
|
|
|
|
mSpellLineEdit = !mSpellLineEdit;
|
|
|
|
// maybe for later, for now plaintext is given to KSpell
|
|
|
|
// if (textFormat() == TQt::RichText ) {
|
|
|
|
// kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl;
|
|
|
|
// mKSpellForDialog = new KSpell(this, i18n("Spellcheck - KMail"), this,
|
|
|
|
// TQT_SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML);
|
|
|
|
// }
|
|
|
|
// else {
|
|
|
|
|
|
|
|
// Don't use mSpellConfig here. Reason is that the spell dialog, KSpellDlg, uses its own
|
|
|
|
// spell config, and therefore the two wouldn't be in sync.
|
|
|
|
mKSpellForDialog = new KSpell( TQT_TQWIDGET(this), i18n("Spellcheck - KMail"), TQT_TQOBJECT(this),
|
|
|
|
TQT_SLOT(slotSpellcheck2(KSpell*))/*, mSpellConfig*/ );
|
|
|
|
// }
|
|
|
|
|
|
|
|
TQStringList l = KSpellingHighlighter::personalWords();
|
|
|
|
for ( TQStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
|
|
|
|
mKSpellForDialog->addPersonal( *it );
|
|
|
|
}
|
|
|
|
connect (mKSpellForDialog, TQT_SIGNAL( death()),
|
|
|
|
this, TQT_SLOT (slotSpellDone()));
|
|
|
|
connect (mKSpellForDialog, TQT_SIGNAL (misspelling (const TQString &, const TQStringList &, unsigned int)),
|
|
|
|
this, TQT_SLOT (slotMisspelling (const TQString &, const TQStringList &, unsigned int)));
|
|
|
|
connect (mKSpellForDialog, TQT_SIGNAL (corrected (const TQString &, const TQString &, unsigned int)),
|
|
|
|
this, TQT_SLOT (slotCorrected (const TQString &, const TQString &, unsigned int)));
|
|
|
|
connect (mKSpellForDialog, TQT_SIGNAL (done(const TQString &)),
|
|
|
|
this, TQT_SLOT (slotSpellResult (const TQString&)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::cut()
|
|
|
|
{
|
|
|
|
KEdit::cut();
|
|
|
|
if ( textFormat() != TQt::RichText && mHighlighter )
|
|
|
|
mHighlighter->restartBackgroundSpellCheck();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::clear()
|
|
|
|
{
|
|
|
|
KEdit::clear();
|
|
|
|
if ( textFormat() != TQt::RichText && mHighlighter )
|
|
|
|
mHighlighter->restartBackgroundSpellCheck();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::del()
|
|
|
|
{
|
|
|
|
KEdit::del();
|
|
|
|
if ( textFormat() != TQt::RichText && mHighlighter )
|
|
|
|
mHighlighter->restartBackgroundSpellCheck();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::paste()
|
|
|
|
{
|
|
|
|
mComposer->paste( mPasteMode );
|
|
|
|
}
|
|
|
|
|
|
|
|
// KMEdit indirectly inherits from TQTextEdit, which has virtual paste() method,
|
|
|
|
// but it controls whether it pastes clipboard or selection by an internal
|
|
|
|
// flag that is not accessible in any way, so paste() being virtual is actually
|
|
|
|
// useless, because reimplementations can't known where to paste from anyway.
|
|
|
|
// Roll our own internal flag.
|
|
|
|
void KMEdit::contentsMouseReleaseEvent( TQMouseEvent * e )
|
|
|
|
{
|
|
|
|
if( e->button() != Qt::MidButton )
|
|
|
|
return KEdit::contentsMouseReleaseEvent( e );
|
|
|
|
mPasteMode = TQClipboard::Selection;
|
|
|
|
KEdit::contentsMouseReleaseEvent( e );
|
|
|
|
mPasteMode = TQClipboard::Clipboard;
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::contentsMouseDoubleClickEvent( TQMouseEvent *e )
|
|
|
|
{
|
|
|
|
bool handled = false;
|
|
|
|
if ( e->button() == Qt::LeftButton ) {
|
|
|
|
|
|
|
|
// Get the cursor position for the place where the user clicked to
|
|
|
|
int paragraphPos;
|
|
|
|
int charPos = charAt ( e->pos(), ¶graphPos );
|
|
|
|
TQString paraText = text( paragraphPos );
|
|
|
|
|
|
|
|
// Now select the word under the cursor
|
|
|
|
if ( charPos >= 0 && static_cast<unsigned int>( charPos ) <= paraText.length() ) {
|
|
|
|
|
|
|
|
// Start the selection where the user clicked
|
|
|
|
int start = charPos;
|
|
|
|
unsigned int end = charPos;
|
|
|
|
|
|
|
|
// Extend the selection to the left, until we reach a non-letter and non-digit char
|
|
|
|
for (;;) {
|
|
|
|
if ( ( start - 1 ) < 0 )
|
|
|
|
break;
|
|
|
|
TQChar charToTheLeft = paraText.at( start - 1 );
|
|
|
|
if ( charToTheLeft.isLetter() || charToTheLeft.isDigit() )
|
|
|
|
start--;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extend the selection to the left, until we reach a non-letter and non-digit char
|
|
|
|
for (;;) {
|
|
|
|
if ( ( end + 1 ) >= paraText.length() )
|
|
|
|
break;
|
|
|
|
TQChar charToTheRight = paraText.at( end + 1 );
|
|
|
|
if ( charToTheRight.isLetter() || charToTheRight.isDigit() )
|
|
|
|
end++;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
setSelection( paragraphPos, start, paragraphPos, end + 1 );
|
|
|
|
handled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !handled )
|
|
|
|
return KEdit::contentsMouseDoubleClickEvent( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos)
|
|
|
|
{
|
|
|
|
kdDebug(5006)<<"void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos) : "<<text <<endl;
|
|
|
|
if( mSpellLineEdit )
|
|
|
|
mComposer->sujectLineWidget()->spellCheckerMisspelling( text, lst, pos);
|
|
|
|
else
|
|
|
|
misspelling(text, lst, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotCorrected (const TQString &oldWord, const TQString &newWord, unsigned int pos)
|
|
|
|
{
|
|
|
|
kdDebug(5006)<<"slotCorrected (const TQString &oldWord, const TQString &newWord, unsigned int pos) : "<<oldWord<<endl;
|
|
|
|
if( mSpellLineEdit )
|
|
|
|
mComposer->sujectLineWidget()->spellCheckerCorrected( oldWord, newWord, pos);
|
|
|
|
else {
|
|
|
|
unsigned int l = 0;
|
|
|
|
unsigned int cnt = 0;
|
|
|
|
bool _bold,_underline,_italic;
|
|
|
|
TQColor _color;
|
|
|
|
TQFont _font;
|
|
|
|
posToRowCol (pos, l, cnt);
|
|
|
|
setCursorPosition(l, cnt+1); // the new word will get the same markup now as the first character of the word
|
|
|
|
_bold = bold();
|
|
|
|
_underline = underline();
|
|
|
|
_italic = italic();
|
|
|
|
_color = color();
|
|
|
|
_font = currentFont();
|
|
|
|
corrected(oldWord, newWord, pos);
|
|
|
|
setSelection (l, cnt, l, cnt+newWord.length());
|
|
|
|
setBold(_bold);
|
|
|
|
setItalic(_italic);
|
|
|
|
setUnderline(_underline);
|
|
|
|
setColor(_color);
|
|
|
|
setCurrentFont(_font);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotSpellcheck2(KSpell*)
|
|
|
|
{
|
|
|
|
// Make sure words ignored by the highlighter are ignored by KSpell as well
|
|
|
|
if ( mHighlighter ) {
|
|
|
|
for ( uint i = 0; i < mHighlighter->ignoredWords().size(); i++ )
|
|
|
|
mKSpellForDialog->ignore( mHighlighter->ignoredWords()[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !mSpellLineEdit)
|
|
|
|
{
|
|
|
|
spellcheck_start();
|
|
|
|
|
|
|
|
TQString quotePrefix;
|
|
|
|
if(mComposer && mComposer->msg())
|
|
|
|
{
|
|
|
|
int languageNr = GlobalSettings::self()->replyCurrentLanguage();
|
|
|
|
ReplyPhrases replyPhrases( TQString::number(languageNr) );
|
|
|
|
replyPhrases.readConfig();
|
|
|
|
|
|
|
|
quotePrefix = mComposer->msg()->formatString(
|
|
|
|
replyPhrases.indentPrefix() );
|
|
|
|
}
|
|
|
|
|
|
|
|
kdDebug(5006) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl;
|
|
|
|
TQTextEdit plaintext;
|
|
|
|
plaintext.setText(text());
|
|
|
|
plaintext.setTextFormat(TQt::PlainText);
|
|
|
|
mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls,
|
|
|
|
SpellingFilter::FilterEmailAddresses);
|
|
|
|
|
|
|
|
mKSpellForDialog->check(mSpellingFilter->filteredText());
|
|
|
|
}
|
|
|
|
else if( mComposer )
|
|
|
|
mKSpellForDialog->check( mComposer->sujectLineWidget()->text());
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotSpellResult(const TQString &s)
|
|
|
|
{
|
|
|
|
if( !mSpellLineEdit)
|
|
|
|
spellcheck_stop();
|
|
|
|
|
|
|
|
int dlgResult = mKSpellForDialog->dlgResult();
|
|
|
|
if ( dlgResult == KS_CANCEL )
|
|
|
|
{
|
|
|
|
if( mSpellLineEdit)
|
|
|
|
{
|
|
|
|
//stop spell check
|
|
|
|
mSpellLineEdit = false;
|
|
|
|
TQString tmpText( s );
|
|
|
|
tmpText = tmpText.remove('\n');
|
|
|
|
|
|
|
|
if( tmpText != mComposer->sujectLineWidget()->text() )
|
|
|
|
mComposer->sujectLineWidget()->setText( tmpText );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
setModified(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mKSpellForDialog->cleanUp();
|
|
|
|
KDictSpellingHighlighter::dictionaryChanged();
|
|
|
|
|
|
|
|
emit spellcheck_done( dlgResult );
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::slotSpellDone()
|
|
|
|
{
|
|
|
|
kdDebug(5006)<<" void KMEdit::slotSpellDone()\n";
|
|
|
|
KSpell::spellStatus status = mKSpellForDialog->status();
|
|
|
|
delete mKSpellForDialog;
|
|
|
|
mKSpellForDialog = 0;
|
|
|
|
|
|
|
|
kdDebug(5006) << "spelling: delete SpellingFilter" << endl;
|
|
|
|
delete mSpellingFilter;
|
|
|
|
mSpellingFilter = 0;
|
|
|
|
mComposer->sujectLineWidget()->deselect();
|
|
|
|
if (status == KSpell::Error)
|
|
|
|
{
|
|
|
|
KMessageBox::sorry( topLevelWidget(),
|
|
|
|
i18n("ISpell/Aspell could not be started. Please "
|
|
|
|
"make sure you have ISpell or Aspell properly "
|
|
|
|
"configured and in your PATH.") );
|
|
|
|
emit spellcheck_done( KS_CANCEL );
|
|
|
|
}
|
|
|
|
else if (status == KSpell::Crashed)
|
|
|
|
{
|
|
|
|
spellcheck_stop();
|
|
|
|
KMessageBox::sorry( topLevelWidget(),
|
|
|
|
i18n("ISpell/Aspell seems to have crashed.") );
|
|
|
|
emit spellcheck_done( KS_CANCEL );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( mSpellLineEdit )
|
|
|
|
spellcheck();
|
|
|
|
else if( !mComposer->subjectTextWasSpellChecked() && status == KSpell::FinishedNoMisspellingsEncountered )
|
|
|
|
KMessageBox::information( topLevelWidget(),
|
|
|
|
i18n("No misspellings encountered.") );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KMEdit::setCursorPositionFromStart( unsigned int pos ) {
|
|
|
|
unsigned int l = 0;
|
|
|
|
unsigned int c = 0;
|
|
|
|
posToRowCol( pos, l, c );
|
|
|
|
// kdDebug() << "Num lines: " << numLines() << endl;
|
|
|
|
// kdDebug() << "Position " << pos << " converted to " << l << ":" << c << endl;
|
|
|
|
setCursorPosition( l, c );
|
|
|
|
ensureCursorVisible();
|
|
|
|
}
|
|
|
|
|
|
|
|
int KMEdit::indexOfCurrentLineStart( int paragraph, int index )
|
|
|
|
{
|
|
|
|
Q_ASSERT( paragraph >= 0 && paragraph < paragraphs() );
|
|
|
|
Q_ASSERT( index >= 0 && ( index == 0 || index < paragraphLength( paragraph ) ) );
|
|
|
|
|
|
|
|
const int startLine = lineOfChar( paragraph, index );
|
|
|
|
Q_ASSERT( startLine >= 0 && startLine < linesOfParagraph( paragraph ) );
|
|
|
|
for ( int curIndex = index; curIndex >= 0; curIndex-- ) {
|
|
|
|
const int line = lineOfChar( paragraph, curIndex );
|
|
|
|
if ( line != startLine ) {
|
|
|
|
return curIndex + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "kmedit.moc"
|