Kate
spellcheckdialog.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2009-2010 by Michel Ludwig <michel.ludwig@kdemail.net> 00004 * Copyright (C) 2008 Mirko Stocker <me@misto.ch> 00005 * Copyright (C) 2004-2005 Anders Lund <anders@alweb.dk> 00006 * Copyright (C) 2002 John Firebaugh <jfirebaugh@kde.org> 00007 * Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> 00008 * Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> 00009 * Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 00010 * 00011 * This library is free software; you can redistribute it and/or 00012 * modify it under the terms of the GNU Library General Public 00013 * License as published by the Free Software Foundation; either 00014 * version 2 of the License, or (at your option) any later version. 00015 * 00016 * This library is distributed in the hope that it will be useful, 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00019 * Library General Public License for more details. 00020 * 00021 * You should have received a copy of the GNU Library General Public License 00022 * along with this library; see the file COPYING.LIB. If not, write to 00023 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00024 * Boston, MA 02110-1301, USA. 00025 */ 00026 00027 #include "spellcheckdialog.h" 00028 #include "spellcheckdialog.moc" 00029 00030 #include "katedocument.h" 00031 #include "kateglobal.h" 00032 #include "kateview.h" 00033 #include "spellcheck/spellcheck.h" 00034 00035 #include <kaction.h> 00036 #include <kactioncollection.h> 00037 #include <kicon.h> 00038 #include <kstandardaction.h> 00039 #include <sonnet/dialog.h> 00040 #include <sonnet/backgroundchecker.h> 00041 #include <sonnet/speller.h> 00042 00043 KateSpellCheckDialog::KateSpellCheckDialog( KateView* view ) 00044 : QObject( view ) 00045 , m_view (view) 00046 , m_speller (NULL) 00047 , m_backgroundChecker(NULL) 00048 , m_sonnetDialog(NULL) 00049 , m_globalSpellCheckRange(NULL) 00050 , m_spellCheckCancelledByUser(false) 00051 { 00052 } 00053 00054 KateSpellCheckDialog::~KateSpellCheckDialog() 00055 { 00056 delete m_globalSpellCheckRange; 00057 delete m_sonnetDialog; 00058 delete m_backgroundChecker; 00059 delete m_speller; 00060 } 00061 00062 void KateSpellCheckDialog::createActions( KActionCollection* ac ) 00063 { 00064 ac->addAction( KStandardAction::Spelling, this, SLOT(spellcheck()) ); 00065 00066 KAction *a = new KAction( i18n("Spelling (from cursor)..."), this); 00067 ac->addAction("tools_spelling_from_cursor", a ); 00068 a->setIcon( KIcon( "tools-check-spelling" ) ); 00069 a->setWhatsThis(i18n("Check the document's spelling from the cursor and forward")); 00070 connect( a, SIGNAL( triggered() ), this, SLOT(spellcheckFromCursor()) ); 00071 00072 m_spellcheckSelection = new KAction( i18n("Spellcheck Selection..."), this ); 00073 ac->addAction("tools_spelling_selection", m_spellcheckSelection); 00074 m_spellcheckSelection->setIcon( KIcon( "tools-check-spelling" ) ); 00075 m_spellcheckSelection->setWhatsThis(i18n("Check spelling of the selected text")); 00076 connect( m_spellcheckSelection, SIGNAL( triggered() ), this, SLOT(spellcheckSelection()) ); 00077 } 00078 00079 void KateSpellCheckDialog::updateActions () 00080 { 00081 m_spellcheckSelection->setEnabled (m_view->selection ()); 00082 } 00083 00084 void KateSpellCheckDialog::spellcheckFromCursor() 00085 { 00086 spellcheck( m_view->cursorPosition() ); 00087 } 00088 00089 void KateSpellCheckDialog::spellcheckSelection() 00090 { 00091 spellcheck( m_view->selectionRange().start(), m_view->selectionRange().end() ); 00092 } 00093 00094 void KateSpellCheckDialog::spellcheck() 00095 { 00096 spellcheck( KTextEditor::Cursor( 0, 0 ) ); 00097 } 00098 00099 void KateSpellCheckDialog::spellcheck( const KTextEditor::Cursor &from, const KTextEditor::Cursor &to ) 00100 { 00101 KTextEditor::Cursor start = from; 00102 KTextEditor::Cursor end = to; 00103 00104 if ( end.line() == 0 && end.column() == 0 ) 00105 { 00106 end = m_view->doc()->documentEnd(); 00107 } 00108 00109 if ( !m_speller ) 00110 { 00111 m_speller = new Sonnet::Speller(); 00112 } 00113 m_speller->restore(KGlobal::config().data()); 00114 00115 if ( !m_backgroundChecker ) 00116 { 00117 m_backgroundChecker = new Sonnet::BackgroundChecker(*m_speller); 00118 } 00119 00120 #if KDE_IS_VERSION(4,5,2) 00121 // guard necessary to ensure compilation of KatePart's git repository against <= 4.5.1 00122 m_backgroundChecker->restore(KGlobal::config().data()); 00123 #endif 00124 00125 if ( !m_sonnetDialog ) 00126 { 00127 m_sonnetDialog = new Sonnet::Dialog(m_backgroundChecker, m_view); 00128 m_sonnetDialog->showProgressDialog(200); 00129 m_sonnetDialog->showSpellCheckCompletionMessage(); 00130 m_sonnetDialog->setSpellCheckContinuedAfterReplacement(false); 00131 00132 connect(m_sonnetDialog,SIGNAL(done(const QString&)),this,SLOT(installNextSpellCheckRange())); 00133 00134 connect(m_sonnetDialog,SIGNAL(replace(const QString&,int,const QString&)), 00135 this,SLOT(corrected(const QString&,int,const QString&))); 00136 00137 connect(m_sonnetDialog,SIGNAL(misspelling(const QString&,int)), 00138 this,SLOT(misspelling(const QString&,int))); 00139 00140 connect(m_sonnetDialog,SIGNAL(cancel()), 00141 this,SLOT(cancelClicked())); 00142 00143 connect(m_sonnetDialog,SIGNAL(destroyed(QObject*)), 00144 this,SLOT(objectDestroyed(QObject*))); 00145 } 00146 00147 delete m_globalSpellCheckRange; 00148 // we expand to handle the situation when the last word in the range is replace by a new one 00149 m_globalSpellCheckRange = m_view->doc()->newMovingRange (KTextEditor::Range( start, end ), 00150 KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); 00151 m_spellCheckCancelledByUser = false; 00152 performSpellCheck( *m_globalSpellCheckRange ); 00153 } 00154 00155 KTextEditor::Cursor KateSpellCheckDialog::locatePosition( int pos ) 00156 { 00157 uint remains; 00158 00159 while ( m_spellLastPos < (uint)pos ) 00160 { 00161 remains = pos - m_spellLastPos; 00162 uint l = m_view->doc()->lineLength( m_spellPosCursor.line() ) - m_spellPosCursor.column(); 00163 if ( l > remains ) 00164 { 00165 m_spellPosCursor.setColumn( m_spellPosCursor.column() + remains ); 00166 m_spellLastPos = pos; 00167 } 00168 else 00169 { 00170 m_spellPosCursor.setLine( m_spellPosCursor.line() + 1 ); 00171 m_spellPosCursor.setColumn(0); 00172 m_spellLastPos += l + 1; 00173 } 00174 } 00175 00176 return m_spellPosCursor; 00177 } 00178 00179 void KateSpellCheckDialog::misspelling( const QString& word, int pos ) 00180 { 00181 KTextEditor::Cursor cursor; 00182 int length; 00183 int origPos = m_view->doc()->computePositionWrtOffsets( m_currentDecToEncOffsetList, pos ); 00184 cursor = locatePosition( origPos ); 00185 length = m_view->doc()->computePositionWrtOffsets( m_currentDecToEncOffsetList, pos + word.length() ) 00186 - origPos; 00187 00188 m_view->setCursorPositionInternal (cursor, 1); 00189 m_view->setSelection( KTextEditor::Range(cursor, length) ); 00190 } 00191 00192 void KateSpellCheckDialog::corrected( const QString& word, int pos, const QString& newWord ) 00193 { 00194 int origPos = m_view->doc()->computePositionWrtOffsets( m_currentDecToEncOffsetList, pos ); 00195 00196 int length = m_view->doc()->computePositionWrtOffsets( m_currentDecToEncOffsetList, pos + word.length() ) 00197 - origPos; 00198 00199 KTextEditor::Cursor replacementStartCursor = locatePosition( origPos ); 00200 KTextEditor::Range replacementRange = KTextEditor::Range( replacementStartCursor, length ); 00201 KateDocument *doc = m_view->doc(); 00202 KateGlobal::self()->spellCheckManager()->replaceCharactersEncodedIfNecessary( newWord, doc, replacementRange ); 00203 00204 m_currentSpellCheckRange.setRange( KTextEditor::Range( replacementStartCursor, m_currentSpellCheckRange.end() ) ); 00205 // we have to be careful here: due to static word wrapping the text might change in addition to simply 00206 // the misspelled word being replaced, i.e. new line breaks might be inserted as well. As such, the text 00207 // in the 'Sonnet::Dialog' might be eventually out of sync with the visible text. Therefore, we 'restart' 00208 // spell checking from the current position. 00209 performSpellCheck( KTextEditor::Range( replacementStartCursor, m_globalSpellCheckRange->end() ) ); 00210 } 00211 00212 void KateSpellCheckDialog::performSpellCheck(const KTextEditor::Range& range) 00213 { 00214 if(range.isEmpty()) { 00215 spellCheckDone(); 00216 } 00217 m_languagesInSpellCheckRange = KateGlobal::self()->spellCheckManager()->spellCheckLanguageRanges(m_view->doc(), range); 00218 m_currentLanguageRangeIterator = m_languagesInSpellCheckRange.begin(); 00219 m_currentSpellCheckRange = KTextEditor::Range::invalid(); 00220 installNextSpellCheckRange(); 00221 // first check if there is really something to spell check 00222 if(m_currentSpellCheckRange.isValid()) { 00223 m_sonnetDialog->show(); 00224 } 00225 } 00226 00227 void KateSpellCheckDialog::installNextSpellCheckRange() 00228 { 00229 if ( m_spellCheckCancelledByUser 00230 || m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end() ) 00231 { 00232 spellCheckDone(); 00233 return; 00234 } 00235 KateSpellCheckManager *spellCheckManager = KateGlobal::self()->spellCheckManager(); 00236 KTextEditor::Cursor nextRangeBegin = (m_currentSpellCheckRange.isValid() ? m_currentSpellCheckRange.end() 00237 : KTextEditor::Cursor::invalid()); 00238 m_currentSpellCheckRange = KTextEditor::Range::invalid(); 00239 m_currentDecToEncOffsetList.clear(); 00240 QList<QPair<KTextEditor::Range, QString> > rangeDictionaryPairList; 00241 while ( m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end() ) 00242 { 00243 const KTextEditor::Range& currentLanguageRange = (*m_currentLanguageRangeIterator).first; 00244 const QString& dictionary = (*m_currentLanguageRangeIterator).second; 00245 KTextEditor::Range languageSubRange = (nextRangeBegin.isValid() ? KTextEditor::Range(nextRangeBegin, currentLanguageRange.end()) 00246 : currentLanguageRange); 00247 rangeDictionaryPairList = spellCheckManager->spellCheckWrtHighlightingRanges(m_view->doc(), 00248 languageSubRange, 00249 dictionary, 00250 false, true); 00251 Q_ASSERT(rangeDictionaryPairList.size() <= 1); 00252 if(rangeDictionaryPairList.size() == 0) { 00253 ++m_currentLanguageRangeIterator; 00254 if ( m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end() ) 00255 { 00256 nextRangeBegin = (*m_currentLanguageRangeIterator).first.start(); 00257 } 00258 } 00259 else { 00260 m_currentSpellCheckRange = rangeDictionaryPairList.first().first; 00261 const QString& dictionary = rangeDictionaryPairList.first().second; 00262 00263 m_spellPosCursor = m_currentSpellCheckRange.start(); 00264 m_spellLastPos = 0; 00265 00266 m_currentDecToEncOffsetList.clear(); 00267 KateDocument::OffsetList encToDecOffsetList; 00268 QString text = m_view->doc()->decodeCharacters(m_currentSpellCheckRange, 00269 m_currentDecToEncOffsetList, 00270 encToDecOffsetList); 00271 // ensure that no empty string is passed on to Sonnet as this can lead to a crash 00272 // (bug 228789) 00273 if(text.isEmpty()) { 00274 nextRangeBegin = m_currentSpellCheckRange.end(); 00275 continue; 00276 } 00277 00278 if(m_speller->language() != dictionary) { 00279 m_speller->setLanguage(dictionary); 00280 m_backgroundChecker->setSpeller(*m_speller); 00281 } 00282 00283 m_sonnetDialog->setBuffer(text); 00284 break; 00285 } 00286 } 00287 if ( m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end() ) 00288 { 00289 spellCheckDone(); 00290 return; 00291 } 00292 } 00293 00294 void KateSpellCheckDialog::cancelClicked() 00295 { 00296 m_spellCheckCancelledByUser = true; 00297 } 00298 00299 void KateSpellCheckDialog::spellCheckDone() 00300 { 00301 m_currentSpellCheckRange = KTextEditor::Range::invalid(); 00302 m_currentDecToEncOffsetList.clear(); 00303 m_view->clearSelection(); 00304 } 00305 00306 void KateSpellCheckDialog::objectDestroyed(QObject *object) 00307 { 00308 Q_UNUSED(object); 00309 m_sonnetDialog = NULL; 00310 } 00311 00312 //END 00313 00314 00315 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference