//=============================================================================
//
// File : libkvispellchecker.cpp
// Creation date : Thu Dec 26 2014 15:12:12 GMT by Alexey Sokolov
//
// This file is part of the KVIrc IRC client distribution
// Copyright © 2014 Alexey Sokolov <sokolov@google.com>
//
// This program is FREE software. You can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the HOPE that it will be USEFUL,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================
#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
@type:
function
@title:
$spellchecker.available_dictionaries
@short:
Return available dictionaries.
@syntax:
<hash> $spellchecker.available_dictionaries
@description:
This function returns a hash. For every supported dictionary, key is the language code,
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)
{
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;
}
/*
@doc: spellchecker.check
@type:
function
@title:
$spellchecker.check
@short:
Check a single work for spelling mistakes.
@syntax:
<bool> $spellchecker.check(<word:string>)
@description:
This function returns true if the word is spelled correctly.
*/
static bool spellchecker_kvs_check(KviKvsModuleFunctionCall * c)
{
QString szWord;
KVSM_PARAMETERS_BEGIN(c)
KVSM_PARAMETER("word", KVS_PT_STRING, 0, szWord)
KVSM_PARAMETERS_END(c)
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;
}
/*
@doc: spellchecker.suggestions
@type:
function
@title:
$spellchecker.suggestions
@short:
Get spelling suggestions for a single word
@syntax:
<array> $spellchecker.suggestions(<word:string>)
@description:
This function returns the suggestions for the specified word.
If the word seems to be spelled correctly or there are no dictionaries
selected then the function returns an empty array.
*/
static bool spellchecker_kvs_suggestions(KviKvsModuleFunctionCall * c)
{
QString szWord;
KVSM_PARAMETERS_BEGIN(c)
KVSM_PARAMETER("word", KVS_PT_STRING, 0, szWord)
KVSM_PARAMETERS_END(c)
QHash<QString, int> hAllSuggestions;
#ifdef COMPILE_ENCHANT_SUPPORT
if(!g_pEnchantDicts->isEmpty())
{
std::vector<std::string> suggestions;
KviPointerListIterator<enchant::Dict> it(*g_pEnchantDicts);
for(bool b = it.moveFirst(); b; b = it.moveNext())
{
(*it)->suggest(szWord.toStdString(), suggestions);
for (auto & suggestion : suggestions) {
hAllSuggestions.insert(QString::fromStdString(suggestion), 1);
}
}
}
#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();
for(const auto & szSuggestion : hAllSuggestions.keys())
pArray->append(new KviKvsVariant(szSuggestion));
c->returnValue()->setArray(pArray);
return true;
}
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();
}
#endif
const QStringList & wantedDictionaries = KVI_OPTION_STRINGLIST(KviOption_stringlistSpellCheckerDictionaries);
foreach(QString szLang, wantedDictionaries)
{
if(szLang.isEmpty())
continue;
#ifdef COMPILE_ENCHANT_SUPPORT
try {
enchant::Dict * pDict = g_pEnchantBroker->request_dict(szLang.toUtf8().data());
if(pDict)
{
g_pEnchantDicts->append(pDict);
}
} 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
}
}
/*
@doc: spellchecker.reloadDictionaries
@type:
command
@title:
spellchecker.reloadDictionaries
@short:
Reload spell checker dictionaries
@syntax:
spellchecker.reloadDictionaries
@description:
Reload spell checker dictionaries
*/
static bool spellchecker_kvs_reload_dictionaries(KviKvsModuleCommandCall * c)
{
KVSM_PARAMETERS_BEGIN(c)
KVSM_PARAMETERS_END(c)
spellchecker_reload_dicts();
return true;
}
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();
KVSM_REGISTER_SIMPLE_COMMAND(m, "reloadDictionaries", spellchecker_kvs_reload_dictionaries);
KVSM_REGISTER_FUNCTION(m, "availableDictionaries", spellchecker_kvs_available_dictionaries);
KVSM_REGISTER_FUNCTION(m, "check", spellchecker_kvs_check);
KVSM_REGISTER_FUNCTION(m, "suggestions", spellchecker_kvs_suggestions);
return true;
}
static bool spellchecker_module_cleanup(KviModule *)
{
#ifdef COMPILE_ENCHANT_SUPPORT
while(!g_pEnchantDicts->isEmpty())
delete g_pEnchantDicts->takeFirst();
delete g_pEnchantDicts;
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();
}
g_pWinSpellFactory->Release();
g_pWinSpellFactory = nullptr;
#endif
return true;
}
static bool spellchecker_module_can_unload(KviModule *)
{
return false;
}
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)