1 /************************************************************************
2 **
3 **  Copyright (C) 2015-2021 Kevin B. Hendricks, Stratford Ontario Canada
4 **  Copyright (C) 2012-2013 John Schember <john@nachtimwald.com>
5 **  Copyright (C) 2012-2013 Dave Heiland
6 **
7 **  This file is part of Sigil.
8 **
9 **  Sigil is free software: you can redistribute it and/or modify
10 **  it under the terms of the GNU General Public License as published by
11 **  the Free Software Foundation, either version 3 of the License, or
12 **  (at your option) any later version.
13 **
14 **  Sigil is distributed in the hope that it will be useful,
15 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 **  GNU General Public License for more details.
18 **
19 **  You should have received a copy of the GNU General Public License
20 **  along with Sigil.  If not, see <http://www.gnu.org/licenses/>.
21 **
22 *************************************************************************/
23 
24 #include <QtCore/QHashIterator>
25 #include <QtCore/QSignalMapper>
26 #include <QtGui/QContextMenuEvent>
27 #include <QtWidgets/QMessageBox>
28 #include <QtWidgets/QPushButton>
29 
30 #include "Dialogs/SpellcheckEditor.h"
31 #include "Misc/CaseInsensitiveItem.h"
32 #include "Misc/NumericItem.h"
33 #include "Misc/SettingsStore.h"
34 #include "Misc/SpellCheck.h"
35 #include "Misc/Utility.h"
36 #include "Misc/HTMLSpellCheckML.h"
37 #include "Misc/Language.h"
38 #include "ResourceObjects/Resource.h"
39 
40 static const QString SETTINGS_GROUP = "spellcheck_editor";
41 static const QString SELECTED_DICTIONARY = "selected_dictionary";
42 static const QString SHOW_ALL_WORDS = "show_all_words";
43 static const QString CASE_INSENSITIVE_SORT = "case_insensitive_sort";
44 static const QString SORT_COLUMN = "sort_column";
45 static const QString SORT_ORDER = "sort_order";
46 static const QString FILE_EXTENSION = "ini";
47 
SpellcheckEditor(QWidget * parent)48 SpellcheckEditor::SpellcheckEditor(QWidget *parent)
49     :
50     QDialog(parent),
51     m_Book(NULL),
52     m_SpellcheckEditorModel(new QStandardItemModel(this)),
53     m_ContextMenu(new QMenu(this)),
54     m_MultipleSelection(false),
55     m_SelectRow(-1),
56     m_FilterSC(new QShortcut(QKeySequence(tr("f", "Filter")), this)),
57     m_ShowAllSC(new QShortcut(QKeySequence(tr("s", "ShowAllWords")), this)),
58     m_NoCaseSC(new QShortcut(QKeySequence(tr("c", "Case-InsensitiveSort")), this)),
59     m_RefreshSC(new QShortcut(QKeySequence(tr("r", "Refresh")), this))
60 {
61     ui.setupUi(this);
62     ui.FilterText->installEventFilter(this);
63 
64     SetupSpellcheckEditorTree();
65     CreateContextMenuActions();
66     ConnectSignalsSlots();
67     UpdateDictionaries();
68     ReadSettings();
69 }
70 
~SpellcheckEditor()71 SpellcheckEditor::~SpellcheckEditor()
72 {
73     WriteSettings();
74 }
75 
SetBook(QSharedPointer<Book> book)76 void SpellcheckEditor::SetBook(QSharedPointer <Book> book)
77 {
78     m_Book = book;
79 }
80 
SetupSpellcheckEditorTree()81 void SpellcheckEditor::SetupSpellcheckEditorTree()
82 {
83     ui.SpellcheckEditorTree->setModel(m_SpellcheckEditorModel);
84     ui.SpellcheckEditorTree->setContextMenuPolicy(Qt::CustomContextMenu);
85     ui.SpellcheckEditorTree->setSortingEnabled(true);
86     ui.SpellcheckEditorTree->setWordWrap(true);
87     ui.SpellcheckEditorTree->setAlternatingRowColors(true);
88     ui.SpellcheckEditorTree->header()->setStretchLastSection(false);
89 }
90 
showEvent(QShowEvent * event)91 void SpellcheckEditor::showEvent(QShowEvent *event)
92 {
93     ui.FilterText->clear();
94     Refresh();
95 }
96 
eventFilter(QObject * obj,QEvent * event)97 bool SpellcheckEditor::eventFilter(QObject *obj, QEvent *event)
98 {
99     if (obj == ui.FilterText) {
100         if (event->type() == QEvent::KeyPress) {
101             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
102             int key = keyEvent->key();
103 
104             if (key == Qt::Key_Down) {
105                 ui.SpellcheckEditorTree->setFocus();
106                 return true;
107             }
108         }
109     }
110 
111     // pass the event on to the parent class
112     return QDialog::eventFilter(obj, event);
113 }
114 
toggleShowAllWords()115 void SpellcheckEditor::toggleShowAllWords()
116 {
117   ui.ShowAllWords->click();
118   ui.SpellcheckEditorTree->setFocus();
119 }
120 
toggleCaseInsensitiveSort()121 void SpellcheckEditor::toggleCaseInsensitiveSort()
122 {
123   ui.CaseInsensitiveSort->click();
124   ui.SpellcheckEditorTree->setFocus();
125 }
126 
SelectedRowsCount()127 int SpellcheckEditor::SelectedRowsCount()
128 {
129     int count = 0;
130 
131     if (ui.SpellcheckEditorTree->selectionModel()->hasSelection()) {
132         count = ui.SpellcheckEditorTree->selectionModel()->selectedRows(0).count();
133     }
134 
135     return count;
136 }
137 
GetSelectedItems()138 QList<QStandardItem *> SpellcheckEditor::GetSelectedItems()
139 {
140     QList<QStandardItem *> selected_items;
141     if (SelectedRowsCount() < 1) {
142         return selected_items;
143     }
144 
145     // Shift-click order is top to bottom regardless of starting position
146     // Ctrl-click order is first clicked to last clicked (included shift-clicks stay ordered as is)
147     QModelIndexList selected_indexes = ui.SpellcheckEditorTree->selectionModel()->selectedRows(0);
148     foreach(QModelIndex index, selected_indexes) {
149         selected_items.append(m_SpellcheckEditorModel->itemFromIndex(index));
150     }
151     return selected_items;
152 }
153 
Ignore()154 void SpellcheckEditor::Ignore()
155 {
156     if (SelectedRowsCount() < 1) {
157         emit ShowStatusMessageRequest(tr("No words selected."));
158         return;
159     }
160 
161     m_MultipleSelection = SelectedRowsCount() > 1;
162 
163     SpellCheck *sc = SpellCheck::instance();
164     foreach (QStandardItem *item, GetSelectedItems()) {
165         sc->ignoreWord(HTMLSpellCheckML::textOf(item->text()));
166         MarkSpelledOkay(item->row());
167     }
168 
169     if (m_MultipleSelection) {
170         m_MultipleSelection = false;
171         FindSelectedWord();
172     }
173     emit ShowStatusMessageRequest(tr("Ignored word(s)."));
174     emit SpellingHighlightRefreshRequest();
175 }
176 
Add()177 void SpellcheckEditor::Add()
178 {
179     if (SelectedRowsCount() < 1) {
180         emit ShowStatusMessageRequest(tr("No words selected."));
181         return;
182     }
183 
184     QString dict_name = ui.Dictionaries->currentText();
185     if (dict_name.isEmpty()) {
186         return;
187     }
188 
189     m_MultipleSelection = SelectedRowsCount() > 1;
190 
191     SpellCheck *sc = SpellCheck::instance();
192     SettingsStore settings;
193     QStringList enabled_dicts = settings.enabledUserDictionaries();
194     bool enabled = false;
195     foreach (QStandardItem *item, GetSelectedItems()) {
196         sc->addToUserDictionary(item->text(), dict_name);
197         if (enabled_dicts.contains(dict_name)) {
198             enabled = true;
199             MarkSpelledOkay(item->row());
200         }
201     }
202 
203     if (m_MultipleSelection) {
204         m_MultipleSelection = false;
205         FindSelectedWord();
206     }
207     if (enabled) {
208         emit ShowStatusMessageRequest(tr("Added word(s) to dictionary."));
209     } else {
210         emit ShowStatusMessageRequest(tr("Added word(s) to dictionary. The dictionary is not enabled in Preferences."));
211     }
212     emit SpellingHighlightRefreshRequest();
213 }
214 
ChangeAll()215 void SpellcheckEditor::ChangeAll()
216 {
217     QString old_word = GetSelectedWord();
218     if (old_word.isEmpty()) {
219         emit ShowStatusMessageRequest(tr("No words selected."));
220         return;
221     }
222     QString new_word = ui.cbChangeAll->currentText();
223 
224     if (new_word.contains("<") || new_word.contains(">") || new_word.contains("&")) {
225         Utility::DisplayStdErrorDialog(tr("The new word cannot contain \"<\", \">\", or \"&\"."));
226         return;
227     }
228 
229     m_SelectRow = GetSelectedRow();
230 
231     emit UpdateWordRequest(old_word, new_word);
232 }
233 
MarkSpelledOkay(int row)234 void SpellcheckEditor::MarkSpelledOkay(int row)
235 {
236     m_SpellcheckEditorModel->invisibleRootItem()->child(row, 2)->setText(tr("No"));
237     if (ui.ShowAllWords->checkState() == Qt::Unchecked) {
238         m_SpellcheckEditorModel->removeRows(row, 1);
239         if (row >= m_SpellcheckEditorModel->rowCount()) {
240             row--;
241         }
242         if (row >= 0) {
243             ui.SpellcheckEditorTree->selectionModel()->clear();
244             QModelIndex index = m_SpellcheckEditorModel->index(row, 0);
245             ui.SpellcheckEditorTree->setCurrentIndex(index);
246             ui.SpellcheckEditorTree->selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
247         }
248     }
249 }
250 
CreateModel(int sort_column,Qt::SortOrder sort_order)251 void SpellcheckEditor::CreateModel(int sort_column, Qt::SortOrder sort_order)
252 {
253     m_SpellcheckEditorModel->clear();
254     QStringList header;
255     header.append(tr("Word"));
256     header.append(tr("Count"));
257     header.append(tr("Language"));
258     header.append(tr("Misspelled?"));
259     m_SpellcheckEditorModel->setHorizontalHeaderLabels(header);
260     ui.SpellcheckEditorTree->header()->setSectionResizeMode(0, QHeaderView::Stretch);
261 
262     QHash<QString, int> unique_words = m_Book->GetUniqueWordsInHTMLFiles();
263 
264     int total_misspelled_words = 0;
265     SpellCheck *sc = SpellCheck::instance();
266     Language *lp = Language::instance();
267 
268     QHashIterator<QString, int> i(unique_words);
269     while (i.hasNext()) {
270         i.next();
271         QString lcword = i.key();
272         QString code = HTMLSpellCheckML::langOf(lcword);
273         QString lang = lp->GetLanguageName(code, code);
274         QString word = HTMLSpellCheckML::textOf(lcword);
275         int count = unique_words.value(lcword);
276         bool misspelled = !sc->spell(lcword);
277         if (misspelled) {
278             total_misspelled_words++;
279         }
280 
281         if (ui.ShowAllWords->checkState() == Qt::Unchecked && !misspelled) {
282             continue;
283         }
284 
285         QList<QStandardItem *> row_items;
286 
287         if (ui.CaseInsensitiveSort->checkState() == Qt::Unchecked) {
288             QStandardItem *word_item = new QStandardItem(word);
289             word_item->setData(code);
290             word_item->setEditable(false);
291             row_items << word_item;
292         } else {
293             CaseInsensitiveItem *word_item = new CaseInsensitiveItem();
294             word_item->setText(word);
295             word_item->setData(code);
296             word_item->setEditable(false);
297             row_items << word_item;
298         }
299         NumericItem *count_item = new NumericItem();
300         count_item->setText(QString::number(count));
301         row_items << count_item;
302 
303         QStandardItem *lang_item = new QStandardItem(lang);
304         row_items << lang_item;
305 
306         QStandardItem *misspelled_item = new QStandardItem();
307         misspelled_item->setEditable(false);
308         if (misspelled) {
309             misspelled_item->setText(tr("Yes"));
310         } else {
311             misspelled_item->setText(tr("No"));
312         }
313         row_items << misspelled_item ;
314 
315         m_SpellcheckEditorModel->invisibleRootItem()->appendRow(row_items);
316     }
317 
318     ui.SpellcheckEditorTree->resizeColumnToContents(0);
319     ui.SpellcheckEditorTree->resizeColumnToContents(1);
320     ui.SpellcheckEditorTree->resizeColumnToContents(2);
321     ui.SpellcheckEditorTree->resizeColumnToContents(3);
322 
323     // Changing the sortIndicator order should not cause the entire wordlist to be regenerated
324     // disconnect(ui.SpellcheckEditorTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
325 
326     ui.SpellcheckEditorTree->header()->setSortIndicator(sort_column, sort_order);
327 
328     // Changing the sortIndicator order should not cause the entire wordlist to be regenerated
329     // connect(ui.SpellcheckEditorTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
330 
331 
332     ui.SpellcheckEditorTree->header()->setToolTip("<table><tr><td>" % tr("Misspelled Words") % ":</td><td style=\"text-align:right;\">" % QString::number(total_misspelled_words) % "</td></tr><tr><td>" % tr("Total Unique Words") % ":</td><td style=\"text-align:right;\">" % QString::number(unique_words.count()) % "</td></tr></table>");
333 }
334 
Refresh(int sort_column,Qt::SortOrder sort_order)335 void SpellcheckEditor::Refresh(int sort_column, Qt::SortOrder sort_order)
336 {
337     QApplication::setOverrideCursor(Qt::WaitCursor);
338 
339     WriteSettings();
340     CreateModel(sort_column, sort_order);
341     UpdateDictionaries();
342 
343     ReadSettings();
344 
345     ui.FilterText->setFocus();
346     FilterEditTextChangedSlot(ui.FilterText->text());
347 
348     SelectRow(m_SelectRow);
349     UpdateSuggestions();
350 
351     QApplication::restoreOverrideCursor();
352 }
353 
UpdateDictionaries()354 void SpellcheckEditor::UpdateDictionaries()
355 {
356     ui.Dictionaries->clear();
357     SpellCheck *sc = SpellCheck::instance();
358     QStringList dicts = sc->userDictionaries();
359     if (dicts.count() > 0) {
360         ui.Dictionaries->addItems(dicts);
361     }
362 }
363 
DictionaryChanged(QString dictionary)364 void SpellcheckEditor::DictionaryChanged(QString dictionary)
365 {
366     ui.Dictionaries->setToolTip(dictionary);
367 }
368 
ChangeState(int state)369 void SpellcheckEditor::ChangeState(int state)
370 {
371     Refresh();
372 }
373 
SelectAll()374 void SpellcheckEditor::SelectAll()
375 {
376     ui.SpellcheckEditorTree->selectAll();
377 }
378 
GetSelectedWord()379 QString SpellcheckEditor::GetSelectedWord()
380 {
381     QString word;
382 
383     if (SelectedRowsCount() != 1 || m_MultipleSelection) {
384         return word;
385     }
386 
387     QModelIndex index = ui.SpellcheckEditorTree->selectionModel()->selectedRows(0).first();
388     // word = m_SpellcheckEditorModel->itemFromIndex(index)->text();
389     word = m_SpellcheckEditorModel->itemFromIndex(index)->data().toString() + ": " +
390         m_SpellcheckEditorModel->itemFromIndex(index)->text();
391     return word;
392 }
393 
GetSelectedRow()394 int SpellcheckEditor::GetSelectedRow()
395 {
396     int row = -1;
397 
398     if (SelectedRowsCount() != 1 || m_MultipleSelection) {
399         return row;
400     }
401 
402     return ui.SpellcheckEditorTree->selectionModel()->selectedRows(0).first().row();
403 }
404 
SelectRow(int row)405 void SpellcheckEditor::SelectRow(int row)
406 {
407     QStandardItem *root_item = m_SpellcheckEditorModel->invisibleRootItem();
408 
409     if (root_item->rowCount() > 0 && row >= 0) {
410         if (row >= root_item->rowCount()) {
411             row = root_item->rowCount() - 1;
412         }
413 
414         QStandardItem *child = root_item->child(row, 0);
415         if (child) {
416             QModelIndex index = child->index();
417             ui.SpellcheckEditorTree->setFocus();
418             ui.SpellcheckEditorTree->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
419             ui.SpellcheckEditorTree->setCurrentIndex(child->index());
420         }
421     }
422 
423     m_SelectRow = -1;
424 }
425 
UpdateSuggestions()426 void SpellcheckEditor::UpdateSuggestions()
427 {
428     ui.cbChangeAll->clear();
429     QString word = GetSelectedWord();
430     if (!word.isEmpty()) {
431         SpellCheck *sc = SpellCheck::instance();
432         ui.cbChangeAll->addItems(sc->suggest(word));
433     }
434 }
435 
FindSelectedWord()436 void SpellcheckEditor::FindSelectedWord()
437 {
438     QString word = GetSelectedWord();
439     if (!word.isEmpty()) {
440         emit FindWordRequest(word);
441     }
442 }
443 
SelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)444 void SpellcheckEditor::SelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
445 {
446     UpdateSuggestions();
447     // do not do a FindSelectedWord() here just because selection changed, wait for user to double-click
448     // so that paging up and down in the wordlist is not painfully slow in larger epubs
449 }
450 
FilterEditTextChangedSlot(const QString & text)451 void SpellcheckEditor::FilterEditTextChangedSlot(const QString &text)
452 {
453     const QString lowercaseText = text.toLower();
454     QModelIndex root_index = m_SpellcheckEditorModel->indexFromItem(m_SpellcheckEditorModel->invisibleRootItem());
455 
456     for (int row = 0; row < m_SpellcheckEditorModel->invisibleRootItem()->rowCount(); row++) {
457         QStandardItem *item = m_SpellcheckEditorModel->item(row, 0);
458         bool hidden = !(text.isEmpty() || item->text().toLower().contains(lowercaseText));
459         ui.SpellcheckEditorTree->setRowHidden(item->row(), root_index, hidden);
460     }
461 }
462 
Sort(int logicalindex,Qt::SortOrder order)463 void SpellcheckEditor::Sort(int logicalindex, Qt::SortOrder order)
464 {
465     Refresh(logicalindex, order);
466 }
467 
ReadSettings()468 void SpellcheckEditor::ReadSettings()
469 {
470     SettingsStore settings;
471     settings.beginGroup(SETTINGS_GROUP);
472     // The size of the window and it's full screen status
473     QByteArray geometry = settings.value("geometry").toByteArray();
474 
475     if (!geometry.isNull()) {
476         restoreGeometry(geometry);
477     }
478 
479     // Selected Dictionary
480     int index = -1;
481     QString dictionary;
482     if (settings.contains(SELECTED_DICTIONARY)) {
483         dictionary = settings.value(SELECTED_DICTIONARY).toString();
484         index = ui.Dictionaries->findText(dictionary);
485     }
486     if (index < 0) {
487         index = 0;
488     }
489     ui.Dictionaries->setCurrentIndex(index);
490     if (dictionary.isEmpty()) {
491         dictionary = ui.Dictionaries->currentText();
492     }
493     ui.Dictionaries->setToolTip(dictionary);
494 
495     // Checkboxes
496     // Disconnect signals to avoid refresh when setting.
497     if (settings.contains(SHOW_ALL_WORDS)) {
498         if (settings.value(SHOW_ALL_WORDS).toBool()) {
499             disconnect(ui.ShowAllWords, SIGNAL(stateChanged(int)),
500                        this, SLOT(ChangeState(int)));
501             ui.ShowAllWords->setCheckState(Qt::Checked);
502             connect(ui.ShowAllWords, SIGNAL(stateChanged(int)),
503                     this, SLOT(ChangeState(int)));
504         }
505     }
506     if (settings.contains(CASE_INSENSITIVE_SORT)) {
507         if (settings.value(CASE_INSENSITIVE_SORT).toBool()) {
508             disconnect(ui.CaseInsensitiveSort, SIGNAL(stateChanged(int)),
509                        this, SLOT(ChangeState(int)));
510             ui.CaseInsensitiveSort->setCheckState(Qt::Checked);
511             connect(ui.CaseInsensitiveSort, SIGNAL(stateChanged(int)),
512                     this, SLOT(ChangeState(int)));
513         }
514     }
515 
516     // Sort Order.
517     if (settings.contains(SORT_COLUMN) && settings.contains(SORT_ORDER)) {
518         int sort_column = settings.value(SORT_COLUMN).toInt();
519         Qt::SortOrder sort_order = Qt::AscendingOrder;
520         if (!settings.value(SORT_ORDER).toBool()) {
521             sort_order = Qt::DescendingOrder;
522         }
523 
524 
525         // Changing the  sortIndicator should not cause the entire wordlist to be regenerated!
526         // disconnect(ui.SpellcheckEditorTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
527 
528         ui.SpellcheckEditorTree->header()->setSortIndicator(sort_column, sort_order);
529 
530         // Changing the  sortIndicator should not cause the entire wordlist to be regenerated!
531         // connect(ui.SpellcheckEditorTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(Sort(int, Qt::SortOrder)));
532     }
533 
534     settings.endGroup();
535 }
536 
WriteSettings()537 void SpellcheckEditor::WriteSettings()
538 {
539     SettingsStore settings;
540     settings.beginGroup(SETTINGS_GROUP);
541     // The size of the window and it's full screen status
542     settings.setValue("geometry", saveGeometry());
543 
544     // Selected Dictionary
545     settings.setValue(SELECTED_DICTIONARY, ui.Dictionaries->currentText());
546 
547     // Checkboxes
548     settings.setValue(SHOW_ALL_WORDS, ui.ShowAllWords->checkState() == Qt::Checked);
549     settings.setValue(CASE_INSENSITIVE_SORT, ui.CaseInsensitiveSort->checkState() == Qt::Checked);
550     settings.setValue(SORT_COLUMN, ui.SpellcheckEditorTree->header()->sortIndicatorSection());
551     settings.setValue(SORT_ORDER, ui.SpellcheckEditorTree->header()->sortIndicatorOrder() == Qt::AscendingOrder);
552 
553     settings.endGroup();
554 }
555 
CreateContextMenuActions()556 void SpellcheckEditor::CreateContextMenuActions()
557 {
558     m_Ignore    = new QAction(tr("Ignore"),            this);
559     m_Add       = new QAction(tr("Add to Dictionary"), this);
560     m_Find      = new QAction(tr("Find in Text"),      this);
561     m_SelectAll = new QAction(tr("Select All"),        this);
562     m_Ignore->setShortcut(QKeySequence(Qt::Key_F1));
563     m_Add->setShortcut(QKeySequence(Qt::Key_F2));
564     m_Find->setShortcut(QKeySequence(Qt::Key_F3));
565     // Has to be added to the dialog itself for the keyboard shortcut to work.
566     addAction(m_Ignore);
567     addAction(m_Add);
568     addAction(m_Find);
569 }
570 
OpenContextMenu(const QPoint & point)571 void SpellcheckEditor::OpenContextMenu(const QPoint &point)
572 {
573     SetupContextMenu(point);
574     m_ContextMenu->exec(ui.SpellcheckEditorTree->viewport()->mapToGlobal(point));
575     if (!m_ContextMenu.isNull()) {
576         m_ContextMenu->clear();
577         // Make sure every action is enabled - in case shortcut is used after context menu disables some.
578         m_Ignore->setEnabled(true);
579         m_Add->setEnabled(true);
580         m_Find->setEnabled(true);
581         m_SelectAll->setEnabled(true);
582     }
583 }
584 
SetupContextMenu(const QPoint & point)585 void SpellcheckEditor::SetupContextMenu(const QPoint &point)
586 {
587     int selected_rows_count = SelectedRowsCount();
588     m_ContextMenu->addAction(m_Ignore);
589     m_Ignore->setEnabled(selected_rows_count > 0);
590     m_ContextMenu->addAction(m_Add);
591     m_Add->setEnabled(selected_rows_count > 0);
592     m_ContextMenu->addAction(m_Find);
593     m_Find->setEnabled(selected_rows_count > 0);
594     m_ContextMenu->addSeparator();
595     m_ContextMenu->addAction(m_SelectAll);
596 }
597 
ForceClose()598 void SpellcheckEditor::ForceClose()
599 {
600     close();
601 }
602 
ConnectSignalsSlots()603 void SpellcheckEditor::ConnectSignalsSlots()
604 {
605     QItemSelectionModel *selectionModel = ui.SpellcheckEditorTree->selectionModel();
606     connect(selectionModel,     SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
607             this,               SLOT(SelectionChanged(const QItemSelection &, const QItemSelection &)));
608 
609     connect(ui.FilterText,  SIGNAL(textChanged(QString)), this, SLOT(FilterEditTextChangedSlot(QString)));
610     connect(ui.Refresh, SIGNAL(clicked()), this, SLOT(Refresh()));
611     connect(ui.Ignore, SIGNAL(clicked()), this, SLOT(Ignore()));
612     connect(ui.Add, SIGNAL(clicked()), this, SLOT(Add()));
613     connect(ui.ChangeAll, SIGNAL(clicked()), this, SLOT(ChangeAll()));
614     connect(ui.SpellcheckEditorTree, SIGNAL(customContextMenuRequested(const QPoint &)),
615             this,        SLOT(OpenContextMenu(const QPoint &)));
616 
617     // Changing the sortIndicator order should not cause the entire wordlist to be regenerated
618     // connect(ui.SpellcheckEditorTree->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
619     //         this, SLOT(Sort(int, Qt::SortOrder)));
620 
621     connect(m_Ignore,    SIGNAL(triggered()), this, SLOT(Ignore()));
622     connect(m_Add,       SIGNAL(triggered()), this, SLOT(Add()));
623     connect(m_Find,      SIGNAL(triggered()), this, SLOT(FindSelectedWord()));
624     connect(m_SelectAll, SIGNAL(triggered()), this, SLOT(SelectAll()));
625     connect(ui.SpellcheckEditorTree, SIGNAL(doubleClicked(const QModelIndex &)),
626             this,         SLOT(FindSelectedWord()));
627     connect(ui.ShowAllWords,  SIGNAL(stateChanged(int)),
628             this,               SLOT(ChangeState(int)));
629     connect(ui.CaseInsensitiveSort,  SIGNAL(stateChanged(int)),
630             this,               SLOT(ChangeState(int)));
631     connect(ui.Dictionaries, SIGNAL(activated(const QString &)),
632             this,            SLOT(DictionaryChanged(const QString &)));
633 
634     connect(m_FilterSC, SIGNAL(activated()), ui.FilterText, SLOT(setFocus()));
635     connect(m_ShowAllSC, SIGNAL(activated()), this, SLOT(toggleShowAllWords()));
636     connect(m_NoCaseSC, SIGNAL(activated()), this, SLOT(toggleCaseInsensitiveSort()));
637     connect(m_RefreshSC, SIGNAL(activated()), this, SLOT(Refresh()));
638 
639 }
640