]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSpellchecker.cpp
More SpellChecker cleanups.
[lyx.git] / src / frontends / qt4 / GuiSpellchecker.cpp
1 /**
2  * \file GuiSpellchecker.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Edwin Leuven
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiSpellchecker.h"
15
16 #include "qt_helpers.h"
17
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "buffer_funcs.h"
22 #include "Cursor.h"
23 #include "CutAndPaste.h"
24 #include "Language.h"
25 #include "LyX.h"
26 #include "LyXRC.h"
27 #include "Paragraph.h"
28
29 #include "support/debug.h"
30 #include "support/docstring.h"
31 #include "support/docstring_list.h"
32 #include "support/ExceptionMessage.h"
33 #include "support/gettext.h"
34 #include "support/lstrings.h"
35 #include "support/textutils.h"
36
37 #include <QListWidgetItem>
38
39 #include "SpellChecker.h"
40
41 #include "frontends/alert.h"
42
43 using namespace std;
44 using namespace lyx::support;
45
46 namespace lyx {
47 namespace frontend {
48
49
50 GuiSpellchecker::GuiSpellchecker(GuiView & lv)
51         : GuiDialog(lv, "spellchecker", qt_("Spellchecker")), exitEarly_(false),
52           progress_(0), count_(0)
53 {
54         setupUi(this);
55
56         connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
57         connect(replacePB, SIGNAL(clicked()), this, SLOT(replace()));
58         connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
59         connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept()));
60         connect(addPB, SIGNAL(clicked()), this, SLOT(add()));
61
62         connect(replaceCO, SIGNAL(highlighted(QString)),
63                 this, SLOT(replaceChanged(QString)));
64         connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
65                 this, SLOT(replace()));
66         connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
67                 this, SLOT(suggestionChanged(QListWidgetItem*)));
68
69         wordED->setReadOnly(true);
70
71         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
72         bc().setCancel(closePB);
73 }
74
75
76 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
77 {
78         if (replaceCO->count() != 0)
79                 replaceCO->setItemText(0, item->text());
80         else
81                 replaceCO->addItem(item->text());
82
83         replaceCO->setCurrentIndex(0);
84 }
85
86
87 void GuiSpellchecker::replaceChanged(const QString & str)
88 {
89         if (suggestionsLW->currentItem()
90             && suggestionsLW->currentItem()->text() == str)
91                 return;
92
93         for (int i = 0; i != suggestionsLW->count(); ++i) {
94                 if (suggestionsLW->item(i)->text() == str) {
95                         suggestionsLW->setCurrentRow(i);
96                         break;
97                 }
98         }
99 }
100
101
102 void GuiSpellchecker::reject()
103 {
104         slotClose();
105         QDialog::reject();
106 }
107
108
109 void GuiSpellchecker::updateContents()
110 {
111         // The clauses below are needed because the spellchecker
112         // has many flaws (see bugs 1950, 2218).
113         // Basically, we have to distinguish the case where a
114         // spellcheck has already been performed for the whole
115         // document (exitEarly() == true, isVisible() == false) 
116         // from the rest (exitEarly() == false, isVisible() == true).
117         // FIXME: rewrite the whole beast!
118         static bool check_after_early_exit;
119         if (exitEarly()) {
120                 // a spellcheck has already been performed,
121                 check();
122                 check_after_early_exit = true;
123         }
124         else if (isVisible()) {
125                 // the above check triggers a second update,
126                 // and isVisible() is true then. Prevent a
127                 // second check that skips the first word
128                 if (check_after_early_exit)
129                         // don't check, but reset the bool.
130                         // business as usual after this.
131                         check_after_early_exit = false;
132                 else
133                         // perform spellcheck (default case)
134                         check();
135         }
136 }
137
138
139 void GuiSpellchecker::accept()
140 {
141         theSpellChecker()->accept(word_);
142         check();
143 }
144
145
146 void GuiSpellchecker::add()
147 {
148         theSpellChecker()->insert(word_);
149         check();
150 }
151
152
153 void GuiSpellchecker::ignore()
154 {
155         check();
156 }
157
158
159 void GuiSpellchecker::replace()
160 {
161         replace(qstring_to_ucs4(replaceCO->currentText()));
162 }
163
164
165 void GuiSpellchecker::updateSuggestions(docstring_list & words)
166 {
167         wordED->setText(toqstr(word_.word()));
168         suggestionsLW->clear();
169
170         if (words.empty() == 0) {
171                 suggestionChanged(new QListWidgetItem(wordED->text()));
172                 return;
173         }
174         for (size_t i = 0; i != words.size(); ++i)
175                 suggestionsLW->addItem(toqstr(words[i]));
176
177         suggestionChanged(suggestionsLW->item(0));
178         suggestionsLW->setCurrentRow(0);
179 }
180
181
182 bool GuiSpellchecker::initialiseParams(string const &)
183 {
184         LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
185
186         if (!theSpellChecker())
187                 return false;
188
189         DocIterator const begin = doc_iterator_begin(&buffer());
190         Cursor const & cur = bufferview()->cursor();
191         progress_ = countWords(begin, cur);
192         total_ = progress_ + countWords(cur, doc_iterator_end(&buffer()));
193         count_ = 0;
194         return true;
195 }
196
197
198 void GuiSpellchecker::clearParams()
199 {
200         LYXERR(Debug::GUI, "Spellchecker::clearParams");
201 }
202
203
204 void GuiSpellchecker::check()
205 {
206         LYXERR(Debug::GUI, "Check the spelling of a word");
207
208         DocIterator from = bufferview()->cursor();
209         while (from && from.pos() && isLetter(from))
210                 from.backwardPos();
211         DocIterator to;
212         exitEarly_ = false;
213         WordLangTuple word_lang;
214         docstring_list suggestions;
215
216         int progress;
217         try {
218                 progress = buffer().spellCheck(from, to, word_lang, suggestions);
219         } catch (ExceptionMessage const & message) {
220                 if (message.type_ == WarningException) {
221                         Alert::warning(message.title_, message.details_);
222                         slotClose();
223                         return;
224                 }
225                 throw message;
226         }
227         LYXERR(Debug::GUI, "Found word \"" << word_lang.word() << "\"");
228         count_ += progress;
229         progress_ += progress;
230
231         // end of document
232         if (from == to) {
233                 showSummary();
234                 exitEarly_ = true;
235                 return;
236         }
237         word_ = word_lang;
238
239         int const progress_bar = total_
240                 ? int(100.0 * float(progress_)/total_) : 100;
241         LYXERR(Debug::GUI, "Updating spell progress.");
242         // set progress bar
243         spellcheckPR->setValue(progress_bar);
244         // set suggestions
245         updateSuggestions(suggestions);
246
247         // FIXME: if we used a lfun like in find/replace, dispatch would do
248         // that for us
249         int const size = to.pos() - from.pos();
250         BufferView * bv = const_cast<BufferView *>(bufferview());
251         bv->putSelectionAt(from, size, false);
252 }
253
254
255 void GuiSpellchecker::showSummary()
256 {
257         if (count_ == 0) {
258                 slotClose();
259                 return;
260         }
261
262         docstring message;
263         if (count_ != 1)
264                 message = bformat(_("%1$d words checked."), count_);
265         else
266                 message = _("One word checked.");
267
268         slotClose();
269         Alert::information(_("Spelling check completed"), message);
270 }
271
272
273 void GuiSpellchecker::replace(docstring const & replacement)
274 {
275         LYXERR(Debug::GUI, "GuiSpellchecker::replace("
276                            << to_utf8(replacement) << ")");
277         BufferView * bv = const_cast<BufferView *>(bufferview());
278         cap::replaceSelectionWithString(bv->cursor(), replacement, true);
279         bv->buffer().markDirty();
280         // If we used an LFUN, we would not need that
281         bv->processUpdateFlags(Update::Force | Update::FitCursor);
282         // fix up the count
283         --count_;
284         check();
285 }
286
287
288 void GuiSpellchecker::replaceAll(docstring const & replacement)
289 {
290         // TODO: add to list
291         replace(replacement);
292 }
293
294
295 Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); }
296
297 } // namespace frontend
298 } // namespace lyx
299
300 #include "moc_GuiSpellchecker.cpp"