Commit 7ecb04cd authored by Stefano Crocco's avatar Stefano Crocco Committed by David Faure
Browse files

Restore spell checking support in HTML pages

The spell checking configuration uses the Sonnet dialog, even if not all
options are supported by QtWebEngine.
parent 4fb458cb
cmake_minimum_required(VERSION 3.0)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# KDE Application Version, managed by release script
set (RELEASE_SERVICE_VERSION_MAJOR "21")
......@@ -35,6 +36,8 @@ find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts KCMUtils Archive C
find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Activities DocTools) # Optional
find_package(Hunspell)
if(NOT Qt5WebEngineWidgets_VERSION VERSION_LESS "5.13.0")
add_definitions(-DWEBENGINE_PDF_VIEWER)
endif()
......@@ -62,6 +65,11 @@ check_symbol_exists(mallinfo "stdlib.h" KDE_MALLINFO_STDL
# TODO KDE_MALLINFO_FIELD_uordblks
# TODO KDE_MALLINFO_FIELD_usmblks
if(Hunspell_FOUND)
set(WEBENGINEPART_DICTIONARY_DIR ${CMAKE_INSTALL_PREFIX}/share/konqueror/webengine_dictionaries CACHE PATH "The directory where dictionary files for WebEnginePart will be put")
add_compile_definitions(WEBENGINEPART_DICTIONARY_DIR="${WEBENGINEPART_DICTIONARY_DIR}")
endif()
configure_file (config-konqueror.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-konqueror.h )
configure_file (konqueror-version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/konqueror-version.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
......
# This file is part of the KDE project.
#
# Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
#
# 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) version 3 or any later version
# accepted by the membership of KDE e.V. (or its successor approved
# by the membership of KDE e.V.), which shall act as a proxy
# defined in Section 14 of version 3 of the license.
#
# 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, see <http://www.gnu.org/licenses/>.
#[=======================================================================[.rst:
FindHunspell
-------
Finds the Hunspell program and its availlable dictionaries
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``Hunspell_FOUND``
True if the system has the hunspell executable.
``Hunspell_EXECUTABLE``
The path to the hunspell executable
``Hunspell_DICTIONARIES``
A list of all dictionaries availlable to hunspell, as reported by hunspell -D
``Hunspell_UNIQUE_DICTIONARIES``
A list of dictionaries availlable to hunspell without dictionaries with the same name. If more than one dictionary with the same name exists, the first one reported by hunspell -D will be included
#]=======================================================================]
find_program(Hunspell_EXECUTABLE hunspell)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Hunspell
FOUND_VAR Hunspell_FOUND
REQUIRED_VARS Hunspell_EXECUTABLE
)
if (Hunspell_FOUND)
#Find which hunspell dictionaries are availlable
execute_process(COMMAND ${Hunspell_EXECUTABLE} "-D" OUTPUT_VARIABLE Hunspell_OUTPUT ERROR_VARIABLE Hunspell_OUTPUT)
#Remove useless output
string(REGEX MATCH "AVAILABLE DICTIONARIES [^\n]*\n(.*)" Hunspell_DICTIONARIES ${Hunspell_OUTPUT})
#Convert string into list
string(REGEX REPLACE "\n" ";" Hunspell_DICTIONARIES ${CMAKE_MATCH_1})
set(Hunspell_UNIQUE_DICTIONARIES "")
set(Hunspell_unique_dict_names "")
#
foreach(D ${Hunspell_DICTIONARIES})
get_filename_component(base ${D} NAME)
list(FIND Hunspell_unique_dict_names ${base} dict_found)
if (${dict_found} EQUAL -1)
list(APPEND Hunspell_unique_dict_names ${base})
list(APPEND Hunspell_UNIQUE_DICTIONARIES ${D})
endif()
endforeach()
endif()
# This file is part of the KDE project.
#
# Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
#
# 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) version 3 or any later version
# accepted by the membership of KDE e.V. (or its successor approved
# by the membership of KDE e.V.), which shall act as a proxy
# defined in Section 14 of version 3 of the license.
#
# 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, see <http://www.gnu.org/licenses/>.
#[=======================================================================[.rst:
FindWebEngineDictConverter
-------
Finds the qwebengine_convert_dict tool
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``WebEngineDictConverter_FOUND``
True if the system has the qwebengine_convert_dict executable.
``WebEngineDictConverter_EXECUTABLE``
The path to the qwebengine_convert_dict executable
#]=======================================================================]
include(ECMQueryQmake)
query_qmake(QT_BINARIES_DIR QT_INSTALL_BINS)
find_program(WebEngineDictConverter_EXECUTABLE qwebengine_convert_dict HINTS ${QT_BINARIES_DIR})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WebEngineDictConverter
FOUND_VAR WebEngineDictConverter_FOUND
REQUIRED_VARS WebEngineDictConverter_EXECUTABLE
)
......@@ -8,6 +8,7 @@ set(konq_LIB_SRCS
konq_historyentry.cpp
konq_historyloader.cpp
konq_historyprovider.cpp # konqueror and konqueror/sidebar
konq_spellcheckingconfigurationdispatcher.cpp #konqueror and webenginepart
)
ecm_qt_declare_logging_category(konq_LIB_SRCS HEADER libkonq_debug.h IDENTIFIER LIBKONQ_LOG CATEGORY_NAME org.kde.libkonq)
......
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "konq_spellcheckingconfigurationdispatcher.h"
KonqSpellCheckingConfigurationDispatcher * KonqSpellCheckingConfigurationDispatcher::self()
{
static KonqSpellCheckingConfigurationDispatcher s_self;
return &s_self;
}
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef KONQ_SPELLCHECKINGCONFIGURATIONDISPATCHER_H
#define KONQ_SPELLCHECKINGCONFIGURATIONDISPATCHER_H
#include <QObject>
#include <libkonq_export.h>
class LIBKONQ_EXPORT KonqSpellCheckingConfigurationDispatcher : public QObject
{
Q_OBJECT
public:
static KonqSpellCheckingConfigurationDispatcher* self();
signals:
void spellCheckingConfigurationChanged(bool enabled);
};
#endif // KONQ_SPELLCHECKINGCONFIGURATIONDISPATCHER_H
......@@ -17,6 +17,8 @@
Boston, MA 02110-1301, USA.
*/
#include <QtGlobal>
#include "konqapplication.h"
#include "konqsettings.h"
#include <QDBusConnection>
......@@ -41,6 +43,12 @@ KonquerorApplication::KonquerorApplication(int &argc, char **argv)
dbus.connect(QString(), KONQ_MAIN_PATH, dbusInterface, QStringLiteral("removeFromCombo"), this,
SLOT(slotRemoveFromCombo(QString,QDBusMessage)));
dbus.connect(QString(), KONQ_MAIN_PATH, dbusInterface, QStringLiteral("comboCleared"), this, SLOT(slotComboCleared(QDBusMessage)));
#ifdef WEBENGINEPART_DICTIONARY_DIR
if (!qEnvironmentVariableIsSet("QTWEBENGINE_DICTIONARIES_PATH")) {
qputenv("QTWEBENGINE_DICTIONARIES_PATH", WEBENGINEPART_DICTIONARY_DIR);
}
#endif
}
void KonquerorApplication::slotReparseConfiguration()
......
......@@ -56,6 +56,7 @@
#include <konq_events.h>
#include <konqpixmapprovider.h>
#include <konqsettings.h>
#include <konq_spellcheckingconfigurationdispatcher.h>
#include <kwidgetsaddons_version.h>
#include <kparts_version.h>
......@@ -1842,12 +1843,26 @@ void KonqMainWindow::slotConfigureDone()
void KonqMainWindow::slotConfigureSpellChecking()
{
#pragma message("TODO KF5: Port Sonnet::ConfigDialog usage somehow")
#if 0 // KF5 TODO
Sonnet::ConfigDialog dialog(KSharedConfig::openConfig().data(), this);
Sonnet::ConfigDialog dialog(this);
dialog.setWindowIcon(QIcon::fromTheme("konqueror"));
dialog.exec();
#endif
if (dialog.exec() == QDialog::Accepted) {
updateSpellCheckConfiguration();
}
}
void KonqMainWindow::updateSpellCheckConfiguration()
{
//HACK: since Sonnet doesn't allow to find out whether the spell checker should be enabled by default
//we need to open its config file and read the setting from there. We then store it in our own configuration file
//so that it can be read from there
KSharedConfig::Ptr cfg = KSharedConfig::openConfig("KDE/Sonnet.conf");
KConfigGroup grp = cfg->group("General");
bool enabled = grp.readEntry("checkerEnabledByDefault", false);
cfg = KSharedConfig::openConfig();
grp = cfg->group("General");
grp.writeEntry("SpellCheckingEnabled", enabled);
cfg->sync();
emit KonqSpellCheckingConfigurationDispatcher::self()->spellCheckingConfigurationChanged(enabled);
}
void KonqMainWindow::slotConfigureToolbars()
......
......@@ -552,6 +552,12 @@ private Q_SLOTS:
void showPageSecurity();
void toggleCompleteFullScreen(bool on);
/**
* Copies the "checkerEnabledByDefault" setting from the Sonnet configuration file
* to Konqueror's own and emits the spellCheckConfigurationChanged signal
*/
void updateSpellCheckConfiguration();
private:
void updateWindowIcon();
......
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Wallet)
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Notifications )
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Sonnet)
add_definitions(-DTRANSLATION_DOMAIN=\"webenginepart\")
......@@ -31,6 +32,7 @@ set(kwebenginepartlib_LIB_SRCS
webfieldsdataview.cpp
ui/credentialsdetailswidget.cpp
webengineurlrequestinterceptor.cpp
spellcheckermanager.cpp
)
ki18n_wrap_ui(kwebenginepartlib_LIB_SRCS webenginecustomizecacheablefieldsdlg.ui ui/credentialsdetailswidget.ui)
......@@ -60,10 +62,12 @@ target_link_libraries(kwebenginepartlib
KF5::Wallet
KF5::Notifications
PRIVATE
KF5::Konq
Qt5::PrintSupport
KF5::SonnetCore
KF5::IconThemes #for KIconLoader used by WebEnginePartErrorSchemeHandler
KF5::WindowSystem # for KUserTimestamp
KF5::SonnetCore
)
target_include_directories(kwebenginepartlib PUBLIC
......@@ -77,6 +81,7 @@ install(TARGETS kwebenginepartlib ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
add_library(webenginepart MODULE webenginepartfactory.cpp)
kcoreaddons_desktop_to_json(webenginepart webenginepart.desktop)
target_link_libraries(webenginepart kwebenginepartlib)
install(TARGETS webenginepart DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts)
......@@ -89,4 +94,33 @@ install(FILES error.html DESTINATION ${KDE_INSTALL_DATADIR}/webenginepart)
install(FILES settings/kconf_update/webenginepart.upd DESTINATION ${KDE_INSTALL_KCONFUPDATEDIR})
#COMPILE HUNSPELL DICTIONARIES AS BDIC FILES
if(${Hunspell_FOUND})
find_package(WebEngineDictConverter)
if (DictConverter_FOUND)
#Iterate on all dictionaries
foreach(D ${Hunspell_UNIQUE_DICTIONARIES})
get_filename_component(base_name ${D} NAME)
#Full name of the file to create
set(BDIC_name ${CMAKE_CURRENT_BINARY_DIR}/${base_name}.bdic)
#Name of the target to create
set(BDIC_target_name ${base_name}.bdic_target)
#Check that both .dic and .aff files exist for the given dictionary, otherwise the conversion tool will fail
if (EXISTS ${D}.dic AND EXISTS ${D}.aff)
add_custom_command(
OUTPUT ${BDIC_name}
COMMAND ${DictConverter_EXECUTABLE} ${D} ${BDIC_name}
)
#Install the bdic file
install(FILES ${BDIC_name} DESTINATION ${WEBENGINEPART_DICTIONARY_DIR})
#Create a new target which depends on the file
add_custom_target(${BDIC_target_name} DEPENDS ${BDIC_name})
#Add the new target as dependency to kwebenginepartlib, otherwise the dictionaries won't be built
add_dependencies(kwebenginepartlib ${BDIC_target_name})
endif()
endforeach()
endif()
endif()
add_subdirectory(about)
......@@ -737,6 +737,7 @@ void WebEngineSettings::init( KConfig * config, bool reset )
// These numbers should be calculated from real "logical" DPI/72, using a default dpi of 96 for now
computeFontSizes(96);
}
......
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "spellcheckermanager.h"
#include "webenginepage.h"
#include <QDir>
#include <QStringList>
#include <QDebug>
#include <QWebEngineProfile>
#include <QMenu>
#include <QAction>
#include <KActionCollection>
#include <KLocalizedString>
#include <KSharedConfig>
#include <KConfigGroup>
#include <konq_spellcheckingconfigurationdispatcher.h>
#ifndef WEBENGINEPART_DICTIONARY_DIR
#define WEBENGINEPART_DICTIONARY_DIR ""
#endif
SpellCheckerManager::SpellCheckerManager(): QObject()
{
m_dictionaryDir = QString(WEBENGINEPART_DICTIONARY_DIR);
connect(KonqSpellCheckingConfigurationDispatcher::self(), &KonqSpellCheckingConfigurationDispatcher::spellCheckingConfigurationChanged,
this, &SpellCheckerManager::updateConfiguration);
}
SpellCheckerManager::~SpellCheckerManager()
{
}
SpellCheckerManager * SpellCheckerManager::self()
{
static SpellCheckerManager s_self;
return &s_self;
}
void SpellCheckerManager::detectDictionaries()
{
if (m_dictionaryDir.isEmpty()) {
m_dicts.clear();
m_enabledDicts.clear();
return;
}
QStringList files = QDir(WEBENGINEPART_DICTIONARY_DIR).entryList({"*.bdic"});
QStringList languages;
std::transform(files.constBegin(), files.constEnd(), std::back_inserter(languages), [](const QString &f){return f.chopped(5);});
QMap<QString, QString> dicts = m_speller.availableDictionaries();
for (auto it = dicts.constBegin(); it != dicts.constEnd(); ++it) {
if (languages.contains(it.value())) {
m_dicts[it.value()] = it.key();
}
}
QMap<QString, QString> preferred = m_speller.preferredDictionaries();
for (auto it = preferred.constBegin(); it != preferred.constEnd(); ++it) {
if (m_dicts.contains(it.value())) {
m_enabledDicts << it.value();
}
}
}
void SpellCheckerManager::updateConfiguration(bool spellCheckingEnabled)
{
detectDictionaries();
QWebEngineProfile * prof = QWebEngineProfile::defaultProfile();
prof->setSpellCheckEnabled(spellCheckingEnabled);
prof->setSpellCheckLanguages(m_enabledDicts);
}
void SpellCheckerManager::setup()
{
if (m_setupDone) {
return;
}
m_setupDone = true;
KSharedConfigPtr cfg = KSharedConfig::openConfig();
KConfigGroup grp = cfg->group("General");
updateConfiguration(grp.readEntry("SpellCheckingEnabled", false));
}
QMenu * SpellCheckerManager::spellCheckingMenu(const QStringList &suggestions, KActionCollection* coll, WebEnginePage* page)
{
QMenu *menu = new QMenu();
menu->setTitle(i18n("Spelling"));
QWebEngineProfile *prof = QWebEngineProfile::defaultProfile();
bool spellingEnabled = prof->isSpellCheckEnabled();
QAction *a = new QAction(i18n("Spell Checking Enabled"), coll);
a->setCheckable(true);
a->setChecked(spellingEnabled);
connect(a, &QAction::toggled, this, &SpellCheckerManager::spellCheckingToggled);
menu->addAction(a);
if (spellingEnabled) {
if (!suggestions.isEmpty()) {
menu->addSeparator();
for (const QString &s : suggestions) {
a = new QAction(s, menu);
menu->addAction(a);
connect(a, &QAction::triggered, page, [page, s](){page->replaceMisspelledWord(s);});
}
}
menu->addSeparator();
QMenu *langs = new QMenu(menu);
langs->setTitle(i18n("&Languages"));
menu->addMenu(langs);
QStringList enabledLangs = prof->spellCheckLanguages();
for (auto it = m_dicts.constBegin(); it != m_dicts.constEnd(); ++it) {
a = new QAction(it.value(), coll);
a->setCheckable(true);
const QString lang = it.key();
a->setChecked(enabledLangs.contains(lang));
connect(a, &QAction::toggled, this, [this, lang](bool on){on ? addLanguage(lang) : removeLanguage(lang);});
langs->addAction(a);
}
}
return menu;
}
void SpellCheckerManager::addLanguage(const QString& lang)
{
QWebEngineProfile *prof = QWebEngineProfile::defaultProfile();
QStringList langs = prof->spellCheckLanguages();
if (!langs.contains(lang)) {
langs << lang;
prof->setSpellCheckLanguages(langs);
}
}
void SpellCheckerManager::removeLanguage(const QString& lang)
{
QWebEngineProfile *prof = QWebEngineProfile::defaultProfile();
QStringList langs = prof->spellCheckLanguages();
langs.removeAll(lang);
prof->setSpellCheckLanguages(langs);
}
void SpellCheckerManager::spellCheckingToggled(bool on)
{
QWebEngineProfile::defaultProfile()->setSpellCheckEnabled(on);
}
/*
* This file is part of the KDE project.
*
* Copyright 2021 Stefano Crocco <posta@stefanocrocco.it>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPELLCHECKERMANAGER_H
#define SPELLCHECKERMANAGER_H
#include <QObject>
#include <QMap>
#include <QDateTime>
#include <Sonnet/Speller>
class QMenu;
class KActionCollection;
class QWidget;
class WebEnginePage;
class SpellCheckerManager : public QObject
{
Q_OBJECT
public:
SpellCheckerManager();
~SpellCheckerManager();
static SpellCheckerManager* self();
QMenu *spellCheckingMenu(const QStringList &suggestions, KActionCollection *coll, WebEnginePage *page);
void setup();
public slots:
void updateConfiguration(bool spellCheckingEnabled);
private:
void removeLanguage(const QString &lang);
void addLanguage(const QString &lang);
void detectDictionaries();
private slots:
void spellCheckingToggled(bool on);
private:
bool m_setupDone = false;
QString m_dictionaryDir;
QMap<QString, QString> m_dicts;