diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -59,3 +59,7 @@ if (APPLE) add_subdirectory( nsspellchecker ) endif () + +if (WIN32) + add_subdirectory( ispellchecker ) +endif () diff --git a/src/plugins/ispellchecker/CMakeLists.txt b/src/plugins/ispellchecker/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/plugins/ispellchecker/CMakeLists.txt @@ -0,0 +1,15 @@ +# needs windows 8 or higher +add_definitions(-DWINVER=0x0602 -D_WIN32_WINNT=0x0602) + +set(sonnet_ispellchecker_PART_SRCS + ispellcheckerclient.cpp + ispellcheckerdict.cpp +) +ecm_qt_declare_logging_category(sonnet_ispellchecker_PART_SRCS HEADER ispellcheckerdebug.h IDENTIFIER SONNET_ISPELLCHECKER CATEGORY_NAME sonnet.plugins.ispellchecker) + +add_library(sonnet_ispellchecker MODULE ${sonnet_ispellchecker_PART_SRCS}) +target_include_directories(sonnet_ispellchecker SYSTEM PUBLIC ${HUNSPELL_INCLUDE_DIRS}) +target_link_libraries(sonnet_ispellchecker PRIVATE KF5::SonnetCore ${HUNSPELL_LIBRARIES}) +target_compile_definitions(sonnet_ispellchecker PRIVATE DEFINITIONS SONNET_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}") + +install(TARGETS sonnet_ispellchecker DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/sonnet/) diff --git a/src/plugins/ispellchecker/ispellcheckerclient.h b/src/plugins/ispellchecker/ispellcheckerclient.h new file mode 100644 --- /dev/null +++ b/src/plugins/ispellchecker/ispellcheckerclient.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.0-or-later + + Copyright (C) 2019 Christoph Cullmann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSPELL_ISPELLCHECKCLIENT_H +#define KSPELL_ISPELLCHECKCLIENT_H + +#include "client_p.h" + +#include +#include + +namespace Sonnet { +class SpellerPlugin; +} +using Sonnet::SpellerPlugin; + +class ISpellCheckerClient : public Sonnet::Client +{ + Q_OBJECT + Q_INTERFACES(Sonnet::Client) + Q_PLUGIN_METADATA(IID "org.kde.Sonnet.ISpellCheckerClient") +public: + explicit ISpellCheckerClient(QObject *parent = nullptr); + ~ISpellCheckerClient() override; + + int reliability() const override + { + return 40; + } + + SpellerPlugin *createSpeller(const QString &language) override; + + QStringList languages() const override; + + QString name() const override + { + return QStringLiteral("ISpellChecker"); + } + +private: + bool m_wasCOMInitialized = false; + ISpellCheckerFactory* m_spellCheckerFactory = nullptr; + QStringList m_languages; +}; + +#endif diff --git a/src/plugins/ispellchecker/ispellcheckerclient.cpp b/src/plugins/ispellchecker/ispellcheckerclient.cpp new file mode 100644 --- /dev/null +++ b/src/plugins/ispellchecker/ispellcheckerclient.cpp @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.0-or-later + + Copyright (C) 2019 Christoph Cullmann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "ispellcheckerclient.h" +#include "ispellcheckerdict.h" +#include "ispellcheckerdebug.h" + +using namespace Sonnet; + +ISpellCheckerClient::ISpellCheckerClient(QObject *parent) + : Client(parent) +{ + qCDebug(SONNET_ISPELLCHECKER) << " ISpellCheckerClient::ISpellCheckerClient"; + + // init com if needed + m_wasCOMInitialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)); + + // get factory + if (SUCCEEDED(CoCreateInstance(__uuidof(SpellCheckerFactory), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_spellCheckerFactory)))) { + // if we have a factory, cache the language names + IEnumString* enumLanguages = nullptr; + if (SUCCEEDED(m_spellCheckerFactory->get_SupportedLanguages(&enumLanguages))) { + HRESULT hr = S_OK; + while (S_OK == hr) { + LPOLESTR string = nullptr; + hr = enumLanguages->Next(1, &string, nullptr); + if (S_OK == hr) { + m_languages.push_back(QString::fromWCharArray(string)); + CoTaskMemFree(string); + } + } + enumLanguages->Release(); + } + } else { + m_spellCheckerFactory = nullptr; + } +} + +ISpellCheckerClient::~ISpellCheckerClient() +{ + // de-init com if needed + if (m_wasCOMInitialized) { + CoUninitialize(); + } + + // release factory + if (m_spellCheckerFactory) { + m_spellCheckerFactory->Release(); + } +} + +SpellerPlugin *ISpellCheckerClient::createSpeller(const QString &language) +{ + // create requested spellchecker, might internally fail to create instance + qCDebug(SONNET_ISPELLCHECKER) << " SpellerPlugin *ISpellCheckerClient::createSpeller(const QString &language) ;" << language; + ISpellCheckerDict *ad = new ISpellCheckerDict(m_spellCheckerFactory, language); + return ad; +} + +QStringList ISpellCheckerClient::languages() const +{ + return m_languages; +} diff --git a/src/plugins/ispellchecker/ispellcheckerdict.h b/src/plugins/ispellchecker/ispellcheckerdict.h new file mode 100644 --- /dev/null +++ b/src/plugins/ispellchecker/ispellcheckerdict.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.0-or-later + + Copyright (C) 2019 Christoph Cullmann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSPELL_ISPELLCHECKDICT_H +#define KSPELL_ISPELLCHECKDICT_H + +#include "spellerplugin_p.h" + +#include "ispellcheckerclient.h" + +class ISpellCheckerDict : public Sonnet::SpellerPlugin +{ +public: + explicit ISpellCheckerDict(ISpellCheckerFactory *spellCheckerFactory, const QString &language); + ~ISpellCheckerDict() override; + bool isCorrect(const QString &word) const override; + + QStringList suggest(const QString &word) const override; + + bool storeReplacement(const QString &bad, const QString &good) override; + + bool addToPersonal(const QString &word) override; + bool addToSession(const QString &word) override; + +private: + // spell checker com object + ISpellChecker *m_spellChecker = nullptr; +}; + +#endif diff --git a/src/plugins/ispellchecker/ispellcheckerdict.cpp b/src/plugins/ispellchecker/ispellcheckerdict.cpp new file mode 100644 --- /dev/null +++ b/src/plugins/ispellchecker/ispellcheckerdict.cpp @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: LGPL-2.0-or-later + + Copyright (C) 2019 Christoph Cullmann + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "ispellcheckerdict.h" +#include "ispellcheckerdebug.h" + +using namespace Sonnet; + +ISpellCheckerDict::ISpellCheckerDict(ISpellCheckerFactory *spellCheckerFactory, const QString &language) + : SpellerPlugin(language) +{ + // try to init checker + if (!SUCCEEDED(spellCheckerFactory->CreateSpellChecker(language.toStdWString().c_str(), &m_spellChecker))) { + m_spellChecker = nullptr; + } +} + +ISpellCheckerDict::~ISpellCheckerDict() +{ + // release com if needed + if (m_spellChecker) { + m_spellChecker->Release(); + } +} + +bool ISpellCheckerDict::isCorrect(const QString &word) const +{ + // check if we are incorrect, we only need to check one enum entry for that, only empty enum means OK + bool ok = true; + IEnumSpellingError* enumSpellingError = nullptr; + if (m_spellChecker && SUCCEEDED(m_spellChecker->Check(word.toStdWString().c_str(), &enumSpellingError))) { + ISpellingError *spellingError = nullptr; + if (S_OK == enumSpellingError->Next(&spellingError)) { + ok = false; + spellingError->Release(); + } + enumSpellingError->Release(); + } + return ok; +} + +QStringList ISpellCheckerDict::suggest(const QString &word) const +{ + // query suggestions + QStringList replacements; + IEnumString* words = nullptr; + if (m_spellChecker && SUCCEEDED(m_spellChecker->Suggest(word.toStdWString().c_str(), &words))) { + HRESULT hr = S_OK; + while (S_OK == hr) { + LPOLESTR string = nullptr; + hr = words->Next(1, &string, nullptr); + if (S_OK == hr) { + replacements.push_back(QString::fromWCharArray(string)); + CoTaskMemFree(string); + } + } + words->Release(); + } + return replacements; +} + +bool ISpellCheckerDict::storeReplacement(const QString &bad, const QString &good) +{ + Q_UNUSED(bad); + Q_UNUSED(good); + qCDebug(SONNET_ISPELLCHECKER) << "ISpellCheckerDict::storeReplacement not implemented"; + return false; +} + +bool ISpellCheckerDict::addToPersonal(const QString &word) +{ + // add word "permanently" to the dictionary + return m_spellChecker && SUCCEEDED(m_spellChecker->Add(word.toStdWString().c_str())); +} + +bool ISpellCheckerDict::addToSession(const QString &word) +{ + // ignore word for this session + return m_spellChecker && SUCCEEDED(m_spellChecker->Ignore(word.toStdWString().c_str())); +}