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