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