Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
|
||||
# you can find find_package() in kdelibs/cmake/modules/OptionalFindPackage.cmake
|
||||
# it is the same as FIND_PACKAGE(<name>) but additionally creates an OPTION(WITH_<name>)
|
||||
# so the checking for the software can be disabled via ccmake or -DWITH_<name>=OFF
|
||||
find_package(ASPELL)
|
||||
set_package_properties(ASPELL PROPERTIES
|
||||
URL "http://aspell.net/"
|
||||
DESCRIPTION "Spell checking support via Aspell"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if (ASPELL_INCLUDE_DIR AND ASPELL_LIBRARIES)
|
||||
add_subdirectory( aspell )
|
||||
endif ()
|
||||
|
||||
|
||||
find_package(HSPELL)
|
||||
set_package_properties(HSPELL PROPERTIES
|
||||
URL "http://ivrix.org.il/projects/spell-checker/"
|
||||
DESCRIPTION "Spell checking support for Hebrew"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if (HSPELL_FOUND)
|
||||
add_subdirectory( hspell )
|
||||
endif ()
|
||||
|
||||
find_package(HUNSPELL)
|
||||
set_package_properties(HUNSPELL PROPERTIES
|
||||
URL "https://hunspell.github.io/"
|
||||
DESCRIPTION "Spell checking support via Hunspell"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if (HUNSPELL_FOUND)
|
||||
add_subdirectory( hunspell )
|
||||
endif ()
|
||||
|
||||
|
||||
find_package(VOIKKO)
|
||||
set_package_properties(VOIKKO PROPERTIES
|
||||
URL "https://voikko.puimula.org/"
|
||||
DESCRIPTION "Spell checking support via Voikko"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if (VOIKKO_FOUND)
|
||||
add_subdirectory( voikko )
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
add_subdirectory( nsspellchecker )
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
add_subdirectory( ispellchecker )
|
||||
endif ()
|
||||
|
||||
# if we did not find any backend, that is bad
|
||||
# do that on Android, too, if we have some backend for it
|
||||
if (NOT ANDROID AND NOT SONNET_BACKEND_FOUND AND NOT SONNET_NO_BACKENDS)
|
||||
message(FATAL_ERROR "Can not build any backend plugin for Sonnet.")
|
||||
endif ()
|
||||
@@ -0,0 +1,21 @@
|
||||
add_library(sonnet_aspell MODULE
|
||||
aspellclient.cpp
|
||||
aspelldict.cpp
|
||||
)
|
||||
|
||||
target_include_directories(sonnet_aspell PRIVATE ${ASPELL_INCLUDE_DIR})
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_aspell
|
||||
HEADER aspell_debug.h
|
||||
IDENTIFIER SONNET_LOG_ASPELL
|
||||
CATEGORY_NAME kf.sonnet.clients.aspell
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.aspell
|
||||
DESCRIPTION "Sonnet Aspell plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
target_link_libraries(sonnet_aspell PRIVATE KF6::SonnetCore ${ASPELL_LIBRARIES})
|
||||
|
||||
install(TARGETS sonnet_aspell DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* kspell_aspellclient.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "aspellclient.h"
|
||||
#include "aspelldict.h"
|
||||
|
||||
#include "aspell_debug.h"
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QCoreApplication>
|
||||
#endif
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
ASpellClient::ASpellClient(QObject *parent)
|
||||
: Client(parent)
|
||||
, m_config(new_aspell_config())
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
aspell_config_replace(m_config, "data-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData());
|
||||
aspell_config_replace(m_config, "dict-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData());
|
||||
#endif
|
||||
}
|
||||
|
||||
ASpellClient::~ASpellClient()
|
||||
{
|
||||
delete_aspell_config(m_config);
|
||||
}
|
||||
|
||||
SpellerPlugin *ASpellClient::createSpeller(const QString &language)
|
||||
{
|
||||
ASpellDict *ad = new ASpellDict(language);
|
||||
return ad;
|
||||
}
|
||||
|
||||
QStringList ASpellClient::languages() const
|
||||
{
|
||||
AspellDictInfoList *l = get_aspell_dict_info_list(m_config);
|
||||
AspellDictInfoEnumeration *el = aspell_dict_info_list_elements(l);
|
||||
|
||||
QStringList langs;
|
||||
const AspellDictInfo *di = nullptr;
|
||||
while ((di = aspell_dict_info_enumeration_next(el))) {
|
||||
langs.append(QString::fromLatin1(di->name));
|
||||
}
|
||||
|
||||
delete_aspell_dict_info_enumeration(el);
|
||||
|
||||
return langs;
|
||||
}
|
||||
|
||||
#include "moc_aspellclient.cpp"
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* kspell_aspellclient.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_ASPELLCLIENT_H
|
||||
#define KSPELL_ASPELLCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
|
||||
#include "aspell.h"
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellerPlugin;
|
||||
}
|
||||
using Sonnet::SpellerPlugin;
|
||||
|
||||
class ASpellClient : public Sonnet::Client
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(Sonnet::Client)
|
||||
Q_PLUGIN_METADATA(IID "org.kde.Sonnet.ASpellClient")
|
||||
|
||||
public:
|
||||
explicit ASpellClient(QObject *parent = nullptr);
|
||||
~ASpellClient() override;
|
||||
|
||||
int reliability() const override
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
SpellerPlugin *createSpeller(const QString &language) override;
|
||||
|
||||
QStringList languages() const override;
|
||||
|
||||
QString name() const override
|
||||
{
|
||||
return QStringLiteral("ASpell");
|
||||
}
|
||||
|
||||
private:
|
||||
AspellConfig *const m_config;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* kspell_aspelldict.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "aspelldict.h"
|
||||
|
||||
#include "aspell_debug.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QCoreApplication>
|
||||
#endif
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
ASpellDict::ASpellDict(const QString &lang)
|
||||
: SpellerPlugin(lang)
|
||||
{
|
||||
m_config = new_aspell_config();
|
||||
aspell_config_replace(m_config, "lang", lang.toLatin1().constData());
|
||||
/* All communication with Aspell is done in UTF-8 */
|
||||
/* For reference, please look at BR#87250 */
|
||||
aspell_config_replace(m_config, "encoding", "utf-8");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
aspell_config_replace(m_config, "data-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData());
|
||||
aspell_config_replace(m_config, "dict-dir", QString::fromLatin1("%1/data/aspell").arg(QCoreApplication::applicationDirPath()).toLatin1().constData());
|
||||
#endif
|
||||
|
||||
AspellCanHaveError *possible_err = new_aspell_speller(m_config);
|
||||
|
||||
if (aspell_error_number(possible_err) != 0) {
|
||||
qCWarning(SONNET_LOG_ASPELL) << "aspell error: " << aspell_error_message(possible_err);
|
||||
} else {
|
||||
m_speller = to_aspell_speller(possible_err);
|
||||
}
|
||||
}
|
||||
|
||||
ASpellDict::~ASpellDict()
|
||||
{
|
||||
delete_aspell_speller(m_speller);
|
||||
delete_aspell_config(m_config);
|
||||
}
|
||||
|
||||
bool ASpellDict::isCorrect(const QString &word) const
|
||||
{
|
||||
/* ASpell is expecting length of a string in char representation */
|
||||
/* word.length() != word.toUtf8().length() for nonlatin strings */
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
int correct = aspell_speller_check(m_speller, word.toUtf8().constData(), word.toUtf8().length());
|
||||
return correct;
|
||||
}
|
||||
|
||||
QStringList ASpellDict::suggest(const QString &word) const
|
||||
{
|
||||
if (!m_speller) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
/* ASpell is expecting length of a string in char representation */
|
||||
/* word.length() != word.toUtf8().length() for nonlatin strings */
|
||||
const AspellWordList *suggestions = aspell_speller_suggest(m_speller, word.toUtf8().constData(), word.toUtf8().length());
|
||||
|
||||
AspellStringEnumeration *elements = aspell_word_list_elements(suggestions);
|
||||
|
||||
QStringList qsug;
|
||||
const char *cword;
|
||||
|
||||
while ((cword = aspell_string_enumeration_next(elements))) {
|
||||
/* Since while creating the class ASpellDict the encoding is set */
|
||||
/* to utf-8, one has to convert output from Aspell to QString's UTF-16 */
|
||||
qsug.append(QString::fromUtf8(cword));
|
||||
}
|
||||
|
||||
delete_aspell_string_enumeration(elements);
|
||||
return qsug;
|
||||
}
|
||||
|
||||
bool ASpellDict::storeReplacement(const QString &bad, const QString &good)
|
||||
{
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
/* ASpell is expecting length of a string in char representation */
|
||||
/* word.length() != word.toUtf8().length() for nonlatin strings */
|
||||
return aspell_speller_store_replacement(m_speller, bad.toUtf8().constData(), bad.toUtf8().length(), good.toUtf8().constData(), good.toUtf8().length());
|
||||
}
|
||||
|
||||
bool ASpellDict::addToPersonal(const QString &word)
|
||||
{
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
qCDebug(SONNET_LOG_ASPELL) << "Adding" << word << "to aspell personal dictionary";
|
||||
/* ASpell is expecting length of a string in char representation */
|
||||
/* word.length() != word.toUtf8().length() for nonlatin strings */
|
||||
aspell_speller_add_to_personal(m_speller, word.toUtf8().constData(), word.toUtf8().length());
|
||||
/* Add is not enough, one has to save it. This is not documented */
|
||||
/* in ASpell's API manual. I found it in */
|
||||
/* aspell-0.60.2/example/example-c.c */
|
||||
return aspell_speller_save_all_word_lists(m_speller);
|
||||
}
|
||||
|
||||
bool ASpellDict::addToSession(const QString &word)
|
||||
{
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
/* ASpell is expecting length of a string in char representation */
|
||||
/* word.length() != word.toUtf8().length() for nonlatin strings */
|
||||
return aspell_speller_add_to_session(m_speller, word.toUtf8().constData(), word.toUtf8().length());
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* kspell_aspelldict.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_ASPELLDICT_H
|
||||
#define KSPELL_ASPELLDICT_H
|
||||
|
||||
#include "spellerplugin_p.h"
|
||||
|
||||
#include "aspell.h"
|
||||
|
||||
class ASpellDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
explicit ASpellDict(const QString &lang);
|
||||
~ASpellDict() 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:
|
||||
AspellConfig *m_config = nullptr;
|
||||
AspellSpeller *m_speller = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
find_package(ZLIB)
|
||||
set_package_properties(ZLIB PROPERTIES DESCRIPTION "Support for gzip compressed files and data streams"
|
||||
URL "https://www.zlib.net"
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Required by the hspell sonnet plugin"
|
||||
)
|
||||
|
||||
add_library(sonnet_hspell MODULE
|
||||
hspellclient.cpp
|
||||
hspelldict.cpp
|
||||
)
|
||||
|
||||
target_include_directories(sonnet_hspell PRIVATE
|
||||
${HSPELL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_hspell
|
||||
HEADER hspell_debug.h
|
||||
IDENTIFIER SONNET_LOG_HSPELL
|
||||
CATEGORY_NAME kf.sonnet.clients.hspell
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.hspell
|
||||
DESCRIPTION "Sonnet Hspell plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
target_link_libraries(sonnet_hspell PRIVATE KF6::SonnetCore ${HSPELL_LIBRARIES} ZLIB::ZLIB)
|
||||
|
||||
install(TARGETS sonnet_hspell DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* kspell_hspellclient.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2005 Mashrab Kuvatov <kmashrab@uni-bremen.de>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "hspellclient.h"
|
||||
|
||||
#include "hspell.h"
|
||||
#include "hspelldict.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
HSpellClient::HSpellClient(QObject *parent)
|
||||
: Client(parent)
|
||||
{
|
||||
}
|
||||
|
||||
HSpellClient::~HSpellClient()
|
||||
{
|
||||
}
|
||||
|
||||
SpellerPlugin *HSpellClient::createSpeller(const QString &language)
|
||||
{
|
||||
HSpellDict *ad = new HSpellDict(language);
|
||||
return ad;
|
||||
}
|
||||
|
||||
QStringList HSpellClient::languages() const
|
||||
{
|
||||
QString dictPath(QString::fromUtf8(hspell_get_dictionary_path()));
|
||||
if (QUrl(dictPath).isLocalFile() && QFileInfo::exists(dictPath)) {
|
||||
return {QStringLiteral("he")};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#include "moc_hspellclient.cpp"
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* kspell_hspellclient.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2005 Mashrab Kuvatov <kmashrab@uni-bremen.de>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_HSPELLCLIENT_H
|
||||
#define KSPELL_HSPELLCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
|
||||
/* libhspell is a C library and it does not have #ifdef __cplusplus */
|
||||
extern "C" {
|
||||
#include "hspell.h"
|
||||
}
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellerPlugin;
|
||||
}
|
||||
using Sonnet::SpellerPlugin;
|
||||
|
||||
class HSpellClient : public Sonnet::Client
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(Sonnet::Client)
|
||||
Q_PLUGIN_METADATA(IID "org.kde.Sonnet.HSpellClient")
|
||||
public:
|
||||
explicit HSpellClient(QObject *parent = nullptr);
|
||||
~HSpellClient();
|
||||
|
||||
int reliability() const override
|
||||
{
|
||||
return 20;
|
||||
}
|
||||
|
||||
SpellerPlugin *createSpeller(const QString &language) override;
|
||||
|
||||
QStringList languages() const override;
|
||||
|
||||
QString name() const override
|
||||
{
|
||||
return QString::fromLatin1("HSpell");
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* kspell_hspelldict.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2005 Mashrab Kuvatov <kmashrab@uni-bremen.de>
|
||||
* SPDX-FileCopyrightText: 2013 Martin Sandsmark <martin.sandsmark@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "hspelldict.h"
|
||||
|
||||
#include "hspell_debug.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
HSpellDict::HSpellDict(const QString &lang)
|
||||
: SpellerPlugin(lang)
|
||||
{
|
||||
int int_error = hspell_init(&m_speller, HSPELL_OPT_DEFAULT);
|
||||
if (int_error == -1) {
|
||||
qCWarning(SONNET_LOG_HSPELL) << "HSpellDict::HSpellDict: Init failed";
|
||||
initialized = false;
|
||||
} else {
|
||||
/* hspell understands only iso8859-8-i */
|
||||
m_decoder = QStringDecoder("iso8859-8-i");
|
||||
m_encoder = QStringEncoder("iso8859-8-i");
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
QSettings settings(QStringLiteral("KDE"), QStringLiteral("SonnetHSpellPlugin"));
|
||||
const QStringList personalWordsList = settings.value(QStringLiteral("PersonalWords"), QStringList()).toStringList();
|
||||
m_personalWords = QSet<QString>(personalWordsList.begin(), personalWordsList.end());
|
||||
QVariantHash replacementMap = settings.value(QStringLiteral("Replacements"), QVariant()).toHash();
|
||||
for (const QString &key : replacementMap.keys()) {
|
||||
m_replacements[key] = replacementMap[key].toString();
|
||||
}
|
||||
}
|
||||
|
||||
HSpellDict::~HSpellDict()
|
||||
{
|
||||
/* It exists in =< hspell-0.8 */
|
||||
if (initialized) {
|
||||
hspell_uninit(m_speller);
|
||||
}
|
||||
}
|
||||
|
||||
bool HSpellDict::isCorrect(const QString &word) const
|
||||
{
|
||||
if (m_sessionWords.contains(word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_personalWords.contains(word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
// Not much we can do, so just return true (less annoying for the user)
|
||||
return true;
|
||||
}
|
||||
|
||||
int preflen;
|
||||
QByteArray wordISO = m_encoder.encode(word);
|
||||
|
||||
// returns 1 if the word is correct, 0 otherwise
|
||||
int correct = hspell_check_word(m_speller, wordISO.constData(),
|
||||
&preflen); // this argument might be removed, it isn't useful
|
||||
|
||||
// gimatria is a representation of numbers with hebrew letters, we accept these
|
||||
if (correct != 1) {
|
||||
if (hspell_is_canonic_gimatria(wordISO.constData()) != 0) {
|
||||
correct = 1;
|
||||
}
|
||||
}
|
||||
return correct == 1;
|
||||
}
|
||||
|
||||
QStringList HSpellDict::suggest(const QString &word) const
|
||||
{
|
||||
QStringList suggestions;
|
||||
|
||||
if (m_replacements.contains(word)) {
|
||||
suggestions.append(m_replacements[word]);
|
||||
}
|
||||
|
||||
struct corlist correctionList;
|
||||
int suggestionCount;
|
||||
corlist_init(&correctionList);
|
||||
const QByteArray encodedWord = m_encoder.encode(word);
|
||||
hspell_trycorrect(m_speller, encodedWord.constData(), &correctionList);
|
||||
for (suggestionCount = 0; suggestionCount < corlist_n(&correctionList); suggestionCount++) {
|
||||
suggestions.append(m_decoder.decode(corlist_str(&correctionList, suggestionCount)));
|
||||
}
|
||||
corlist_free(&correctionList);
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
bool HSpellDict::storeReplacement(const QString &bad, const QString &good)
|
||||
{
|
||||
m_replacements[bad] = good;
|
||||
storePersonalWords();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HSpellDict::addToPersonal(const QString &word)
|
||||
{
|
||||
m_personalWords.insert(word);
|
||||
storePersonalWords();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HSpellDict::addToSession(const QString &word)
|
||||
{
|
||||
m_sessionWords.insert(word);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HSpellDict::storePersonalWords()
|
||||
{
|
||||
QSettings settings(QStringLiteral("KDE"), QStringLiteral("SonnetHSpellPlugin"));
|
||||
const QStringList personalWordsList(m_personalWords.begin(), m_personalWords.end());
|
||||
settings.setValue(QStringLiteral("PersonalWords"), QVariant(personalWordsList));
|
||||
QVariantHash variantHash;
|
||||
for (const QString &key : m_replacements.keys()) {
|
||||
variantHash[key] = QVariant(m_replacements[key]);
|
||||
}
|
||||
settings.setValue(QStringLiteral("Replacements"), variantHash);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* kspell_hspelldict.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2005 Mashrab Kuvatov <kmashrab@uni-bremen.de>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_HSPELLDICT_H
|
||||
#define KSPELL_HSPELLDICT_H
|
||||
|
||||
#include <QSet>
|
||||
#include <QStringDecoder>
|
||||
#include <QStringEncoder>
|
||||
|
||||
#include "spellerplugin_p.h"
|
||||
/* libhspell is a C library and it does not have #ifdef __cplusplus */
|
||||
extern "C" {
|
||||
#include "hspell.h"
|
||||
}
|
||||
|
||||
class HSpellDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
explicit HSpellDict(const QString &lang);
|
||||
~HSpellDict();
|
||||
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;
|
||||
inline bool isInitialized() const
|
||||
{
|
||||
return initialized;
|
||||
}
|
||||
|
||||
private:
|
||||
void storePersonalWords();
|
||||
|
||||
struct dict_radix *m_speller;
|
||||
mutable QStringDecoder m_decoder;
|
||||
mutable QStringEncoder m_encoder;
|
||||
bool initialized;
|
||||
QSet<QString> m_sessionWords;
|
||||
QSet<QString> m_personalWords;
|
||||
QHash<QString, QString> m_replacements;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
add_library(sonnet_hunspell MODULE
|
||||
hunspellclient.cpp
|
||||
hunspelldict.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_hunspell
|
||||
HEADER hunspelldebug.h
|
||||
IDENTIFIER SONNET_HUNSPELL
|
||||
CATEGORY_NAME kf.sonnet.clients.hunspell
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.hunspell
|
||||
DESCRIPTION "Sonnet HUnspell plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
# see: https://phabricator.kde.org/R246:0a96acf251baa5c9dd042d093ab2bf8fcee10502
|
||||
set(USE_OLD_HUNSPELL_API TRUE)
|
||||
if (PKG_HUNSPELL_VERSION GREATER "1.5.0")
|
||||
set(USE_OLD_HUNSPELL_API FALSE) # new API introduced in v1.5.1 (cf. https://github.com/hunspell/hunspell/commit/8006703dafeebce19f2144c5cf180812eb99693a)
|
||||
endif()
|
||||
message(STATUS "Using old hunspell API: ${USE_OLD_HUNSPELL_API}")
|
||||
|
||||
configure_file(config-hunspell.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hunspell.h)
|
||||
|
||||
target_include_directories(sonnet_hunspell SYSTEM PUBLIC ${HUNSPELL_INCLUDE_DIRS})
|
||||
target_link_libraries(sonnet_hunspell PRIVATE KF6::SonnetCore ${HUNSPELL_LIBRARIES})
|
||||
|
||||
target_compile_definitions(sonnet_hunspell PRIVATE DEFINITIONS SONNET_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS sonnet_hunspell DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1 @@
|
||||
#cmakedefine01 USE_OLD_HUNSPELL_API
|
||||
@@ -0,0 +1,19 @@
|
||||
TARGET = sonnet-hunspell
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
QT -= gui
|
||||
|
||||
SOURCES += hunspelldict.cpp \
|
||||
hunspellclient.cpp \
|
||||
hunspelldebug.cpp
|
||||
HEADERS += hunspellclient.h
|
||||
|
||||
DEFINES += SONNETUI_EXPORT=""
|
||||
DEFINES += SONNETCORE_EXPORT=""
|
||||
DEFINES += INSTALLATION_PLUGIN_PATH=""
|
||||
DEFINES += SONNET_STATIC
|
||||
|
||||
INCLUDEPATH += ../../core
|
||||
INCLUDEPATH += ../../../../hunspell/src
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* kspell_hunspellclient.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Montel Laurent <montel@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "hunspellclient.h"
|
||||
#include "hunspelldebug.h"
|
||||
#include "hunspelldict.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
HunspellClient::HunspellClient(QObject *parent)
|
||||
: Client(parent)
|
||||
{
|
||||
qCDebug(SONNET_HUNSPELL) << " HunspellClient::HunspellClient";
|
||||
|
||||
QStringList dirList;
|
||||
// search QStandardPaths
|
||||
dirList.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("hunspell"), QStandardPaths::LocateDirectory));
|
||||
|
||||
auto maybeAddPath = [&dirList](const QString &path) {
|
||||
if (QFileInfo::exists(path)) {
|
||||
dirList.append(path);
|
||||
|
||||
QDir dir(path);
|
||||
for (const QString &subDir : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||
dirList.append(dir.absoluteFilePath(subDir));
|
||||
}
|
||||
}
|
||||
};
|
||||
#ifdef Q_OS_WIN
|
||||
maybeAddPath(QStringLiteral(SONNET_INSTALL_PREFIX "/bin/data/hunspell/"));
|
||||
#else
|
||||
maybeAddPath(QStringLiteral("/System/Library/Spelling"));
|
||||
maybeAddPath(QStringLiteral("/usr/share/hunspell/"));
|
||||
maybeAddPath(QStringLiteral("/usr/share/myspell/"));
|
||||
#endif
|
||||
|
||||
for (const QString &dirString : dirList) {
|
||||
QDir dir(dirString);
|
||||
const QList<QFileInfo> dicts = dir.entryInfoList({QStringLiteral("*.aff")}, QDir::Files);
|
||||
for (const QFileInfo &dict : dicts) {
|
||||
const QString language = dict.baseName();
|
||||
if (dict.isSymbolicLink()) {
|
||||
const QFileInfo actualDict(dict.canonicalFilePath());
|
||||
const QString alias = actualDict.baseName();
|
||||
if (language != alias) {
|
||||
qCDebug(SONNET_HUNSPELL) << "Found alias" << language << "->" << alias;
|
||||
m_languageAliases.insert(language, alias);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
m_languagePaths.insert(language, dict.canonicalPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HunspellClient::~HunspellClient()
|
||||
{
|
||||
}
|
||||
|
||||
SpellerPlugin *HunspellClient::createSpeller(const QString &inputLang)
|
||||
{
|
||||
QString language = inputLang;
|
||||
if (m_languageAliases.contains(language)) {
|
||||
qCDebug(SONNET_HUNSPELL) << "Using alias" << m_languageAliases.value(language) << "for" << language;
|
||||
language = m_languageAliases.value(language);
|
||||
}
|
||||
std::shared_ptr<Hunspell> hunspell = m_hunspellCache.value(language).lock();
|
||||
if (!hunspell) {
|
||||
hunspell = HunspellDict::createHunspell(language, m_languagePaths.value(language));
|
||||
m_hunspellCache.insert(language, hunspell);
|
||||
}
|
||||
qCDebug(SONNET_HUNSPELL) << " SpellerPlugin *HunspellClient::createSpeller(const QString &language) ;" << language;
|
||||
HunspellDict *ad = new HunspellDict(inputLang, hunspell);
|
||||
return ad;
|
||||
}
|
||||
|
||||
QStringList HunspellClient::languages() const
|
||||
{
|
||||
return m_languagePaths.keys() + m_languageAliases.keys();
|
||||
}
|
||||
|
||||
#include "moc_hunspellclient.cpp"
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* kspell_hunspellclient.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Montel Laurent <montel@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_HUNSPELLCLIENT_H
|
||||
#define KSPELL_HUNSPELLCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
#include <QMap>
|
||||
#include <memory>
|
||||
|
||||
class Hunspell;
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellerPlugin;
|
||||
}
|
||||
|
||||
using Sonnet::SpellerPlugin;
|
||||
|
||||
class HunspellClient : public Sonnet::Client
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(Sonnet::Client)
|
||||
Q_PLUGIN_METADATA(IID "org.kde.Sonnet.HunspellClient")
|
||||
public:
|
||||
explicit HunspellClient(QObject *parent = nullptr);
|
||||
~HunspellClient() override;
|
||||
|
||||
int reliability() const override
|
||||
{
|
||||
return 40;
|
||||
}
|
||||
|
||||
SpellerPlugin *createSpeller(const QString &language) override;
|
||||
|
||||
QStringList languages() const override;
|
||||
|
||||
QString name() const override
|
||||
{
|
||||
return QStringLiteral("Hunspell");
|
||||
}
|
||||
|
||||
private:
|
||||
QMap<QString, QString> m_languagePaths;
|
||||
QMap<QString, std::weak_ptr<Hunspell>> m_hunspellCache;
|
||||
QMap<QString, QString> m_languageAliases;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* kspell_hunspelldict.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Montel Laurent <montel@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "hunspelldict.h"
|
||||
|
||||
#include "config-hunspell.h"
|
||||
#include "hunspelldebug.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QTextStream>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
HunspellDict::HunspellDict(const QString &lang, const std::shared_ptr<Hunspell> &speller)
|
||||
: SpellerPlugin(lang)
|
||||
{
|
||||
if (!speller) {
|
||||
qCWarning(SONNET_HUNSPELL) << "Can't create a client without a speller";
|
||||
return;
|
||||
}
|
||||
m_decoder = QStringDecoder(speller->get_dic_encoding());
|
||||
if (!m_decoder.isValid()) {
|
||||
qCWarning(SONNET_HUNSPELL) << "Failed to find a text codec for name" << speller->get_dic_encoding() << "defaulting to locale text codec";
|
||||
m_decoder = QStringDecoder(QStringDecoder::System);
|
||||
Q_ASSERT(m_decoder.isValid());
|
||||
}
|
||||
m_encoder = QStringEncoder(speller->get_dic_encoding());
|
||||
if (!m_encoder.isValid()) {
|
||||
qCWarning(SONNET_HUNSPELL) << "Failed to find a text codec for name" << speller->get_dic_encoding() << "defaulting to locale text codec";
|
||||
m_encoder = QStringEncoder(QStringEncoder::System);
|
||||
Q_ASSERT(m_encoder.isValid());
|
||||
}
|
||||
m_speller = speller;
|
||||
|
||||
const QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % lang);
|
||||
QFile userDicFile(userDic);
|
||||
if (userDicFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
qCDebug(SONNET_HUNSPELL) << "Load a user dictionary" << userDic;
|
||||
QTextStream userDicIn(&userDicFile);
|
||||
while (!userDicIn.atEnd()) {
|
||||
const QString word = userDicIn.readLine();
|
||||
if (word.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (word.contains(QLatin1Char('/'))) {
|
||||
QStringList wordParts = word.split(QLatin1Char('/'));
|
||||
speller->add_with_affix(toDictEncoding(wordParts.at(0)).constData(), toDictEncoding(wordParts.at(1)).constData());
|
||||
}
|
||||
if (word.at(0) == QLatin1Char('*')) {
|
||||
speller->remove(toDictEncoding(word.mid(1)).constData());
|
||||
} else {
|
||||
speller->add(toDictEncoding(word).constData());
|
||||
}
|
||||
}
|
||||
userDicFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Hunspell> HunspellDict::createHunspell(const QString &lang, QString path)
|
||||
{
|
||||
qCDebug(SONNET_HUNSPELL) << "Loading dictionary for" << lang << "from" << path;
|
||||
|
||||
if (!path.endsWith(QLatin1Char('/'))) {
|
||||
path += QLatin1Char('/');
|
||||
}
|
||||
path += lang;
|
||||
QString dictionary = path + QStringLiteral(".dic");
|
||||
QString aff = path + QStringLiteral(".aff");
|
||||
|
||||
if (!QFileInfo::exists(dictionary) || !QFileInfo::exists(aff)) {
|
||||
qCWarning(SONNET_HUNSPELL) << "Unable to find dictionary for" << lang << "in path" << path;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Hunspell> speller = std::make_shared<Hunspell>(aff.toLocal8Bit().constData(), dictionary.toLocal8Bit().constData());
|
||||
qCDebug(SONNET_HUNSPELL) << "Created " << speller.get();
|
||||
|
||||
return speller;
|
||||
}
|
||||
|
||||
HunspellDict::~HunspellDict()
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray HunspellDict::toDictEncoding(const QString &word) const
|
||||
{
|
||||
if (m_encoder.isValid()) {
|
||||
return m_encoder.encode(word);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool HunspellDict::isCorrect(const QString &word) const
|
||||
{
|
||||
qCDebug(SONNET_HUNSPELL) << " isCorrect :" << word;
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if USE_OLD_HUNSPELL_API
|
||||
int result = m_speller->spell(toDictEncoding(word).constData());
|
||||
qCDebug(SONNET_HUNSPELL) << " result :" << result;
|
||||
return result != 0;
|
||||
#else
|
||||
bool result = m_speller->spell(toDictEncoding(word).toStdString());
|
||||
qCDebug(SONNET_HUNSPELL) << " result :" << result;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList HunspellDict::suggest(const QString &word) const
|
||||
{
|
||||
if (!m_speller) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QStringList lst;
|
||||
#if USE_OLD_HUNSPELL_API
|
||||
char **selection;
|
||||
int nbWord = m_speller->suggest(&selection, toDictEncoding(word).constData());
|
||||
for (int i = 0; i < nbWord; ++i) {
|
||||
lst << m_decoder.decode(selection[i]);
|
||||
}
|
||||
m_speller->free_list(&selection, nbWord);
|
||||
#else
|
||||
const auto suggestions = m_speller->suggest(toDictEncoding(word).toStdString());
|
||||
for_each(suggestions.begin(), suggestions.end(), [this, &lst](const std::string &suggestion) {
|
||||
lst << m_decoder.decode(suggestion.c_str());
|
||||
});
|
||||
#endif
|
||||
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool HunspellDict::storeReplacement(const QString &bad, const QString &good)
|
||||
{
|
||||
Q_UNUSED(bad);
|
||||
Q_UNUSED(good);
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
qCDebug(SONNET_HUNSPELL) << "HunspellDict::storeReplacement not implemented";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HunspellDict::addToPersonal(const QString &word)
|
||||
{
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
m_speller->add(toDictEncoding(word).constData());
|
||||
const QString userDic = QDir::home().filePath(QLatin1String(".hunspell_") % language());
|
||||
QFile userDicFile(userDic);
|
||||
if (userDicFile.open(QIODevice::Append | QIODevice::Text)) {
|
||||
QTextStream out(&userDicFile);
|
||||
out << word << '\n';
|
||||
userDicFile.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HunspellDict::addToSession(const QString &word)
|
||||
{
|
||||
if (!m_speller) {
|
||||
return false;
|
||||
}
|
||||
int r = m_speller->add(toDictEncoding(word).constData());
|
||||
return r == 0;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* kspell_aspelldict.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Montel Laurent <montel@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_HUNSPELLDICT_H
|
||||
#define KSPELL_HUNSPELLDICT_H
|
||||
|
||||
#include "hunspell.hxx"
|
||||
#include "spellerplugin_p.h"
|
||||
|
||||
#include <QStringDecoder>
|
||||
#include <QStringEncoder>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class HunspellDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
explicit HunspellDict(const QString &name, const std::shared_ptr<Hunspell> &speller);
|
||||
~HunspellDict() 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;
|
||||
|
||||
static std::shared_ptr<Hunspell> createHunspell(const QString &lang, QString path);
|
||||
|
||||
private:
|
||||
QByteArray toDictEncoding(const QString &word) const;
|
||||
|
||||
std::shared_ptr<Hunspell> m_speller;
|
||||
mutable QStringEncoder m_encoder;
|
||||
mutable QStringDecoder m_decoder;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
include(CheckIncludeFile)
|
||||
|
||||
# needs windows 8 or higher
|
||||
add_definitions(-DWINVER=0x0602 -D_WIN32_WINNT=0x0602)
|
||||
|
||||
CHECK_INCLUDE_FILE(spellcheck.h HAS_SPELLCHECK_H)
|
||||
|
||||
if (NOT HAS_SPELLCHECK_H)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_library(sonnet_ispellchecker MODULE
|
||||
ispellcheckerclient.cpp
|
||||
ispellcheckerdict.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_ispellchecker
|
||||
HEADER ispellcheckerdebug.h
|
||||
IDENTIFIER SONNET_ISPELLCHECKER
|
||||
CATEGORY_NAME kf.sonnet.clients.ispellchecker
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.ispellchecker
|
||||
DESCRIPTION "Sonnet ISpellChecker plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
target_link_libraries(sonnet_ispellchecker PRIVATE KF6::SonnetCore)
|
||||
target_compile_definitions(sonnet_ispellchecker PRIVATE DEFINITIONS SONNET_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
install(TARGETS sonnet_ispellchecker DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "ispellcheckerclient.h"
|
||||
#include "ispellcheckerdebug.h"
|
||||
#include "ispellcheckerdict.h"
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
ISpellCheckerClient::ISpellCheckerClient(QObject *parent)
|
||||
: Client(parent)
|
||||
{
|
||||
qCDebug(SONNET_ISPELLCHECKER) << " ISpellCheckerClient::ISpellCheckerClient";
|
||||
|
||||
// init com if needed, use same variant as e.g. Qt in qtbase/src/corelib/io/qfilesystemengine_win.cpp
|
||||
CoInitialize(nullptr);
|
||||
|
||||
// get factory & collect all known languages + instantiate the spell checkers for them
|
||||
ISpellCheckerFactory *spellCheckerFactory = nullptr;
|
||||
if (SUCCEEDED(CoCreateInstance(__uuidof(SpellCheckerFactory), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&spellCheckerFactory))) && spellCheckerFactory) {
|
||||
// if we have a factory, cache the language names
|
||||
IEnumString *enumLanguages = nullptr;
|
||||
if (SUCCEEDED(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) {
|
||||
ISpellChecker *spellChecker = nullptr;
|
||||
if (SUCCEEDED(spellCheckerFactory->CreateSpellChecker(string, &spellChecker)) && spellChecker) {
|
||||
m_languages.insert(QString::fromWCharArray(string), spellChecker);
|
||||
}
|
||||
CoTaskMemFree(string);
|
||||
}
|
||||
}
|
||||
enumLanguages->Release();
|
||||
}
|
||||
spellCheckerFactory->Release();
|
||||
}
|
||||
}
|
||||
|
||||
ISpellCheckerClient::~ISpellCheckerClient()
|
||||
{
|
||||
// FIXME: we at the moment leak all checkers as sonnet does the cleanup to late for proper com cleanup :/
|
||||
}
|
||||
|
||||
SpellerPlugin *ISpellCheckerClient::createSpeller(const QString &language)
|
||||
{
|
||||
// create requested spellchecker if we know the language
|
||||
qCDebug(SONNET_ISPELLCHECKER) << " SpellerPlugin *ISpellCheckerClient::createSpeller(const QString &language) ;" << language;
|
||||
const auto it = m_languages.find(language);
|
||||
if (it != m_languages.end()) {
|
||||
return new ISpellCheckerDict(it.value(), language);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QStringList ISpellCheckerClient::languages() const
|
||||
{
|
||||
return m_languages.keys();
|
||||
}
|
||||
|
||||
#include "moc_ispellcheckerclient.cpp"
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KSPELL_ISPELLCHECKCLIENT_H
|
||||
#define KSPELL_ISPELLCHECKCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
|
||||
#include <spellcheck.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include <QMap>
|
||||
|
||||
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:
|
||||
// we internally keep all spell checker interfaces alive
|
||||
QMap<QString, ISpellChecker *> m_languages;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "ispellcheckerdict.h"
|
||||
#include "ispellcheckerdebug.h"
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
ISpellCheckerDict::ISpellCheckerDict(ISpellChecker *spellChecker, const QString &language)
|
||||
: SpellerPlugin(language)
|
||||
, m_spellChecker(spellChecker)
|
||||
{
|
||||
Q_ASSERT(m_spellChecker);
|
||||
}
|
||||
|
||||
ISpellCheckerDict::~ISpellCheckerDict()
|
||||
{
|
||||
// we don't own m_spellChecker!
|
||||
}
|
||||
|
||||
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 (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 (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 SUCCEEDED(m_spellChecker->Add(word.toStdWString().c_str()));
|
||||
}
|
||||
|
||||
bool ISpellCheckerDict::addToSession(const QString &word)
|
||||
{
|
||||
// ignore word for this session
|
||||
return SUCCEEDED(m_spellChecker->Ignore(word.toStdWString().c_str()));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KSPELL_ISPELLCHECKDICT_H
|
||||
#define KSPELL_ISPELLCHECKDICT_H
|
||||
|
||||
#include "spellerplugin_p.h"
|
||||
|
||||
#include "ispellcheckerclient.h"
|
||||
|
||||
class ISpellCheckerDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
explicit ISpellCheckerDict(ISpellChecker *spellChecker, 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, we don't own this
|
||||
ISpellChecker *const m_spellChecker;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
add_library(sonnet_nsspellchecker MODULE
|
||||
nsspellcheckerclient.mm
|
||||
nsspellcheckerdict.mm
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_nsspellchecker
|
||||
HEADER nsspellcheckerdebug.h
|
||||
IDENTIFIER SONNET_NSSPELLCHECKER
|
||||
CATEGORY_NAME kf.sonnet.clients.nsspellchecker
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.nsspellchecker
|
||||
DESCRIPTION "Sonnet NSSpellChecker plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
target_link_libraries(sonnet_nsspellchecker PRIVATE KF6::SonnetCore "-framework AppKit")
|
||||
|
||||
install(TARGETS sonnet_nsspellchecker DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1,17 @@
|
||||
TARGET = sonnet-nsspellchecker
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
QT -= gui
|
||||
|
||||
OBJECTIVE_SOURCES += nsspellcheckerdict.mm \
|
||||
nsspellcheckerclient.mm
|
||||
HEADERS += nsspellcheckerclient.h
|
||||
|
||||
DEFINES += SONNETUI_EXPORT=""
|
||||
DEFINES += SONNETCORE_EXPORT=""
|
||||
DEFINES += INSTALLATION_PLUGIN_PATH=""
|
||||
DEFINES += SONNET_STATIC
|
||||
|
||||
INCLUDEPATH += ../../core
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* nsspellcheckerclient.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shaforostoff@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_NSSPELLCLIENT_H
|
||||
#define KSPELL_NSSPELLCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellerPlugin;
|
||||
}
|
||||
using Sonnet::SpellerPlugin;
|
||||
|
||||
class NSSpellCheckerClient : public Sonnet::Client
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(Sonnet::Client)
|
||||
Q_PLUGIN_METADATA(IID "org.kde.Sonnet.NSSpellClient")
|
||||
public:
|
||||
explicit NSSpellCheckerClient(QObject *parent = nullptr);
|
||||
~NSSpellCheckerClient();
|
||||
|
||||
int reliability() const;
|
||||
|
||||
SpellerPlugin *createSpeller(const QString &language);
|
||||
QStringList languages() const;
|
||||
QString name() const
|
||||
{
|
||||
return QStringLiteral("NSSpellChecker");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* nsspellcheckerclient.mm
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shaforostoff@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "nsspellcheckerclient.h"
|
||||
#include "nsspellcheckerdict.h"
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
NSSpellCheckerClient::NSSpellCheckerClient(QObject *parent)
|
||||
: Client(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NSSpellCheckerClient::~NSSpellCheckerClient()
|
||||
{
|
||||
}
|
||||
|
||||
int NSSpellCheckerClient::reliability() const
|
||||
{
|
||||
return qEnvironmentVariableIsSet("SONNET_PREFER_NSSPELLCHECKER") ? 9999 : 30;
|
||||
}
|
||||
|
||||
SpellerPlugin *NSSpellCheckerClient::createSpeller(const QString &language)
|
||||
{
|
||||
return new NSSpellCheckerDict(language);
|
||||
}
|
||||
|
||||
QStringList NSSpellCheckerClient::languages() const
|
||||
{
|
||||
QStringList lst;
|
||||
NSArray* availableLanguages = [[NSSpellChecker sharedSpellChecker]
|
||||
availableLanguages];
|
||||
for (NSString* lang_code in availableLanguages) {
|
||||
lst.append(QString::fromNSString(lang_code));
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
|
||||
#include "moc_nsspellcheckerclient.cpp"
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* nsspellcheckerdict.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shaforostoff@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef KSPELL_NSSPELLDICT_H
|
||||
#define KSPELL_NSSPELLDICT_H
|
||||
|
||||
#include "spellerplugin_p.h"
|
||||
|
||||
class NSSpellCheckerDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
explicit NSSpellCheckerDict(const QString &lang);
|
||||
~NSSpellCheckerDict();
|
||||
virtual bool isCorrect(const QString &word) const;
|
||||
|
||||
virtual QStringList suggest(const QString &word) const;
|
||||
|
||||
virtual bool storeReplacement(const QString &bad, const QString &good);
|
||||
|
||||
virtual bool addToPersonal(const QString &word);
|
||||
virtual bool addToSession(const QString &word);
|
||||
|
||||
private:
|
||||
#ifdef __OBJC__
|
||||
NSString *m_langCode;
|
||||
#else
|
||||
void *m_langCode;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* nsspellcheckerdict.mm
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Nick Shaforostoff <shaforostoff@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "nsspellcheckerdict.h"
|
||||
#include "nsspellcheckerdebug.h"
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
NSSpellCheckerDict::NSSpellCheckerDict(const QString &lang)
|
||||
: SpellerPlugin(lang)
|
||||
, m_langCode([lang.toNSString() retain])
|
||||
{
|
||||
NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
|
||||
if ([checker setLanguage:m_langCode]) {
|
||||
qCDebug(SONNET_NSSPELLCHECKER) << "Loading dictionary for" << lang;
|
||||
[checker updatePanels];
|
||||
} else {
|
||||
qCWarning(SONNET_NSSPELLCHECKER) << "Loading dictionary for unsupported language" << lang;
|
||||
}
|
||||
}
|
||||
|
||||
NSSpellCheckerDict::~NSSpellCheckerDict()
|
||||
{
|
||||
[m_langCode release];
|
||||
}
|
||||
|
||||
bool NSSpellCheckerDict::isCorrect(const QString &word) const
|
||||
{
|
||||
NSString *nsWord = word.toNSString();
|
||||
NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
|
||||
NSRange range = [checker checkSpellingOfString:nsWord
|
||||
startingAt:0 language:m_langCode
|
||||
wrap:NO inSpellDocumentWithTag:0 wordCount:nullptr];
|
||||
if (range.length == 0) {
|
||||
// Check if the user configured a replacement text for this string. Sadly
|
||||
// we can only signal an error if that's the case, Sonnet has no other way
|
||||
// to take such substitutions into account.
|
||||
if (NSDictionary *replacements = [checker userReplacementsDictionary]) {
|
||||
return [replacements objectForKey:nsWord] == nil;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList NSSpellCheckerDict::suggest(const QString &word) const
|
||||
{
|
||||
NSString *nsWord = word.toNSString();
|
||||
NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
|
||||
NSArray *suggestions = [checker guessesForWordRange:NSMakeRange(0, word.length())
|
||||
inString:nsWord language:m_langCode inSpellDocumentWithTag:0];
|
||||
QStringList lst;
|
||||
NSDictionary *replacements = [checker userReplacementsDictionary];
|
||||
QString replacement;
|
||||
if ([replacements objectForKey:nsWord]) {
|
||||
// return the replacement text from the userReplacementsDictionary first.
|
||||
replacement = QString::fromNSString([replacements valueForKey:nsWord]);
|
||||
lst << replacement;
|
||||
}
|
||||
for (NSString *suggestion in suggestions) {
|
||||
// the replacement text from the userReplacementsDictionary will be in
|
||||
// the suggestions list; don't add it again.
|
||||
QString str = QString::fromNSString(suggestion);
|
||||
if (str != replacement) {
|
||||
lst << str;
|
||||
}
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
bool NSSpellCheckerDict::storeReplacement(const QString &bad,
|
||||
const QString &good)
|
||||
{
|
||||
qCDebug(SONNET_NSSPELLCHECKER) << "Not storing replacement" << good << "for" << bad;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NSSpellCheckerDict::addToPersonal(const QString &word)
|
||||
{
|
||||
NSString *nsWord = word.toNSString();
|
||||
NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
|
||||
if (![checker hasLearnedWord:nsWord]) {
|
||||
[checker learnWord:nsWord];
|
||||
[checker updatePanels];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NSSpellCheckerDict::addToSession(const QString &word)
|
||||
{
|
||||
qCDebug(SONNET_NSSPELLCHECKER) << "Not storing" << word << "in the session dictionary";
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
add_library(sonnet_voikko MODULE
|
||||
voikkoclient.cpp
|
||||
voikkodict.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(sonnet_voikko
|
||||
HEADER voikkodebug.h
|
||||
IDENTIFIER SONNET_VOIKKO
|
||||
CATEGORY_NAME kf.sonnet.clients.voikko
|
||||
OLD_CATEGORY_NAMES sonnet.plugins.voikko
|
||||
DESCRIPTION "Sonnet Voikko plugin"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
target_include_directories(sonnet_voikko PRIVATE ${VOIKKO_INCLUDE_DIR})
|
||||
|
||||
target_link_libraries(sonnet_voikko PRIVATE KF6::SonnetCore ${VOIKKO_LIBRARIES})
|
||||
|
||||
install(TARGETS sonnet_voikko DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf6/sonnet/)
|
||||
|
||||
set(SONNET_BACKEND_FOUND TRUE PARENT_SCOPE)
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* voikkoclient.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Jesse Jaara <jesse.jaara@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "voikkoclient.h"
|
||||
#include "voikkodebug.h"
|
||||
#include "voikkodict.h"
|
||||
|
||||
VoikkoClient::VoikkoClient(QObject *parent)
|
||||
: Sonnet::Client(parent)
|
||||
{
|
||||
qCDebug(SONNET_VOIKKO) << "Initializing Voikko spell checker plugin.";
|
||||
|
||||
char **dictionaries = voikkoListSupportedSpellingLanguages(nullptr);
|
||||
|
||||
if (!dictionaries) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; dictionaries[i] != nullptr; ++i) {
|
||||
QString language = QString::fromUtf8(dictionaries[i]);
|
||||
m_supportedLanguages.append(language);
|
||||
qCDebug(SONNET_VOIKKO) << "Found dictionary for language:" << language;
|
||||
}
|
||||
|
||||
voikkoFreeCstrArray(dictionaries);
|
||||
}
|
||||
|
||||
VoikkoClient::~VoikkoClient()
|
||||
{
|
||||
}
|
||||
|
||||
int VoikkoClient::reliability() const
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
Sonnet::SpellerPlugin *VoikkoClient::createSpeller(const QString &language)
|
||||
{
|
||||
VoikkoDict *speller = new VoikkoDict(language);
|
||||
if (speller->initFailed()) {
|
||||
delete speller;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return speller;
|
||||
}
|
||||
|
||||
QStringList VoikkoClient::languages() const
|
||||
{
|
||||
return m_supportedLanguages;
|
||||
}
|
||||
|
||||
QString VoikkoClient::name() const
|
||||
{
|
||||
return QStringLiteral("Voikko");
|
||||
}
|
||||
|
||||
#include "moc_voikkoclient.cpp"
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* voikkoclient.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Jesse Jaara <jesse.jaara@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef SONNET_VOIKKOCLIENT_H
|
||||
#define SONNET_VOIKKOCLIENT_H
|
||||
|
||||
#include "client_p.h"
|
||||
|
||||
class VoikkoClient : public Sonnet::Client
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(Sonnet::Client)
|
||||
Q_PLUGIN_METADATA(IID "org.kde.Sonnet.VoikkoClient")
|
||||
|
||||
public:
|
||||
explicit VoikkoClient(QObject *parent = nullptr);
|
||||
~VoikkoClient();
|
||||
|
||||
int reliability() const override;
|
||||
|
||||
Sonnet::SpellerPlugin *createSpeller(const QString &language) override;
|
||||
|
||||
QStringList languages() const override;
|
||||
|
||||
QString name() const override;
|
||||
|
||||
private:
|
||||
QStringList m_supportedLanguages;
|
||||
};
|
||||
|
||||
#endif // SONNET_VOIKKOCLIENT_H
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* voikkodict.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Jesse Jaara <jesse.jaara@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "voikkodict.h"
|
||||
#include "voikkodebug.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QStandardPaths>
|
||||
#ifdef Q_IS_WIN
|
||||
#include <QSysInfo>
|
||||
#endif
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace
|
||||
{
|
||||
// QString literals used in loading and storing user dictionary
|
||||
inline const QString replacement_bad_str() Q_DECL_NOEXCEPT
|
||||
{
|
||||
return QStringLiteral("bad");
|
||||
}
|
||||
|
||||
inline const QString replacement_good_str() Q_DECL_NOEXCEPT
|
||||
{
|
||||
return QStringLiteral("good");
|
||||
}
|
||||
|
||||
inline const QString personal_words_str() Q_DECL_NOEXCEPT
|
||||
{
|
||||
return QStringLiteral("PersonalWords");
|
||||
}
|
||||
|
||||
inline const QString replacements_str() Q_DECL_NOEXCEPT
|
||||
{
|
||||
return QStringLiteral("Replacements");
|
||||
}
|
||||
|
||||
// Set path to: QStandardPaths::GenericDataLocation/Sonnet/Voikko-user-dictionary.json
|
||||
QString getUserDictionaryPath() Q_DECL_NOEXCEPT
|
||||
{
|
||||
QString directory = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Resolve the windows' Roaming directory manually
|
||||
if (QSysInfo::windowsVersion() == QSysInfo::WV_XP || QSysInfo::windowsVersion() == QSysInfo::WV_2003) {
|
||||
// In Xp Roaming is "<user>/Application Data"
|
||||
// DataLocation: "<user>/Local Settings/Application Data"
|
||||
directory += QStringLiteral("/../../Application Data");
|
||||
} else {
|
||||
directory += QStringLiteral("/../Roaming");
|
||||
}
|
||||
#endif
|
||||
|
||||
directory += QStringLiteral("/Sonnet");
|
||||
QDir path(directory);
|
||||
path.mkpath(path.absolutePath());
|
||||
|
||||
return path.absoluteFilePath(QStringLiteral("Voikko-user-dictionary.json"));
|
||||
}
|
||||
|
||||
void addReplacementToNode(QJsonObject &languageNode, const QString &bad, const QString &good) Q_DECL_NOEXCEPT
|
||||
{
|
||||
QJsonObject pair;
|
||||
pair[replacement_bad_str()] = good;
|
||||
pair[replacement_good_str()] = bad;
|
||||
|
||||
auto replaceList = languageNode[replacements_str()].toArray();
|
||||
replaceList.append(pair);
|
||||
languageNode[replacements_str()] = replaceList;
|
||||
}
|
||||
|
||||
void addPersonalWordToNode(QJsonObject &languageNode, const QString &word) Q_DECL_NOEXCEPT
|
||||
{
|
||||
auto arr = languageNode[personal_words_str()].toArray();
|
||||
arr.append(word);
|
||||
languageNode[personal_words_str()] = arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the root json object from fileName.
|
||||
*
|
||||
* Returns an empty node in case of an IO error or the file is empty.
|
||||
*/
|
||||
QJsonObject readJsonRootObject(const QString &fileName) Q_DECL_NOEXCEPT
|
||||
{
|
||||
QFile userDictFile(fileName);
|
||||
|
||||
if (!userDictFile.exists()) {
|
||||
return QJsonObject(); // Nothing has been saved so far.
|
||||
}
|
||||
|
||||
if (!userDictFile.open(QIODevice::ReadOnly)) {
|
||||
qCWarning(SONNET_VOIKKO) << "Could not open personal dictionary. Failed to open file" << fileName;
|
||||
qCWarning(SONNET_VOIKKO) << "Reason:" << userDictFile.errorString();
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonDocument dictDoc = QJsonDocument::fromJson(userDictFile.readAll());
|
||||
userDictFile.close();
|
||||
|
||||
return dictDoc.object();
|
||||
}
|
||||
}
|
||||
|
||||
class VoikkoDictPrivate
|
||||
{
|
||||
public:
|
||||
VoikkoHandle *m_handle;
|
||||
const VoikkoDict *q;
|
||||
|
||||
QSet<QString> m_sessionWords;
|
||||
QSet<QString> m_personalWords;
|
||||
QHash<QString, QString> m_replacements;
|
||||
|
||||
QString m_userDictionaryFilepath;
|
||||
|
||||
// Used when converting Qstring to wchar_t strings
|
||||
QList<wchar_t> m_conversionBuffer;
|
||||
|
||||
VoikkoDictPrivate(const QString &language, const VoikkoDict *publicPart) Q_DECL_NOEXCEPT : q(publicPart),
|
||||
m_userDictionaryFilepath(getUserDictionaryPath()),
|
||||
m_conversionBuffer(256)
|
||||
{
|
||||
const char *error;
|
||||
m_handle = voikkoInit(&error, language.toUtf8().data(), nullptr);
|
||||
|
||||
if (error != nullptr) {
|
||||
qCWarning(SONNET_VOIKKO) << "Failed to initialize Voikko spelling backend. Reason:" << error;
|
||||
} else { // Continue to load user's own words
|
||||
loadUserDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new ignored/personal word or replacement pair in the user's
|
||||
* dictionary m_userDictionaryFilepath.
|
||||
*
|
||||
* returns true on success else false
|
||||
*/
|
||||
bool storePersonal(const QString &personalWord, const QString &bad = QString(), const QString &good = QString()) const Q_DECL_NOEXCEPT
|
||||
{
|
||||
QFile userDictFile(m_userDictionaryFilepath);
|
||||
|
||||
if (!userDictFile.open(QIODevice::ReadWrite)) {
|
||||
qCWarning(SONNET_VOIKKO) << "Could not save personal dictionary. Failed to open file:" << m_userDictionaryFilepath;
|
||||
qCWarning(SONNET_VOIKKO) << "Reason:" << userDictFile.errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument dictDoc = QJsonDocument::fromJson(userDictFile.readAll());
|
||||
auto root = readJsonRootObject(m_userDictionaryFilepath);
|
||||
auto languageNode = root[q->language()].toObject();
|
||||
|
||||
// Empty value means we are storing a bad:good pair
|
||||
if (personalWord.isEmpty()) {
|
||||
addReplacementToNode(languageNode, bad, good);
|
||||
} else {
|
||||
addPersonalWordToNode(languageNode, personalWord);
|
||||
}
|
||||
|
||||
root[q->language()] = languageNode;
|
||||
dictDoc.setObject(root);
|
||||
|
||||
userDictFile.reset();
|
||||
userDictFile.write(dictDoc.toJson());
|
||||
userDictFile.close();
|
||||
qCDebug(SONNET_VOIKKO) << "Changes to user dictionary saved to file: " << m_userDictionaryFilepath;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user's own personal words and replacement pairs from
|
||||
* m_userDictionaryFilepath.
|
||||
*/
|
||||
void loadUserDictionary() Q_DECL_NOEXCEPT
|
||||
{
|
||||
// If root is empty we will fail later on when checking if
|
||||
// languageNode is empty.
|
||||
auto root = readJsonRootObject(m_userDictionaryFilepath);
|
||||
auto languageNode = root[q->language()].toObject();
|
||||
|
||||
if (languageNode.isEmpty()) {
|
||||
return; // Nothing to load
|
||||
}
|
||||
|
||||
loadUserWords(languageNode);
|
||||
loadUserReplacements(languageNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given QString to a \0 terminated wchar_t string.
|
||||
* Uses QList as a buffer and return it's internal data pointer.
|
||||
*/
|
||||
inline const wchar_t *QStringToWchar(const QString &str) Q_DECL_NOEXCEPT
|
||||
{
|
||||
m_conversionBuffer.resize(str.length() + 1);
|
||||
int size = str.toWCharArray(m_conversionBuffer.data());
|
||||
m_conversionBuffer[size] = '\0';
|
||||
|
||||
return m_conversionBuffer.constData();
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Extract and append user defined words from the languageNode.
|
||||
*/
|
||||
inline void loadUserWords(const QJsonObject &languageNode) Q_DECL_NOEXCEPT
|
||||
{
|
||||
const auto words = languageNode[personal_words_str()].toArray();
|
||||
for (auto word : words) {
|
||||
m_personalWords.insert(word.toString());
|
||||
}
|
||||
qCDebug(SONNET_VOIKKO) << QStringLiteral("Loaded %1 words from the user dictionary.").arg(words.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and append user defined replacement pairs from the languageNode.
|
||||
*/
|
||||
inline void loadUserReplacements(const QJsonObject &languageNode) Q_DECL_NOEXCEPT
|
||||
{
|
||||
const auto words = languageNode[replacements_str()].toArray();
|
||||
for (auto pair : words) {
|
||||
m_replacements[pair.toObject()[replacement_bad_str()].toString()] = pair.toObject()[replacement_good_str()].toString();
|
||||
}
|
||||
qCDebug(SONNET_VOIKKO) << QStringLiteral("Loaded %1 replacements from the user dictionary.").arg(words.size());
|
||||
}
|
||||
};
|
||||
|
||||
VoikkoDict::VoikkoDict(const QString &language) Q_DECL_NOEXCEPT : SpellerPlugin(language), d(new VoikkoDictPrivate(language, this))
|
||||
{
|
||||
qCDebug(SONNET_VOIKKO) << "Loading dictionary for language:" << language;
|
||||
}
|
||||
|
||||
VoikkoDict::~VoikkoDict()
|
||||
{
|
||||
}
|
||||
|
||||
bool VoikkoDict::isCorrect(const QString &word) const
|
||||
{
|
||||
// Check the session word list and personal word list first
|
||||
if (d->m_sessionWords.contains(word) || d->m_personalWords.contains(word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return voikkoSpellUcs4(d->m_handle, d->QStringToWchar(word)) == VOIKKO_SPELL_OK;
|
||||
}
|
||||
|
||||
QStringList VoikkoDict::suggest(const QString &word) const
|
||||
{
|
||||
QStringList suggestions;
|
||||
|
||||
auto userDictPos = d->m_replacements.constFind(word);
|
||||
if (userDictPos != d->m_replacements.constEnd()) {
|
||||
suggestions.append(*userDictPos);
|
||||
}
|
||||
|
||||
auto voikkoSuggestions = voikkoSuggestUcs4(d->m_handle, d->QStringToWchar(word));
|
||||
|
||||
if (!voikkoSuggestions) {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
for (int i = 0; voikkoSuggestions[i] != nullptr; ++i) {
|
||||
QString suggestion = QString::fromWCharArray(voikkoSuggestions[i]);
|
||||
suggestions.append(suggestion);
|
||||
}
|
||||
qCDebug(SONNET_VOIKKO) << "Misspelled:" << word << "|Suggestons:" << suggestions.join(QLatin1String(", "));
|
||||
|
||||
voikko_free_suggest_ucs4(voikkoSuggestions);
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
bool VoikkoDict::storeReplacement(const QString &bad, const QString &good)
|
||||
{
|
||||
qCDebug(SONNET_VOIKKO) << "Adding new replacement pair to user dictionary:" << bad << "->" << good;
|
||||
d->m_replacements[bad] = good;
|
||||
return d->storePersonal(QString(), bad, good);
|
||||
}
|
||||
|
||||
bool VoikkoDict::addToPersonal(const QString &word)
|
||||
{
|
||||
qCDebug(SONNET_VOIKKO()) << "Adding new word to user dictionary" << word;
|
||||
d->m_personalWords.insert(word);
|
||||
return d->storePersonal(word);
|
||||
}
|
||||
|
||||
bool VoikkoDict::addToSession(const QString &word)
|
||||
{
|
||||
qCDebug(SONNET_VOIKKO()) << "Adding new word to session dictionary" << word;
|
||||
d->m_sessionWords.insert(word);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VoikkoDict::initFailed() const Q_DECL_NOEXCEPT
|
||||
{
|
||||
return !d->m_handle;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* voikkodict.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Jesse Jaara <jesse.jaara@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef SONNET_VOIKKODICT_H
|
||||
#define SONNET_VOIKKODICT_H
|
||||
|
||||
#include "spellerplugin_p.h"
|
||||
#include <libvoikko/voikko.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class VoikkoClient;
|
||||
class VoikkoDictPrivate;
|
||||
|
||||
class VoikkoDict : public Sonnet::SpellerPlugin
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Declare VoikkoClient as friend so we can use the protected constructor.
|
||||
*/
|
||||
friend class VoikkoClient;
|
||||
|
||||
~VoikkoDict();
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @returns true if initializing Voikko backend failed.
|
||||
*/
|
||||
bool initFailed() const Q_DECL_NOEXCEPT;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Constructor is protected so that only spellers created
|
||||
* and validated through VoikkoClient can be used.
|
||||
*/
|
||||
explicit VoikkoDict(const QString &language) Q_DECL_NOEXCEPT;
|
||||
|
||||
private:
|
||||
QScopedPointer<VoikkoDictPrivate> d;
|
||||
};
|
||||
|
||||
#endif // SONNET_VOIKKODICT_H
|
||||
Reference in New Issue
Block a user