]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSpellchecker.cpp
#7379 avoid the wrap spell check question when buffer is empty
[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)
63                 : p(parent) {}
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         Ui::SpellcheckerUi ui;
74         ///
75         SpellcheckerWidget * p;
76         ///
77         GuiView * gv_;
78         /// current word being checked and lang code
79         WordLangTuple word_;
80 };
81
82
83 SpellcheckerWidget::SpellcheckerWidget(GuiView * gv, QWidget * parent)
84         : QWidget(parent), d(new Private(this))
85 {
86         d->ui.setupUi(this);
87         d->gv_ = gv;
88
89         connect(d->ui.suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
90                 this, SLOT(on_replacePB_clicked()));
91
92         // language
93         QAbstractItemModel * language_model = guiApp->languageModel();
94         // FIXME: it would be nice if sorting was enabled/disabled via a checkbox.
95         language_model->sort(0);
96         d->ui.languageCO->setModel(language_model);
97         d->ui.languageCO->setModelColumn(1);
98
99         d->ui.wordED->setReadOnly(true);
100
101         d->ui.suggestionsLW->installEventFilter(this);
102 }
103
104
105 SpellcheckerWidget::~SpellcheckerWidget()
106 {
107         delete d;
108 }
109
110
111 bool SpellcheckerWidget::eventFilter(QObject *obj, QEvent *event)
112 {
113         if (obj == d->ui.suggestionsLW && event->type() == QEvent::KeyPress) {
114                 QKeyEvent *e = static_cast<QKeyEvent *> (event);
115                 if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
116                         if (d->ui.suggestionsLW->currentItem()) {
117                                 on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem());
118                                 on_replacePB_clicked();
119                         }
120                         return true;
121                 } else if (e->key() == Qt::Key_Right) {
122                         if (d->ui.suggestionsLW->currentItem())
123                                 on_suggestionsLW_itemClicked(d->ui.suggestionsLW->currentItem());
124                         return true;
125                 }
126         }
127         // standard event processing
128         return QWidget::eventFilter(obj, event);
129 }
130
131
132 void SpellcheckerWidget::on_suggestionsLW_itemClicked(QListWidgetItem * item)
133 {
134         if (d->ui.replaceCO->count() != 0)
135                 d->ui.replaceCO->setItemText(0, item->text());
136         else
137                 d->ui.replaceCO->addItem(item->text());
138
139         d->ui.replaceCO->setCurrentIndex(0);
140 }
141
142
143 void SpellcheckerWidget::on_replaceCO_highlighted(const QString & str)
144 {
145         QListWidget * lw = d->ui.suggestionsLW;
146         if (lw->currentItem() && lw->currentItem()->text() == str)
147                 return;
148
149         for (int i = 0; i != lw->count(); ++i) {
150                 if (lw->item(i)->text() == str) {
151                         lw->setCurrentRow(i);
152                         break;
153                 }
154         }
155 }
156
157
158 void SpellcheckerWidget::updateView()
159 {
160         BufferView * bv = d->gv_->documentBufferView();
161         setEnabled(bv != 0);
162         if (bv && hasFocus())
163                 d->check();
164 }
165
166
167 bool SpellcheckerWidget::Private::continueFromBeginning()
168 {
169                 QMessageBox::StandardButton const answer = QMessageBox::question(p,
170                         qt_("Spell Checker"),
171                         qt_("We reached the end of the document, would you like to "
172                                 "continue from the beginning?"),
173                         QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
174                 if (answer == QMessageBox::No)
175                         return false;
176                 dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
177                 return true;
178 }
179
180
181 void SpellcheckerWidget::Private::forward()
182 {
183         BufferView * bv = gv_->documentBufferView();
184         DocIterator from = bv->cursor();
185
186         dispatch(FuncRequest(LFUN_ESCAPE));
187         dispatch(FuncRequest(LFUN_CHAR_FORWARD));
188         if (bv->cursor().atLastPos()) {
189                 continueFromBeginning();
190                 return;
191         }
192         if (from == bv->cursor()) {
193                 //FIXME we must be at the end of a cell
194                 dispatch(FuncRequest(LFUN_CHAR_FORWARD));
195         }
196 }
197         
198         
199 void SpellcheckerWidget::on_languageCO_activated(int index)
200 {
201         string const lang =
202                 fromqstr(d->ui.languageCO->itemData(index).toString());
203         if (!d->word_.lang() || d->word_.lang()->lang() == lang)
204                 // nothing changed
205                 return;
206         dispatch(FuncRequest(LFUN_LANGUAGE, lang));
207         d->check();
208 }
209
210
211 void SpellcheckerWidget::on_ignoreAllPB_clicked()
212 {
213         /// replace all occurrences of word
214         if (d->word_.lang() && !d->word_.word().empty())
215                 theSpellChecker()->accept(d->word_);
216         d->forward();
217         d->check();
218 }
219
220
221 void SpellcheckerWidget::on_addPB_clicked()
222 {
223         /// insert word in personal dictionary
224         theSpellChecker()->insert(d->word_);
225         d->forward();
226         d->check();
227 }
228
229
230 void SpellcheckerWidget::on_ignorePB_clicked()
231 {
232         d->forward();
233         d->check();
234 }
235
236
237 void SpellcheckerWidget::on_findNextPB_clicked()
238 {
239         docstring const data = find2string(
240                                 qstring_to_ucs4(d->ui.wordED->text()),
241                                 true, true, true);
242         dispatch(FuncRequest(LFUN_WORD_FIND, data));
243 }
244
245
246 void SpellcheckerWidget::on_replacePB_clicked()
247 {
248         docstring const replacement = qstring_to_ucs4(d->ui.replaceCO->currentText());
249         docstring const data = replace2string(
250                 replacement, qstring_to_ucs4(d->ui.wordED->text()),
251                 true, true, false, false);
252
253         LYXERR(Debug::GUI, "Replace (" << replacement << ")");
254         dispatch(FuncRequest(LFUN_WORD_REPLACE, data));
255         d->forward();
256         d->check();
257 }
258
259
260 void SpellcheckerWidget::on_replaceAllPB_clicked()
261 {
262         docstring const data = replace2string(
263                 qstring_to_ucs4(d->ui.replaceCO->currentText()),
264                 qstring_to_ucs4(d->ui.wordED->text()),
265                 true, true, true, true);
266         dispatch(FuncRequest(LFUN_WORD_REPLACE, data));
267         d->forward();
268         d->check(); // continue spellchecking
269 }
270
271
272 void SpellcheckerWidget::Private::updateSuggestions(docstring_list & words)
273 {
274         QString const suggestion = toqstr(word_.word());
275         ui.wordED->setText(suggestion);
276         QListWidget * lw = ui.suggestionsLW;
277         lw->clear();
278
279         if (words.empty()) {
280                 p->on_suggestionsLW_itemClicked(new QListWidgetItem(suggestion));
281                 return;
282         }
283         for (size_t i = 0; i != words.size(); ++i)
284                 lw->addItem(toqstr(words[i]));
285
286         p->on_suggestionsLW_itemClicked(lw->item(0));
287         lw->setCurrentRow(0);
288 }
289
290
291 void SpellcheckerWidget::Private::check()
292 {
293         BufferView * bv = gv_->documentBufferView();
294         if (!bv || bv->buffer().text().empty())
295                 return;
296
297         DocIterator from = bv->cursor();
298         DocIterator to;
299         WordLangTuple word_lang;
300         docstring_list suggestions;
301
302         int progress;
303         try {
304                 progress = bv->buffer().spellCheck(from, to, word_lang, suggestions);
305         } catch (ExceptionMessage const & message) {
306                 if (message.type_ == WarningException) {
307                         Alert::warning(message.title_, message.details_);
308                         return;
309                 }
310                 throw message;
311         }
312
313         // end of document
314         if (from == doc_iterator_end(&bv->buffer())) {
315                 if (continueFromBeginning())
316                         check();
317                 return;
318         }
319
320         word_ = word_lang;
321
322         // set suggestions
323         updateSuggestions(suggestions);
324         // set language
325         int const pos = ui.languageCO->findData(toqstr(word_lang.lang()->lang()));
326         if (pos != -1)
327                 ui.languageCO->setCurrentIndex(pos);
328
329         // FIXME LFUN
330         // If we used a LFUN, dispatch would do all of this for us
331         int const size = to.pos() - from.pos();
332         bv->putSelectionAt(from, size, false);
333         bv->processUpdateFlags(Update::Force | Update::FitCursor);      
334 }
335
336
337 GuiSpellchecker::GuiSpellchecker(GuiView & parent,
338                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
339         : DockView(parent, "spellchecker", qt_("Spellchecker"),
340                    area, flags)
341 {
342         widget_ = new SpellcheckerWidget(&parent);
343         setWidget(widget_);
344         setFocusProxy(widget_);
345 }
346
347
348 GuiSpellchecker::~GuiSpellchecker()
349 {
350         setFocusProxy(0);
351         delete widget_;
352 }
353
354
355 void GuiSpellchecker::updateView()
356 {
357         widget_->updateView();
358 }
359
360
361 Dialog * createGuiSpellchecker(GuiView & lv) 
362
363         GuiSpellchecker * gui = new GuiSpellchecker(lv, Qt::RightDockWidgetArea);
364 #ifdef Q_WS_MACX
365         gui->setFloating(true);
366 #endif
367         return gui;
368 }
369
370
371 } // namespace frontend
372 } // namespace lyx
373
374 #include "moc_GuiSpellchecker.cpp"