1 /***************************************************************************
2 * copyright : (C) 2007 by Pascal Brachet *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 ***************************************************************************/
10
11 #include "spellerdialog.h"
12
13 #include "smallUsefulFunctions.h"
14 #include "utilsUI.h"
15
16 #include "qdocumentline.h"
17
18 #include <QItemEditorCreatorBase>
19 #include <QStyledItemDelegate>
20
21 const QRegularExpressionValidator wordValidator(QRegularExpression("[^<].*"), nullptr);
22
SpellerDialog(QWidget * parent,SpellerUtility * utility)23 SpellerDialog::SpellerDialog(QWidget *parent, SpellerUtility *utility)
24 : QDialog(parent), m_statusBar(nullptr), m_speller(utility), editor(nullptr), editorView(nullptr)
25 {
26 ui.setupUi(this);
27 setModal(true);
28 UtilsUi::resizeInFontHeight(this, 31, 26);
29
30 m_statusBar = new QStatusBar();
31 delete ui.dummyStatusBar;
32 layout()->addWidget(m_statusBar);
33 // workaround for wrong status bar text color (black) in modern style
34 // TODO: change the style and remove this extra label
35 QLabel *messageArea = new QLabel(m_statusBar);
36 connect(m_statusBar, SIGNAL(messageChanged(QString)), messageArea, SLOT(setText(QString)));
37 m_statusBar->addPermanentWidget(messageArea, 1);
38
39 connect(ui.pushButtonIgnoreList, SIGNAL(clicked()), this, SLOT(toggleIgnoreList()));
40 connect(ui.pushButtonAdd, SIGNAL(clicked()), this, SLOT(addIgnoredWord()));
41 connect(ui.pushButtonRemove, SIGNAL(clicked()), this, SLOT(removeIgnoredWord()));
42 connect(ui.pushButtonIgnore, SIGNAL(clicked()), this, SLOT(slotIgnore()));
43 connect(ui.pushButtonAlwaysIgnore, SIGNAL(clicked()), this, SLOT(slotAlwaysIgnore()));
44 connect(ui.pushButtonReplace, SIGNAL(clicked()), this, SLOT(slotReplace()));
45 connect(ui.listSuggestions, SIGNAL(itemSelectionChanged()), this, SLOT(updateItem()));
46
47 ui.listSuggestions->setEnabled(false);
48 ui.lineEditNew->setEnabled(false);
49 ui.pushButtonIgnore->setEnabled(false);
50 ui.pushButtonAlwaysIgnore->setEnabled(false);
51 ui.pushButtonReplace->setEnabled(false);
52 ui.lineEditOriginal->setEnabled(false);
53
54 ui.ignoreListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
55 IgnoreListViewDelegate *itemDelegate = new IgnoreListViewDelegate(ui.ignoreListView);
56 ui.ignoreListView->setItemDelegate(itemDelegate);
57 connect(itemDelegate, SIGNAL(closeEditor(QWidget *)), this, SLOT(finishEditIgnoreList()));
58
59 toggleIgnoreList(true); // start with hidden ignore list
60 }
61
~SpellerDialog()62 SpellerDialog::~SpellerDialog()
63 {
64 ui.lineEditOriginal->clear();
65 ui.listSuggestions->clear();
66 ui.lineEditNew->clear();
67
68 }
69
setEditorView(LatexEditorView * edView)70 void SpellerDialog::setEditorView(LatexEditorView *edView)
71 {
72 editor = edView ? edView->editor : nullptr;
73 editorView = edView;
74 if(edView)
75 mReplacementList=edView->getReplacementList();
76 }
77
startSpelling()78 void SpellerDialog::startSpelling()
79 {
80 if (!editor) return;
81 ignoreListChanged = false;
82 if (editor->cursor().hasSelection()) {
83
84 m_statusBar->showMessage(tr("Check spelling selection..."));
85 startLine = editor->cursor().selectionStart().lineNumber();
86 startIndex = editor->cursor().selectionStart().columnNumber();
87 endLine = editor->cursor().selectionEnd().lineNumber();
88 endIndex = editor->cursor().selectionEnd().columnNumber();
89 } else {
90 m_statusBar->showMessage(tr("Check spelling from cursor..."));
91 editor->getCursorPosition(startLine, startIndex);
92 endLine = editor->document()->lines() - 1;
93 endIndex = editor->text(endLine).length();
94 }
95 curLine = startLine;
96 // determine tokenIndex from cursor index
97 QDocumentLineHandle *dlh=editor->document()->line(curLine).handle();
98 tl=dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList >();
99 for(tokenListIndex=0;tokenListIndex<tl.length();++tokenListIndex){
100 Token tk=tl.at(tokenListIndex);
101 if(tk.start+tk.length>startIndex)
102 break;
103 }
104 --tokenListIndex; // index is increased as first step in SpellingNextWord()
105 show();
106 SpellingNextWord();
107 }
108
closeEvent(QCloseEvent * ce)109 void SpellerDialog::closeEvent(QCloseEvent *ce)
110 {
111 if (editorView && ignoreListChanged) {
112 ignoreListChanged = false;
113 editorView->reCheckSyntax(0);
114 }
115 if (editor) editor->setCursorPosition(startLine, startIndex);
116 ce->accept();
117 }
118
accept()119 void SpellerDialog::accept()
120 {
121 if (editorView && ignoreListChanged) {
122 ignoreListChanged = false;
123 editorView->reCheckSyntax(0);
124 }
125 if (editor) editor->setCursorPosition(startLine, startIndex);
126 QDialog::accept();
127 }
128
updateItem()129 void SpellerDialog::updateItem()
130 {
131 int current = -1;
132 QList<QListWidgetItem *> items;
133 items = ui.listSuggestions->selectedItems();
134 if (items.count() > 0) {
135 ui.listSuggestions->setCurrentItem(items[0]);
136 current = ui.listSuggestions->row(items[0]);
137 }
138 if (current >= 0) {
139 ui.lineEditNew->setText(ui.listSuggestions->currentItem()->text());
140 }
141 }
142
slotIgnore()143 void SpellerDialog::slotIgnore()
144 {
145 SpellingNextWord();
146 }
147
slotAlwaysIgnore()148 void SpellerDialog::slotAlwaysIgnore()
149 {
150 //todo: real time update of now allowed words
151 m_speller->addToIgnoreList(ui.lineEditOriginal->text());
152 ignoreListChanged = true;
153 SpellingNextWord();
154 }
155
slotReplace()156 void SpellerDialog::slotReplace()
157 {
158 if (!editor) return;
159 if (editor->cursor().hasSelection()) {
160 QString selectedword = editor->cursor().selectedText();
161 editor->insertText(ui.lineEditNew->text());
162 }
163 SpellingNextWord();
164 }
165
SpellingNextWord()166 void SpellerDialog::SpellingNextWord()
167 {
168 if (!editor || !m_speller) return;
169 for (; curLine <= endLine; curLine++) {
170 QDocumentLineHandle *dlh=editor->document()->line(curLine).handle();
171 tl=dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList >();
172 while(tokenListIndex<tl.length()-1){
173 ++tokenListIndex;
174 Token tk=tl.at(tokenListIndex);
175 if (tk.type!=Token::word)
176 continue;
177 if(tk.subtype != Token::text && tk.subtype != Token::title && tk.subtype != Token::shorttitle && tk.subtype != Token::todo && tk.subtype != Token::none)
178 continue;
179 QString word=tk.getText();
180 word = latexToPlainWordwithReplacementList(word, mReplacementList);
181 if (tk.ignoreSpelling || m_speller->check(word)) continue;
182 QStringList suggWords = m_speller->suggest(word);
183
184 QDocumentCursor wordSelection(editor->document(), curLine, tk.start);
185 wordSelection.movePosition(tk.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
186 editor->setCursor(wordSelection);
187
188 ui.listSuggestions->setEnabled(true);
189 ui.lineEditNew->setEnabled(true);
190 ui.pushButtonIgnore->setEnabled(true);
191 ui.pushButtonAlwaysIgnore->setEnabled(true);
192 ui.pushButtonReplace->setEnabled(true);
193 ui.lineEditOriginal->setEnabled(true);
194 ui.lineEditOriginal->setText(word);
195 ui.listSuggestions->clear();
196 ui.lineEditNew->clear();
197 m_statusBar->clearMessage();
198 if (!suggWords.isEmpty()) {
199 ui.listSuggestions->addItems(suggWords);
200 ui.lineEditNew->setText(suggWords.at(0));
201 }
202 return;
203 }
204 tokenListIndex = -1;
205 }
206
207 //no word found
208 ui.listSuggestions->setEnabled(false);
209 ui.lineEditNew->setEnabled(false);
210 ui.pushButtonIgnore->setEnabled(false);
211 ui.pushButtonAlwaysIgnore->setEnabled(false);
212 ui.pushButtonReplace->setEnabled(false);
213 ui.lineEditOriginal->setEnabled(false);
214 ui.lineEditOriginal->clear();
215 ui.listSuggestions->clear();
216 ui.lineEditNew->clear();
217 m_statusBar->showMessage("<b>" + tr("No more misspelled words") + "</b>");
218 }
219
toggleIgnoreList(bool forceHide)220 void SpellerDialog::toggleIgnoreList(bool forceHide)
221 {
222 QList<QWidget *> hideableWidgets = QList<QWidget *>() << ui.ignoreListView << ui.labelIgnoredWords << ui.pushButtonAdd << ui.pushButtonRemove << ui.labelAsHideableSpacer;
223
224 if (ui.ignoreListView->isVisible() || forceHide) {
225 foreach (QWidget * w, hideableWidgets) w->hide();
226 ui.pushButtonIgnoreList->setText(tr("Show User Words"));
227 ui.pushButtonIgnoreList->setIcon(getRealIcon("down-arrow-circle-silver"));
228 resize(width(), height() - (ui.ignoreListView->height() + ui.gridLayout->verticalSpacing()));
229 } else {
230 resize(width(), height() + (ui.listSuggestions->height() + ui.gridLayout->verticalSpacing()));
231 ui.pushButtonIgnoreList->setText(tr("Hide User Words"));
232 ui.pushButtonIgnoreList->setIcon(getRealIcon("up-arrow-circle-silver"));
233 if (m_speller && !ui.ignoreListView->model())
234 ui.ignoreListView->setModel(m_speller->ignoreListModel());
235 foreach (QWidget * w, hideableWidgets) w->show();
236 }
237 }
238
addIgnoredWord()239 void SpellerDialog::addIgnoredWord()
240 {
241 if (!m_speller) return;
242 finishEditIgnoreList(); // needed for possible cleanup if an editor is open, harmless otherwise
243
244 QStringListModel *m = m_speller->ignoreListModel();
245 m->insertRow(0);
246 m->setData(m->index(0), QVariant(tr("<new>", "Placeholder for new added word in ignore list")));
247
248 ui.ignoreListView->selectionModel()->setCurrentIndex(m->index(0), QItemSelectionModel::Select);
249 ui.ignoreListView->edit(m->index(0));
250 }
251
removeIgnoredWord()252 void SpellerDialog::removeIgnoredWord()
253 {
254 if (!m_speller) return;
255 if (!ui.ignoreListView->model()) return;
256 QString selectedWord = ui.ignoreListView->model()->data(ui.ignoreListView->currentIndex(), Qt::DisplayRole).toString();
257 m_speller->removeFromIgnoreList(selectedWord);
258 }
259
finishEditIgnoreList()260 void SpellerDialog::finishEditIgnoreList()
261 {
262 QString word = ui.ignoreListView->model()->data(ui.ignoreListView->currentIndex(), Qt::DisplayRole).toString();
263 int dummy;
264 if (wordValidator.validate(word, dummy) == QValidator::Acceptable) {
265 m_speller->addToIgnoreList(word);
266 } else {
267 ui.ignoreListView->model()->removeRow(ui.ignoreListView->currentIndex().row());
268 }
269 }
270
ValidatedLineEdit(QWidget * parent)271 ValidatedLineEdit::ValidatedLineEdit(QWidget *parent) : QLineEdit(parent)
272 {
273 setValidator(&wordValidator);
274 }
275
IgnoreListViewDelegate(QObject * parent)276 IgnoreListViewDelegate::IgnoreListViewDelegate(QObject *parent) : QStyledItemDelegate(parent)
277 {
278 QItemEditorCreatorBase *creator = new QStandardItemEditorCreator<ValidatedLineEdit>();
279 QItemEditorFactory *factory = new QItemEditorFactory();
280 factory->registerEditor(QVariant::String, creator);
281 setItemEditorFactory(factory);
282 }
283
setModelData(QWidget * editor,QAbstractItemModel * model,const QModelIndex & index) const284 void IgnoreListViewDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
285 {
286 QByteArray n = editor->metaObject()->userProperty().name();
287 if (!n.isEmpty()) {
288 QString word = editor->property(n).toString();
289 int pos;
290 if (wordValidator.validate(word, pos) == QValidator::Acceptable) {
291 model->setData(index, editor->property(n), Qt::EditRole);
292 }
293 }
294 }
295