aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGravatar ctrlaltca2025-12-22 11:51:29 +0100
committerGravatar GitHub2025-12-22 11:51:29 +0100
commite6317d4552dd84c2551bbf63b08e55205e8c9f68 (patch)
tree788a84f42379dd15212d9f9fe031518bed638def /src
parentWindows builds: add libffi, too (#2742) (diff)
downloadKVIrc-e6317d4552dd84c2551bbf63b08e55205e8c9f68.tar.gz
KVIrc-e6317d4552dd84c2551bbf63b08e55205e8c9f68.tar.bz2
KVIrc-e6317d4552dd84c2551bbf63b08e55205e8c9f68.zip
Add a spellchecker backend based on windows >=8 integrated spellchecker (#2743)
* Add a spellchecker backend based on windows >=8 integrated spellchecker * CI: remove enchant from windows builds * Assume Qt5 is Windows 7, Qt6 is Windows >= 10 * Qt5 build is picking up a foreign libenchant from the build system, disable it
Diffstat (limited to 'src')
-rw-r--r--src/kvilib/config/KviBuildInfo.cpp3
-rw-r--r--src/kvirc/ui/KviInputEditor.cpp10
-rw-r--r--src/modules/CMakeLists.txt4
-rw-r--r--src/modules/options/OptionsWidget_textEncoding.cpp4
-rw-r--r--src/modules/spellchecker/libkvispellchecker.cpp157
5 files changed, 154 insertions, 24 deletions
diff --git a/src/kvilib/config/KviBuildInfo.cpp b/src/kvilib/config/KviBuildInfo.cpp
index 131377702..5f22f4dd9 100644
--- a/src/kvilib/config/KviBuildInfo.cpp
+++ b/src/kvilib/config/KviBuildInfo.cpp
@@ -86,6 +86,9 @@ const QStringList feature_list{
#ifdef COMPILE_ENCHANT_SUPPORT
"Enchant",
#endif
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ "Winspell",
+#endif
// This is not in "else" branch of below because some scripts may need it,
// eg. /echo $features(Qt5) to determine if some features only available when Qt5 are present
"Qt5",
diff --git a/src/kvirc/ui/KviInputEditor.cpp b/src/kvirc/ui/KviInputEditor.cpp
index 46134cd83..f22bf7503 100644
--- a/src/kvirc/ui/KviInputEditor.cpp
+++ b/src/kvirc/ui/KviInputEditor.cpp
@@ -335,7 +335,7 @@ int KviInputEditor::heightHint() const
bool KviInputEditor::checkWordSpelling(const QString & szWord)
{
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
if(szWord.isEmpty())
return true;
@@ -367,7 +367,7 @@ bool KviInputEditor::checkWordSpelling(const QString & szWord)
void KviInputEditor::splitTextIntoSpellCheckerBlocks(const QString & szText, std::vector<KviInputEditorSpellCheckerBlock> & lBuffer)
{
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
if(szText.isEmpty())
return;
@@ -524,7 +524,7 @@ void KviInputEditor::rebuildTextBlocks()
std::vector<KviInputEditorSpellCheckerBlock> lSpellCheckerBlocks;
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
splitTextIntoSpellCheckerBlocks(m_szTextBuffer, lSpellCheckerBlocks);
#else
ADD_SPELLCHECKER_BLOCK(lSpellCheckerBlocks, m_szTextBuffer, 0, false, true);
@@ -1155,7 +1155,7 @@ void KviInputEditor::showContextPopup(const QPoint & pos)
pAction = g_pInputPopup->addAction(*(g_pIconManager->getSmallIcon(KviIconManager::BigGrin)), __tr2qs("Insert Icon"), this, SLOT(popupTextIconWindow()));
pAction->setEnabled(!m_bReadOnly);
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
// check if the cursor is in a spell-checkable block
std::vector<KviInputEditorSpellCheckerBlock> lBuffer;
@@ -1234,7 +1234,7 @@ void KviInputEditor::fillSpellCheckerCorrectionsPopup()
pWidgetAction->setDefaultWidget(pLabel);
m_SpellCheckerPopup.addAction(pWidgetAction);
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
// check if the cursor is in a spellcheckable block
std::vector<KviInputEditorSpellCheckerBlock> lBuffer;
diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt
index f48633919..65679bf96 100644
--- a/src/modules/CMakeLists.txt
+++ b/src/modules/CMakeLists.txt
@@ -52,6 +52,6 @@ if(COMPILE_PYTHON_SUPPORT)
subdirs(python)
endif()
-if(COMPILE_ENCHANT_SUPPORT)
+if(COMPILE_ENCHANT_SUPPORT OR COMPILE_WINSPELLCHECKER_SUPPORT)
subdirs(spellchecker)
-endif() \ No newline at end of file
+endif()
diff --git a/src/modules/options/OptionsWidget_textEncoding.cpp b/src/modules/options/OptionsWidget_textEncoding.cpp
index d23416d8b..411defe01 100644
--- a/src/modules/options/OptionsWidget_textEncoding.cpp
+++ b/src/modules/options/OptionsWidget_textEncoding.cpp
@@ -133,7 +133,7 @@ OptionsWidget_textEncoding::OptionsWidget_textEncoding(QWidget * parent)
else
m_pForcedLocaleCombo->setCurrentIndex(iMatch);
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
{
gbox = addGroupBox(0, 2, 0, 2, Qt::Horizontal, __tr2qs_ctx("Spell Checker Dictionaries", "options"));
@@ -226,7 +226,7 @@ void OptionsWidget_textEncoding::commit()
}
}
-#ifdef COMPILE_ENCHANT_SUPPORT
+#if defined(COMPILE_ENCHANT_SUPPORT) || defined(COMPILE_WINSPELLCHECKER_SUPPORT)
QStringList wantedDictionaries;
for(int i = 0; i < m_pSpellCheckerDictionaries->rowCount(); ++i)
{
diff --git a/src/modules/spellchecker/libkvispellchecker.cpp b/src/modules/spellchecker/libkvispellchecker.cpp
index fa76521b4..be04c564f 100644
--- a/src/modules/spellchecker/libkvispellchecker.cpp
+++ b/src/modules/spellchecker/libkvispellchecker.cpp
@@ -25,10 +25,20 @@
#include "KviModule.h"
#include "KviOptions.h"
+#ifdef COMPILE_ENCHANT_SUPPORT
#include <enchant++.h>
static enchant::Broker * g_pEnchantBroker = nullptr;
static KviPointerList<enchant::Dict> * g_pEnchantDicts = nullptr;
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+#include <spellcheck.h>
+#include <winerror.h>
+
+static ISpellCheckerFactory *g_pWinSpellFactory = nullptr;
+static KviPointerList<ISpellChecker> * g_pWinSpellDicts = nullptr;
+#endif
/*
@doc: spellchecker.available_dictionaries
@@ -45,23 +55,46 @@ static KviPointerList<enchant::Dict> * g_pEnchantDicts = nullptr;
value is name of provider for that language (e.g. Aspell).
*/
+#ifdef COMPILE_ENCHANT_SUPPORT
static void spellchecker_enumerate_dicts(
- const char * szLang,
- const char * /*szName*/,
- const char * szDesc,
- const char * /*szFile*/,
- void * pData)
+ const char * szLang,
+ const char * /*szName*/,
+ const char * szDesc,
+ const char * /*szFile*/,
+ void * pData)
{
KviKvsHash * pHash = reinterpret_cast<KviKvsHash *>(pData);
pHash->set(szLang, new KviKvsVariant(szDesc));
}
+#endif
static bool spellchecker_kvs_available_dictionaries(KviKvsModuleFunctionCall * c)
{
KVSM_PARAMETERS_BEGIN(c)
KVSM_PARAMETERS_END(c)
KviKvsHash * pHash = new KviKvsHash;
+
+#ifdef COMPILE_ENCHANT_SUPPORT
g_pEnchantBroker->list_dicts(spellchecker_enumerate_dicts, pHash);
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ IEnumString *enumDicts;
+
+ if(SUCCEEDED(g_pWinSpellFactory->get_SupportedLanguages(&enumDicts))) {
+ HRESULT hr = S_OK;
+ while (S_OK == hr) {
+ LPOLESTR string = nullptr;
+ hr = enumDicts->Next(1, &string, nullptr);
+ if (S_OK == hr) {
+ pHash->set(QString::fromWCharArray(string), new KviKvsVariant(QStringLiteral("Windows Spellchecker")));
+ CoTaskMemFree(string);
+ }
+ }
+ enumDicts->Release();
+ }
+#endif
+
c->returnValue()->setHash(pHash);
return true;
}
@@ -87,10 +120,34 @@ static bool spellchecker_kvs_check(KviKvsModuleFunctionCall * c)
KVSM_PARAMETER("word", KVS_PT_STRING, 0, szWord)
KVSM_PARAMETERS_END(c)
- bool bResult = g_pEnchantDicts->isEmpty();
+ bool bResult = false;
+
+#ifdef COMPILE_ENCHANT_SUPPORT
+ bResult = g_pEnchantDicts->isEmpty();
KviPointerListIterator<enchant::Dict> it(*g_pEnchantDicts);
for(bool b = it.moveFirst(); b; b = it.moveNext())
bResult |= (*it)->check(szWord.toStdString());
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ bResult = g_pWinSpellDicts->isEmpty();
+
+ IEnumSpellingError *errors = nullptr;
+ ISpellingError *error = nullptr;
+
+ KviPointerListIterator<ISpellChecker> it(*g_pWinSpellDicts);
+ for(bool b = it.moveFirst(); b; b = it.moveNext()) {
+ if(SUCCEEDED((*it)->Check(szWord.toStdWString().c_str(), &errors))) {
+ if (errors->Next(&error) == S_OK) {
+ error->Release();
+ } else {
+ // no error found for this dict
+ bResult = true;
+ }
+ errors->Release();
+ }
+ }
+#endif
c->returnValue()->setBoolean(bResult);
return true;
@@ -121,6 +178,7 @@ static bool spellchecker_kvs_suggestions(KviKvsModuleFunctionCall * c)
QHash<QString, int> hAllSuggestions;
+#ifdef COMPILE_ENCHANT_SUPPORT
if(!g_pEnchantDicts->isEmpty())
{
std::vector<std::string> suggestions;
@@ -134,6 +192,31 @@ static bool spellchecker_kvs_suggestions(KviKvsModuleFunctionCall * c)
}
}
}
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ if(!g_pWinSpellDicts->isEmpty())
+ {
+ IEnumString *suggestions;
+
+ KviPointerListIterator<ISpellChecker> it(*g_pWinSpellDicts);
+ for(bool b = it.moveFirst(); b; b = it.moveNext())
+ {
+ if(SUCCEEDED((*it)->Suggest(szWord.toStdWString().c_str(), &suggestions))) {
+ HRESULT hr = S_OK;
+ while (S_OK == hr) {
+ LPOLESTR suggestion = nullptr;
+ hr = suggestions->Next(1, &suggestion, nullptr);
+ if (S_OK == hr) {
+ hAllSuggestions.insert(QString::fromWCharArray(suggestion), 1);
+ CoTaskMemFree(suggestion);
+ }
+ }
+ suggestions->Release();
+ }
+ }
+ }
+#endif
KviKvsArray * pArray = new KviKvsArray();
@@ -146,8 +229,17 @@ static bool spellchecker_kvs_suggestions(KviKvsModuleFunctionCall * c)
static void spellchecker_reload_dicts()
{
+#ifdef COMPILE_ENCHANT_SUPPORT
while(!g_pEnchantDicts->isEmpty())
delete g_pEnchantDicts->takeFirst();
+#endif
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ while(!g_pWinSpellDicts->isEmpty()) {
+ auto pDict = g_pWinSpellDicts->takeFirst();
+ pDict->Release();
+ delete pDict;
+ }
+#endif
const QStringList & wantedDictionaries = KVI_OPTION_STRINGLIST(KviOption_stringlistSpellCheckerDictionaries);
foreach(QString szLang, wantedDictionaries)
@@ -155,6 +247,7 @@ static void spellchecker_reload_dicts()
if(szLang.isEmpty())
continue;
+#ifdef COMPILE_ENCHANT_SUPPORT
try {
enchant::Dict * pDict = g_pEnchantBroker->request_dict(szLang.toUtf8().data());
if(pDict)
@@ -164,6 +257,15 @@ static void spellchecker_reload_dicts()
} catch (enchant::Exception e) {
qDebug("Can't load spellchecker dictionary %s: %s", szLang.toUtf8().data(), e.what());
}
+#endif
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ ISpellChecker *pDict;
+ if(SUCCEEDED(g_pWinSpellFactory->CreateSpellChecker(szLang.toStdWString().c_str(), &pDict))) {
+ g_pWinSpellDicts->append(pDict);
+ } else {
+ qDebug("Can't load spellchecker dictionary %s", szLang.toUtf8().data());
+ }
+#endif
}
}
@@ -191,8 +293,18 @@ static bool spellchecker_kvs_reload_dictionaries(KviKvsModuleCommandCall * c)
static bool spellchecker_module_init(KviModule * m)
{
+#ifdef COMPILE_ENCHANT_SUPPORT
g_pEnchantBroker = new enchant::Broker();
g_pEnchantDicts = new KviPointerList<enchant::Dict>(/* bAutoDelete = */ false);
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ if (FAILED(CoCreateInstance(__uuidof(SpellCheckerFactory), nullptr,
+ CLSCTX_INPROC_SERVER, IID_PPV_ARGS (&g_pWinSpellFactory)))) {
+ return false;
+ }
+ g_pWinSpellDicts = new KviPointerList<ISpellChecker>(/* bAutoDelete = */ false);
+#endif
spellchecker_reload_dicts();
@@ -205,6 +317,7 @@ static bool spellchecker_module_init(KviModule * m)
static bool spellchecker_module_cleanup(KviModule *)
{
+#ifdef COMPILE_ENCHANT_SUPPORT
while(!g_pEnchantDicts->isEmpty())
delete g_pEnchantDicts->takeFirst();
@@ -212,6 +325,20 @@ static bool spellchecker_module_cleanup(KviModule *)
g_pEnchantDicts = nullptr;
delete g_pEnchantBroker;
g_pEnchantBroker = nullptr;
+#endif
+
+#ifdef COMPILE_WINSPELLCHECKER_SUPPORT
+ while(!g_pWinSpellDicts->isEmpty()) {
+ auto pDict = g_pWinSpellDicts->takeFirst();
+ pDict->Release();
+ delete pDict;
+ }
+
+ g_pWinSpellFactory->Release();
+ delete g_pWinSpellFactory;
+ g_pWinSpellFactory = nullptr;
+#endif
+
return true;
}
@@ -221,12 +348,12 @@ static bool spellchecker_module_can_unload(KviModule *)
}
KVIRC_MODULE(
- "SpellChecker", // module name
- "4.0.0", // module version
- "Copyright (C) 2014 Alexey Sokolov (sokolov at google dot com)", // author & (C)
- "Spell checker",
- spellchecker_module_init,
- spellchecker_module_can_unload,
- 0,
- spellchecker_module_cleanup,
- 0)
+ "SpellChecker", // module name
+ "4.0.0", // module version
+ "Copyright (C) 2014 Alexey Sokolov (sokolov at google dot com)", // author & (C)
+ "Spell checker",
+ spellchecker_module_init,
+ spellchecker_module_can_unload,
+ 0,
+ spellchecker_module_cleanup,
+ 0)