aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugin/plugin.cpp2
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp167
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp197
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h4
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h19
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp527
-rw-r--r--src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h161
-rw-r--r--src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp1
-rw-r--r--src/styles/SelectionListItem.qml7
-rw-r--r--src/virtualkeyboard/abstractinputmethod.cpp17
-rw-r--r--src/virtualkeyboard/abstractinputmethod.h1
-rw-r--r--src/virtualkeyboard/content/components/Keyboard.qml245
-rw-r--r--src/virtualkeyboard/content/components/PopupList.qml (renamed from src/virtualkeyboard/content/components/LanguagePopupList.qml)18
-rw-r--r--src/virtualkeyboard/content/components/WordCandidatePopupList.qml24
-rw-r--r--src/virtualkeyboard/content/content.qrc2
-rw-r--r--src/virtualkeyboard/selectionlistmodel.cpp24
-rw-r--r--src/virtualkeyboard/selectionlistmodel.h12
-rw-r--r--tests/auto/inputpanel/data/inputpanel/inputpanel.qml57
-rw-r--r--tests/auto/inputpanel/data/tst_inputpanel.qml73
-rw-r--r--tests/auto/inputpanel/tst_inputpanel.cpp15
20 files changed, 1343 insertions, 230 deletions
diff --git a/src/plugin/plugin.cpp b/src/plugin/plugin.cpp
index 4841f53b..956bafbd 100644
--- a/src/plugin/plugin.cpp
+++ b/src/plugin/plugin.cpp
@@ -191,7 +191,7 @@ QPlatformInputContext *QVirtualKeyboardPlugin::create(const QString &system, con
qmlRegisterType(QUrl(componentsPath + QLatin1String("TraceInputArea.qml")), pluginUri, 2, 0, "TraceInputArea");
qmlRegisterType(QUrl(componentsPath + QLatin1String("TraceInputKey.qml")), pluginUri, 2, 0, "TraceInputKey");
qmlRegisterType(QUrl(componentsPath + QLatin1String("WordCandidatePopupList.qml")), pluginUri, 2, 0, "WordCandidatePopupList");
- qmlRegisterType(QUrl(componentsPath + QLatin1String("LanguagePopupList.qml")), pluginUri, 2, 1, "LanguagePopupList");
+ qmlRegisterType(QUrl(componentsPath + QLatin1String("PopupList.qml")), pluginUri, 2, 3, "PopupList");
qmlRegisterType(QUrl(componentsPath + QLatin1String("SelectionControl.qml")), pluginUri, 2, 1, "SelectionControl");
qmlRegisterType(QUrl(componentsPath + QLatin1String("InputModeKey.qml")), pluginUri, 2, 3, "InputModeKey");
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp
index d437f8fe..ee8d31e3 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod.cpp
@@ -108,16 +108,24 @@ bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboar
update();
break;
case Qt::Key_Backspace:
- if (!d->word.isEmpty()) {
- d->word.remove(d->word.length() - 1, 1);
- ic->setPreeditText(d->word);
- if (d->updateSuggestions()) {
- emit selectionListChanged(SelectionListModel::WordCandidateList);
- emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->activeWordIndex);
+ {
+ QString word = d->wordCandidates.wordAt(0);
+ if (!word.isEmpty()) {
+ word.remove(word.length() - 1, 1);
+ ic->setPreeditText(word);
+ if (!word.isEmpty()) {
+ d->wordCandidates.updateWord(0, word);
+ if (d->updateSuggestions()) {
+ emit selectionListChanged(SelectionListModel::WordCandidateList);
+ emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->wordCandidates.index());
+ }
+ } else {
+ d->reset();
}
accept = true;
}
break;
+ }
default:
if (inputMethodHints.testFlag(Qt::ImhNoPredictiveText))
break;
@@ -127,10 +135,11 @@ bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboar
}
if (text.length() > 0) {
QChar c = text.at(0);
- bool addToWord = d->isValidInputChar(c) && (!d->word.isEmpty() || !d->isJoiner(c));
+ QString word = d->wordCandidates.wordAt(0);
+ bool addToWord = d->isValidInputChar(c) && (!word.isEmpty() || !d->isJoiner(c));
if (addToWord) {
/* Automatic space insertion. */
- if (d->word.isEmpty()) {
+ if (word.isEmpty()) {
QString surroundingText = ic->surroundingText();
int cursorPosition = ic->cursorPosition();
/* Rules for automatic space insertion:
@@ -144,7 +153,7 @@ bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboar
if (!lastChar.isSpace() &&
lastChar != Qt::Key_Minus &&
d->isAutoSpaceAllowed()) {
- ic->commit(" ");
+ ic->commit(QLatin1String(" "));
}
}
}
@@ -152,21 +161,22 @@ bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboar
pre-edit text. The update is triggered if the text editor has
a selection which the pre-edit text will replace.
*/
- d->ignoreUpdate = d->word.isEmpty();
- d->word.append(text);
- ic->setPreeditText(d->word);
+ d->ignoreUpdate = word.isEmpty();
+ word.append(text);
+ d->wordCandidates.updateWord(0, word);
+ ic->setPreeditText(word);
d->ignoreUpdate = false;
if (d->updateSuggestions()) {
emit selectionListChanged(SelectionListModel::WordCandidateList);
- emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->activeWordIndex);
+ emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->wordCandidates.index());
}
accept = true;
} else if (text.length() > 1) {
- bool addSpace = !d->word.isEmpty() || d->autoSpaceAllowed;
+ bool addSpace = !word.isEmpty() || d->autoSpaceAllowed;
update();
d->autoSpaceAllowed = true;
if (addSpace && d->isAutoSpaceAllowed())
- ic->commit(" ");
+ ic->commit(QLatin1String(" "));
ic->commit(text);
d->autoSpaceAllowed = addSpace;
accept = true;
@@ -185,7 +195,10 @@ bool HunspellInputMethod::keyEvent(Qt::Key key, const QString &text, Qt::Keyboar
QList<SelectionListModel::Type> HunspellInputMethod::selectionLists()
{
Q_D(const HunspellInputMethod);
- Qt::InputMethodHints inputMethodHints = inputContext()->inputMethodHints();
+ InputContext *ic = inputContext();
+ if (!ic)
+ return QList<SelectionListModel::Type>();
+ Qt::InputMethodHints inputMethodHints = ic->inputMethodHints();
if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded || inputMethodHints.testFlag(Qt::ImhNoPredictiveText) || inputMethodHints.testFlag(Qt::ImhHiddenText))
return QList<SelectionListModel::Type>();
return QList<SelectionListModel::Type>() << SelectionListModel::WordCandidateList;
@@ -195,7 +208,7 @@ int HunspellInputMethod::selectionListItemCount(SelectionListModel::Type type)
{
Q_UNUSED(type)
Q_D(HunspellInputMethod);
- return d->wordCandidates.count();
+ return d->wordCandidates.size();
}
QVariant HunspellInputMethod::selectionListData(SelectionListModel::Type type, int index, int role)
@@ -204,15 +217,28 @@ QVariant HunspellInputMethod::selectionListData(SelectionListModel::Type type, i
Q_D(HunspellInputMethod);
switch (role) {
case SelectionListModel::DisplayRole:
- result = QVariant(d->wordCandidates.at(index));
+ result = QVariant(d->wordCandidates.wordAt(index));
break;
case SelectionListModel::WordCompletionLengthRole:
{
- const QString wordCandidate(d->wordCandidates.at(index));
- int wordCompletionLength = wordCandidate.length() - d->word.length();
- result.setValue((wordCompletionLength > 0 && wordCandidate.startsWith(d->word)) ? wordCompletionLength : 0);
+ const QString wordCandidate(d->wordCandidates.wordAt(index));
+ const QString word(d->wordCandidates.wordAt(0));
+ int wordCompletionLength = wordCandidate.length() - word.length();
+ result.setValue((wordCompletionLength > 0 && wordCandidate.startsWith(word)) ? wordCompletionLength : 0);
break;
}
+ case SelectionListModel::DictionaryTypeRole:
+ {
+ const QString wordCandidate(d->wordCandidates.wordAt(index));
+ SelectionListModel::DictionaryType dictionaryType =
+ d->userDictionaryWords && d->userDictionaryWords->contains(wordCandidate) ?
+ SelectionListModel::UserDictionary : SelectionListModel::DefaultDictionary;
+ result = QVariant(static_cast<int>(dictionaryType));
+ break;
+ }
+ case SelectionListModel::CanRemoveSuggestionRole:
+ result.setValue(index > 0 && d->wordCandidates.wordFlagsAt(index).testFlag(HunspellWordList::SpellCheckOk));
+ break;
default:
result = AbstractInputMethod::selectionListData(type, index, role);
break;
@@ -224,16 +250,33 @@ void HunspellInputMethod::selectionListItemSelected(SelectionListModel::Type typ
{
Q_UNUSED(type)
Q_D(HunspellInputMethod);
- QString finalWord = d->wordCandidates.at(index);
+ d->wordCandidates.setIndex(index);
+ d->addToDictionary();
+ QString finalWord = d->wordCandidates.wordAt(index);
reset();
inputContext()->commit(finalWord);
d->autoSpaceAllowed = true;
}
+bool HunspellInputMethod::selectionListRemoveItem(SelectionListModel::Type type, int index)
+{
+ Q_D(HunspellInputMethod);
+ Q_UNUSED(type)
+
+ if (index <= 0 || index >= d->wordCandidates.size())
+ return false;
+
+ QString word = d->wordCandidates.wordAt(index);
+ d->removeFromDictionary(word);
+
+ return true;
+}
+
bool HunspellInputMethod::reselect(int cursorPosition, const InputEngine::ReselectFlags &reselectFlags)
{
Q_D(HunspellInputMethod);
- Q_ASSERT(d->word.isEmpty());
+ QString word(d->wordCandidates.wordAt(0));
+ Q_ASSERT(word.isEmpty());
if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded)
return false;
@@ -250,61 +293,54 @@ bool HunspellInputMethod::reselect(int cursorPosition, const InputEngine::Resele
QChar c = surroundingText.at(i);
if (!d->isValidInputChar(c))
break;
- d->word.insert(0, c);
+ word.insert(0, c);
--replaceFrom;
}
- while (replaceFrom < 0 && d->isJoiner(d->word.at(0))) {
- d->word.remove(0, 1);
+ while (replaceFrom < 0 && d->isJoiner(word.at(0))) {
+ word.remove(0, 1);
++replaceFrom;
}
}
- if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == 0) {
- d->word.clear();
+ if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == 0)
return false;
- }
if (reselectFlags.testFlag(InputEngine::WordAfterCursor)) {
for (int i = cursorPosition; i < surroundingText.length(); ++i) {
QChar c = surroundingText.at(i);
if (!d->isValidInputChar(c))
break;
- d->word.append(c);
+ word.append(c);
}
- while (replaceFrom > -d->word.length()) {
- int lastPos = d->word.length() - 1;
- if (!d->isJoiner(d->word.at(lastPos)))
+ while (replaceFrom > -word.length()) {
+ int lastPos = word.length() - 1;
+ if (!d->isJoiner(word.at(lastPos)))
break;
- d->word.remove(lastPos, 1);
+ word.remove(lastPos, 1);
}
}
- if (d->word.isEmpty())
+ if (word.isEmpty())
return false;
- if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == -d->word.length()) {
- d->word.clear();
+ if (reselectFlags.testFlag(InputEngine::WordAtCursor) && replaceFrom == -word.length())
return false;
- }
- if (d->isJoiner(d->word.at(0))) {
- d->word.clear();
+ if (d->isJoiner(word.at(0)))
return false;
- }
- if (d->isJoiner(d->word.at(d->word.length() - 1))) {
- d->word.clear();
+ if (d->isJoiner(word.at(word.length() - 1)))
return false;
- }
- ic->setPreeditText(d->word, QList<QInputMethodEvent::Attribute>(), replaceFrom, d->word.length());
+ d->wordCandidates.updateWord(0, word);
+ ic->setPreeditText(word, QList<QInputMethodEvent::Attribute>(), replaceFrom, word.length());
d->autoSpaceAllowed = false;
if (d->updateSuggestions()) {
emit selectionListChanged(SelectionListModel::WordCandidateList);
- emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->activeWordIndex);
+ emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->wordCandidates.index());
}
return true;
@@ -321,37 +357,50 @@ void HunspellInputMethod::update()
Q_D(HunspellInputMethod);
if (d->ignoreUpdate)
return;
- if (!d->word.isEmpty()) {
- QString finalWord = d->hasSuggestions() ? d->wordCandidates.at(d->activeWordIndex) : d->word;
- d->reset();
- inputContext()->commit(finalWord);
+
+ QString finalWord;
+ if (!d->wordCandidates.isEmpty()) {
+ d->addToDictionary();
+ finalWord = d->wordCandidates.wordAt(d->wordCandidates.index());
}
+ d->reset();
+ inputContext()->commit(finalWord);
d->autoSpaceAllowed = false;
}
-void HunspellInputMethod::updateSuggestions(const QStringList &wordList, int activeWordIndex)
+void HunspellInputMethod::updateSuggestions(const QSharedPointer<HunspellWordList> &wordList, int tag)
{
Q_D(HunspellInputMethod);
if (d->dictionaryState == HunspellInputMethodPrivate::DictionaryNotLoaded) {
+ qCDebug(lcHunspell) << "updateSuggestions: skip (dictionary not loaded)";
update();
return;
}
- d->wordCandidates.clear();
- d->wordCandidates.append(wordList);
- // Make sure the exact match is up-to-date
- if (!d->word.isEmpty() && !d->wordCandidates.isEmpty() && d->wordCandidates.at(0) != d->word)
- d->wordCandidates.replace(0, d->word);
- d->activeWordIndex = activeWordIndex;
+ if (d->wordCandidatesUpdateTag != tag) {
+ qCDebug(lcHunspell) << "updateSuggestions: skip tag" << tag << "current" << d->wordCandidatesUpdateTag;
+ return;
+ }
+ QString word(d->wordCandidates.wordAt(0));
+ d->wordCandidates = *wordList;
+ if (d->wordCandidates.wordAt(0).compare(word) != 0)
+ d->wordCandidates.updateWord(0, word);
emit selectionListChanged(SelectionListModel::WordCandidateList);
- emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->activeWordIndex);
+ emit selectionListActiveItemChanged(SelectionListModel::WordCandidateList, d->wordCandidates.index());
}
void HunspellInputMethod::dictionaryLoadCompleted(bool success)
{
Q_D(HunspellInputMethod);
+ InputContext *ic = inputContext();
+ if (!ic)
+ return;
+
+ QList<SelectionListModel::Type> oldSelectionLists = selectionLists();
d->dictionaryState = success ? HunspellInputMethodPrivate::DictionaryReady :
HunspellInputMethodPrivate::DictionaryNotLoaded;
- emit selectionListsChanged();
+ QList<SelectionListModel::Type> newSelectionLists = selectionLists();
+ if (oldSelectionLists != newSelectionLists)
+ emit selectionListsChanged();
}
} // namespace QtVirtualKeyboard
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp
index 3a6777b2..2027a987 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.cpp
@@ -34,10 +34,13 @@
#include <QDir>
#include <QTextCodec>
#include <QtCore/QLibraryInfo>
+#include <QStandardPaths>
QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {
+const int HunspellInputMethodPrivate::userDictionaryMaxSize = 100;
+
/*!
\class QtVirtualKeyboard::HunspellInputMethodPrivate
\internal
@@ -47,13 +50,13 @@ HunspellInputMethodPrivate::HunspellInputMethodPrivate(HunspellInputMethod *q_pt
q_ptr(q_ptr),
hunspellWorker(new HunspellWorker()),
locale(),
- word(),
- wordCandidates(),
- activeWordIndex(-1),
wordCompletionPoint(2),
ignoreUpdate(false),
autoSpaceAllowed(false),
- dictionaryState(DictionaryNotLoaded)
+ dictionaryState(DictionaryNotLoaded),
+ userDictionaryWords(new HunspellWordList(userDictionaryMaxSize)),
+ blacklistedWords(new HunspellWordList(userDictionaryMaxSize)),
+ wordCandidatesUpdateTag(0)
{
if (hunspellWorker)
hunspellWorker->start();
@@ -69,7 +72,8 @@ bool HunspellInputMethodPrivate::createHunspell(const QString &locale)
if (!hunspellWorker)
return false;
if (this->locale != locale) {
- hunspellWorker->removeAllTasks();
+ clearSuggestionsRelatedTasks();
+ hunspellWorker->waitForAllTasks();
QString hunspellDataPath(qEnvironmentVariable("QT_VIRTUALKEYBOARD_HUNSPELL_DATA_PATH"));
const QString pathListSep(
#if defined(Q_OS_WIN32)
@@ -96,55 +100,65 @@ bool HunspellInputMethodPrivate::createHunspell(const QString &locale)
emit q->selectionListsChanged();
hunspellWorker->addTask(loadDictionaryTask);
this->locale = locale;
+
+ loadCustomDictionary(userDictionaryWords, QLatin1String("userdictionary"));
+ addToHunspell(userDictionaryWords);
+ loadCustomDictionary(blacklistedWords, QLatin1String("blacklist"));
+ removeFromHunspell(blacklistedWords);
}
return true;
}
void HunspellInputMethodPrivate::reset()
{
- if (clearSuggestions()) {
+ if (clearSuggestions(true)) {
Q_Q(HunspellInputMethod);
emit q->selectionListChanged(SelectionListModel::WordCandidateList);
- emit q->selectionListActiveItemChanged(SelectionListModel::WordCandidateList, activeWordIndex);
+ emit q->selectionListActiveItemChanged(SelectionListModel::WordCandidateList, wordCandidates.index());
}
- word.clear();
autoSpaceAllowed = false;
}
bool HunspellInputMethodPrivate::updateSuggestions()
{
bool wordCandidateListChanged = false;
+ QString word = wordCandidates.wordAt(0);
if (!word.isEmpty() && dictionaryState != HunspellInputMethodPrivate::DictionaryNotLoaded) {
- if (hunspellWorker)
- hunspellWorker->removeAllTasksExcept<HunspellLoadDictionaryTask>();
- if (wordCandidates.isEmpty()) {
- wordCandidates.append(word);
- activeWordIndex = 0;
- wordCandidateListChanged = true;
- } else if (wordCandidates.at(0) != word) {
- wordCandidates.replace(0, word);
- activeWordIndex = 0;
- wordCandidateListChanged = true;
- }
+ wordCandidateListChanged = true;
if (word.length() >= wordCompletionPoint) {
if (hunspellWorker) {
- QSharedPointer<HunspellWordList> wordList(new HunspellWordList());
+ QSharedPointer<HunspellWordList> wordList(new HunspellWordList(wordCandidates));
+
+ // Clear obsolete tasks from the worker queue
+ clearSuggestionsRelatedTasks();
+
+ // Build suggestions
QSharedPointer<HunspellBuildSuggestionsTask> buildSuggestionsTask(new HunspellBuildSuggestionsTask());
- buildSuggestionsTask->word = word;
buildSuggestionsTask->wordList = wordList;
buildSuggestionsTask->autoCorrect = false;
hunspellWorker->addTask(buildSuggestionsTask);
+
+ // Filter out blacklisted word (sometimes Hunspell suggests,
+ // e.g. with different text case)
+ QSharedPointer<HunspellFilterWordTask> filterWordTask(new HunspellFilterWordTask());
+ filterWordTask->wordList = wordList;
+ filterWordTask->filterList = blacklistedWords;
+ hunspellWorker->addTask(filterWordTask);
+
+ // Boost words from user dictionary
+ QSharedPointer<HunspellBoostWordTask> boostWordTask(new HunspellBoostWordTask());
+ boostWordTask->wordList = wordList;
+ boostWordTask->boostList = userDictionaryWords;
+ hunspellWorker->addTask(boostWordTask);
+
+ // Update word candidate list
QSharedPointer<HunspellUpdateSuggestionsTask> updateSuggestionsTask(new HunspellUpdateSuggestionsTask());
updateSuggestionsTask->wordList = wordList;
+ updateSuggestionsTask->tag = ++wordCandidatesUpdateTag;
Q_Q(HunspellInputMethod);
- q->connect(updateSuggestionsTask.data(), SIGNAL(updateSuggestions(QStringList, int)), SLOT(updateSuggestions(QStringList, int)));
+ QObject::connect(updateSuggestionsTask.data(), &HunspellUpdateSuggestionsTask::updateSuggestions, q, &HunspellInputMethod::updateSuggestions);
hunspellWorker->addTask(updateSuggestionsTask);
}
- } else if (wordCandidates.length() > 1) {
- wordCandidates.clear();
- wordCandidates.append(word);
- activeWordIndex = 0;
- wordCandidateListChanged = true;
}
} else {
wordCandidateListChanged = clearSuggestions();
@@ -152,20 +166,20 @@ bool HunspellInputMethodPrivate::updateSuggestions()
return wordCandidateListChanged;
}
-bool HunspellInputMethodPrivate::clearSuggestions()
+bool HunspellInputMethodPrivate::clearSuggestions(bool clearInputWord)
{
- if (hunspellWorker)
- hunspellWorker->removeAllTasksExcept<HunspellLoadDictionaryTask>();
- if (wordCandidates.isEmpty())
- return false;
- wordCandidates.clear();
- activeWordIndex = -1;
- return true;
+ clearSuggestionsRelatedTasks();
+ return clearInputWord ? wordCandidates.clear() : wordCandidates.clearSuggestions();
}
-bool HunspellInputMethodPrivate::hasSuggestions() const
+void HunspellInputMethodPrivate::clearSuggestionsRelatedTasks()
{
- return !wordCandidates.isEmpty();
+ if (hunspellWorker) {
+ hunspellWorker->removeAllTasksOfType<HunspellBuildSuggestionsTask>();
+ hunspellWorker->removeAllTasksOfType<HunspellFilterWordTask>();
+ hunspellWorker->removeAllTasksOfType<HunspellBoostWordTask>();
+ hunspellWorker->removeAllTasksOfType<HunspellUpdateSuggestionsTask>();
+ }
}
bool HunspellInputMethodPrivate::isAutoSpaceAllowed() const
@@ -211,5 +225,114 @@ bool HunspellInputMethodPrivate::isJoiner(const QChar &c) const
return false;
}
+QString HunspellInputMethodPrivate::customDictionaryLocation(const QString &dictionaryType) const
+{
+ if (dictionaryType.isEmpty() || locale.isEmpty())
+ return QString();
+
+ QString location = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
+ if (location.isEmpty())
+ return QString();
+
+ return QStringLiteral("%1/qtvirtualkeyboard/hunspell/%2-%3.txt")
+ .arg(location)
+ .arg(dictionaryType)
+ .arg(locale);
+}
+
+void HunspellInputMethodPrivate::loadCustomDictionary(const QSharedPointer<HunspellWordList> &wordList,
+ const QString &dictionaryType) const
+{
+ QSharedPointer<HunspellLoadWordListTask> loadWordsTask(new HunspellLoadWordListTask());
+ loadWordsTask->filePath = customDictionaryLocation(dictionaryType);
+ loadWordsTask->wordList = wordList;
+ hunspellWorker->addTask(loadWordsTask);
+}
+
+void HunspellInputMethodPrivate::saveCustomDictionary(const QSharedPointer<HunspellWordList> &wordList,
+ const QString &dictionaryType) const
+{
+ QSharedPointer<HunspellSaveWordListTask> saveWordsTask(new HunspellSaveWordListTask());
+ saveWordsTask->filePath = customDictionaryLocation(dictionaryType);
+ saveWordsTask->wordList = wordList;
+ hunspellWorker->addTask(saveWordsTask);
+}
+
+void HunspellInputMethodPrivate::addToHunspell(const QSharedPointer<HunspellWordList> &wordList) const
+{
+ QSharedPointer<HunspellAddWordTask> addWordTask(new HunspellAddWordTask());
+ addWordTask->wordList = wordList;
+ hunspellWorker->addTask(addWordTask);
+}
+
+void HunspellInputMethodPrivate::removeFromHunspell(const QSharedPointer<HunspellWordList> &wordList) const
+{
+ QSharedPointer<HunspellRemoveWordTask> removeWordTask(new HunspellRemoveWordTask());
+ removeWordTask->wordList = wordList;
+ hunspellWorker->addTask(removeWordTask);
+}
+
+void HunspellInputMethodPrivate::removeFromDictionary(const QString &word)
+{
+ if (userDictionaryWords->removeWord(word) > 0) {
+ saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary"));
+ } else if (!blacklistedWords->contains(word)) {
+ blacklistedWords->appendWord(word);
+ saveCustomDictionary(blacklistedWords, QLatin1String("blacklist"));
+ }
+
+ QSharedPointer<HunspellWordList> wordList(new HunspellWordList());
+ wordList->appendWord(word);
+ removeFromHunspell(wordList);
+
+ updateSuggestions();
+}
+
+void HunspellInputMethodPrivate::addToDictionary()
+{
+ Q_Q(HunspellInputMethod);
+ // This feature is not allowed when dealing with sensitive information
+ const Qt::InputMethodHints inputMethodHints(q->inputContext()->inputMethodHints());
+ const bool userDictionaryEnabled =
+ !inputMethodHints.testFlag(Qt::ImhHiddenText) &&
+ !inputMethodHints.testFlag(Qt::ImhSensitiveData);
+ if (!userDictionaryEnabled)
+ return;
+
+ if (wordCandidates.isEmpty())
+ return;
+
+ QString word;
+ HunspellWordList::Flags wordFlags;
+ const int activeWordIndex = wordCandidates.index();
+ wordCandidates.wordAt(activeWordIndex, word, wordFlags);
+ if (activeWordIndex == 0) {
+ if (blacklistedWords->removeWord(word) > 0) {
+ saveCustomDictionary(blacklistedWords, QLatin1String("blacklist"));
+ } else if (word.length() > 1 && !wordFlags.testFlag(HunspellWordList::SpellCheckOk) && !userDictionaryWords->contains(word)) {
+ userDictionaryWords->appendWord(word);
+ saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary"));
+ } else {
+ // Avoid adding words to Hunspell which are too short or passed spell check
+ return;
+ }
+
+ QSharedPointer<HunspellWordList> wordList(new HunspellWordList());
+ wordList->appendWord(word);
+ addToHunspell(wordList);
+ } else {
+ // Check if found in the user dictionary and move as last in the list.
+ // This way the list is always ordered by use.
+ // If userDictionaryMaxSize is greater than zero the number of words in the
+ // list will be limited to that amount. By pushing last used items to end of
+ // list we can avoid (to certain extent) removing frequently used words.
+ int userDictionaryIndex = userDictionaryWords->indexOfWord(word);
+ if (userDictionaryIndex != -1) {
+ userDictionaryWords->moveWord(userDictionaryIndex, userDictionaryWords->size() - 1);
+ saveCustomDictionary(userDictionaryWords, QLatin1String("userdictionary"));
+ }
+ }
+}
+
} // namespace QtVirtualKeyboard
QT_END_NAMESPACE
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h
index 68a4e702..bb9548c0 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p.h
@@ -47,6 +47,7 @@ QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {
class HunspellInputMethodPrivate;
+class HunspellWordList;
class HunspellInputMethod : public AbstractInputMethod
{
@@ -68,6 +69,7 @@ public:
int selectionListItemCount(SelectionListModel::Type type);
QVariant selectionListData(SelectionListModel::Type type, int index, int role);
void selectionListItemSelected(SelectionListModel::Type type, int index);
+ bool selectionListRemoveItem(SelectionListModel::Type type, int index);
bool reselect(int cursorPosition, const InputEngine::ReselectFlags &reselectFlags);
@@ -75,7 +77,7 @@ public:
void update();
protected Q_SLOTS:
- void updateSuggestions(const QStringList &wordList, int activeWordIndex);
+ void updateSuggestions(const QSharedPointer<HunspellWordList> &wordList, int tag);
void dictionaryLoadCompleted(bool success);
protected:
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h
index ebf202fd..8bb75f69 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellinputmethod_p_p.h
@@ -64,22 +64,31 @@ public:
bool createHunspell(const QString &locale);
void reset();
bool updateSuggestions();
- bool clearSuggestions();
- bool hasSuggestions() const;
+ bool clearSuggestions(bool clearInputWord = false);
+ void clearSuggestionsRelatedTasks();
bool isAutoSpaceAllowed() const;
bool isValidInputChar(const QChar &c) const;
bool isJoiner(const QChar &c) const;
+ QString customDictionaryLocation(const QString &dictionaryType) const;
+ void loadCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, const QString &dictionaryType) const;
+ void saveCustomDictionary(const QSharedPointer<HunspellWordList> &wordList, const QString &dictionaryType) const;
+ void addToHunspell(const QSharedPointer<HunspellWordList> &wordList) const;
+ void removeFromHunspell(const QSharedPointer<HunspellWordList> &wordList) const;
+ void removeFromDictionary(const QString &word);
+ void addToDictionary();
HunspellInputMethod *q_ptr;
QScopedPointer<HunspellWorker> hunspellWorker;
QString locale;
- QString word;
- QStringList wordCandidates;
- int activeWordIndex;
+ HunspellWordList wordCandidates;
int wordCompletionPoint;
bool ignoreUpdate;
bool autoSpaceAllowed;
DictionaryState dictionaryState;
+ QSharedPointer<HunspellWordList> userDictionaryWords;
+ QSharedPointer<HunspellWordList> blacklistedWords;
+ int wordCandidatesUpdateTag;
+ static const int userDictionaryMaxSize;
};
} // namespace QtVirtualKeyboard
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp
index 46cbf49c..6387ee16 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker.cpp
@@ -28,17 +28,308 @@
****************************************************************************/
#include <QtHunspellInputMethod/private/hunspellworker_p.h>
-#include <QLoggingCategory>
#include <QVector>
#include <QTextCodec>
#include <QFileInfo>
#include <QRegularExpression>
#include <QTime>
+#include <QFile>
+#include <QDir>
+#include <QtAlgorithms>
QT_BEGIN_NAMESPACE
namespace QtVirtualKeyboard {
-Q_DECLARE_LOGGING_CATEGORY(lcHunspell)
+HunspellWordList::HunspellWordList(int limit) :
+ _index(0),
+ _limit(limit)
+{
+}
+
+HunspellWordList::HunspellWordList(HunspellWordList &other)
+{
+ *this = other;
+}
+
+HunspellWordList &HunspellWordList::operator=(HunspellWordList &other)
+{
+ if (this != &other) {
+ QMutexLocker guard(&_lock);
+ QMutexLocker otherGuard(&other._lock);
+ _list = other._list;
+ _flags = other._flags;
+ _index = other._index;
+ _limit = other._limit;
+ _searchIndex = other._searchIndex;
+ }
+ return *this;
+}
+
+int HunspellWordList::index() const
+{
+ return _index < _list.size() ? _index : -1;
+}
+
+void HunspellWordList::setIndex(int index)
+{
+ QMutexLocker guard(&_lock);
+ _index = index;
+}
+
+bool HunspellWordList::clear()
+{
+ QMutexLocker guard(&_lock);
+ bool result = !_list.isEmpty();
+ _list.clear();
+ _flags.clear();
+ _index = 0;
+ _searchIndex.clear();
+ return result;
+}
+
+bool HunspellWordList::clearSuggestions()
+{
+ QMutexLocker guard(&_lock);
+ if (_list.isEmpty())
+ return false;
+
+ _searchIndex.clear();
+ if (_list.size() > 1) {
+ QString word = _list.at(0);
+ Flags flags = _flags.at(0);
+ _list.clear();
+ _flags.clear();
+ if (!word.isEmpty()) {
+ _index = 0;
+ _list.append(word);
+ _flags.append(flags);
+ }
+ return true;
+ } else if (_list.at(0).isEmpty()) {
+ _list.clear();
+ _flags.clear();
+ _index = 0;
+ return true;
+ }
+ return false;
+}
+
+bool HunspellWordList::hasSuggestions() const
+{
+ return _list.size() > 1;
+}
+
+int HunspellWordList::size() const
+{
+ return _list.size();
+}
+
+int HunspellWordList::isEmpty() const
+{
+ return _list.isEmpty() || _list.at(0).isEmpty();
+}
+
+bool HunspellWordList::contains(const QString &word)
+{
+ QMutexLocker guard(&_lock);
+
+ // Use index search when the search index is available.
+ // This provides a lot faster search than QList::contains().
+ // Search index is available when it has been rebuilt using
+ // rebuildSearchIndex() method. Search index is automatically
+ // cleared when the word list is modified.
+ if (!_searchIndex.isEmpty()) {
+ Q_ASSERT(_searchIndex.size() == _list.size());
+
+ SearchContext searchContext(word, _list);
+ return std::binary_search(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](const int &a, const int &b) {
+ const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a];
+ const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b];
+ return wordA.compare(wordB, Qt::CaseInsensitive) < 0;
+ });
+ }
+
+ return _list.contains(word, Qt::CaseInsensitive);
+}
+
+QString HunspellWordList::findWordCompletion(const QString &word)
+{
+ QMutexLocker guard(&_lock);
+
+ if (!_searchIndex.isEmpty()) {
+ Q_ASSERT(_searchIndex.size() == _list.size());
+
+ SearchContext searchContext(word, _list);
+ auto match = std::lower_bound(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](const int &a, const int &b) {
+ const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a];
+ const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b];
+ return wordA.compare(wordB, Qt::CaseInsensitive) < 0;
+ });
+
+ if (match == _searchIndex.end())
+ return QString();
+
+ if (!word.compare(_list[*match], Qt::CaseInsensitive)) {
+ match++;
+ if (match == _searchIndex.end())
+ return QString();
+ }
+
+ return _list[*match].startsWith(word, Qt::CaseInsensitive) ? _list[*match] : QString();
+ }
+
+ QString bestMatch;
+ for (int i = 0, count = _list.size(); i < count; ++i) {
+ const QString &wordB(_list[i]);
+ if (wordB.length() > bestMatch.length() &&
+ word.length() < wordB.length() &&
+ wordB.startsWith(word, Qt::CaseInsensitive))
+ bestMatch = wordB;
+ }
+
+ return bestMatch;
+}
+
+int HunspellWordList::indexOfWord(const QString &word)
+{
+ QMutexLocker guard(&_lock);
+
+ if (!_searchIndex.isEmpty()) {
+ Q_ASSERT(_searchIndex.size() == _list.size());
+
+ SearchContext searchContext(word, _list);
+ auto match = std::lower_bound(_searchIndex.begin(), _searchIndex.end(), -1, [searchContext](int a, int b) {
+ const QString &wordA = (a == -1) ? searchContext.word : searchContext.list[a];
+ const QString &wordB = (b == -1) ? searchContext.word : searchContext.list[b];
+ return wordA.compare(wordB, Qt::CaseInsensitive) < 0;
+ });
+ return (match != _searchIndex.end()) ? *match : -1;
+ }
+
+ return _list.indexOf(word);
+}
+
+QString HunspellWordList::wordAt(int index)
+{
+ QMutexLocker guard(&_lock);
+
+ return index >= 0 && index < _list.size() ? _list.at(index) : QString();
+}
+
+void HunspellWordList::wordAt(int index, QString &word, Flags &flags)
+{
+ QMutexLocker guard(&_lock);
+ Q_ASSERT(index >= 0 && index < _list.size());
+
+ word = _list.at(index);
+ flags = _flags.at(index);
+}
+
+const HunspellWordList::Flags &HunspellWordList::wordFlagsAt(int index)
+{
+ QMutexLocker guard(&_lock);
+
+ return _flags[index];
+}
+
+void HunspellWordList::appendWord(const QString &word, const Flags &flags)
+{
+ QMutexLocker guard(&_lock);
+
+ _searchIndex.clear();
+ if (_limit > 0) {
+ while (_list.size() >= _limit) {
+ _list.removeAt(0);
+ _flags.removeAt(0);
+ }
+ }
+ _list.append(word);
+ _flags.append(flags);
+}
+
+void HunspellWordList::insertWord(int index, const QString &word, const Flags &flags)
+{
+ QMutexLocker guard(&_lock);
+ Q_ASSERT(_limit == 0);
+
+ _searchIndex.clear();
+ _list.insert(index, word);
+ _flags.insert(index, flags);
+}
+
+void HunspellWordList::updateWord(int index, const QString &word, const Flags &flags)
+{
+ Q_ASSERT(index >= 0);
+ QMutexLocker guard(&_lock);
+
+ if (index < _list.size()) {
+ if (word != _list[index])
+ _searchIndex.clear();
+ _list[index] = word;
+ _flags[index] = flags;
+ } else {
+ _searchIndex.clear();
+ _list.append(word);
+ _flags.append(flags);
+ }
+}
+
+void HunspellWordList::moveWord(int from, int to)
+{
+ QMutexLocker guard(&_lock);
+
+ if (from < 0 || from >= _list.size())
+ return;
+ if (to < 0 || to >= _list.size())
+ return;
+ if (from == to)
+ return;
+
+ _searchIndex.clear();
+ _list.move(from, to);
+ _flags.move(from, to);
+}
+
+int HunspellWordList::removeWord(const QString &word)
+{
+ QMutexLocker guard(&_lock);
+ int removeCount = 0;
+ for (int i = 0, count = _list.size(); i < count;) {
+ if (!_list[i].compare(word, Qt::CaseInsensitive)) {
+ _list.removeAt(i);
+ _flags.removeAt(i);
+ --count;
+ ++removeCount;
+ } else {
+ ++i;
+ }
+ }
+ if (removeCount > 0)
+ _searchIndex.clear();
+ return removeCount;
+}
+
+void HunspellWordList::removeWordAt(int index)
+{
+ QMutexLocker guard(&_lock);
+
+ _list.removeAt(index);
+}
+
+void HunspellWordList::rebuildSearchIndex()
+{
+ QMutexLocker guard(&_lock);
+ _searchIndex.clear();
+
+ if (_list.isEmpty())
+ return;
+
+ _searchIndex.resize(_list.size());
+ std::iota(_searchIndex.begin(), _searchIndex.end(), 0);
+
+ const QStringList list(_list);
+ std::sort(_searchIndex.begin(), _searchIndex.end(), [list](int a, int b) { return list[a].compare(list[b], Qt::CaseInsensitive) < 0; });
+}
/*!
\class QtVirtualKeyboard::HunspellTask
@@ -69,9 +360,6 @@ void HunspellLoadDictionaryTask::run()
qCDebug(lcHunspell) << "HunspellLoadDictionaryTask::run(): locale:" << locale;
- QTime perf;
- perf.start();
-
if (*hunspellPtr) {
Hunspell_destroy(*hunspellPtr);
*hunspellPtr = nullptr;
@@ -97,13 +385,11 @@ void HunspellLoadDictionaryTask::run()
by the QTextCodec.
*/
if (!QTextCodec::codecForName(Hunspell_get_dic_encoding(*hunspellPtr))) {
- qCWarning(lcHunspell) << "The Hunspell dictionary" << dicPath << "cannot be used because it uses an unknown text codec" << QString(Hunspell_get_dic_encoding(*hunspellPtr));
+ qCWarning(lcHunspell) << "The Hunspell dictionary" << dicPath << "cannot be used because it uses an unknown text codec" << QLatin1String(Hunspell_get_dic_encoding(*hunspellPtr));
Hunspell_destroy(*hunspellPtr);
*hunspellPtr = nullptr;
}
}
-
- qCDebug(lcHunspell) << "HunspellLoadDictionaryTask::run(): time:" << perf.elapsed() << "ms";
} else {
qCWarning(lcHunspell) << "Hunspell dictionary is missing for" << locale << ". Search paths" << searchPaths;
}
@@ -118,11 +404,11 @@ void HunspellLoadDictionaryTask::run()
void HunspellBuildSuggestionsTask::run()
{
- QTime perf;
- perf.start();
+ if (wordList->isEmpty())
+ return;
- wordList->list.append(word);
- wordList->index = 0;
+ wordList->clearSuggestions();
+ QString word = wordList->wordAt(0);
/* Select text codec based on the dictionary encoding.
Hunspell_get_dic_encoding() should always return at least
@@ -138,34 +424,45 @@ void HunspellBuildSuggestionsTask::run()
/* Collect word candidates from the Hunspell suggestions.
Insert word completions in the beginning of the list.
*/
- const int firstWordCompletionIndex = wordList->list.length();
+ const int firstWordCompletionIndex = wordList->size();
int lastWordCompletionIndex = firstWordCompletionIndex;
bool suggestCapitalization = false;
for (int i = 0; i < n; i++) {
QString wordCandidate(textCodec->toUnicode(slst[i]));
- wordCandidate.replace(QChar(0x2019), '\'');
- if (wordCandidate.compare(word) != 0) {
- QString normalizedWordCandidate = removeAccentsAndDiacritics(wordCandidate);
- /* Prioritize word Capitalization */
- if (!suggestCapitalization && !wordCandidate.compare(word, Qt::CaseInsensitive)) {
- wordList->list.insert(1, wordCandidate);
+ wordCandidate.replace(QChar(0x2019), QLatin1Char('\''));
+ QString normalizedWordCandidate = removeAccentsAndDiacritics(wordCandidate);
+ /* Prioritize word Capitalization */
+ if (!wordCandidate.compare(word, Qt::CaseInsensitive)) {
+ if (suggestCapitalization) {
+ bool wordCandidateIsCapital = wordCandidate.at(0).isUpper();
+ bool wordIsCapital = word.at(0).isUpper();
+ if (wordCandidateIsCapital == wordIsCapital) {
+ if (wordCandidateIsCapital)
+ wordCandidate = wordCandidate.toLower();
+ else
+ wordCandidate[0] = wordCandidate.at(0).toUpper();
+ }
+ wordList->insertWord(1, wordCandidate);
lastWordCompletionIndex++;
suggestCapitalization = true;
- /* Prioritize word completions, missing punctuation or missing accents */
- } else if (normalizedWordCandidate.startsWith(word) ||
- wordCandidate.contains(QChar('\''))) {
- wordList->list.insert(lastWordCompletionIndex++, wordCandidate);
- } else {
- wordList->list.append(wordCandidate);
}
+ /* Prioritize word completions, missing punctuation or missing accents */
+ } else if ((normalizedWordCandidate.length() > word.length() &&
+ normalizedWordCandidate.startsWith(word)) ||
+ wordCandidate.contains(QLatin1Char('\''))) {
+ wordList->insertWord(lastWordCompletionIndex++, wordCandidate);
+ } else {
+ wordList->appendWord(wordCandidate);
}
}
/* Prioritize words with missing spaces next to word completions.
*/
- for (int i = lastWordCompletionIndex; i < wordList->list.length(); i++) {
- if (QString(wordList->list.at(i)).replace(" ", "").compare(word) == 0) {
+ for (int i = lastWordCompletionIndex; i < wordList->size(); i++) {
+ QString wordCandidate(wordList->wordAt(i));
+ if (wordCandidate.contains(QLatin1String(" "))) {
+ wordList->updateWord(i, wordCandidate, wordList->wordFlagsAt(i) | HunspellWordList::CompoundWord);
if (i != lastWordCompletionIndex) {
- wordList->list.move(i, lastWordCompletionIndex);
+ wordList->moveWord(i, lastWordCompletionIndex);
}
lastWordCompletionIndex++;
}
@@ -178,21 +475,28 @@ void HunspellBuildSuggestionsTask::run()
which may be suboptimal for the purpose, but gives some clue
how much the suggested word differs from the given word.
*/
- if (autoCorrect && wordList->list.length() > 1 && (!spellCheck(word) || suggestCapitalization)) {
- if (lastWordCompletionIndex > firstWordCompletionIndex || levenshteinDistance(word, wordList->list.at(firstWordCompletionIndex)) < 3)
- wordList->index = firstWordCompletionIndex;
+ if (autoCorrect && wordList->size() > 1 && (!spellCheck(word) || suggestCapitalization)) {
+ if (lastWordCompletionIndex > firstWordCompletionIndex || levenshteinDistance(word, wordList->wordAt(firstWordCompletionIndex)) < 3)
+ wordList->setIndex(firstWordCompletionIndex);
}
}
Hunspell_free_list(hunspell, &slst, n);
- qCDebug(lcHunspell) << "HunspellBuildSuggestionsTask::run(): time:" << perf.elapsed() << "ms";
+ for (int i = 0, count = wordList->size(); i < count; ++i) {
+ HunspellWordList::Flags flags;
+ wordList->wordAt(i, word, flags);
+ if (flags.testFlag(HunspellWordList::CompoundWord))
+ continue;
+ if (Hunspell_spell(hunspell, textCodec->fromUnicode(word).constData()) != 0)
+ wordList->updateWord(i, word, wordList->wordFlagsAt(i) | HunspellWordList::SpellCheckOk);
+ }
}
bool HunspellBuildSuggestionsTask::spellCheck(const QString &word)
{
if (!hunspell)
return false;
- if (word.contains(QRegularExpression("[0-9]")))
+ if (word.contains(QRegularExpression(QLatin1Literal("[0-9]"))))
return true;
return Hunspell_spell(hunspell, textCodec->fromUnicode(word).constData()) != 0;
}
@@ -243,7 +547,140 @@ QString HunspellBuildSuggestionsTask::removeAccentsAndDiacritics(const QString&
void HunspellUpdateSuggestionsTask::run()
{
- emit updateSuggestions(wordList->list, wordList->index);
+ emit updateSuggestions(wordList, tag);
+}
+
+void HunspellAddWordTask::run()
+{
+ const QTextCodec *textCodec;
+ textCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(hunspell));
+ if (!textCodec)
+ return;
+
+ QString tmpWord;
+ tmpWord.reserve(64);
+ for (int i = 0, count = wordList->size(); i < count; ++i) {
+ const QString word(wordList->wordAt(i));
+ if (word.length() < 2)
+ continue;
+ Hunspell_add(hunspell, textCodec->fromUnicode(word).constData());
+ if (HunspellAddWordTask::alternativeForm(word, tmpWord))
+ Hunspell_add(hunspell, textCodec->fromUnicode(tmpWord).constData());
+ }
+}
+
+bool HunspellAddWordTask::alternativeForm(const QString &word, QString &alternativeForm)
+{
+ if (word.length() < 2)
+ return false;
+ if (!word.mid(1).isLower())
+ return false;
+
+ const QChar initial(word.at(0));
+ const QChar newInitial = initial.isUpper() ? initial.toLower() : initial.toUpper();
+ if (newInitial == initial)
+ return false;
+
+ alternativeForm.truncate(0);
+ alternativeForm.append(word);
+ alternativeForm[0] = newInitial;
+
+ return true;
+}
+
+void HunspellRemoveWordTask::run()
+{
+ const QTextCodec *textCodec;
+ textCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(hunspell));
+ if (!textCodec)
+ return;
+
+ QString tmpWord;
+ tmpWord.reserve(64);
+ for (int i = 0, count = wordList->size(); i < count; ++i) {
+ const QString word(wordList->wordAt(i));
+ if (word.isEmpty())
+ continue;
+ Hunspell_remove(hunspell, textCodec->fromUnicode(word).constData());
+ if (HunspellAddWordTask::alternativeForm(word, tmpWord))
+ Hunspell_remove(hunspell, textCodec->fromUnicode(tmpWord).constData());
+ }
+}
+
+void HunspellLoadWordListTask::run()
+{
+ wordList->clear();
+
+ QFile inputFile(filePath);
+ if (inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QTextStream inStream(&inputFile);
+ inStream.setCodec(QTextCodec::codecForName("UTF-8"));
+ QString word;
+ word.reserve(64);
+ while (inStream.readLineInto(&word)) {
+ if (!word.isEmpty())
+ wordList->appendWord(word);
+ }
+ inputFile.close();
+ }
+}
+
+void HunspellSaveWordListTask::run()
+{
+ QFile outputFile(filePath);
+ if (!QFileInfo::exists(filePath))
+ QDir().mkpath(QFileInfo(filePath).absoluteDir().path());
+ if (outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
+ QTextStream outStream(&outputFile);
+ outStream.setCodec(QTextCodec::codecForName("UTF-8"));
+ for (int i = 0, count = wordList->size(); i < count; ++i) {
+ const QString word(wordList->wordAt(i));
+ outStream << word.toUtf8() << '\n';
+ }
+ outputFile.close();
+ }
+}
+
+void HunspellFilterWordTask::run()
+{
+ if (filterList->isEmpty())
+ return;
+
+ filterList->rebuildSearchIndex();
+
+ for (int i = startIndex, count = wordList->size(); i < count;) {
+ if (filterList->contains(wordList->wordAt(i))) {
+ wordList->removeWordAt(i);
+ --count;
+ } else {
+ ++i;
+ }
+ }
+}
+
+void HunspellBoostWordTask::run()
+{
+ if (boostList->isEmpty())
+ return;
+
+ boostList->rebuildSearchIndex();
+
+ const QString word(wordList->wordAt(0));
+ const QString wordCompletion(boostList->findWordCompletion(word));
+ if (!wordCompletion.isEmpty()) {
+ int from = wordList->indexOfWord(wordCompletion);
+ if (from != 1) {
+ int to;
+ for (to = 1; to < wordList->size() && wordList->wordAt(to).startsWith(word); ++to)
+ ;
+ if (from != -1) {
+ if (to < from)
+ wordList->moveWord(from, to);
+ } else {
+ wordList->insertWord(to, wordCompletion, HunspellWordList::SpellCheckOk);
+ }
+ }
+ }
}
/*!
@@ -253,11 +690,13 @@ void HunspellUpdateSuggestionsTask::run()
HunspellWorker::HunspellWorker(QObject *parent) :
QThread(parent),
+ idleSema(),
taskSema(),
taskLock(),
hunspell(nullptr)
{
abort = false;
+ qRegisterMetaType<QSharedPointer<HunspellWordList>>("QSharedPointer<HunspellWordList>");
}
HunspellWorker::~HunspellWorker()
@@ -282,12 +721,30 @@ void HunspellWorker::removeAllTasks()
taskList.clear();
}
+void HunspellWorker::waitForAllTasks()
+{
+ qCDebug(lcHunspell) << "waitForAllTasks enter";
+ while (isRunning()) {
+ idleSema.acquire();
+ QMutexLocker guard(&taskLock);
+ if (taskList.isEmpty()) {
+ idleSema.release();
+ break;
+ }
+ idleSema.release();
+ }
+ qCDebug(lcHunspell) << "waitForAllTasks leave";
+}
+
void HunspellWorker::run()
{
+ QTime perf;
while (!abort) {
+ idleSema.release();
taskSema.acquire();
if (abort)
break;
+ idleSema.acquire();
QSharedPointer<HunspellTask> currentTask;
{
QMutexLocker guard(&taskLock);
@@ -304,7 +761,9 @@ void HunspellWorker::run()
currentTask->hunspell = hunspell;
else
continue;
+ perf.start();
currentTask->run();
+ qCDebug(lcHunspell) << QString(QLatin1String(currentTask->metaObject()->className()) + "::run(): time:").toLatin1().constData() << perf.elapsed() << "ms";
}
}
if (hunspell) {
diff --git a/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h
index 1fe61bba..5ffb34a9 100644
--- a/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h
+++ b/src/plugins/hunspell/hunspellinputmethod/hunspellworker_p.h
@@ -46,6 +46,8 @@
#include <QMutex>
#include <QStringList>
#include <QSharedPointer>
+#include <QVector>
+#include <QLoggingCategory>
#include <hunspell/hunspell.h>
QT_BEGIN_NAMESPACE
@@ -53,6 +55,65 @@ class QTextCodec;
namespace QtVirtualKeyboard {
+Q_DECLARE_LOGGING_CATEGORY(lcHunspell)
+
+class HunspellWordList
+{
+public:
+ enum Flag
+ {
+ SpellCheckOk = 0x1,
+ CompoundWord = 0x2
+ };
+ Q_DECLARE_FLAGS(Flags, Flag)
+
+ HunspellWordList(int limit = 0);
+ HunspellWordList(HunspellWordList &other);
+
+ HunspellWordList &operator=(HunspellWordList &other);
+
+ int index() const;
+ void setIndex(int index);
+ bool clear();
+ bool clearSuggestions();
+ bool hasSuggestions() const;
+ int size() const;
+ int isEmpty() const;
+ bool contains(const QString &word);
+ QString findWordCompletion(const QString &word);
+ int indexOfWord(const QString &word);
+ QString wordAt(int index);
+ void wordAt(int index, QString &word, Flags &flags);
+ const Flags &wordFlagsAt(int index);
+ void appendWord(const QString &word, const Flags &flags = Flags());
+ void insertWord(int index, const QString &word, const Flags &flags = Flags());
+ void updateWord(int index, const QString &word, const Flags &flags = Flags());
+ void moveWord(int from, int to);
+ int removeWord(const QString &word);
+ void removeWordAt(int index);
+ void rebuildSearchIndex();
+
+private:
+ class SearchContext {
+ public:
+ SearchContext(const QString &word,
+ const QStringList &list) :
+ word(word),
+ list(list)
+ {}
+ const QString &word;
+ const QStringList &list;
+ };
+
+private:
+ QMutex _lock;
+ QStringList _list;
+ QVector<Flags> _flags;
+ QVector<int> _searchIndex;
+ int _index;
+ int _limit;
+};
+
class HunspellTask : public QObject
{
Q_OBJECT
@@ -84,24 +145,11 @@ public:
const QStringList searchPaths;
};
-class HunspellWordList
-{
-public:
- HunspellWordList() :
- list(),
- index(-1)
- {}
-
- QStringList list;
- int index;
-};
-
class HunspellBuildSuggestionsTask : public HunspellTask
{
Q_OBJECT
const QTextCodec *textCodec;
public:
- QString word;
QSharedPointer<HunspellWordList> wordList;
bool autoCorrect;
@@ -120,7 +168,80 @@ public:
void run();
signals:
- void updateSuggestions(const QStringList &wordList, int activeWordIndex);
+ void updateSuggestions(const QSharedPointer<HunspellWordList> &wordList, int tag);
+
+public:
+ int tag;
+};
+
+class HunspellAddWordTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ QSharedPointer<HunspellWordList> wordList;
+
+ void run();
+
+ static bool alternativeForm(const QString &word, QString &alternativeForm);
+};
+
+class HunspellRemoveWordTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ QSharedPointer<HunspellWordList> wordList;
+
+ void run();
+};
+
+class HunspellLoadWordListTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ QSharedPointer<HunspellWordList> wordList;
+ QString filePath;
+
+ void run();
+};
+
+class HunspellSaveWordListTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ QSharedPointer<HunspellWordList> wordList;
+ QString filePath;
+
+ void run();
+};
+
+class HunspellFilterWordTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ HunspellFilterWordTask() :
+ HunspellTask(),
+ startIndex(1)
+ {}
+
+ QSharedPointer<HunspellWordList> wordList;
+ QSharedPointer<HunspellWordList> filterList;
+ int startIndex;
+
+ void run();
+};
+
+class HunspellBoostWordTask : public HunspellTask
+{
+ Q_OBJECT
+public:
+ HunspellBoostWordTask() :
+ HunspellTask()
+ {}
+
+ QSharedPointer<HunspellWordList> wordList;
+ QSharedPointer<HunspellWordList> boostList;
+
+ void run();
};
class HunspellWorker : public QThread
@@ -132,16 +253,19 @@ public:
void addTask(QSharedPointer<HunspellTask> task);
void removeAllTasks();
+ void waitForAllTasks();
template <class X>
- void removeAllTasksExcept() {
+ void removeAllTasksOfType() {
QMutexLocker guard(&taskLock);
for (int i = 0; i < taskList.size();) {
QSharedPointer<X> task(taskList[i].objectCast<X>());
- if (!task)
+ if (task) {
+ qCDebug(lcHunspell) << "Remove task" << QLatin1String(task->metaObject()->className());
taskList.removeAt(i);
- else
+ } else {
i++;
+ }
}
}
@@ -154,6 +278,7 @@ private:
private:
friend class HunspellLoadDictionaryTask;
QList<QSharedPointer<HunspellTask> > taskList;
+ QSemaphore idleSema;
QSemaphore taskSema;
QMutex taskLock;
Hunhandle *hunspell;
@@ -163,4 +288,6 @@ private:
} // namespace QtVirtualKeyboard
QT_END_NAMESPACE
+Q_DECLARE_METATYPE(QSharedPointer<QT_PREPEND_NAMESPACE(QtVirtualKeyboard)::HunspellWordList>);
+
#endif // HUNSPELLWORKER_P_H
diff --git a/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp b/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp
index 49fadf24..86877623 100644
--- a/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp
+++ b/src/plugins/lipi-toolkit/plugin/lipiinputmethod.cpp
@@ -274,6 +274,7 @@ public:
// Double swipe: commit word, or insert space
cancelRecognition();
#ifdef HAVE_HUNSPELL
+ int activeWordIndex = wordCandidates.index();
if (activeWordIndex != -1) {
q->selectionListItemSelected(SelectionListModel::WordCandidateList, activeWordIndex);
return;
diff --git a/src/styles/SelectionListItem.qml b/src/styles/SelectionListItem.qml
index f3c9fdc9..3419b7e2 100644
--- a/src/styles/SelectionListItem.qml
+++ b/src/styles/SelectionListItem.qml
@@ -27,7 +27,7 @@
**
****************************************************************************/
-import QtQuick 2.0
+import QtQuick 2.11
/*!
\qmltype SelectionListItem
@@ -60,5 +60,10 @@ Item {
selectionListItem.ListView.view.currentIndex = index
selectionListItem.ListView.view.model.selectItem(index)
}
+ onPressAndHold: {
+ if (index === -1)
+ return
+ selectionListItem.ListView.view.longPressItem(index)
+ }
}
}
diff --git a/src/virtualkeyboard/abstractinputmethod.cpp b/src/virtualkeyboard/abstractinputmethod.cpp
index 7c51f437..622e6333 100644
--- a/src/virtualkeyboard/abstractinputmethod.cpp
+++ b/src/virtualkeyboard/abstractinputmethod.cpp
@@ -165,6 +165,13 @@ void AbstractInputMethod::selectionListItemSelected(SelectionListModel::Type typ
Q_UNUSED(index)
}
+bool AbstractInputMethod::selectionListRemoveItem(SelectionListModel::Type type, int index)
+{
+ Q_UNUSED(type)
+ Q_UNUSED(index)
+ return false;
+}
+
/*!
\since QtQuick.VirtualKeyboard 2.0
@@ -305,7 +312,7 @@ bool AbstractInputMethod::clickPreeditText(int cursorPosition)
/*!
\fn QVariant QtVirtualKeyboard::AbstractInputMethod::selectionListData(SelectionListModel::Type type, int index, int role)
- Returns item data for the selection list identified by \a type. The \a role
+ Returns item data for the selection list identified by \a type. The \a \l {QtVirtualKeyboard::SelectionListModel::Role}{role}
parameter specifies which data is requested. The \a index parameter is a
zero based index into the list.
*/
@@ -318,6 +325,14 @@ bool AbstractInputMethod::clickPreeditText(int cursorPosition)
*/
/*!
+ \fn bool QtVirtualKeyboard::AbstractInputMethod::selectionListRemoveItem(SelectionListModel::Type type, int index)
+
+ This method is called when an item at \a index must be removed from dictionary.
+ The selection list is identified by the \a type parameter.
+ The function returns \c true if the word was successfully removed.
+*/
+
+/*!
\fn void QtVirtualKeyboard::AbstractInputMethod::selectionListChanged(int type)
The input method emits this signal when the contents of the selection list
diff --git a/src/virtualkeyboard/abstractinputmethod.h b/src/virtualkeyboard/abstractinputmethod.h
index ed272cc9..8689d84d 100644
--- a/src/virtualkeyboard/abstractinputmethod.h
+++ b/src/virtualkeyboard/abstractinputmethod.h
@@ -60,6 +60,7 @@ public:
virtual int selectionListItemCount(SelectionListModel::Type type);
virtual QVariant selectionListData(SelectionListModel::Type type, int index, int role);
virtual void selectionListItemSelected(SelectionListModel::Type type, int index);
+ virtual bool selectionListRemoveItem(SelectionListModel::Type type, int index);
virtual QList<InputEngine::PatternRecognitionMode> patternRecognitionModes() const;
virtual Trace *traceBegin(int traceId, InputEngine::PatternRecognitionMode patternRecognitionMode,
diff --git a/src/virtualkeyboard/content/components/Keyboard.qml b/src/virtualkeyboard/content/components/Keyboard.qml
index 8485d3c2..9bc04ad7 100644
--- a/src/virtualkeyboard/content/components/Keyboard.qml
+++ b/src/virtualkeyboard/content/components/Keyboard.qml
@@ -30,7 +30,7 @@
import QtQuick 2.0
import QtQuick.Layouts 1.0
import QtQuick.Window 2.2
-import QtQuick.VirtualKeyboard 2.2
+import QtQuick.VirtualKeyboard 2.3
import QtQuick.VirtualKeyboard.Styles 2.1
import QtQuick.VirtualKeyboard.Settings 2.2
import Qt.labs.folderlistmodel 2.0
@@ -184,6 +184,10 @@ Item {
}
break
}
+ if (wordCandidateContextMenu.active) {
+ hideWordCandidateContextMenu()
+ break
+ }
if (wordCandidateView.count) {
if (wordCandidateView.currentIndex > 0) {
wordCandidateView.decrementCurrentIndex()
@@ -230,6 +234,14 @@ Item {
alternativeKeys.close()
keyboardInputArea.setActiveKey(null)
keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (wordCandidateContextMenu.active) {
+ if (wordCandidateContextMenuList.currentIndex > 0) {
+ wordCandidateContextMenuList.decrementCurrentIndex()
+ } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) {
+ wordCandidateContextMenuList.currentIndex = wordCandidateContextMenuList.count - 1
+ } else {
+ hideWordCandidateContextMenu()
+ }
} else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) {
keyboardInputArea.navigateToNextKey(0, 0, false)
initialKey = keyboardInputArea.initialKey
@@ -262,6 +274,10 @@ Item {
}
break
}
+ if (wordCandidateContextMenu.active) {
+ hideWordCandidateContextMenu()
+ break
+ }
if (wordCandidateView.count) {
if (wordCandidateView.currentIndex + 1 < wordCandidateView.count) {
wordCandidateView.incrementCurrentIndex()
@@ -308,6 +324,16 @@ Item {
alternativeKeys.close()
keyboardInputArea.setActiveKey(null)
keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (wordCandidateContextMenu.active) {
+ if (wordCandidateContextMenuList.currentIndex + 1 < wordCandidateContextMenuList.count) {
+ wordCandidateContextMenuList.incrementCurrentIndex()
+ } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) {
+ wordCandidateContextMenuList.currentIndex = 0
+ } else {
+ hideWordCandidateContextMenu()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
} else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) {
keyboardInputArea.navigateToNextKey(0, 0, false)
initialKey = keyboardInputArea.initialKey
@@ -343,10 +369,10 @@ Item {
keyboardInputArea.setActiveKey(keyboardInputArea.initialKey)
keyboardInputArea.press(keyboardInputArea.initialKey, true)
}
- } else if (wordCandidateView.count > 0) {
- wordCandidateView.model.selectItem(wordCandidateView.currentIndex)
- if (!InputContext.preeditText.length)
- keyboardInputArea.navigateToNextKey(0, 1, true)
+ } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
+ if (!isAutoRepeat) {
+ pressAndHoldTimer.restart()
+ }
}
break
default:
@@ -361,13 +387,26 @@ Item {
languagePopupList.model.selectItem(languagePopupList.currentIndex)
break
}
- if (!languagePopupListActive && !alternativeKeys.active && keyboard.activeKey && !isAutoRepeat) {
+ if (isAutoRepeat)
+ break
+ if (!languagePopupListActive && !alternativeKeys.active && !wordCandidateContextMenu.active && keyboard.activeKey) {
keyboardInputArea.release(keyboard.activeKey)
pressAndHoldTimer.stop()
alternativeKeys.close()
keyboardInputArea.setActiveKey(null)
if (!languagePopupListActive && keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (wordCandidateContextMenu.active) {
+ if (!wordCandidateContextMenu.openedByNavigationKeyLongPress) {
+ wordCandidateContextMenu.selectCurrentItem()
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else {
+ wordCandidateContextMenu.openedByNavigationKeyLongPress = false
+ }
+ } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
+ wordCandidateView.model.selectItem(wordCandidateView.currentIndex)
+ if (!InputContext.preeditText.length)
+ keyboardInputArea.navigateToNextKey(0, 1, true)
}
break
default:
@@ -444,6 +483,9 @@ Item {
keyboardInputArea.initialKey = null
if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (!wordCandidateContextMenu.active) {
+ wordCandidateContextMenu.show(wordCandidateView.currentIndex)
+ wordCandidateContextMenu.openedByNavigationKeyLongPress = true
}
}
}
@@ -507,6 +549,8 @@ Item {
return languagePopupList.highlightItem
} else if (alternativeKeys.listView.count > 0) {
return alternativeKeys.listView.highlightItem
+ } else if (wordCandidateContextMenu.active) {
+ return wordCandidateContextMenuList.highlightItem
} else if (wordCandidateView.count > 0) {
return wordCandidateView.highlightItem
}
@@ -620,6 +664,7 @@ Item {
if (empty)
wordCandidateViewAutoHideTimer.restart()
wordCandidateView.empty = empty
+ keyboard.hideWordCandidateContextMenu()
}
}
Connections {
@@ -664,6 +709,10 @@ Item {
}
}
}
+
+ function longPressItem(index) {
+ return keyboard.showWordCandidateContextMenu(index)
+ }
}
Item {
@@ -1012,6 +1061,7 @@ Item {
}
Item {
+ id: languagePopup
z: 1
anchors.fill: parent
@@ -1021,13 +1071,19 @@ Item {
enabled: languagePopupList.enabled
}
- LanguagePopupList {
+ PopupList {
id: languagePopupList
+ objectName: "languagePopupList"
z: 2
anchors.left: parent.left
anchors.top: parent.top
enabled: false
model: languageListModel
+ delegate: keyboard.style ? keyboard.style.languageListDelegate : null
+ highlight: keyboard.style ? keyboard.style.languageListHighlight : defaultHighlight
+ add: keyboard.style ? keyboard.style.languageListAdd : null
+ remove: keyboard.style ? keyboard.style.languageListRemove : null
+ background: keyboard.style ? keyboard.style.languageListBackground : null
property rect previewRect: Qt.rect(keyboard.x + languagePopupList.x,
keyboard.y + languagePopupList.y,
languagePopupList.width,
@@ -1058,42 +1114,165 @@ Item {
}
}
}
- }
- function showLanguagePopup(parentItem, customLayoutsOnly) {
- if (!languagePopupList.enabled) {
- var locales = keyboard.listLocales(customLayoutsOnly, parent.externalLanguageSwitchEnabled)
- if (parent.externalLanguageSwitchEnabled) {
- var currentIndex = 0
+ function show(locales, parentItem, customLayoutsOnly) {
+ if (!languagePopupList.enabled) {
+ languageListModel.clear()
for (var i = 0; i < locales.length; i++) {
- if (locales[i] === keyboard.locale) {
- currentIndex = i
- break
- }
+ languageListModel.append({localeName: locales[i].name, displayName: locales[i].locale.nativeLanguageName, localeIndex: locales[i].index})
+ if (locales[i].index === keyboard.localeIndex)
+ languagePopupList.currentIndex = i
}
- parent.externalLanguageSwitch(locales, currentIndex)
- return
+ languagePopupList.positionViewAtIndex(languagePopupList.currentIndex, ListView.Center)
+ languagePopupList.anchors.leftMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, (parentItem.width - languagePopupList.width) / 2, 0).x)})
+ languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, 0, -languagePopupList.height).y)})
+ }
+ languagePopupList.enabled = true
+ }
+
+ function hide() {
+ if (languagePopupList.enabled) {
+ languagePopupList.enabled = false
+ languagePopupList.anchors.leftMargin = undefined
+ languagePopupList.anchors.topMargin = undefined
+ languageListModel.clear()
}
- languageListModel.clear()
- for (i = 0; i < locales.length; i++) {
- languageListModel.append({localeName: locales[i].name, displayName: locales[i].locale.nativeLanguageName, localeIndex: locales[i].index})
- if (locales[i].index === keyboard.localeIndex)
- languagePopupList.currentIndex = i
+ }
+ }
+
+ function showLanguagePopup(parentItem, customLayoutsOnly) {
+ var locales = keyboard.listLocales(customLayoutsOnly, parent.externalLanguageSwitchEnabled)
+ if (parent.externalLanguageSwitchEnabled) {
+ var currentIndex = 0
+ for (var i = 0; i < locales.length; i++) {
+ if (locales[i] === keyboard.locale) {
+ currentIndex = i
+ break
+ }
}
- languagePopupList.positionViewAtIndex(languagePopupList.currentIndex, ListView.Center)
- languagePopupList.anchors.leftMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, (parentItem.width - languagePopupList.width) / 2, 0).x)})
- languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, 0, -languagePopupList.height).y)})
+ parent.externalLanguageSwitch(locales, currentIndex)
+ return
}
- languagePopupList.enabled = true
+ languagePopup.show(locales, parentItem, customLayoutsOnly)
}
function hideLanguagePopup() {
- if (languagePopupList.enabled) {
- languagePopupList.enabled = false
- languagePopupList.anchors.leftMargin = undefined
- languagePopupList.anchors.topMargin = undefined
- languageListModel.clear()
+ languagePopup.hide()
+ }
+
+ MouseArea {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: keyboard.parent.parent ? keyboard.parent.parent.height : Screen.height
+ onPressed: keyboard.hideWordCandidateContextMenu()
+ enabled: wordCandidateContextMenuList.enabled
+ }
+
+ Item {
+ id: wordCandidateContextMenu
+ objectName: "wordCandidateContextMenu"
+ z: 1
+ anchors.fill: parent
+ property int previousWordCandidateIndex: -1
+ readonly property bool active: wordCandidateContextMenuList.visible
+ property bool openedByNavigationKeyLongPress
+
+ PopupList {
+ id: wordCandidateContextMenuList
+ objectName: "wordCandidateContextMenuList"
+ z: 2
+ anchors.left: parent.left
+ anchors.top: parent.top
+ enabled: false
+ model: wordCandidateContextMenuListModel
+ property rect previewRect: Qt.rect(keyboard.x + wordCandidateContextMenuList.x,
+ keyboard.y + wordCandidateContextMenuList.y,
+ wordCandidateContextMenuList.width,
+ wordCandidateContextMenuList.height)
}
+
+ ListModel {
+ id: wordCandidateContextMenuListModel
+
+ function selectItem(index) {
+ wordCandidateContextMenu.previousWordCandidateIndex = -1
+ wordCandidateContextMenuList.currentIndex = index
+ keyboard.soundEffect.play(wordCandidateContextMenuList.currentItem.soundEffect)
+ switch (get(index).action) {
+ case "remove":
+ wordCandidateView.model.removeItem(wordCandidateView.currentIndex)
+ break
+ }
+ keyboard.hideWordCandidateContextMenu()
+ }
+ }
+
+ function show(wordCandidateIndex) {
+ if (wordCandidateContextMenu.enabled)
+ wordCandidateContextMenu.hide()
+
+ wordCandidateContextMenuListModel.clear()
+
+ var canRemoveSuggestion = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.CanRemoveSuggestionRole)
+ if (canRemoveSuggestion) {
+ var dictionaryType = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.DictionaryTypeRole)
+ var removeItemText;
+ switch (dictionaryType) {
+ case SelectionListModel.UserDictionary:
+ //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the user dictionary.
+ removeItemText = qsTr("Remove from dictionary")
+ break
+ case SelectionListModel.DefaultDictionary:
+ // Fallthrough
+ default:
+ //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the default dictionary.
+ removeItemText = qsTr("Block word")
+ break
+ }
+ wordCandidateContextMenuListModel.append({action: "remove", display: removeItemText, wordCompletionLength: 0})
+ }
+
+ if (wordCandidateContextMenuListModel.count === 0)
+ return
+
+ previousWordCandidateIndex = wordCandidateView.currentIndex
+ wordCandidateView.currentIndex = wordCandidateIndex
+
+ wordCandidateContextMenuList.anchors.leftMargin = Qt.binding(function() {
+ var leftBorder = Math.round(wordCandidateView.mapFromItem(wordCandidateView.currentItem, (wordCandidateView.currentItem.width - wordCandidateContextMenuList.width) / 2, 0).x)
+ var rightBorder = Math.round(wordCandidateContextMenuList.parent.width - wordCandidateContextMenuList.width)
+ return Math.min(leftBorder, rightBorder)
+ })
+
+ wordCandidateContextMenuList.enabled = true
+ }
+
+ function hide() {
+ if (wordCandidateContextMenuList.enabled) {
+ if (previousWordCandidateIndex !== -1) {
+ wordCandidateView.currentIndex = previousWordCandidateIndex
+ previousWordCandidateIndex = -1
+ }
+ wordCandidateContextMenuList.enabled = false
+ wordCandidateContextMenuList.anchors.leftMargin = undefined
+ wordCandidateContextMenuListModel.clear()
+ }
+ openedByNavigationKeyLongPress = false
+ }
+
+ function selectCurrentItem() {
+ if (active && wordCandidateContextMenuList.currentIndex !== -1)
+ wordCandidateContextMenuListModel.selectItem(wordCandidateContextMenuList.currentIndex)
+ }
+ }
+
+ function showWordCandidateContextMenu(wordCandidateIndex) {
+ wordCandidateContextMenu.show(wordCandidateIndex)
+ }
+
+ function hideWordCandidateContextMenu() {
+ wordCandidateContextMenu.hide()
}
function updateInputMethod() {
diff --git a/src/virtualkeyboard/content/components/LanguagePopupList.qml b/src/virtualkeyboard/content/components/PopupList.qml
index 2c8b8c99..dcd02ee1 100644
--- a/src/virtualkeyboard/content/components/LanguagePopupList.qml
+++ b/src/virtualkeyboard/content/components/PopupList.qml
@@ -28,15 +28,14 @@
****************************************************************************/
import QtQuick 2.0
-import QtQuick.VirtualKeyboard 2.1
+import QtQuick.VirtualKeyboard 2.3
ListView {
- id: languagePopupList
- objectName: "languagePopupList"
-
property int maxVisibleItems: 5
readonly property int preferredVisibleItems: count < maxVisibleItems ? count : maxVisibleItems
readonly property real contentWidth: contentItem.childrenRect.width
+ property alias background: popupListBackground.sourceComponent
+ property alias defaultHighlight: defaultHighlight
clip: true
visible: enabled && count > 0
@@ -44,12 +43,12 @@ ListView {
height: currentItem ? currentItem.height * preferredVisibleItems + (spacing * preferredVisibleItems - 1) : 0
orientation: ListView.Vertical
snapMode: ListView.SnapToItem
- delegate: keyboard.style.languageListDelegate
- highlight: keyboard.style.languageListHighlight ? keyboard.style.languageListHighlight : defaultHighlight
+ delegate: keyboard.style.popupListDelegate
+ highlight: keyboard.style.popupListHighlight ? keyboard.style.popupListHighlight : defaultHighlight
highlightMoveDuration: 0
highlightResizeDuration: 0
- add: keyboard.style.languageListAdd
- remove: keyboard.style.languageListRemove
+ add: keyboard.style.popupListAdd
+ remove: keyboard.style.popupListRemove
keyNavigationWraps: true
onCurrentItemChanged: if (currentItem) keyboard.soundEffect.register(currentItem.soundEffect)
@@ -60,7 +59,8 @@ ListView {
}
Loader {
- sourceComponent: keyboard.style.languageListBackground
+ id: popupListBackground
+ sourceComponent: keyboard.style.popupListBackground
anchors.fill: parent
z: -1
}
diff --git a/src/virtualkeyboard/content/components/WordCandidatePopupList.qml b/src/virtualkeyboard/content/components/WordCandidatePopupList.qml
index 7740cbf9..e255142a 100644
--- a/src/virtualkeyboard/content/components/WordCandidatePopupList.qml
+++ b/src/virtualkeyboard/content/components/WordCandidatePopupList.qml
@@ -28,12 +28,11 @@
****************************************************************************/
import QtQuick 2.0
-import QtQuick.VirtualKeyboard 2.1
+import QtQuick.VirtualKeyboard 2.3
-ListView {
+PopupList {
id: wordCandidatePopupList
- property int maxVisibleItems: 5
readonly property int preferredVisibleItems: {
if (!currentItem)
return 0
@@ -43,13 +42,10 @@ ListView {
--result
return result
}
- readonly property real contentWidth: contentItem.childrenRect.width
readonly property bool flipVertical: currentItem &&
Qt.inputMethod.cursorRectangle.y + (Qt.inputMethod.cursorRectangle.height / 2) > (parent.height / 2) &&
Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height + (currentItem.height * 2) > parent.height
- clip: true
- visible: enabled && count > 0
height: currentItem ? currentItem.height * preferredVisibleItems + (spacing * preferredVisibleItems - 1) : 0
Binding {
target: wordCandidatePopupList
@@ -66,19 +62,9 @@ ListView {
value: Math.round(wordCandidatePopupList.flipVertical ? Qt.inputMethod.cursorRectangle.y - wordCandidatePopupList.height : Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height)
when: wordCandidatePopupList.visible
}
- orientation: ListView.Vertical
- snapMode: ListView.SnapToItem
- delegate: keyboard.style.popupListDelegate
- highlight: keyboard.style.popupListHighlight ? keyboard.style.popupListHighlight : null
- highlightMoveDuration: 0
- highlightResizeDuration: 0
- add: keyboard.style.popupListAdd
- remove: keyboard.style.popupListRemove
- keyNavigationWraps: true
model: enabled ? InputContext.inputEngine.wordCandidateListModel : null
onContentWidthChanged: viewResizeTimer.restart()
- onCurrentItemChanged: if (currentItem) keyboard.soundEffect.register(currentItem.soundEffect)
Timer {
id: viewResizeTimer
@@ -92,10 +78,4 @@ ListView {
onActiveItemChanged: wordCandidatePopupList.currentIndex = index
onItemSelected: if (wordCandidatePopupList.currentItem) keyboard.soundEffect.play(wordCandidatePopupList.currentItem.soundEffect)
}
-
- Loader {
- sourceComponent: keyboard.style.popupListBackground
- anchors.fill: parent
- z: -1
- }
}
diff --git a/src/virtualkeyboard/content/content.qrc b/src/virtualkeyboard/content/content.qrc
index d29dbe33..1e6392f4 100644
--- a/src/virtualkeyboard/content/content.qrc
+++ b/src/virtualkeyboard/content/content.qrc
@@ -27,7 +27,7 @@
<file>components/TraceInputArea.qml</file>
<file>components/HandwritingModeKey.qml</file>
<file>components/WordCandidatePopupList.qml</file>
- <file>components/LanguagePopupList.qml</file>
+ <file>components/PopupList.qml</file>
<file>components/SelectionControl.qml</file>
<file>components/ShadowInputControl.qml</file>
<file>components/InputModeKey.qml</file>
diff --git a/src/virtualkeyboard/selectionlistmodel.cpp b/src/virtualkeyboard/selectionlistmodel.cpp
index 1f8f9db9..33ec8de9 100644
--- a/src/virtualkeyboard/selectionlistmodel.cpp
+++ b/src/virtualkeyboard/selectionlistmodel.cpp
@@ -118,6 +118,22 @@ public:
the completion part expressed as the
number of characters counted from the
end of the string.
+ \value DictionaryTypeRole
+ An integer specifying \ l {QtVirtualKeyboard::SelectionListModel::DictionaryType}{dictionary type}.
+ \value CanRemoveSuggestionRole
+ A boolean value indicating if the word candidate
+ can be removed from dictionary.
+*/
+
+/*!
+ \enum QtVirtualKeyboard::SelectionListModel::DictionaryType
+
+ This enum specifies the dictionary type of a word.
+
+ \value DefaultDictionary
+ The word candidate is from the default dictionary.
+ \value UserDictionary
+ The word candidate is from the user dictionary.
*/
SelectionListModel::SelectionListModel(QObject *parent) :
@@ -226,6 +242,14 @@ void SelectionListModel::selectItem(int index)
}
}
+void SelectionListModel::removeItem(int index)
+{
+ Q_D(SelectionListModel);
+ if (index >= 0 && index < d->rowCount && d->dataSource) {
+ d->dataSource->selectionListRemoveItem(d->type, index);
+ }
+}
+
/*!
* \internal
*/
diff --git a/src/virtualkeyboard/selectionlistmodel.h b/src/virtualkeyboard/selectionlistmodel.h
index ec9b84e3..22d0ed3d 100644
--- a/src/virtualkeyboard/selectionlistmodel.h
+++ b/src/virtualkeyboard/selectionlistmodel.h
@@ -56,11 +56,19 @@ public:
enum Role
{
DisplayRole = Qt::DisplayRole,
- WordCompletionLengthRole = Qt::UserRole + 1
+ WordCompletionLengthRole = Qt::UserRole + 1,
+ DictionaryTypeRole,
+ CanRemoveSuggestionRole,
+ };
+ enum DictionaryType
+ {
+ DefaultDictionary = 0,
+ UserDictionary
};
Q_ENUM(Type)
Q_ENUM(Role)
+ Q_ENUM(DictionaryType)
~SelectionListModel();
void setDataSource(AbstractInputMethod *dataSource, Type type);
@@ -72,6 +80,7 @@ public:
int count() const;
Q_INVOKABLE void selectItem(int index);
+ Q_INVOKABLE void removeItem(int index);
Q_INVOKABLE QVariant dataAt(int index, int role = Qt::DisplayRole) const;
Q_SIGNALS:
@@ -92,5 +101,6 @@ QT_END_NAMESPACE
Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE(QtVirtualKeyboard)::SelectionListModel::Type)
Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE(QtVirtualKeyboard)::SelectionListModel::Role)
+Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE(QtVirtualKeyboard)::SelectionListModel::DictionaryType)
#endif // SELECTIONLISTMODEL_H
diff --git a/tests/auto/inputpanel/data/inputpanel/inputpanel.qml b/tests/auto/inputpanel/data/inputpanel/inputpanel.qml
index a467fb15..c3682b8a 100644
--- a/tests/auto/inputpanel/data/inputpanel/inputpanel.qml
+++ b/tests/auto/inputpanel/data/inputpanel/inputpanel.qml
@@ -67,6 +67,7 @@ InputPanel {
naviationHighlight.widthAnimation.running ||
naviationHighlight.heightAnimation.running
readonly property var wordCandidateView: Utils.findChildByProperty(keyboard, "objectName", "wordCandidateView", null)
+ readonly property var wordCandidateContextMenu: Utils.findChildByProperty(keyboard, "objectName", "wordCandidateContextMenu", null)
readonly property var shadowInputControl: Utils.findChildByProperty(keyboard, "objectName", "shadowInputControl", null)
readonly property var shadowInput: Utils.findChildByProperty(keyboard, "objectName", "shadowInput", null)
readonly property var selectionControl: Utils.findChildByProperty(inputPanel, "objectName", "selectionControl", null)
@@ -178,6 +179,12 @@ InputPanel {
}
SignalSpy {
+ id: wordCandidateContextMenuActiveSpy
+ target: wordCandidateContextMenu
+ signalName: "onActiveChanged"
+ }
+
+ SignalSpy {
id: shiftStateSpy
target: InputContext
signalName: "onShiftChanged"
@@ -530,6 +537,14 @@ InputPanel {
InputContext.shiftHandler.toggleShift()
}
+ function setShift(shift) {
+ InputContext.shift = shift
+ }
+
+ function setCapsLock(capsLock) {
+ InputContext.capsLock = capsLock
+ }
+
function style() {
return VirtualKeyboardSettings.styleName
}
@@ -583,6 +598,48 @@ InputPanel {
return true
}
+ function selectionListCurrentIndex() {
+ return inputPanel.wordCandidateView.currentIndex
+ }
+
+ function selectionListSuggestionIsFromUserDictionary() {
+ if (!inputPanel.wordCandidateView.currentItem)
+ return false
+ var dictionaryType = inputPanel.wordCandidateView.model.dataAt(inputPanel.wordCandidateView.currentIndex, SelectionListModel.DictionaryTypeRole)
+ return dictionaryType !== undefined && dictionaryType === SelectionListModel.UserDictionary
+ }
+
+ function openWordCandidateContextMenu() {
+ if (!inputPanel.wordCandidateView.currentItem)
+ return false
+ testcase.wait(200)
+ wordCandidateContextMenuActiveSpy.clear()
+ testcase.mousePress(inputPanel.wordCandidateView.currentItem)
+ wordCandidateContextMenuActiveSpy.wait()
+ testcase.mouseRelease(inputPanel.wordCandidateView.currentItem)
+ return wordCandidateContextMenu.active
+ }
+
+ function selectItemFromWordCandidateContextMenu(index) {
+ if (!inputPanel.wordCandidateView.currentItem)
+ return false
+ if (!wordCandidateContextMenu.active)
+ return false
+ var wordCandidateContextMenuList = Utils.findChildByProperty(keyboard, "objectName", "wordCandidateContextMenuList", null)
+ if (wordCandidateContextMenuList.currentIndex !== index) {
+ wordCandidateContextMenuList.currentIndex = index
+ testcase.waitForRendering(inputPanel)
+ }
+ if (!wordCandidateContextMenuList.currentItem)
+ return false
+ var itemPos = inputPanel.mapFromItem(wordCandidateContextMenuList.currentItem,
+ wordCandidateContextMenuList.currentItem.width / 2,
+ wordCandidateContextMenuList.currentItem.height / 2)
+ testcase.mouseClick(inputPanel, itemPos.x, itemPos.y, Qt.LeftButton, 0, 20)
+ testcase.waitForRendering(inputPanel)
+ return true
+ }
+
function setHandwritingMode(enabled) {
if (inputPanel.keyboard.handwritingMode !== enabled) {
if (!enabled || inputPanel.keyboard.isHandwritingAvailable())
diff --git a/tests/auto/inputpanel/data/tst_inputpanel.qml b/tests/auto/inputpanel/data/tst_inputpanel.qml
index 83c97593..e2efe8b0 100644
--- a/tests/auto/inputpanel/data/tst_inputpanel.qml
+++ b/tests/auto/inputpanel/data/tst_inputpanel.qml
@@ -1820,19 +1820,14 @@ Rectangle {
skip("Prediction/spell correction not enabled")
for (var len = 1; len <= 5; ++len) {
- inputPanel.wordCandidateListChangedSpy.clear()
inputPanel.virtualKeyClick("z")
- waitForRendering(inputPanel)
- if (len >= 3) {
- if (data.wclAutoCommitWord)
- tryVerify(function() { return inputPanel.wordCandidateView.model.count === 0 }, 500)
- else
- wait(500)
+ if (len >= 2) {
+ inputPanel.wordCandidateListChangedSpy.clear()
+ inputPanel.wordCandidateListChangedSpy.wait()
if (inputPanel.wordCandidateView.model.count <= 1)
break
}
}
- waitForRendering(inputPanel)
if (data.wclAutoCommitWord)
compare(inputPanel.wordCandidateView.model.count, 0)
@@ -2018,5 +2013,67 @@ Rectangle {
compare(inputPanel.shadowInput.text, "")
}
+ function test_userDictionary_data() {
+ return [
+ { inputSequence: ['a','s','d','f'], initShift: false },
+ { inputSequence: ['a','s','d'], initShift: false, expectedSuggestion: "asdf", suggestionIsFromUserDictionary: true },
+ { inputSequence: ['a','s','d'], initShift: true, expectedSuggestion: "Asdf", suggestionIsFromUserDictionary: true },
+ //
+ { inputSequence: ['s','d','f','a'], initShift: true },
+ { inputSequence: ['s','d','f'], initShift: true, expectedSuggestion: "Sdfa", suggestionIsFromUserDictionary: true },
+ { inputSequence: ['s','d','f'], initShift: false, expectedSuggestion: "sdfa", suggestionIsFromUserDictionary: true, removeSuggestion: true },
+ //
+ { inputSequence: ['d','f','a','s'], initCapsLock: true },
+ { inputSequence: ['d','f','a'], initCapsLock: true, expectedSuggestion: "DFAS", suggestionIsFromUserDictionary: true },
+ { inputSequence: ['d','f','a'], initShift: false, unexpectedSuggestion: "dfas", suggestionIsFromUserDictionary: true },
+ //
+ { inputSequence: ['f','a','s','d'], initShift: false, initInputMethodHints: Qt.ImhSensitiveData },
+ { inputSequence: ['f','a','s'], initShift: false, unexpectedSuggestion: "fasd" },
+ { inputSequence: ['f','a','s'], initShift: true, unexpectedSuggestion: "Fasd"},
+ //
+ { initLocale: "en_GB", inputSequence: "windo", expectedSuggestion: "Window", suggestionIsFromUserDictionary: false, removeSuggestion: true },
+ { initLocale: "en_GB", inputSequence: "window", },
+ { initLocale: "en_GB", inputSequence: "windo", expectedSuggestion: "Window", suggestionIsFromUserDictionary: false },
+ ]
+ }
+
+ function test_userDictionary(data) {
+ prepareTest(data, true)
+
+ if (!inputPanel.wordCandidateListVisibleHint)
+ skip("Prediction/spell correction not enabled")
+
+ if (data.hasOwnProperty("initShift"))
+ inputPanel.setShift(data.initShift)
+ if (data.hasOwnProperty("initCapsLock"))
+ inputPanel.setCapsLock(data.initCapsLock)
+
+ for (var inputIndex in data.inputSequence)
+ inputPanel.virtualKeyClick(data.inputSequence[inputIndex])
+
+ if (data.hasOwnProperty("expectedSuggestion")) {
+ tryVerify(function() {return inputPanel.selectionListSearchSuggestion(data.expectedSuggestion)}, 1000, "The expected spell correction suggestion \"%1\" was not found".arg(data.expectedSuggestion))
+ verify(inputPanel.selectionListCurrentIndex() > 0)
+ if (data.hasOwnProperty("suggestionIsFromUserDictionary"))
+ compare(inputPanel.selectionListSuggestionIsFromUserDictionary(), data.suggestionIsFromUserDictionary)
+ if (data.hasOwnProperty("removeSuggestion") && data.removeSuggestion) {
+ verify(inputPanel.openWordCandidateContextMenu())
+ inputPanel.wordCandidateListChangedSpy.clear()
+ verify(inputPanel.selectItemFromWordCandidateContextMenu(0))
+ inputPanel.wordCandidateListChangedSpy.wait()
+ tryVerify(function() {return !inputPanel.selectionListSearchSuggestion(data.expectedSuggestion)}, 1000, "An unexpected spell correction suggestion \"%1\" was found".arg(data.unexpectedSuggestion))
+ } else {
+ inputPanel.selectionListSelectCurrentItem()
+ }
+ } else if (data.hasOwnProperty("unexpectedSuggestion")) {
+ var oldIndex = inputPanel.selectionListCurrentIndex()
+ tryVerify(function() {return !inputPanel.selectionListSearchSuggestion(data.unexpectedSuggestion)}, 1000, "An unexpected spell correction suggestion \"%1\" was found".arg(data.unexpectedSuggestion))
+ compare(inputPanel.selectionListCurrentIndex(), oldIndex)
+ } else {
+ inputPanel.selectionListSelectCurrentItem()
+ }
+
+ Qt.inputMethod.reset()
+ }
}
}
diff --git a/tests/auto/inputpanel/tst_inputpanel.cpp b/tests/auto/inputpanel/tst_inputpanel.cpp
index 8b84f67b..409383c7 100644
--- a/tests/auto/inputpanel/tst_inputpanel.cpp
+++ b/tests/auto/inputpanel/tst_inputpanel.cpp
@@ -29,7 +29,22 @@
#include <QtQuickTest/quicktest.h>
#include <QByteArray>
+#include <QStandardPaths>
+#include <QFileInfo>
+#include <QDir>
static bool s_configEnv = qputenv("QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));
+static bool initStandardPaths() {
+ QStandardPaths::setTestModeEnabled(true);
+ auto configLocations = QStringList()
+ << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/qtvirtualkeyboard"
+ << QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/qtvirtualkeyboard";
+ for (const QString &configLocation : configLocations) {
+ if (configLocation != "/qtvirtualkeyboard")
+ QDir(configLocation).removeRecursively();
+ }
+ return true;
+}
+static bool s_initStandardPaths = initStandardPaths();
QUICK_TEST_MAIN(inputpanel)