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