]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSpellchecker.cpp
f6da10a9ebd28828a65bc86ff108f50f50b96fe4
[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  * \author Abdelrazak Younes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GuiSpellchecker.h"
16 #include "GuiApplication.h"
17
18 #include "qt_helpers.h"
19
20 #include "ui_SpellcheckerUi.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "BufferView.h"
25 #include "buffer_funcs.h"
26 #include "Cursor.h"
27 #include "Text.h"
28 #include "CutAndPaste.h"
29 #include "FuncRequest.h"
30 #include "Language.h"
31 #include "LyX.h"
32 #include "LyXRC.h"
33 #include "lyxfind.h"
34 #include "Paragraph.h"
35 #include "WordLangTuple.h"
36
37 #include "support/debug.h"
38 #include "support/docstring.h"
39 #include "support/docstring_list.h"
40 #include "support/ExceptionMessage.h"
41 #include "support/gettext.h"
42 #include "support/lstrings.h"
43 #include "support/textutils.h"
44
45 #include <QKeyEvent>
46 #include <QListWidgetItem>
47 #include <QMessageBox>
48
49 #include "SpellChecker.h"
50
51 #include "frontends/alert.h"
52
53 using namespace std;
54 using namespace lyx::support;
55
56 namespace lyx {
57 namespace frontend {
58
59
60 struct SpellcheckerWidget::Private
61 {
62         Private(SpellcheckerWidget * parent, DockView * dv)
63                 : p(parent), dv_(dv), incheck_(false), wrap_around_(false) {}
64         /// update from controller
65         void updateSuggestions(docstring_list & words);
66         /// move to next position after current word
67         void forward();
68         /// check text until next misspelled/unknown word
69         void check();
70         ///
71         bool continueFromBeginning();
72         ///
73         void setLanguage(Language const * lang);
74         /// test and set guard flag
75         bool inCheck() {
76                 if (incheck_)
77                         return true;
78                 incheck_ = true;
79                 return false;
80         }
81         void canCheck() { incheck_ = false; }
82         ///
83         Ui::SpellcheckerUi ui;
84         ///
85         SpellcheckerWidget * p;
86         ///
87         GuiView * gv_;
88         ///
89         DockView * dv_;
90         /// current word being checked and lang code
91         WordLangTuple word_;
92         ///
93         DocIterator start_;
94         ///
95         bool incheck_;
96         ///
97         bool wrap_around_;
98 };
99
100
101 SpellcheckerWidget::SpellcheckerWidget(GuiView * gv, DockView * dv, QWidget * parent)
102         : QTabWidget(parent), d(new Private(this, dv))
103 {
104         d->ui.setupUi(this);
105         d->gv_ = gv;
106
107         connect(d->ui.suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
108                 this, SLOT(on_replacePB_clicked()));
109
110         // language
111         QAbstractItemModel * language_model = guiApp->languageModel();
112         // FIXME: it would be nice if sorting was enabled/disabled via a checkbox.
113         language_model->sort(0);
114         d->ui.languageCO->setModel(language_model);
115         d->ui.languageCO->setModelColumn(1);
116
117         d->ui.wordED->setReadOnly(true);
118
119         d->ui.suggestionsLW->installEventFilter(this);
120 }
121
122
123 SpellcheckerWidget::~SpellcheckerWidget()
124 {
125         delete d;
126 }
127
128
129 bool SpellcheckerWidget::eventFilter(QObject *obj, QEvent *event)
130 {
131         if (obj == d->ui.suggestionsLW && event->type() == QEvent::KeyPress) {
132                 QKeyEvent *e = static_cast<QKeyEvent *> (event);
133                 if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
134                         if (d->ui.suggestionsLW->currentItem()) {
135                                 on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem());
136                                 on_replacePB_clicked();
137                         }
138                         return true;
139                 } else if (e->key() == Qt::Key_Right) {
140                         if (d->ui.suggestionsLW->currentItem())
141                                 on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem());
142                         return true;
143                 }
144         }
145         // standard event processing
146         return QWidget::eventFilter(obj, event);
147 }
148
149
150 void SpellcheckerWidget::on_suggestionsLW_itemClicked(QListWidgetItem * item)
151 {
152         if (d->ui.replaceCO->count() != 0)
153                 d->ui.replaceCO->setItemText(0, item->text());
154         else
155                 d->ui.replaceCO->addItem(item->text());
156
157         d->ui.replaceCO->setCurrentIndex(0);
158 }
159
160
161 void SpellcheckerWidget::on_replaceCO_highlighted(const QString & str)
162 {
163         QListWidget * lw = d->ui.suggestionsLW;
164         if (lw->currentItem() && lw->currentItem()->text() == str)
165                 return;
166
167         for (int i = 0; i != lw->count(); ++i) {
168                 if (lw->item(i)->text() == str) {
169                         lw->setCurrentRow(i);
170                         break;
171                 }
172         }
173 }
174
175
176 void SpellcheckerWidget::updateView()
177 {
178         BufferView * bv = d->gv_->documentBufferView();
179         setEnabled(bv != 0);
180         if (bv && hasFocus() && d->start_.empty()) {
181                 d->start_ = bv->cursor();
182                 d->check();
183         }
184 }
185
186
187 bool SpellcheckerWidget::Private::continueFromBeginning()
188 {
189         QMessageBox::StandardButton const answer = QMessageBox::question(p,
190                 qt_("Spell Checker"),
191                 qt_("We reached the end of the document, would you like to "
192                         "continue from the beginning?"),
193                 QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
194         if (answer == QMessageBox::No) {
195                 dv_->hide();
196                 return false;
197         }
198         dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
199         wrap_around_ = true;
200         return true;
201 }
202
203
204 void SpellcheckerWidget::Private::forward()
205 {
206         BufferView * bv = gv_->documentBufferView();
207         DocIterator from = bv->cursor();
208
209         dispatch(FuncRequest(LFUN_ESCAPE));
210         dispatch(FuncRequest(LFUN_CHAR_FORWARD));
211         if (bv->cursor().depth() <= 1 && bv->cursor().atLastPos()) {
212                 continueFromBeginning();
213                 return;
214         }
215         if (from == bv->cursor()) {
216                 //FIXME we must be at the end of a cell
217                 dispatch(FuncRequest(LFUN_CHAR_FORWARD));
218         }
219         if (wrap_around_ && start_ < bv->cursor()) {
220                 dv_->hide();
221         }
222 }
223
224
225 void SpellcheckerWidget::on_languageCO_activated(int index)
226 {
227         string const lang =
228                 fromqstr(d->ui.languageCO->itemData(index).toString());
229         if (!d->word_.lang() || d->word_.lang()->lang() == lang)
230                 // nothing changed
231                 return;
232         dispatch(FuncRequest(LFUN_LANGUAGE, lang));
233         d->check();
234 }
235
236
237 bool SpellcheckerWidget::initialiseParams(std::string const &)
238 {
239         BufferView * bv = d->gv_->documentBufferView();
240         if (bv == 0)
241                 return false;
242         std::set<Language const *> languages = 
243         bv->buffer().masterBuffer()->getLanguages();
244         if (!languages.empty())
245                 d->setLanguage(*languages.begin());
246         d->start_ = DocIterator();
247         d->wrap_around_ = false;
248         d->incheck_ = false;
249         return true;
250 }
251
252
253 void SpellcheckerWidget::on_ignoreAllPB_clicked()
254 {
255         /// ignore all occurrences of word
256         if (d->inCheck())
257                 return;
258         LYXERR(Debug::GUI, "Spellchecker: ignore all button");
259         if (d->word_.lang() && !d->word_.word().empty())
260                 theSpellChecker()->accept(d->word_);
261         d->forward();
262         d->check();
263         d->canCheck();
264 }
265
266
267 void SpellcheckerWidget::on_addPB_clicked()
268 {
269         /// insert word in personal dictionary
270         if (d->inCheck())
271                 return;
272         LYXERR(Debug::GUI, "Spellchecker: add word button");
273         theSpellChecker()->insert(d->word_);
274         d->forward();
275         d->check();
276         d->canCheck();
277 }
278
279
280 void SpellcheckerWidget::on_ignorePB_clicked()
281 {
282         /// ignore this occurrence of word
283         if (d->inCheck())
284                 return;
285         LYXERR(Debug::GUI, "Spellchecker: ignore button");
286         d->forward();
287         d->check();
288         d->canCheck();
289 }
290
291
292 void SpellcheckerWidget::on_findNextPB_clicked()
293 {
294         if (d->inCheck())
295                 return;
296         docstring const textfield = qstring_to_ucs4(d->ui.wordED->text());
297         docstring const datastring = find2string(textfield,
298                                 true, true, true);
299         LYXERR(Debug::GUI, "Spellchecker: find next (" << textfield << ")");
300         dispatch(FuncRequest(LFUN_WORD_FIND, datastring));
301         d->canCheck();
302 }
303
304
305 void SpellcheckerWidget::on_replacePB_clicked()
306 {
307         if (d->inCheck())
308                 return;
309         docstring const textfield = qstring_to_ucs4(d->ui.wordED->text());
310         docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText());
311         docstring const datastring = replace2string(replacement, textfield,
312                 true, true, false, false);
313
314         LYXERR(Debug::GUI, "Replace (" << replacement << ")");
315         dispatch(FuncRequest(LFUN_WORD_REPLACE, datastring));
316         d->forward();
317         d->check();
318         d->canCheck();
319 }
320
321
322 void SpellcheckerWidget::on_replaceAllPB_clicked()
323 {
324         if (d->inCheck())
325                 return;
326         docstring const textfield = qstring_to_ucs4(d->ui.wordED->text());
327         docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText());
328         docstring const datastring = replace2string(replacement, textfield,
329                 true, true, true, true);
330
331         LYXERR(Debug::GUI, "Replace all (" << replacement << ")");
332         dispatch(FuncRequest(LFUN_WORD_REPLACE, datastring));
333         d->forward();
334         d->check(); // continue spellchecking
335         d->canCheck();
336 }
337
338
339 void SpellcheckerWidget::Private::updateSuggestions(docstring_list & words)
340 {
341         QString const suggestion = toqstr(word_.word());
342         ui.wordED->setText(suggestion);
343         QListWidget * lw = ui.suggestionsLW;
344         lw->clear();
345
346         if (words.empty()) {
347                 p->on_suggestionsLW_itemClicked(new QListWidgetItem(suggestion));
348                 return;
349         }
350         for (size_t i = 0; i != words.size(); ++i)
351                 lw->addItem(toqstr(words[i]));
352
353         p->on_suggestionsLW_itemClicked(lw->item(0));
354         lw->setCurrentRow(0);
355 }
356
357
358 void SpellcheckerWidget::Private::setLanguage(Language const * lang)
359 {
360         int const pos = ui.languageCO->findData(toqstr(lang->lang()));
361         if (pos != -1)
362                 ui.languageCO->setCurrentIndex(pos);
363 }
364
365
366 void SpellcheckerWidget::Private::check()
367 {
368         BufferView * bv = gv_->documentBufferView();
369         if (!bv || bv->buffer().text().empty())
370                 return;
371
372         DocIterator from = bv->cursor();
373         DocIterator to;
374         WordLangTuple word_lang;
375         docstring_list suggestions;
376
377         LYXERR(Debug::GUI, "Spellchecker: start check at " << from);
378         int progress;
379         try {
380                 progress = bv->buffer().spellCheck(from, to, word_lang, suggestions);
381         } catch (ExceptionMessage const & message) {
382                 if (message.type_ == WarningException) {
383                         Alert::warning(message.title_, message.details_);
384                         return;
385                 }
386                 throw message;
387         }
388
389         // end of document
390         if (from == doc_iterator_end(&bv->buffer())) {
391                 if (wrap_around_ || start_ == doc_iterator_begin(&bv->buffer())) {
392                         dv_->hide();
393                         return;
394                 }
395                 if (continueFromBeginning())
396                         check();
397                 return;
398         }
399
400         if (wrap_around_ && start_ < from) {
401                 dv_->hide();
402                 return;
403         }
404
405         word_ = word_lang;
406
407         // set suggestions
408         updateSuggestions(suggestions);
409         // set language
410         setLanguage(word_lang.lang());
411
412         // FIXME LFUN
413         // If we used a LFUN, dispatch would do all of this for us
414         int const size = to.pos() - from.pos();
415         bv->putSelectionAt(from, size, false);
416         bv->processUpdateFlags(Update::Force | Update::FitCursor);      
417 }
418
419
420 GuiSpellchecker::GuiSpellchecker(GuiView & parent,
421                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
422         : DockView(parent, "spellchecker", qt_("Spellchecker"),
423                    area, flags)
424 {
425         widget_ = new SpellcheckerWidget(&parent, this);
426         setWidget(widget_);
427         setFocusProxy(widget_);
428 }
429
430
431 GuiSpellchecker::~GuiSpellchecker()
432 {
433         setFocusProxy(0);
434         delete widget_;
435 }
436
437
438 void GuiSpellchecker::updateView()
439 {
440         widget_->updateView();
441 }
442
443
444 Dialog * createGuiSpellchecker(GuiView & lv) 
445
446         GuiSpellchecker * gui = new GuiSpellchecker(lv, Qt::RightDockWidgetArea);
447 #ifdef Q_WS_MACX
448         gui->setFloating(true);
449 #endif
450         return gui;
451 }
452
453
454 } // namespace frontend
455 } // namespace lyx
456
457 #include "moc_GuiSpellchecker.cpp"