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,112 @@
|
||||
add_library(KF6SonnetUi)
|
||||
add_library(KF6::SonnetUi ALIAS KF6SonnetUi)
|
||||
|
||||
set_target_properties(KF6SonnetUi PROPERTIES
|
||||
VERSION ${SONNET_VERSION}
|
||||
SOVERSION ${SONNET_SOVERSION}
|
||||
EXPORT_NAME SonnetUi
|
||||
)
|
||||
|
||||
ecm_create_qm_loader(KF6SonnetUi sonnet6_qt)
|
||||
|
||||
qt_wrap_ui(sonnetui_ui_SRCS
|
||||
configui.ui
|
||||
sonnetui.ui
|
||||
)
|
||||
|
||||
target_sources(KF6SonnetUi PRIVATE
|
||||
${sonnetui_ui_SRCS}
|
||||
configdialog.cpp
|
||||
configview.cpp
|
||||
configwidget.cpp
|
||||
dialog.cpp
|
||||
dictionarycombobox.cpp
|
||||
highlighter.cpp
|
||||
spellcheckdecorator.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(KF6SonnetUi
|
||||
HEADER ui_debug.h
|
||||
IDENTIFIER SONNET_LOG_UI
|
||||
CATEGORY_NAME kf.sonnet.ui
|
||||
OLD_CATEGORY_NAMES sonnet.ui
|
||||
DESCRIPTION "Sonnet UI"
|
||||
EXPORT SONNET
|
||||
)
|
||||
|
||||
ecm_generate_headers(SonnetUi_CamelCase_HEADERS
|
||||
HEADER_NAMES
|
||||
Dialog
|
||||
Highlighter
|
||||
ConfigDialog
|
||||
ConfigView
|
||||
ConfigWidget
|
||||
DictionaryComboBox
|
||||
SpellCheckDecorator
|
||||
PREFIX Sonnet
|
||||
REQUIRED_HEADERS SonnetUi_HEADERS
|
||||
)
|
||||
|
||||
ecm_generate_export_header(KF6SonnetUi
|
||||
BASE_NAME SonnetUi
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
VERSION_BASE_NAME Sonnet
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
DEPRECATION_VERSIONS
|
||||
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
|
||||
)
|
||||
|
||||
target_link_libraries(KF6SonnetUi
|
||||
PUBLIC Qt6::Widgets
|
||||
PRIVATE KF6::SonnetCore
|
||||
)
|
||||
|
||||
target_include_directories(KF6SonnetUi
|
||||
INTERFACE
|
||||
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/SonnetUi>"
|
||||
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/Sonnet>"
|
||||
PUBLIC
|
||||
"$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>" # version header
|
||||
)
|
||||
|
||||
install(TARGETS KF6SonnetUi EXPORT KF6SonnetTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES ${SonnetUi_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/SonnetUi/Sonnet COMPONENT Devel)
|
||||
|
||||
install(FILES
|
||||
${SonnetUi_HEADERS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sonnetui_export.h
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/SonnetUi/sonnet COMPONENT Devel)
|
||||
|
||||
if(BUILD_DESIGNERPLUGIN)
|
||||
add_subdirectory(designer)
|
||||
endif()
|
||||
|
||||
if (BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6SonnetUi_QCH
|
||||
NAME SonnetUi
|
||||
BASE_NAME KF6SonnetUi
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${SonnetUi_HEADERS}
|
||||
LINK_QCHS
|
||||
Qt6Core_QCH
|
||||
Qt6Gui_QCH
|
||||
Qt6Widgets_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
BLANK_MACROS
|
||||
SONNETUI_EXPORT
|
||||
SONNETUI_DEPRECATED_EXPORT
|
||||
SONNETUI_DEPRECATED
|
||||
"SONNETUI_DEPRECATED_VERSION(x, y, t)"
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* configdialog.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "configdialog.h"
|
||||
#include "configwidget.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
class Sonnet::ConfigDialogPrivate
|
||||
{
|
||||
public:
|
||||
ConfigDialogPrivate(ConfigDialog *parent)
|
||||
: q(parent)
|
||||
{
|
||||
}
|
||||
|
||||
ConfigWidget *ui = nullptr;
|
||||
ConfigDialog *const q;
|
||||
void slotConfigChanged();
|
||||
};
|
||||
|
||||
void ConfigDialogPrivate::slotConfigChanged()
|
||||
{
|
||||
Q_EMIT q->languageChanged(ui->language());
|
||||
}
|
||||
|
||||
ConfigDialog::ConfigDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, d(new ConfigDialogPrivate(this))
|
||||
{
|
||||
setObjectName(QStringLiteral("SonnetConfigDialog"));
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Spell Checking Configuration"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
d->ui = new ConfigWidget(this);
|
||||
layout->addWidget(d->ui);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &ConfigDialog::slotOk);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(d->ui, SIGNAL(configChanged()), this, SLOT(slotConfigChanged()));
|
||||
|
||||
connect(d->ui, &ConfigWidget::configChanged, this, &ConfigDialog::configChanged);
|
||||
}
|
||||
|
||||
ConfigDialog::~ConfigDialog() = default;
|
||||
|
||||
void ConfigDialog::slotOk()
|
||||
{
|
||||
d->ui->save();
|
||||
accept();
|
||||
}
|
||||
|
||||
void ConfigDialog::slotApply()
|
||||
{
|
||||
d->ui->save();
|
||||
}
|
||||
|
||||
void ConfigDialog::setLanguage(const QString &language)
|
||||
{
|
||||
d->ui->setLanguage(language);
|
||||
}
|
||||
|
||||
QString ConfigDialog::language() const
|
||||
{
|
||||
return d->ui->language();
|
||||
}
|
||||
|
||||
#include "moc_configdialog.cpp"
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* configdialog.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SONNET_CONFIGDIALOG_H
|
||||
#define SONNET_CONFIGDIALOG_H
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
#include <QDialog>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class ConfigDialogPrivate;
|
||||
/// The sonnet ConfigDialog
|
||||
class SONNETUI_EXPORT ConfigDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfigDialog(QWidget *parent);
|
||||
~ConfigDialog() override;
|
||||
|
||||
/**
|
||||
* Sets the language/dictionary that will be selected by default
|
||||
* in this config dialog.
|
||||
* This overrides the setting in the config file.
|
||||
*
|
||||
* @param language the language which will be selected by default.
|
||||
* @since 4.1
|
||||
*/
|
||||
void setLanguage(const QString &language);
|
||||
/**
|
||||
* return selected language
|
||||
* @since 4.8.1
|
||||
*/
|
||||
QString language() const;
|
||||
|
||||
protected Q_SLOTS:
|
||||
virtual void slotOk();
|
||||
virtual void slotApply();
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
/**
|
||||
* This is emitted all the time when we change config and not just language
|
||||
*
|
||||
* @param language the language which the user has selected
|
||||
* @since 4.1
|
||||
*/
|
||||
|
||||
void languageChanged(const QString &language);
|
||||
|
||||
/**
|
||||
* This is emitted when configChanged
|
||||
* @since 4.8.1
|
||||
*/
|
||||
void configChanged();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConfigDialogPrivate> const d;
|
||||
Q_DISABLE_COPY(ConfigDialog)
|
||||
Q_PRIVATE_SLOT(d, void slotConfigChanged())
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>Zack Rusin <zack@kde.org></author>
|
||||
<comment>Licensed under GNU LGPL</comment>
|
||||
<class>SonnetConfigUI</class>
|
||||
<widget class="QWidget" name="SonnetConfigUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>700</width>
|
||||
<height>833</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="nobackendfound">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<italic>true</italic>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No backend like hunspell/aspell/myspell installed</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="textLabel1">
|
||||
<property name="text">
|
||||
<string>Default language:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Sonnet::DictionaryComboBox" name="m_langCombo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox1">
|
||||
<property name="title">
|
||||
<string>Preferred Languages</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item row="0">
|
||||
<widget class="QListWidget" name="languageList">
|
||||
<property name="toolTip">
|
||||
<string>Choose your preferred languages</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox2">
|
||||
<property name="title">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="kcfg_autodetectLanguage">
|
||||
<property name="text">
|
||||
<string>Enable autodetection of &language</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="kcfg_backgroundCheckerEnabled">
|
||||
<property name="text">
|
||||
<string>Enable &background spellchecking</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="kcfg_checkerEnabledByDefault">
|
||||
<property name="text">
|
||||
<string>&Automatic spell checking enabled by default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="kcfg_skipUppercase">
|
||||
<property name="text">
|
||||
<string>Skip all &uppercase words</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="kcfg_skipRunTogether">
|
||||
<property name="text">
|
||||
<string>S&kip run-together words</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Ignored Words</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="ignoredWordsLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QListWidget" name="ignoreListWidget"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLineEdit" name="newIgnoreEdit"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>&Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QVBoxLayout" name="removeButtonLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeButton">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Sonnet::DictionaryComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>sonnet/dictionarycombobox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* configwidget.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "configview.h"
|
||||
#include "ui_configui.h"
|
||||
|
||||
#include "ui_debug.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
class Sonnet::ConfigViewPrivate
|
||||
{
|
||||
public:
|
||||
explicit ConfigViewPrivate(ConfigView *v);
|
||||
Ui_SonnetConfigUI ui;
|
||||
QWidget *wdg = nullptr;
|
||||
QStringList ignoreList;
|
||||
ConfigView *q;
|
||||
void slotUpdateButton(const QString &text);
|
||||
void slotSelectionChanged();
|
||||
void slotIgnoreWordAdded();
|
||||
void slotIgnoreWordRemoved();
|
||||
};
|
||||
|
||||
ConfigViewPrivate::ConfigViewPrivate(ConfigView *v)
|
||||
{
|
||||
q = v;
|
||||
}
|
||||
|
||||
void ConfigViewPrivate::slotUpdateButton(const QString &text)
|
||||
{
|
||||
ui.addButton->setEnabled(!text.isEmpty());
|
||||
}
|
||||
|
||||
void ConfigViewPrivate::slotSelectionChanged()
|
||||
{
|
||||
ui.removeButton->setEnabled(!ui.ignoreListWidget->selectedItems().isEmpty());
|
||||
}
|
||||
|
||||
void ConfigViewPrivate::slotIgnoreWordAdded()
|
||||
{
|
||||
QString newWord = ui.newIgnoreEdit->text();
|
||||
ui.newIgnoreEdit->clear();
|
||||
if (newWord.isEmpty() || ignoreList.contains(newWord)) {
|
||||
return;
|
||||
}
|
||||
ignoreList.append(newWord);
|
||||
|
||||
ui.ignoreListWidget->clear();
|
||||
ui.ignoreListWidget->addItems(ignoreList);
|
||||
|
||||
Q_EMIT q->configChanged();
|
||||
}
|
||||
|
||||
void ConfigViewPrivate::slotIgnoreWordRemoved()
|
||||
{
|
||||
const QList<QListWidgetItem *> selectedItems = ui.ignoreListWidget->selectedItems();
|
||||
for (const QListWidgetItem *item : selectedItems) {
|
||||
ignoreList.removeAll(item->text());
|
||||
}
|
||||
|
||||
ui.ignoreListWidget->clear();
|
||||
ui.ignoreListWidget->addItems(ignoreList);
|
||||
|
||||
Q_EMIT q->configChanged();
|
||||
}
|
||||
|
||||
ConfigView::ConfigView(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new ConfigViewPrivate(this))
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setObjectName(QStringLiteral("SonnetConfigUILayout"));
|
||||
d->wdg = new QWidget(this);
|
||||
d->ui.setupUi(d->wdg);
|
||||
d->ui.languageList->setProperty("_breeze_force_frame", true);
|
||||
|
||||
for (int i = 0; i < d->ui.m_langCombo->count(); i++) {
|
||||
const QString tag = d->ui.m_langCombo->itemData(i).toString();
|
||||
if (tag.isEmpty()) { // skip separator
|
||||
continue;
|
||||
}
|
||||
auto *item = new QListWidgetItem(d->ui.m_langCombo->itemText(i), d->ui.languageList);
|
||||
item->setData(Qt::UserRole, tag);
|
||||
}
|
||||
|
||||
d->ui.kcfg_backgroundCheckerEnabled->hide(); // hidden by default
|
||||
|
||||
connect(d->ui.addButton, &QAbstractButton::clicked, this, [this] {
|
||||
d->slotIgnoreWordAdded();
|
||||
});
|
||||
connect(d->ui.removeButton, &QAbstractButton::clicked, this, [this] {
|
||||
d->slotIgnoreWordRemoved();
|
||||
});
|
||||
|
||||
layout->addWidget(d->wdg);
|
||||
connect(d->ui.newIgnoreEdit, &QLineEdit::textChanged, this, [this](const QString &text) {
|
||||
d->slotUpdateButton(text);
|
||||
});
|
||||
connect(d->ui.ignoreListWidget, &QListWidget::itemSelectionChanged, this, [this] {
|
||||
d->slotSelectionChanged();
|
||||
});
|
||||
d->ui.addButton->setEnabled(false);
|
||||
d->ui.removeButton->setEnabled(false);
|
||||
|
||||
connect(d->ui.m_langCombo, &DictionaryComboBox::dictionaryChanged, this, &ConfigView::configChanged);
|
||||
connect(d->ui.languageList, &QListWidget::itemChanged, this, &ConfigView::configChanged);
|
||||
|
||||
connect(d->ui.kcfg_backgroundCheckerEnabled, &QAbstractButton::clicked, this, &ConfigView::configChanged);
|
||||
connect(d->ui.kcfg_skipUppercase, &QAbstractButton::clicked, this, &ConfigView::configChanged);
|
||||
connect(d->ui.kcfg_skipRunTogether, &QAbstractButton::clicked, this, &ConfigView::configChanged);
|
||||
connect(d->ui.kcfg_checkerEnabledByDefault, &QAbstractButton::clicked, this, &ConfigView::configChanged);
|
||||
connect(d->ui.kcfg_autodetectLanguage, &QAbstractButton::clicked, this, &ConfigView::configChanged);
|
||||
}
|
||||
|
||||
ConfigView::~ConfigView() = default;
|
||||
|
||||
void ConfigView::setNoBackendFoundVisible(bool show)
|
||||
{
|
||||
d->ui.nobackendfound->setVisible(show);
|
||||
}
|
||||
|
||||
bool ConfigView::noBackendFoundVisible() const
|
||||
{
|
||||
return d->ui.nobackendfound->isVisible();
|
||||
}
|
||||
|
||||
void ConfigView::setBackgroundCheckingButtonShown(bool b)
|
||||
{
|
||||
d->ui.kcfg_backgroundCheckerEnabled->setVisible(b);
|
||||
}
|
||||
|
||||
bool ConfigView::backgroundCheckingButtonShown() const
|
||||
{
|
||||
return !d->ui.kcfg_backgroundCheckerEnabled->isHidden();
|
||||
}
|
||||
|
||||
void ConfigView::setLanguage(const QString &language)
|
||||
{
|
||||
d->ui.m_langCombo->setCurrentByDictionary(language);
|
||||
}
|
||||
|
||||
QString ConfigView::language() const
|
||||
{
|
||||
if (d->ui.m_langCombo->count()) {
|
||||
return d->ui.m_langCombo->currentDictionary();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigView::setPreferredLanguages(const QStringList &preferredLanguages)
|
||||
{
|
||||
for (int i = 0; i < d->ui.languageList->count(); ++i) {
|
||||
QListWidgetItem *item = d->ui.languageList->item(i);
|
||||
QString tag = item->data(Qt::UserRole).toString();
|
||||
if (preferredLanguages.contains(tag)) {
|
||||
item->setCheckState(Qt::Checked);
|
||||
} else {
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
Q_EMIT configChanged();
|
||||
}
|
||||
|
||||
QStringList ConfigView::preferredLanguages() const
|
||||
{
|
||||
QStringList preferredLanguages;
|
||||
for (int i = 0; i < d->ui.languageList->count(); i++) {
|
||||
if (d->ui.languageList->item(i)->checkState() == Qt::Unchecked) {
|
||||
continue;
|
||||
}
|
||||
preferredLanguages << d->ui.languageList->item(i)->data(Qt::UserRole).toString();
|
||||
}
|
||||
return preferredLanguages;
|
||||
}
|
||||
|
||||
void ConfigView::setIgnoreList(const QStringList &ignoreList)
|
||||
{
|
||||
d->ignoreList = ignoreList;
|
||||
d->ignoreList.sort();
|
||||
d->ui.ignoreListWidget->clear();
|
||||
d->ui.ignoreListWidget->addItems(d->ignoreList);
|
||||
Q_EMIT configChanged();
|
||||
}
|
||||
|
||||
QStringList ConfigView::ignoreList() const
|
||||
{
|
||||
return d->ignoreList;
|
||||
}
|
||||
|
||||
#include "moc_configview.cpp"
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2020 Benjamin Port <benjamin.port@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SONNET_CONFIGVIEW_H
|
||||
#define SONNET_CONFIGVIEW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class ConfigViewPrivate;
|
||||
|
||||
class SONNETUI_EXPORT ConfigView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString language READ language WRITE setLanguage)
|
||||
Q_PROPERTY(QStringList ignoreList READ ignoreList WRITE setIgnoreList)
|
||||
Q_PROPERTY(QStringList preferredLanguages READ preferredLanguages WRITE setPreferredLanguages)
|
||||
Q_PROPERTY(bool backgroundCheckingButtonShown READ backgroundCheckingButtonShown WRITE setBackgroundCheckingButtonShown)
|
||||
Q_PROPERTY(bool showNoBackendFound READ noBackendFoundVisible WRITE setNoBackendFoundVisible)
|
||||
public:
|
||||
explicit ConfigView(QWidget *parent = nullptr);
|
||||
~ConfigView() override;
|
||||
|
||||
bool backgroundCheckingButtonShown() const;
|
||||
bool noBackendFoundVisible() const;
|
||||
QStringList preferredLanguages() const;
|
||||
QString language() const;
|
||||
QStringList ignoreList() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void setNoBackendFoundVisible(bool show);
|
||||
void setBackgroundCheckingButtonShown(bool);
|
||||
void setPreferredLanguages(const QStringList &ignoreList);
|
||||
void setLanguage(const QString &language);
|
||||
void setIgnoreList(const QStringList &ignoreList);
|
||||
|
||||
Q_SIGNALS:
|
||||
void configChanged();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConfigViewPrivate> const d;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* configwidget.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "configwidget.h"
|
||||
#include "ui_configui.h"
|
||||
|
||||
#include "loader_p.h"
|
||||
#include "settings.h"
|
||||
#include "settingsimpl_p.h"
|
||||
|
||||
#include "ui_debug.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QListWidgetItem>
|
||||
|
||||
using namespace Sonnet;
|
||||
|
||||
class Sonnet::ConfigWidgetPrivate
|
||||
{
|
||||
public:
|
||||
Ui_SonnetConfigUI ui;
|
||||
Settings *settings = nullptr;
|
||||
QWidget *wdg = nullptr;
|
||||
};
|
||||
|
||||
ConfigWidget::ConfigWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new ConfigWidgetPrivate)
|
||||
{
|
||||
d->settings = new Settings(this);
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setObjectName(QStringLiteral("SonnetConfigUILayout"));
|
||||
d->wdg = new QWidget(this);
|
||||
d->ui.setupUi(d->wdg);
|
||||
d->ui.languageList->setProperty("_breeze_force_frame", true);
|
||||
|
||||
d->ui.m_langCombo->setCurrentByDictionary(d->settings->defaultLanguage());
|
||||
|
||||
QStringList preferredLanguages = d->settings->preferredLanguages();
|
||||
for (int i = 0; i < d->ui.m_langCombo->count(); i++) {
|
||||
const QString tag = d->ui.m_langCombo->itemData(i).toString();
|
||||
if (tag.isEmpty()) { // skip separator
|
||||
continue;
|
||||
}
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(d->ui.m_langCombo->itemText(i), d->ui.languageList);
|
||||
item->setData(Qt::UserRole, tag);
|
||||
if (preferredLanguages.contains(tag)) {
|
||||
item->setCheckState(Qt::Checked);
|
||||
} else {
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
|
||||
d->ui.kcfg_skipUppercase->setChecked(d->settings->skipUppercase());
|
||||
d->ui.kcfg_skipRunTogether->setChecked(d->settings->skipRunTogether());
|
||||
d->ui.kcfg_checkerEnabledByDefault->setChecked(d->settings->checkerEnabledByDefault());
|
||||
d->ui.kcfg_autodetectLanguage->setChecked(d->settings->autodetectLanguage());
|
||||
QStringList ignoreList = d->settings->currentIgnoreList();
|
||||
ignoreList.sort();
|
||||
d->ui.ignoreListWidget->addItems(ignoreList);
|
||||
d->ui.kcfg_backgroundCheckerEnabled->setChecked(d->settings->backgroundCheckerEnabled());
|
||||
d->ui.kcfg_backgroundCheckerEnabled->hide(); // hidden by default
|
||||
connect(d->ui.addButton, &QAbstractButton::clicked, this, &ConfigWidget::slotIgnoreWordAdded);
|
||||
connect(d->ui.removeButton, &QAbstractButton::clicked, this, &ConfigWidget::slotIgnoreWordRemoved);
|
||||
|
||||
layout->addWidget(d->wdg);
|
||||
connect(d->ui.m_langCombo, &DictionaryComboBox::dictionaryChanged, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.languageList, &QListWidget::itemChanged, this, &ConfigWidget::configChanged);
|
||||
|
||||
connect(d->ui.kcfg_backgroundCheckerEnabled, &QAbstractButton::clicked, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.kcfg_skipUppercase, &QAbstractButton::clicked, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.kcfg_skipRunTogether, &QAbstractButton::clicked, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.kcfg_checkerEnabledByDefault, &QAbstractButton::clicked, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.kcfg_autodetectLanguage, &QAbstractButton::clicked, this, &ConfigWidget::configChanged);
|
||||
connect(d->ui.newIgnoreEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotUpdateButton);
|
||||
connect(d->ui.ignoreListWidget, &QListWidget::itemSelectionChanged, this, &ConfigWidget::slotSelectionChanged);
|
||||
d->ui.nobackendfound->setVisible(d->settings->clients().isEmpty());
|
||||
d->ui.addButton->setEnabled(false);
|
||||
d->ui.removeButton->setEnabled(false);
|
||||
}
|
||||
|
||||
ConfigWidget::~ConfigWidget() = default;
|
||||
|
||||
void ConfigWidget::slotUpdateButton(const QString &text)
|
||||
{
|
||||
d->ui.addButton->setEnabled(!text.isEmpty());
|
||||
}
|
||||
|
||||
void ConfigWidget::slotSelectionChanged()
|
||||
{
|
||||
d->ui.removeButton->setEnabled(!d->ui.ignoreListWidget->selectedItems().isEmpty());
|
||||
}
|
||||
|
||||
void ConfigWidget::save()
|
||||
{
|
||||
setFromGui();
|
||||
}
|
||||
|
||||
void ConfigWidget::setFromGui()
|
||||
{
|
||||
if (d->ui.m_langCombo->count()) {
|
||||
d->settings->setDefaultLanguage(d->ui.m_langCombo->currentDictionary());
|
||||
}
|
||||
|
||||
QStringList preferredLanguages;
|
||||
for (int i = 0; i < d->ui.languageList->count(); i++) {
|
||||
if (d->ui.languageList->item(i)->checkState() == Qt::Unchecked) {
|
||||
continue;
|
||||
}
|
||||
preferredLanguages << d->ui.languageList->item(i)->data(Qt::UserRole).toString();
|
||||
}
|
||||
d->settings->setPreferredLanguages(preferredLanguages);
|
||||
|
||||
d->settings->setSkipUppercase(d->ui.kcfg_skipUppercase->isChecked());
|
||||
d->settings->setSkipRunTogether(d->ui.kcfg_skipRunTogether->isChecked());
|
||||
d->settings->setBackgroundCheckerEnabled(d->ui.kcfg_backgroundCheckerEnabled->isChecked());
|
||||
d->settings->setCheckerEnabledByDefault(d->ui.kcfg_checkerEnabledByDefault->isChecked());
|
||||
d->settings->setAutodetectLanguage(d->ui.kcfg_autodetectLanguage->isChecked());
|
||||
|
||||
if (d->settings->modified()) {
|
||||
d->settings->save();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigWidget::slotIgnoreWordAdded()
|
||||
{
|
||||
QStringList ignoreList = d->settings->currentIgnoreList();
|
||||
QString newWord = d->ui.newIgnoreEdit->text();
|
||||
d->ui.newIgnoreEdit->clear();
|
||||
if (newWord.isEmpty() || ignoreList.contains(newWord)) {
|
||||
return;
|
||||
}
|
||||
ignoreList.append(newWord);
|
||||
d->settings->setCurrentIgnoreList(ignoreList);
|
||||
|
||||
d->ui.ignoreListWidget->clear();
|
||||
d->ui.ignoreListWidget->addItems(ignoreList);
|
||||
|
||||
Q_EMIT configChanged();
|
||||
}
|
||||
|
||||
void ConfigWidget::slotIgnoreWordRemoved()
|
||||
{
|
||||
QStringList ignoreList = d->settings->currentIgnoreList();
|
||||
const QList<QListWidgetItem *> selectedItems = d->ui.ignoreListWidget->selectedItems();
|
||||
for (const QListWidgetItem *item : selectedItems) {
|
||||
ignoreList.removeAll(item->text());
|
||||
}
|
||||
d->settings->setCurrentIgnoreList(ignoreList);
|
||||
|
||||
d->ui.ignoreListWidget->clear();
|
||||
d->ui.ignoreListWidget->addItems(ignoreList);
|
||||
|
||||
Q_EMIT configChanged();
|
||||
}
|
||||
|
||||
void ConfigWidget::setBackgroundCheckingButtonShown(bool b)
|
||||
{
|
||||
d->ui.kcfg_backgroundCheckerEnabled->setVisible(b);
|
||||
}
|
||||
|
||||
bool ConfigWidget::backgroundCheckingButtonShown() const
|
||||
{
|
||||
return !d->ui.kcfg_backgroundCheckerEnabled->isHidden();
|
||||
}
|
||||
|
||||
void ConfigWidget::slotDefault()
|
||||
{
|
||||
d->ui.kcfg_autodetectLanguage->setChecked(Settings::defaultAutodetectLanguage());
|
||||
d->ui.kcfg_skipUppercase->setChecked(Settings::defaultSkipUppercase());
|
||||
d->ui.kcfg_skipRunTogether->setChecked(Settings::defauktSkipRunTogether());
|
||||
d->ui.kcfg_checkerEnabledByDefault->setChecked(Settings::defaultCheckerEnabledByDefault());
|
||||
d->ui.kcfg_backgroundCheckerEnabled->setChecked(Settings::defaultBackgroundCheckerEnabled());
|
||||
d->ui.ignoreListWidget->clear();
|
||||
d->ui.m_langCombo->setCurrentByDictionary(d->settings->defaultLanguage());
|
||||
}
|
||||
|
||||
void ConfigWidget::setLanguage(const QString &language)
|
||||
{
|
||||
d->ui.m_langCombo->setCurrentByDictionary(language);
|
||||
}
|
||||
|
||||
QString ConfigWidget::language() const
|
||||
{
|
||||
if (d->ui.m_langCombo->count()) {
|
||||
return d->ui.m_langCombo->currentDictionary();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_configwidget.cpp"
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SONNET_CONFIGWIDGET_H
|
||||
#define SONNET_CONFIGWIDGET_H
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class ConfigWidgetPrivate;
|
||||
|
||||
/// The sonnet ConfigWidget
|
||||
class SONNETUI_EXPORT ConfigWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConfigWidget(QWidget *parent);
|
||||
~ConfigWidget() override;
|
||||
|
||||
bool backgroundCheckingButtonShown() const;
|
||||
|
||||
/**
|
||||
* Sets the language/dictionary that will be selected by default
|
||||
* in this config widget.
|
||||
* This overrides the setting in the config file.
|
||||
*
|
||||
* @param language the language which will be selected by default.
|
||||
* @since 4.1
|
||||
*/
|
||||
void setLanguage(const QString &language);
|
||||
|
||||
/**
|
||||
* Get the currently selected language for spell checking. Returns an empty string if
|
||||
* Sonnet was built without any spellchecking plugins.
|
||||
* @return the language currently selected in the language combobox
|
||||
* @since 4.1
|
||||
*/
|
||||
QString language() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void save();
|
||||
void setBackgroundCheckingButtonShown(bool);
|
||||
void slotDefault();
|
||||
protected Q_SLOTS:
|
||||
void slotIgnoreWordRemoved();
|
||||
void slotIgnoreWordAdded();
|
||||
private Q_SLOTS:
|
||||
SONNETUI_NO_EXPORT void slotUpdateButton(const QString &text);
|
||||
SONNETUI_NO_EXPORT void slotSelectionChanged();
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Signal sends when config was changed
|
||||
* @since 4.1
|
||||
*/
|
||||
void configChanged();
|
||||
|
||||
private:
|
||||
SONNETUI_NO_EXPORT void setFromGui();
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConfigWidgetPrivate> const d;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
include(ECMAddQtDesignerPlugin)
|
||||
|
||||
ecm_qtdesignerplugin_widget(Sonnet::DictionaryComboBox
|
||||
TOOLTIP "Dictionary Combobox"
|
||||
WHATSTHIS "A combobox to select a dictionary for spellchecking"
|
||||
GROUP "Sonnet (KF6)"
|
||||
)
|
||||
|
||||
ecm_add_qtdesignerplugin(sonnetuiwidgets
|
||||
NAME SonnetUiWidgets
|
||||
OUTPUT_NAME sonnet6widgets
|
||||
WIDGETS
|
||||
Sonnet::DictionaryComboBox
|
||||
LINK_LIBRARIES
|
||||
KF6::SonnetUi
|
||||
INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer"
|
||||
COMPONENT Devel
|
||||
)
|
||||
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* dialog.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "dialog.h"
|
||||
#include "ui_sonnetui.h"
|
||||
|
||||
#include "backgroundchecker.h"
|
||||
#include "settingsimpl_p.h"
|
||||
#include "speller.h"
|
||||
|
||||
#include <QProgressDialog>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QStringListModel>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
// to initially disable sorting in the suggestions listview
|
||||
#define NONSORTINGCOLUMN 2
|
||||
|
||||
class ReadOnlyStringListModel : public QStringListModel
|
||||
{
|
||||
public:
|
||||
explicit ReadOnlyStringListModel(QObject *parent)
|
||||
: QStringListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
};
|
||||
|
||||
class DialogPrivate
|
||||
{
|
||||
public:
|
||||
Ui_SonnetUi ui;
|
||||
ReadOnlyStringListModel *suggestionsModel = nullptr;
|
||||
QWidget *wdg = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
QProgressDialog *progressDialog = nullptr;
|
||||
QString originalBuffer;
|
||||
BackgroundChecker *checker = nullptr;
|
||||
|
||||
QString currentWord;
|
||||
int currentPosition;
|
||||
QMap<QString, QString> replaceAllMap;
|
||||
bool restart; // used when text is distributed across several qtextedits, eg in KAider
|
||||
|
||||
QMap<QString, QString> dictsMap;
|
||||
|
||||
int progressDialogTimeout;
|
||||
bool showCompletionMessageBox;
|
||||
bool spellCheckContinuedAfterReplacement;
|
||||
bool canceled;
|
||||
|
||||
void deleteProgressDialog(bool directly)
|
||||
{
|
||||
if (progressDialog) {
|
||||
progressDialog->hide();
|
||||
if (directly) {
|
||||
delete progressDialog;
|
||||
} else {
|
||||
progressDialog->deleteLater();
|
||||
}
|
||||
progressDialog = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Dialog::Dialog(BackgroundChecker *checker, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, d(new DialogPrivate)
|
||||
{
|
||||
setModal(true);
|
||||
setWindowTitle(tr("Check Spelling", "@title:window"));
|
||||
|
||||
d->checker = checker;
|
||||
|
||||
d->canceled = false;
|
||||
d->showCompletionMessageBox = false;
|
||||
d->spellCheckContinuedAfterReplacement = true;
|
||||
d->progressDialogTimeout = -1;
|
||||
d->progressDialog = nullptr;
|
||||
|
||||
initGui();
|
||||
initConnections();
|
||||
}
|
||||
|
||||
Dialog::~Dialog() = default;
|
||||
|
||||
void Dialog::initConnections()
|
||||
{
|
||||
connect(d->ui.m_addBtn, &QAbstractButton::clicked, this, &Dialog::slotAddWord);
|
||||
connect(d->ui.m_replaceBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceWord);
|
||||
connect(d->ui.m_replaceAllBtn, &QAbstractButton::clicked, this, &Dialog::slotReplaceAll);
|
||||
connect(d->ui.m_skipBtn, &QAbstractButton::clicked, this, &Dialog::slotSkip);
|
||||
connect(d->ui.m_skipAllBtn, &QAbstractButton::clicked, this, &Dialog::slotSkipAll);
|
||||
connect(d->ui.m_suggestBtn, &QAbstractButton::clicked, this, &Dialog::slotSuggest);
|
||||
connect(d->ui.m_language, &DictionaryComboBox::textActivated, this, &Dialog::slotChangeLanguage);
|
||||
connect(d->ui.m_suggestions, &QListView::clicked, this, &Dialog::slotSelectionChanged);
|
||||
connect(d->checker, &BackgroundChecker::misspelling, this, &Dialog::slotMisspelling);
|
||||
connect(d->checker, &BackgroundChecker::done, this, &Dialog::slotDone);
|
||||
connect(d->ui.m_suggestions, &QListView::doubleClicked, this, [this](const QModelIndex &) {
|
||||
slotReplaceWord();
|
||||
});
|
||||
connect(d->buttonBox, &QDialogButtonBox::accepted, this, &Dialog::slotFinished);
|
||||
connect(d->buttonBox, &QDialogButtonBox::rejected, this, &Dialog::slotCancel);
|
||||
connect(d->ui.m_replacement, &QLineEdit::returnPressed, this, &Dialog::slotReplaceWord);
|
||||
connect(d->ui.m_autoCorrect, &QPushButton::clicked, this, &Dialog::slotAutocorrect);
|
||||
// button use by kword/kpresenter
|
||||
// hide by default
|
||||
d->ui.m_autoCorrect->hide();
|
||||
}
|
||||
|
||||
void Dialog::initGui()
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
d->wdg = new QWidget(this);
|
||||
d->ui.setupUi(d->wdg);
|
||||
layout->addWidget(d->wdg);
|
||||
setGuiEnabled(false);
|
||||
|
||||
d->buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
|
||||
layout->addWidget(d->wdg);
|
||||
layout->addWidget(d->buttonBox);
|
||||
|
||||
// d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN );
|
||||
fillDictionaryComboBox();
|
||||
d->restart = false;
|
||||
|
||||
d->suggestionsModel = new ReadOnlyStringListModel(this);
|
||||
d->ui.m_suggestions->setModel(d->suggestionsModel);
|
||||
}
|
||||
|
||||
void Dialog::activeAutoCorrect(bool _active)
|
||||
{
|
||||
if (_active) {
|
||||
d->ui.m_autoCorrect->show();
|
||||
} else {
|
||||
d->ui.m_autoCorrect->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::showProgressDialog(int timeout)
|
||||
{
|
||||
d->progressDialogTimeout = timeout;
|
||||
}
|
||||
|
||||
void Dialog::showSpellCheckCompletionMessage(bool b)
|
||||
{
|
||||
d->showCompletionMessageBox = b;
|
||||
}
|
||||
|
||||
void Dialog::setSpellCheckContinuedAfterReplacement(bool b)
|
||||
{
|
||||
d->spellCheckContinuedAfterReplacement = b;
|
||||
}
|
||||
|
||||
void Dialog::slotAutocorrect()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
Q_EMIT autoCorrect(d->currentWord, d->ui.m_replacement->text());
|
||||
slotReplaceWord();
|
||||
}
|
||||
|
||||
void Dialog::setGuiEnabled(bool b)
|
||||
{
|
||||
d->wdg->setEnabled(b);
|
||||
}
|
||||
|
||||
void Dialog::setProgressDialogVisible(bool b)
|
||||
{
|
||||
if (!b) {
|
||||
d->deleteProgressDialog(true);
|
||||
} else if (d->progressDialogTimeout >= 0) {
|
||||
if (d->progressDialog) {
|
||||
return;
|
||||
}
|
||||
d->progressDialog = new QProgressDialog(this);
|
||||
d->progressDialog->setLabelText(tr("Spell checking in progress…", "@info:progress"));
|
||||
d->progressDialog->setWindowTitle(tr("Check Spelling", "@title:window"));
|
||||
d->progressDialog->setModal(true);
|
||||
d->progressDialog->setAutoClose(false);
|
||||
d->progressDialog->setAutoReset(false);
|
||||
// create an 'indefinite' progress box as we currently cannot get progress feedback from
|
||||
// the speller
|
||||
d->progressDialog->reset();
|
||||
d->progressDialog->setRange(0, 0);
|
||||
d->progressDialog->setValue(0);
|
||||
connect(d->progressDialog, &QProgressDialog::canceled, this, &Dialog::slotCancel);
|
||||
d->progressDialog->setMinimumDuration(d->progressDialogTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::slotFinished()
|
||||
{
|
||||
setProgressDialogVisible(false);
|
||||
Q_EMIT stop();
|
||||
// FIXME: should we emit done here?
|
||||
Q_EMIT spellCheckDone(d->checker->text());
|
||||
Q_EMIT spellCheckStatus(tr("Spell check stopped."));
|
||||
accept();
|
||||
}
|
||||
|
||||
void Dialog::slotCancel()
|
||||
{
|
||||
d->canceled = true;
|
||||
d->deleteProgressDialog(false); // this method can be called in response to
|
||||
// pressing 'Cancel' on the dialog
|
||||
Q_EMIT cancel();
|
||||
Q_EMIT spellCheckStatus(tr("Spell check canceled."));
|
||||
reject();
|
||||
}
|
||||
|
||||
QString Dialog::originalBuffer() const
|
||||
{
|
||||
return d->originalBuffer;
|
||||
}
|
||||
|
||||
QString Dialog::buffer() const
|
||||
{
|
||||
return d->checker->text();
|
||||
}
|
||||
|
||||
void Dialog::setBuffer(const QString &buf)
|
||||
{
|
||||
d->originalBuffer = buf;
|
||||
// it is possible to change buffer inside slot connected to done() signal
|
||||
d->restart = true;
|
||||
}
|
||||
|
||||
void Dialog::fillDictionaryComboBox()
|
||||
{
|
||||
// Since m_language is changed to DictionaryComboBox most code here is gone,
|
||||
// So fillDictionaryComboBox() could be removed and code moved to initGui()
|
||||
// because the call in show() looks obsolete
|
||||
Speller speller = d->checker->speller();
|
||||
d->dictsMap = speller.availableDictionaries();
|
||||
|
||||
updateDictionaryComboBox();
|
||||
}
|
||||
|
||||
void Dialog::updateDictionaryComboBox()
|
||||
{
|
||||
const Speller &speller = d->checker->speller();
|
||||
d->ui.m_language->setCurrentByDictionary(speller.language());
|
||||
}
|
||||
|
||||
void Dialog::updateDialog(const QString &word)
|
||||
{
|
||||
d->ui.m_unknownWord->setText(word);
|
||||
d->ui.m_contextLabel->setText(d->checker->currentContext());
|
||||
const QStringList suggs = d->checker->suggest(word);
|
||||
|
||||
if (suggs.isEmpty()) {
|
||||
d->ui.m_replacement->clear();
|
||||
} else {
|
||||
d->ui.m_replacement->setText(suggs.first());
|
||||
}
|
||||
fillSuggestions(suggs);
|
||||
}
|
||||
|
||||
void Dialog::show()
|
||||
{
|
||||
d->canceled = false;
|
||||
fillDictionaryComboBox();
|
||||
if (d->originalBuffer.isEmpty()) {
|
||||
d->checker->start();
|
||||
} else {
|
||||
d->checker->setText(d->originalBuffer);
|
||||
}
|
||||
setProgressDialogVisible(true);
|
||||
}
|
||||
|
||||
void Dialog::slotAddWord()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
d->checker->addWordToPersonal(d->currentWord);
|
||||
d->checker->continueChecking();
|
||||
}
|
||||
|
||||
void Dialog::slotReplaceWord()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
QString replacementText = d->ui.m_replacement->text();
|
||||
Q_EMIT replace(d->currentWord, d->currentPosition, replacementText);
|
||||
|
||||
if (d->spellCheckContinuedAfterReplacement) {
|
||||
d->checker->replace(d->currentPosition, d->currentWord, replacementText);
|
||||
d->checker->continueChecking();
|
||||
} else {
|
||||
d->checker->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::slotReplaceAll()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
d->replaceAllMap.insert(d->currentWord, d->ui.m_replacement->text());
|
||||
slotReplaceWord();
|
||||
}
|
||||
|
||||
void Dialog::slotSkip()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
d->checker->continueChecking();
|
||||
}
|
||||
|
||||
void Dialog::slotSkipAll()
|
||||
{
|
||||
setGuiEnabled(false);
|
||||
setProgressDialogVisible(true);
|
||||
//### do we want that or should we have a d->ignoreAll list?
|
||||
Speller speller = d->checker->speller();
|
||||
speller.addToPersonal(d->currentWord);
|
||||
d->checker->setSpeller(speller);
|
||||
d->checker->continueChecking();
|
||||
}
|
||||
|
||||
void Dialog::slotSuggest()
|
||||
{
|
||||
const QStringList suggs = d->checker->suggest(d->ui.m_replacement->text());
|
||||
fillSuggestions(suggs);
|
||||
}
|
||||
|
||||
void Dialog::slotChangeLanguage(const QString &lang)
|
||||
{
|
||||
const QString languageCode = d->dictsMap[lang];
|
||||
if (!languageCode.isEmpty()) {
|
||||
d->checker->changeLanguage(languageCode);
|
||||
slotSuggest();
|
||||
Q_EMIT languageChanged(languageCode);
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::slotSelectionChanged(const QModelIndex &item)
|
||||
{
|
||||
d->ui.m_replacement->setText(item.data().toString());
|
||||
}
|
||||
|
||||
void Dialog::fillSuggestions(const QStringList &suggs)
|
||||
{
|
||||
d->suggestionsModel->setStringList(suggs);
|
||||
}
|
||||
|
||||
void Dialog::slotMisspelling(const QString &word, int start)
|
||||
{
|
||||
setGuiEnabled(true);
|
||||
setProgressDialogVisible(false);
|
||||
Q_EMIT misspelling(word, start);
|
||||
// NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods
|
||||
// this dramatically reduces spellchecking time in Lokalize
|
||||
// as this doesn't fetch suggestions for words that are present in msgid
|
||||
if (!updatesEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->currentWord = word;
|
||||
d->currentPosition = start;
|
||||
if (d->replaceAllMap.contains(word)) {
|
||||
d->ui.m_replacement->setText(d->replaceAllMap[word]);
|
||||
slotReplaceWord();
|
||||
} else {
|
||||
updateDialog(word);
|
||||
}
|
||||
QDialog::show();
|
||||
}
|
||||
|
||||
void Dialog::slotDone()
|
||||
{
|
||||
d->restart = false;
|
||||
Q_EMIT spellCheckDone(d->checker->text());
|
||||
if (d->restart) {
|
||||
updateDictionaryComboBox();
|
||||
d->checker->setText(d->originalBuffer);
|
||||
d->restart = false;
|
||||
} else {
|
||||
setProgressDialogVisible(false);
|
||||
Q_EMIT spellCheckStatus(tr("Spell check complete."));
|
||||
accept();
|
||||
if (!d->canceled && d->showCompletionMessageBox) {
|
||||
QMessageBox::information(this, tr("Spell check complete."), tr("Check Spelling", "@title:window"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_dialog.cpp"
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* dialog.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2003 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SONNET_DIALOG_H
|
||||
#define SONNET_DIALOG_H
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
#include <QDialog>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QModelIndex;
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class BackgroundChecker;
|
||||
class DialogPrivate;
|
||||
/**
|
||||
* @class Sonnet::Dialog dialog.h <Sonnet/Dialog>
|
||||
*
|
||||
* @short Spellcheck dialog
|
||||
*
|
||||
* \code
|
||||
* Sonnet::Dialog dlg = new Sonnet::Dialog(
|
||||
* new Sonnet::BackgroundChecker(this), this);
|
||||
* //connect signals
|
||||
* ...
|
||||
* dlg->setBuffer( someText );
|
||||
* dlg->show();
|
||||
* \endcode
|
||||
*
|
||||
* You can change buffer inside a slot connected to done() signal
|
||||
* and spellcheck will continue with new data automatically.
|
||||
*/
|
||||
class SONNETUI_EXPORT Dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Dialog(BackgroundChecker *checker, QWidget *parent);
|
||||
~Dialog() override;
|
||||
|
||||
QString originalBuffer() const;
|
||||
QString buffer() const;
|
||||
|
||||
void show();
|
||||
void activeAutoCorrect(bool _active);
|
||||
|
||||
// Hide warning about done(), which is a slot in QDialog and a signal here.
|
||||
using QDialog::done;
|
||||
|
||||
/**
|
||||
* Controls whether an (indefinite) progress dialog is shown when the spell
|
||||
* checking takes longer than the given time to complete. By default no
|
||||
* progress dialog is shown. If the progress dialog is set to be shown, no
|
||||
* time consuming operation (for example, showing a notification message) should
|
||||
* be performed in a slot connected to the 'done' signal as this might trigger
|
||||
* the progress dialog unnecessarily.
|
||||
*
|
||||
* @param timeout time after which the progress dialog should appear; a negative
|
||||
* value can be used to hide it
|
||||
* @since 4.4
|
||||
*/
|
||||
void showProgressDialog(int timeout = 500);
|
||||
|
||||
/**
|
||||
* Controls whether a message box indicating the completion of the spell checking
|
||||
* is shown or not. By default it is not shown.
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
void showSpellCheckCompletionMessage(bool b = true);
|
||||
|
||||
/**
|
||||
* Controls whether the spell checking is continued after the replacement of a
|
||||
* misspelled word has been performed. By default it is continued.
|
||||
*
|
||||
* @since 4.4
|
||||
*/
|
||||
void setSpellCheckContinuedAfterReplacement(bool b);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setBuffer(const QString &);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* The dialog won't be closed if you setBuffer() in slot connected to this signal
|
||||
* Also emitted after stop() signal
|
||||
* @Since 5.65
|
||||
*/
|
||||
void spellCheckDone(const QString &newBuffer);
|
||||
void misspelling(const QString &word, int start);
|
||||
void replace(const QString &oldWord, int start, const QString &newWord);
|
||||
|
||||
void stop();
|
||||
void cancel();
|
||||
void autoCorrect(const QString ¤tWord, const QString &replaceWord);
|
||||
|
||||
/**
|
||||
* Signal sends when spell checking is finished/stopped/completed
|
||||
* @since 4.1
|
||||
*/
|
||||
void spellCheckStatus(const QString &);
|
||||
|
||||
/**
|
||||
* Emitted when the user changes the language used for spellchecking,
|
||||
* which is shown in a combobox of this dialog.
|
||||
*
|
||||
* @param dictionary the new language the user selected
|
||||
* @since 4.1
|
||||
*/
|
||||
void languageChanged(const QString &language);
|
||||
|
||||
private Q_SLOTS:
|
||||
SONNETUI_NO_EXPORT void slotMisspelling(const QString &word, int start);
|
||||
SONNETUI_NO_EXPORT void slotDone();
|
||||
|
||||
SONNETUI_NO_EXPORT void slotFinished();
|
||||
SONNETUI_NO_EXPORT void slotCancel();
|
||||
|
||||
SONNETUI_NO_EXPORT void slotAddWord();
|
||||
SONNETUI_NO_EXPORT void slotReplaceWord();
|
||||
SONNETUI_NO_EXPORT void slotReplaceAll();
|
||||
SONNETUI_NO_EXPORT void slotSkip();
|
||||
SONNETUI_NO_EXPORT void slotSkipAll();
|
||||
SONNETUI_NO_EXPORT void slotSuggest();
|
||||
SONNETUI_NO_EXPORT void slotChangeLanguage(const QString &);
|
||||
SONNETUI_NO_EXPORT void slotSelectionChanged(const QModelIndex &);
|
||||
SONNETUI_NO_EXPORT void slotAutocorrect();
|
||||
|
||||
SONNETUI_NO_EXPORT void setGuiEnabled(bool b);
|
||||
SONNETUI_NO_EXPORT void setProgressDialogVisible(bool b);
|
||||
|
||||
private:
|
||||
SONNETUI_NO_EXPORT void updateDialog(const QString &word);
|
||||
SONNETUI_NO_EXPORT void fillDictionaryComboBox();
|
||||
SONNETUI_NO_EXPORT void updateDictionaryComboBox();
|
||||
SONNETUI_NO_EXPORT void fillSuggestions(const QStringList &suggs);
|
||||
SONNETUI_NO_EXPORT void initConnections();
|
||||
SONNETUI_NO_EXPORT void initGui();
|
||||
SONNETUI_NO_EXPORT void continueChecking();
|
||||
|
||||
private:
|
||||
DialogPrivate *const d;
|
||||
Q_DISABLE_COPY(Dialog)
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2003 Ingo Kloecker <kloecker@kde.org>
|
||||
* SPDX-FileCopyrightText: 2008 Tom Albers <tomalbers@kde.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "dictionarycombobox.h"
|
||||
|
||||
#include "ui_debug.h"
|
||||
#include <speller.h>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
//@cond PRIVATE
|
||||
class DictionaryComboBoxPrivate
|
||||
{
|
||||
public:
|
||||
explicit DictionaryComboBoxPrivate(DictionaryComboBox *combo)
|
||||
: q(combo)
|
||||
{
|
||||
}
|
||||
|
||||
DictionaryComboBox *const q;
|
||||
void slotDictionaryChanged(int idx);
|
||||
};
|
||||
|
||||
void DictionaryComboBoxPrivate::slotDictionaryChanged(int idx)
|
||||
{
|
||||
Q_EMIT q->dictionaryChanged(q->itemData(idx).toString());
|
||||
Q_EMIT q->dictionaryNameChanged(q->itemText(idx));
|
||||
}
|
||||
|
||||
//@endcon
|
||||
|
||||
DictionaryComboBox::DictionaryComboBox(QWidget *parent)
|
||||
: QComboBox(parent)
|
||||
, d(new DictionaryComboBoxPrivate(this))
|
||||
{
|
||||
reloadCombo();
|
||||
connect(this, SIGNAL(activated(int)), SLOT(slotDictionaryChanged(int)));
|
||||
}
|
||||
|
||||
DictionaryComboBox::~DictionaryComboBox() = default;
|
||||
|
||||
QString DictionaryComboBox::currentDictionaryName() const
|
||||
{
|
||||
return currentText();
|
||||
}
|
||||
|
||||
QString DictionaryComboBox::currentDictionary() const
|
||||
{
|
||||
return itemData(currentIndex()).toString();
|
||||
}
|
||||
|
||||
bool DictionaryComboBox::assignDictionnaryName(const QString &name)
|
||||
{
|
||||
if (name.isEmpty() || name == currentText()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = findText(name);
|
||||
if (idx == -1) {
|
||||
qCDebug(SONNET_LOG_UI) << "name not found" << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
setCurrentIndex(idx);
|
||||
d->slotDictionaryChanged(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DictionaryComboBox::setCurrentByDictionaryName(const QString &name)
|
||||
{
|
||||
assignDictionnaryName(name);
|
||||
}
|
||||
|
||||
bool DictionaryComboBox::assignByDictionnary(const QString &dictionary)
|
||||
{
|
||||
if (dictionary.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (dictionary == itemData(currentIndex()).toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int idx = findData(dictionary);
|
||||
if (idx == -1) {
|
||||
qCDebug(SONNET_LOG_UI) << "dictionary not found" << dictionary;
|
||||
return false;
|
||||
}
|
||||
|
||||
setCurrentIndex(idx);
|
||||
d->slotDictionaryChanged(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DictionaryComboBox::setCurrentByDictionary(const QString &dictionary)
|
||||
{
|
||||
assignByDictionnary(dictionary);
|
||||
}
|
||||
|
||||
void DictionaryComboBox::reloadCombo()
|
||||
{
|
||||
clear();
|
||||
Sonnet::Speller speller;
|
||||
QMap<QString, QString> preferredDictionaries = speller.preferredDictionaries();
|
||||
QMapIterator<QString, QString> i(preferredDictionaries);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
addItem(i.key(), i.value());
|
||||
}
|
||||
if (count()) {
|
||||
insertSeparator(count());
|
||||
}
|
||||
|
||||
QMap<QString, QString> dictionaries = speller.availableDictionaries();
|
||||
i = dictionaries;
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (preferredDictionaries.contains(i.key())) {
|
||||
continue;
|
||||
}
|
||||
addItem(i.key(), i.value());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Sonnet
|
||||
|
||||
#include "moc_dictionarycombobox.cpp"
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2003 Ingo Kloecker <kloecker@kde.org>
|
||||
* SPDX-FileCopyrightText: 2008 Tom Albers <tomalbers@kde.nl>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef SONNET_DICTIONARYCOMBOBOX_H
|
||||
#define SONNET_DICTIONARYCOMBOBOX_H
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class DictionaryComboBoxPrivate;
|
||||
/**
|
||||
* @class Sonnet::DictionaryComboBox dictionarycombobox.h <Sonnet/DictionaryComboBox>
|
||||
*
|
||||
* @short A combo box for selecting the dictionary used for spell checking.
|
||||
* @author Ingo Kloecker <kloecker@kde.org>
|
||||
* @author Tom Albers <tomalbers@kde.nl>
|
||||
* @since 4.2
|
||||
**/
|
||||
|
||||
class SONNETUI_EXPORT DictionaryComboBox : public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
explicit DictionaryComboBox(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~DictionaryComboBox() override;
|
||||
|
||||
/**
|
||||
* Clears the widget and reloads the dictionaries from Sonnet.
|
||||
* Remember to set the dictionary you want selected after calling this function.
|
||||
*/
|
||||
void reloadCombo();
|
||||
|
||||
/**
|
||||
* Returns the current dictionary name, for example "German (Switzerland)"
|
||||
*/
|
||||
QString currentDictionaryName() const;
|
||||
|
||||
/**
|
||||
* Returns the current dictionary, for example "de_CH"
|
||||
*/
|
||||
QString currentDictionary() const;
|
||||
|
||||
/**
|
||||
* Sets the current dictionaryName to the given dictionaryName
|
||||
*/
|
||||
void setCurrentByDictionaryName(const QString &dictionaryName);
|
||||
|
||||
/**
|
||||
* Sets the current dictionary to the given dictionary
|
||||
* Return true if dictionary was found.
|
||||
* @since 5.40
|
||||
* TODO merge with previous method in kf6
|
||||
*/
|
||||
bool assignByDictionnary(const QString &dictionary);
|
||||
|
||||
/**
|
||||
* Sets the current dictionaryName to the given dictionaryName
|
||||
* Return true if dictionary was found.
|
||||
* @since 5.40
|
||||
* TODO merge with previous method in kf6
|
||||
*/
|
||||
bool assignDictionnaryName(const QString &name);
|
||||
|
||||
/**
|
||||
* Sets the current dictionary to the given dictionary.
|
||||
*/
|
||||
void setCurrentByDictionary(const QString &dictionary);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* @em Emitted whenever the current dictionary changes. Either
|
||||
* by user intervention or on setCurrentByDictionaryName() or on
|
||||
* setCurrentByDictionary(). For example "de_CH".
|
||||
*/
|
||||
void dictionaryChanged(const QString &dictionary);
|
||||
|
||||
/**
|
||||
* @em Emitted whenever the current dictionary changes. Either
|
||||
* by user intervention or on setCurrentByDictionaryName() or on
|
||||
* setCurrentByDictionary(). For example "German (Switzerland)".
|
||||
*/
|
||||
void dictionaryNameChanged(const QString &dictionaryName);
|
||||
|
||||
private:
|
||||
std::unique_ptr<DictionaryComboBoxPrivate> const d;
|
||||
Q_PRIVATE_SLOT(d, void slotDictionaryChanged(int))
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,526 @@
|
||||
/*
|
||||
* highlighter.cpp
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2006 Laurent Montel <montel@kde.org>
|
||||
* SPDX-FileCopyrightText: 2013 Martin Sandsmark <martin.sandsmark@org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "highlighter.h"
|
||||
|
||||
#include "languagefilter_p.h"
|
||||
#include "loader_p.h"
|
||||
#include "settingsimpl_p.h"
|
||||
#include "speller.h"
|
||||
#include "tokenizer_p.h"
|
||||
|
||||
#include "ui_debug.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QEvent>
|
||||
#include <QHash>
|
||||
#include <QKeyEvent>
|
||||
#include <QMetaMethod>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QTextCharFormat>
|
||||
#include <QTextCursor>
|
||||
#include <QTextEdit>
|
||||
#include <QTimer>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
// Cache of previously-determined languages (when using AutoDetectLanguage)
|
||||
// There is one such cache per block (paragraph)
|
||||
class LanguageCache : public QTextBlockUserData
|
||||
{
|
||||
public:
|
||||
// Key: QPair<start, length>
|
||||
// Value: language name
|
||||
QMap<QPair<int, int>, QString> languages;
|
||||
|
||||
// Remove all cached language information after @p pos
|
||||
void invalidate(int pos)
|
||||
{
|
||||
QMutableMapIterator<QPair<int, int>, QString> it(languages);
|
||||
it.toBack();
|
||||
while (it.hasPrevious()) {
|
||||
it.previous();
|
||||
if (it.key().first + it.key().second >= pos) {
|
||||
it.remove();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString languageAtPos(int pos) const
|
||||
{
|
||||
// The data structure isn't really great for such lookups...
|
||||
QMapIterator<QPair<int, int>, QString> it(languages);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
if (it.key().first <= pos && it.key().first + it.key().second >= pos) {
|
||||
return it.value();
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
};
|
||||
|
||||
class HighlighterPrivate
|
||||
{
|
||||
public:
|
||||
HighlighterPrivate(Highlighter *qq, const QColor &col)
|
||||
: textEdit(nullptr)
|
||||
, plainTextEdit(nullptr)
|
||||
, spellColor(col)
|
||||
, q(qq)
|
||||
{
|
||||
tokenizer = new WordTokenizer();
|
||||
active = true;
|
||||
automatic = false;
|
||||
autoDetectLanguageDisabled = false;
|
||||
wordCount = 0;
|
||||
errorCount = 0;
|
||||
intraWordEditing = false;
|
||||
completeRehighlightRequired = false;
|
||||
spellColor = spellColor.isValid() ? spellColor : Qt::red;
|
||||
languageFilter = new LanguageFilter(new SentenceTokenizer());
|
||||
|
||||
loader = Loader::openLoader();
|
||||
loader->settings()->restore();
|
||||
|
||||
spellchecker = new Sonnet::Speller();
|
||||
spellCheckerFound = spellchecker->isValid();
|
||||
rehighlightRequest = new QTimer(q);
|
||||
q->connect(rehighlightRequest, &QTimer::timeout, q, &Highlighter::slotRehighlight);
|
||||
|
||||
if (!spellCheckerFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
disablePercentage = loader->settings()->disablePercentageWordError();
|
||||
disableWordCount = loader->settings()->disableWordErrorCount();
|
||||
|
||||
completeRehighlightRequired = true;
|
||||
rehighlightRequest->setInterval(0);
|
||||
rehighlightRequest->setSingleShot(true);
|
||||
rehighlightRequest->start();
|
||||
}
|
||||
|
||||
~HighlighterPrivate();
|
||||
WordTokenizer *tokenizer = nullptr;
|
||||
LanguageFilter *languageFilter = nullptr;
|
||||
Loader *loader = nullptr;
|
||||
Speller *spellchecker = nullptr;
|
||||
QTextEdit *textEdit = nullptr;
|
||||
QPlainTextEdit *plainTextEdit = nullptr;
|
||||
bool active;
|
||||
bool automatic;
|
||||
bool autoDetectLanguageDisabled;
|
||||
bool completeRehighlightRequired;
|
||||
bool intraWordEditing;
|
||||
bool spellCheckerFound; // cached d->dict->isValid() value
|
||||
QMetaObject::Connection contentsChangeConnection;
|
||||
int disablePercentage = 0;
|
||||
int disableWordCount = 0;
|
||||
int wordCount, errorCount;
|
||||
QTimer *rehighlightRequest = nullptr;
|
||||
QColor spellColor;
|
||||
Highlighter *const q;
|
||||
};
|
||||
|
||||
HighlighterPrivate::~HighlighterPrivate()
|
||||
{
|
||||
delete spellchecker;
|
||||
delete languageFilter;
|
||||
delete tokenizer;
|
||||
}
|
||||
|
||||
Highlighter::Highlighter(QTextEdit *edit, const QColor &_col)
|
||||
: QSyntaxHighlighter(edit)
|
||||
, d(new HighlighterPrivate(this, _col))
|
||||
{
|
||||
d->textEdit = edit;
|
||||
d->textEdit->installEventFilter(this);
|
||||
d->textEdit->viewport()->installEventFilter(this);
|
||||
}
|
||||
|
||||
Highlighter::Highlighter(QPlainTextEdit *edit, const QColor &col)
|
||||
: QSyntaxHighlighter(edit)
|
||||
, d(new HighlighterPrivate(this, col))
|
||||
{
|
||||
d->plainTextEdit = edit;
|
||||
setDocument(d->plainTextEdit->document());
|
||||
d->plainTextEdit->installEventFilter(this);
|
||||
d->plainTextEdit->viewport()->installEventFilter(this);
|
||||
}
|
||||
|
||||
Highlighter::~Highlighter()
|
||||
{
|
||||
if (d->contentsChangeConnection) {
|
||||
// prevent crash from QSyntaxHighlighter::~QSyntaxHighlighter -> (...) -> QTextDocument::contentsChange() signal emission:
|
||||
// ASSERT failure in Sonnet::Highlighter: "Called object is not of the correct type (class destructor may have already run)"
|
||||
QObject::disconnect(d->contentsChangeConnection);
|
||||
}
|
||||
}
|
||||
|
||||
bool Highlighter::spellCheckerFound() const
|
||||
{
|
||||
return d->spellCheckerFound;
|
||||
}
|
||||
|
||||
void Highlighter::slotRehighlight()
|
||||
{
|
||||
if (d->completeRehighlightRequired) {
|
||||
d->wordCount = 0;
|
||||
d->errorCount = 0;
|
||||
rehighlight();
|
||||
} else {
|
||||
// rehighlight the current para only (undo/redo safe)
|
||||
QTextCursor cursor;
|
||||
if (d->textEdit) {
|
||||
cursor = d->textEdit->textCursor();
|
||||
} else {
|
||||
cursor = d->plainTextEdit->textCursor();
|
||||
}
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.clearSelection();
|
||||
}
|
||||
cursor.insertText(QString());
|
||||
}
|
||||
// if (d->checksDone == d->checksRequested)
|
||||
// d->completeRehighlightRequired = false;
|
||||
QTimer::singleShot(0, this, SLOT(slotAutoDetection()));
|
||||
}
|
||||
|
||||
bool Highlighter::automatic() const
|
||||
{
|
||||
return d->automatic;
|
||||
}
|
||||
|
||||
bool Highlighter::autoDetectLanguageDisabled() const
|
||||
{
|
||||
return d->autoDetectLanguageDisabled;
|
||||
}
|
||||
|
||||
bool Highlighter::intraWordEditing() const
|
||||
{
|
||||
return d->intraWordEditing;
|
||||
}
|
||||
|
||||
void Highlighter::setIntraWordEditing(bool editing)
|
||||
{
|
||||
d->intraWordEditing = editing;
|
||||
}
|
||||
|
||||
void Highlighter::setAutomatic(bool automatic)
|
||||
{
|
||||
if (automatic == d->automatic) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->automatic = automatic;
|
||||
if (d->automatic) {
|
||||
slotAutoDetection();
|
||||
}
|
||||
}
|
||||
|
||||
void Highlighter::setAutoDetectLanguageDisabled(bool autoDetectDisabled)
|
||||
{
|
||||
d->autoDetectLanguageDisabled = autoDetectDisabled;
|
||||
}
|
||||
|
||||
void Highlighter::slotAutoDetection()
|
||||
{
|
||||
bool savedActive = d->active;
|
||||
|
||||
// don't disable just because 1 of 4 is misspelled.
|
||||
if (d->automatic && d->wordCount >= 10) {
|
||||
// tme = Too many errors
|
||||
/* clang-format off */
|
||||
bool tme = (d->errorCount >= d->disableWordCount)
|
||||
&& (d->errorCount * 100 >= d->disablePercentage * d->wordCount);
|
||||
/* clang-format on */
|
||||
|
||||
if (d->active && tme) {
|
||||
d->active = false;
|
||||
} else if (!d->active && !tme) {
|
||||
d->active = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (d->active != savedActive) {
|
||||
if (d->active) {
|
||||
Q_EMIT activeChanged(tr("As-you-type spell checking enabled."));
|
||||
} else {
|
||||
qCDebug(SONNET_LOG_UI) << "Sonnet: Disabling spell checking, too many errors";
|
||||
Q_EMIT activeChanged(
|
||||
tr("Too many misspelled words. "
|
||||
"As-you-type spell checking disabled."));
|
||||
}
|
||||
|
||||
d->completeRehighlightRequired = true;
|
||||
d->rehighlightRequest->setInterval(100);
|
||||
d->rehighlightRequest->setSingleShot(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Highlighter::setActive(bool active)
|
||||
{
|
||||
if (active == d->active) {
|
||||
return;
|
||||
}
|
||||
d->active = active;
|
||||
rehighlight();
|
||||
|
||||
if (d->active) {
|
||||
Q_EMIT activeChanged(tr("As-you-type spell checking enabled."));
|
||||
} else {
|
||||
Q_EMIT activeChanged(tr("As-you-type spell checking disabled."));
|
||||
}
|
||||
}
|
||||
|
||||
bool Highlighter::isActive() const
|
||||
{
|
||||
return d->active;
|
||||
}
|
||||
|
||||
void Highlighter::contentsChange(int pos, int add, int rem)
|
||||
{
|
||||
// Invalidate the cache where the text has changed
|
||||
const QTextBlock &lastBlock = document()->findBlock(pos + add - rem);
|
||||
QTextBlock block = document()->findBlock(pos);
|
||||
do {
|
||||
LanguageCache *cache = dynamic_cast<LanguageCache *>(block.userData());
|
||||
if (cache) {
|
||||
cache->invalidate(pos - block.position());
|
||||
}
|
||||
block = block.next();
|
||||
} while (block.isValid() && block < lastBlock);
|
||||
}
|
||||
|
||||
static bool hasNotEmptyText(const QString &text)
|
||||
{
|
||||
for (int i = 0; i < text.length(); ++i) {
|
||||
if (!text.at(i).isSpace()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Highlighter::highlightBlock(const QString &text)
|
||||
{
|
||||
if (!hasNotEmptyText(text) || !d->active || !d->spellCheckerFound) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d->contentsChangeConnection) {
|
||||
d->contentsChangeConnection = connect(document(), &QTextDocument::contentsChange, this, &Highlighter::contentsChange);
|
||||
}
|
||||
|
||||
d->languageFilter->setBuffer(text);
|
||||
|
||||
LanguageCache *cache = dynamic_cast<LanguageCache *>(currentBlockUserData());
|
||||
if (!cache) {
|
||||
cache = new LanguageCache;
|
||||
setCurrentBlockUserData(cache);
|
||||
}
|
||||
|
||||
const bool autodetectLanguage = d->spellchecker->testAttribute(Speller::AutoDetectLanguage);
|
||||
while (d->languageFilter->hasNext()) {
|
||||
Token sentence = d->languageFilter->next();
|
||||
if (autodetectLanguage && !d->autoDetectLanguageDisabled) {
|
||||
QString lang;
|
||||
QPair<int, int> spos = QPair<int, int>(sentence.position(), sentence.length());
|
||||
// try cache first
|
||||
if (cache->languages.contains(spos)) {
|
||||
lang = cache->languages.value(spos);
|
||||
} else {
|
||||
lang = d->languageFilter->language();
|
||||
if (!d->languageFilter->isSpellcheckable()) {
|
||||
lang.clear();
|
||||
}
|
||||
cache->languages[spos] = lang;
|
||||
}
|
||||
if (lang.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
d->spellchecker->setLanguage(lang);
|
||||
}
|
||||
|
||||
d->tokenizer->setBuffer(sentence.toString());
|
||||
int offset = sentence.position();
|
||||
while (d->tokenizer->hasNext()) {
|
||||
Token word = d->tokenizer->next();
|
||||
if (!d->tokenizer->isSpellcheckable()) {
|
||||
continue;
|
||||
}
|
||||
++d->wordCount;
|
||||
if (d->spellchecker->isMisspelled(word.toString())) {
|
||||
++d->errorCount;
|
||||
setMisspelled(word.position() + offset, word.length());
|
||||
} else {
|
||||
unsetMisspelled(word.position() + offset, word.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
// QTimer::singleShot( 0, this, SLOT(checkWords()) );
|
||||
setCurrentBlockState(0);
|
||||
}
|
||||
|
||||
QString Highlighter::currentLanguage() const
|
||||
{
|
||||
return d->spellchecker->language();
|
||||
}
|
||||
|
||||
void Highlighter::setCurrentLanguage(const QString &lang)
|
||||
{
|
||||
QString prevLang = d->spellchecker->language();
|
||||
d->spellchecker->setLanguage(lang);
|
||||
d->spellCheckerFound = d->spellchecker->isValid();
|
||||
if (!d->spellCheckerFound) {
|
||||
qCDebug(SONNET_LOG_UI) << "No dictionary for \"" << lang << "\" staying with the current language.";
|
||||
d->spellchecker->setLanguage(prevLang);
|
||||
return;
|
||||
}
|
||||
d->wordCount = 0;
|
||||
d->errorCount = 0;
|
||||
if (d->automatic || d->active) {
|
||||
d->rehighlightRequest->start(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Highlighter::setMisspelled(int start, int count)
|
||||
{
|
||||
QTextCharFormat format;
|
||||
format.setFontUnderline(true);
|
||||
format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
|
||||
format.setUnderlineColor(d->spellColor);
|
||||
setFormat(start, count, format);
|
||||
}
|
||||
|
||||
void Highlighter::unsetMisspelled(int start, int count)
|
||||
{
|
||||
setFormat(start, count, QTextCharFormat());
|
||||
}
|
||||
|
||||
bool Highlighter::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
if (!d->spellCheckerFound) {
|
||||
return false;
|
||||
}
|
||||
if ((o == d->textEdit || o == d->plainTextEdit) && (e->type() == QEvent::KeyPress)) {
|
||||
QKeyEvent *k = static_cast<QKeyEvent *>(e);
|
||||
// d->autoReady = true;
|
||||
if (d->rehighlightRequest->isActive()) { // try to stay out of the users way
|
||||
d->rehighlightRequest->start(500);
|
||||
}
|
||||
/* clang-format off */
|
||||
if (k->key() == Qt::Key_Enter
|
||||
|| k->key() == Qt::Key_Return
|
||||
|| k->key() == Qt::Key_Up
|
||||
|| k->key() == Qt::Key_Down
|
||||
|| k->key() == Qt::Key_Left
|
||||
|| k->key() == Qt::Key_Right
|
||||
|| k->key() == Qt::Key_PageUp
|
||||
|| k->key() == Qt::Key_PageDown
|
||||
|| k->key() == Qt::Key_Home
|
||||
|| k->key() == Qt::Key_End
|
||||
|| (k->modifiers() == Qt::ControlModifier
|
||||
&& (k->key() == Qt::Key_A
|
||||
|| k->key() == Qt::Key_B
|
||||
|| k->key() == Qt::Key_E
|
||||
|| k->key() == Qt::Key_N
|
||||
|| k->key() == Qt::Key_P))) { /* clang-format on */
|
||||
if (intraWordEditing()) {
|
||||
setIntraWordEditing(false);
|
||||
d->completeRehighlightRequired = true;
|
||||
d->rehighlightRequest->setInterval(500);
|
||||
d->rehighlightRequest->setSingleShot(true);
|
||||
d->rehighlightRequest->start();
|
||||
}
|
||||
} else {
|
||||
setIntraWordEditing(true);
|
||||
}
|
||||
if (k->key() == Qt::Key_Space //
|
||||
|| k->key() == Qt::Key_Enter //
|
||||
|| k->key() == Qt::Key_Return) {
|
||||
QTimer::singleShot(0, this, SLOT(slotAutoDetection()));
|
||||
}
|
||||
} else if (((d->textEdit && (o == d->textEdit->viewport())) //
|
||||
|| (d->plainTextEdit && (o == d->plainTextEdit->viewport()))) //
|
||||
&& (e->type() == QEvent::MouseButtonPress)) {
|
||||
// d->autoReady = true;
|
||||
if (intraWordEditing()) {
|
||||
setIntraWordEditing(false);
|
||||
d->completeRehighlightRequired = true;
|
||||
d->rehighlightRequest->setInterval(0);
|
||||
d->rehighlightRequest->setSingleShot(true);
|
||||
d->rehighlightRequest->start();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Highlighter::addWordToDictionary(const QString &word)
|
||||
{
|
||||
d->spellchecker->addToPersonal(word);
|
||||
}
|
||||
|
||||
void Highlighter::ignoreWord(const QString &word)
|
||||
{
|
||||
d->spellchecker->addToSession(word);
|
||||
}
|
||||
|
||||
QStringList Highlighter::suggestionsForWord(const QString &word, int max)
|
||||
{
|
||||
QStringList suggestions = d->spellchecker->suggest(word);
|
||||
if (max >= 0 && suggestions.count() > max) {
|
||||
suggestions = suggestions.mid(0, max);
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
QStringList Highlighter::suggestionsForWord(const QString &word, const QTextCursor &cursor, int max)
|
||||
{
|
||||
LanguageCache *cache = dynamic_cast<LanguageCache *>(cursor.block().userData());
|
||||
if (cache) {
|
||||
const QString cachedLanguage = cache->languageAtPos(cursor.positionInBlock());
|
||||
if (!cachedLanguage.isEmpty()) {
|
||||
d->spellchecker->setLanguage(cachedLanguage);
|
||||
}
|
||||
}
|
||||
QStringList suggestions = d->spellchecker->suggest(word);
|
||||
if (max >= 0 && suggestions.count() > max) {
|
||||
suggestions = suggestions.mid(0, max);
|
||||
}
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
bool Highlighter::isWordMisspelled(const QString &word)
|
||||
{
|
||||
return d->spellchecker->isMisspelled(word);
|
||||
}
|
||||
|
||||
void Highlighter::setMisspelledColor(const QColor &color)
|
||||
{
|
||||
d->spellColor = color;
|
||||
}
|
||||
|
||||
bool Highlighter::checkerEnabledByDefault() const
|
||||
{
|
||||
return d->loader->settings()->checkerEnabledByDefault();
|
||||
}
|
||||
|
||||
void Highlighter::setDocument(QTextDocument *document)
|
||||
{
|
||||
d->contentsChangeConnection = {};
|
||||
QSyntaxHighlighter::setDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_highlighter.cpp"
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* highlighter.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
|
||||
* SPDX-FileCopyrightText: 2013 Martin Sandsmark <martin.sandsmark@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SONNET_HIGHLIGHTER_H
|
||||
#define SONNET_HIGHLIGHTER_H
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
#include <QStringList>
|
||||
#include <QSyntaxHighlighter>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QTextEdit;
|
||||
class QPlainTextEdit;
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class HighlighterPrivate;
|
||||
/// The Sonnet Highlighter class, used for drawing pretty red lines in text fields
|
||||
class SONNETUI_EXPORT Highlighter : public QSyntaxHighlighter
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Highlighter(QTextEdit *textEdit, const QColor &col = QColor());
|
||||
|
||||
/**
|
||||
* @brief Highlighter
|
||||
* @param textEdit
|
||||
* @param col define spellchecking color.
|
||||
* @since 5.12
|
||||
*/
|
||||
explicit Highlighter(QPlainTextEdit *textEdit, const QColor &col = QColor());
|
||||
~Highlighter() override;
|
||||
|
||||
/**
|
||||
* Returns whether a spell checking backend with support for the
|
||||
* @ref currentLanguage was found.
|
||||
*
|
||||
* @return true if spell checking is supported for the current language.
|
||||
*/
|
||||
bool spellCheckerFound() const;
|
||||
|
||||
/**
|
||||
* Returns the current language used for spell checking.
|
||||
*
|
||||
* @return the language code for the current language.
|
||||
*/
|
||||
QString currentLanguage() const;
|
||||
|
||||
/**
|
||||
* @short Enable/Disable spell checking.
|
||||
*
|
||||
* If @p active is true then spell checking is enabled; otherwise it
|
||||
* is disabled. Note that you have to disable automatic (de)activation
|
||||
* with @ref setAutomatic() before you change the state of spell
|
||||
* checking if you want to persistently enable/disable spell
|
||||
* checking.
|
||||
*
|
||||
* @param active if true, then spell checking is enabled
|
||||
*
|
||||
* @see isActive(), setAutomatic()
|
||||
*/
|
||||
void setActive(bool active);
|
||||
|
||||
/**
|
||||
* Returns the state of spell checking.
|
||||
*
|
||||
* @return true if spell checking is active
|
||||
*
|
||||
* @see setActive()
|
||||
*/
|
||||
bool isActive() const;
|
||||
|
||||
/**
|
||||
* Returns the state of the automatic disabling of spell checking.
|
||||
*
|
||||
* @return true if spell checking is automatically disabled if there's
|
||||
* too many errors
|
||||
*/
|
||||
bool automatic() const;
|
||||
|
||||
/**
|
||||
* Sets whether to automatically disable spell checking if there's too
|
||||
* many errors.
|
||||
*
|
||||
* @param automatic if true, spell checking will be disabled if there's
|
||||
* a significant amount of errors.
|
||||
*/
|
||||
void setAutomatic(bool automatic);
|
||||
|
||||
/**
|
||||
* Returns whether the automatic language detection is disabled,
|
||||
* overriding the Sonnet settings.
|
||||
*
|
||||
* @return true if the automatic language detection is disabled
|
||||
* @since 5.71
|
||||
*/
|
||||
bool autoDetectLanguageDisabled() const;
|
||||
|
||||
/**
|
||||
* Sets whether to disable the automatic language detection.
|
||||
*
|
||||
* @param autoDetectDisabled if true, the language will not be
|
||||
* detected automatically by the spell checker, even if the option
|
||||
* is enabled in the Sonnet settings.
|
||||
* @since 5.71
|
||||
*/
|
||||
void setAutoDetectLanguageDisabled(bool autoDetectDisabled);
|
||||
|
||||
/**
|
||||
* Adds the given word permanently to the dictionary. It will never
|
||||
* be marked as misspelled again, even after restarting the application.
|
||||
*
|
||||
* @param word the word which will be added to the dictionary
|
||||
* @since 4.1
|
||||
*/
|
||||
void addWordToDictionary(const QString &word);
|
||||
|
||||
/**
|
||||
* Ignores the given word. This word will not be marked misspelled for
|
||||
* this session. It will again be marked as misspelled when creating
|
||||
* new highlighters.
|
||||
*
|
||||
* @param word the word which will be ignored
|
||||
* @since 4.1
|
||||
*/
|
||||
void ignoreWord(const QString &word);
|
||||
|
||||
/**
|
||||
* Returns a list of suggested replacements for the given misspelled word.
|
||||
* If the word is not misspelled, the list will be empty.
|
||||
*
|
||||
* @param word the misspelled word
|
||||
* @param max at most this many suggestions will be returned. If this is
|
||||
* -1, as many suggestions as the spell backend supports will
|
||||
* be returned.
|
||||
* @return a list of suggested replacements for the word
|
||||
* @since 4.1
|
||||
*/
|
||||
QStringList suggestionsForWord(const QString &word, int max = 10);
|
||||
|
||||
/**
|
||||
* Returns a list of suggested replacements for the given misspelled word.
|
||||
* If the word is not misspelled, the list will be empty.
|
||||
*
|
||||
* @param word the misspelled word
|
||||
* @param cursor the cursor pointing to the beginning of that word. This is used
|
||||
* to determine the language to use, when AutoDetectLanguage is enabled.
|
||||
* @param max at most this many suggestions will be returned. If this is
|
||||
* -1, as many suggestions as the spell backend supports will
|
||||
* be returned.
|
||||
* @return a list of suggested replacements for the word
|
||||
* @since 5.42
|
||||
*/
|
||||
QStringList suggestionsForWord(const QString &word, const QTextCursor &cursor, int max = 10);
|
||||
|
||||
/**
|
||||
* Checks if a given word is marked as misspelled by the highlighter.
|
||||
*
|
||||
* @param word the word to be checked
|
||||
* @return true if the given word is misspelled.
|
||||
* @since 4.1
|
||||
*/
|
||||
bool isWordMisspelled(const QString &word);
|
||||
|
||||
/**
|
||||
* Sets the color in which the highlighter underlines misspelled words.
|
||||
* @since 4.2
|
||||
*/
|
||||
void setMisspelledColor(const QColor &color);
|
||||
|
||||
/**
|
||||
* Return true if checker is enabled by default
|
||||
* @since 4.5
|
||||
*/
|
||||
bool checkerEnabledByDefault() const;
|
||||
|
||||
/**
|
||||
* Set a new @ref QTextDocument for this highlighter to operate on.
|
||||
*
|
||||
* @param document the new document to operate on.
|
||||
*/
|
||||
void setDocument(QTextDocument *document);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
/**
|
||||
* Emitted when as-you-type spell checking is enabled or disabled.
|
||||
*
|
||||
* @param description is a i18n description of the new state,
|
||||
* with an optional reason
|
||||
*/
|
||||
void activeChanged(const QString &description);
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString &text) override;
|
||||
virtual void setMisspelled(int start, int count);
|
||||
virtual void unsetMisspelled(int start, int count);
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
bool intraWordEditing() const;
|
||||
void setIntraWordEditing(bool editing);
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Set language to use for spell checking.
|
||||
*
|
||||
* @param language the language code for the new language to use.
|
||||
*/
|
||||
void setCurrentLanguage(const QString &language);
|
||||
|
||||
/**
|
||||
* Run auto detection, disabling spell checking if too many errors are found.
|
||||
*/
|
||||
void slotAutoDetection();
|
||||
|
||||
/**
|
||||
* Force a new highlighting.
|
||||
*/
|
||||
void slotRehighlight();
|
||||
|
||||
private Q_SLOTS:
|
||||
SONNETUI_NO_EXPORT void contentsChange(int pos, int added, int removed);
|
||||
|
||||
private:
|
||||
std::unique_ptr<HighlighterPrivate> const d;
|
||||
Q_DISABLE_COPY(Highlighter)
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,17 @@
|
||||
TARGET = sonnet-ui
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
QT += widgets
|
||||
|
||||
SOURCES += highlighter.cpp
|
||||
HEADERS += highlighter.h
|
||||
|
||||
DEFINES += SONNETUI_EXPORT=""
|
||||
DEFINES += SONNETCORE_EXPORT=""
|
||||
DEFINES += INSTALLATION_PLUGIN_PATH=""
|
||||
DEFINES += SONNET_STATIC
|
||||
|
||||
INCLUDEPATH += ../core
|
||||
|
||||
unix:system("touch sonnetui_export.h")
|
||||
win32:system("type nul > sonnetui_export.h")
|
||||
@@ -0,0 +1,313 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>SonnetUi</class>
|
||||
<widget class="QWidget" name="SonnetUi">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>481</width>
|
||||
<height>311</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>430</width>
|
||||
<height>300</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLabel" name="textLabel2">
|
||||
<property name="whatsThis">
|
||||
<string><qt><p>This word was considered to be an "unknown word" because it does not match any entry in the dictionary currently in use. It may also be a word in a foreign language.</p>
|
||||
<p>If the word is not misspelled, you may add it to the dictionary by clicking <b>Add to Dictionary</b>. If you do not want to add the unknown word to the dictionary, but you want to leave it unchanged, click <b>Ignore</b> or <b>Ignore All</b>.</p>
|
||||
<p>However, if the word is misspelled, you can try to find the correct replacement in the list below. If you cannot find a replacement there, you may type it in the text box below, and click <b>Replace</b> or <b>Replace All</b>.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Unknown word:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="m_unknownWord">
|
||||
<property name="toolTip">
|
||||
<string>Unknown word</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><qt><p>This word was considered to be an "unknown word" because it does not match any entry in the dictionary currently in use. It may also be a word in a foreign language.</p>
|
||||
<p>If the word is not misspelled, you may add it to the dictionary by clicking <b>Add to Dictionary</b>. If you do not want to add the unknown word to the dictionary, but you want to leave it unchanged, click <b>Ignore</b> or <b>Ignore All</b>.</p>
|
||||
<p>However, if the word is misspelled, you can try to find the correct replacement in the list below. If you cannot find a replacement there, you may type it in the text box below, and click <b>Replace</b> or <b>Replace All</b>.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><b>misspelled</b></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="textLabel5">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Select the language of the document you are proofing here.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Language:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_language</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="6">
|
||||
<widget class="QLabel" name="m_contextLabel">
|
||||
<property name="toolTip">
|
||||
<string>Text excerpt showing the unknown word in its context.</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Here you can see a text excerpt showing the unknown word in its context. If this information is not sufficient to choose the best replacement for the unknown word, you can click on the document you are proofing, read a larger part of the text and then return here to continue proofing.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>... the <b>misspelled</b> word shown in context ...</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4" colspan="2">
|
||||
<widget class="QPushButton" name="m_addBtn">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>The unknown word was detected and considered unknown because it is not included in the dictionary.<br>
|
||||
Click here if you consider the unknown word not to be misspelled, and you want to avoid wrongly detecting it again in the future. If you want to let it remain as is, but not add it to the dictionary, then click <b>Ignore</b> or <b>Ignore All</b> instead.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><< Add to Dictionary</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>74</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="5">
|
||||
<widget class="QListView" name="m_suggestions">
|
||||
<property name="toolTip">
|
||||
<string>Suggestion List</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>If the unknown word is misspelled, you should check if the correction for it is available and if it is, click on it. If none of the words in this list is a good replacement you may type the correct word in the edit box above.</p>
|
||||
<p>To correct this word click <b>Replace</b> if you want to correct only this occurrence or <b>Replace All</b> if you want to correct all occurrences.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="textLabel4">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>If the unknown word is misspelled, you should type the correction for your misspelled word here or select it from the list below.</p>
|
||||
<p>You can then click <b>Replace</b> if you want to correct only this occurrence of the word or <b>Replace All</b> if you want to correct all occurrences.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Replace &with:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>m_replacement</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2" colspan="3">
|
||||
<widget class="QLineEdit" name="m_replacement">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>If the unknown word is misspelled, you should type the correction for your misspelled word here or select it from the list below.</p>
|
||||
<p>You can then click <b>Replace</b> if you want to correct only this occurrence of the word or <b>Replace All</b> if you want to correct all occurrences.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="4">
|
||||
<widget class="Sonnet::DictionaryComboBox" name="m_language">
|
||||
<property name="toolTip">
|
||||
<string>Language Selection</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Select the language of the document you are proofing here.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="5" rowspan="3">
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_suggestBtn">
|
||||
<property name="text">
|
||||
<string>S&uggest</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_replaceBtn">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Click here to replace this occurrence of the unknown text with the text in the edit box above (to the left).</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Replace</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_replaceAllBtn">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Click here to replace all occurrences of the unknown text with the text in the edit box above (to the left).</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>R&eplace All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_skipBtn">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Click here to let this occurrence of the unknown word remain as is.</p>
|
||||
<p>This action is useful when the word is a name, an acronym, a foreign word or any other unknown word that you want to use but not add to the dictionary.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Ignore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_skipAllBtn">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Click here to let all occurrences of the unknown word remain as they are.</p>
|
||||
<p>This action is useful when the word is a name, an acronym, a foreign word or any other unknown word that you want to use but not add to the dictionary.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>I&gnore All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="m_autoCorrect">
|
||||
<property name="whatsThis">
|
||||
<string><qt>
|
||||
<p>Click here to let all occurrences of the unknown word remain as they are.</p>
|
||||
<p>This action is useful when the word is a name, an acronym, a foreign word or any other unknown word that you want to use but not add to the dictionary.</p>
|
||||
</qt></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Autocorrect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Sonnet::DictionaryComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>sonnet/dictionarycombobox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>m_addBtn</tabstop>
|
||||
<tabstop>m_replacement</tabstop>
|
||||
<tabstop>m_suggestBtn</tabstop>
|
||||
<tabstop>m_replaceBtn</tabstop>
|
||||
<tabstop>m_replaceAllBtn</tabstop>
|
||||
<tabstop>m_skipBtn</tabstop>
|
||||
<tabstop>m_skipAllBtn</tabstop>
|
||||
<tabstop>m_suggestions</tabstop>
|
||||
<tabstop>m_language</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* spellcheckdecorator.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#include "spellcheckdecorator.h"
|
||||
|
||||
// Local
|
||||
#include <highlighter.h>
|
||||
|
||||
// Qt
|
||||
#include <QContextMenuEvent>
|
||||
#include <QMenu>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QTextEdit>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellCheckDecoratorPrivate
|
||||
{
|
||||
public:
|
||||
SpellCheckDecoratorPrivate(SpellCheckDecorator *installer, QPlainTextEdit *textEdit)
|
||||
: q(installer)
|
||||
, m_plainTextEdit(textEdit)
|
||||
{
|
||||
createDefaultHighlighter();
|
||||
// Catch pressing the "menu" key
|
||||
m_plainTextEdit->installEventFilter(q);
|
||||
// Catch right-click
|
||||
m_plainTextEdit->viewport()->installEventFilter(q);
|
||||
}
|
||||
|
||||
SpellCheckDecoratorPrivate(SpellCheckDecorator *installer, QTextEdit *textEdit)
|
||||
: q(installer)
|
||||
, m_textEdit(textEdit)
|
||||
{
|
||||
createDefaultHighlighter();
|
||||
// Catch pressing the "menu" key
|
||||
m_textEdit->installEventFilter(q);
|
||||
// Catch right-click
|
||||
m_textEdit->viewport()->installEventFilter(q);
|
||||
}
|
||||
|
||||
~SpellCheckDecoratorPrivate()
|
||||
{
|
||||
if (m_plainTextEdit) {
|
||||
m_plainTextEdit->removeEventFilter(q);
|
||||
m_plainTextEdit->viewport()->removeEventFilter(q);
|
||||
}
|
||||
if (m_textEdit) {
|
||||
m_textEdit->removeEventFilter(q);
|
||||
m_textEdit->viewport()->removeEventFilter(q);
|
||||
}
|
||||
}
|
||||
|
||||
bool onContextMenuEvent(QContextMenuEvent *event);
|
||||
void execSuggestionMenu(const QPoint &pos, const QString &word, const QTextCursor &cursor);
|
||||
void createDefaultHighlighter();
|
||||
|
||||
SpellCheckDecorator *const q;
|
||||
QTextEdit *m_textEdit = nullptr;
|
||||
QPlainTextEdit *m_plainTextEdit = nullptr;
|
||||
Highlighter *m_highlighter = nullptr;
|
||||
};
|
||||
|
||||
bool SpellCheckDecoratorPrivate::onContextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
if (!m_highlighter) {
|
||||
createDefaultHighlighter();
|
||||
}
|
||||
|
||||
// Obtain the cursor at the mouse position and the current cursor
|
||||
QTextCursor cursorAtMouse;
|
||||
if (m_textEdit) {
|
||||
cursorAtMouse = m_textEdit->cursorForPosition(event->pos());
|
||||
} else {
|
||||
cursorAtMouse = m_plainTextEdit->cursorForPosition(event->pos());
|
||||
}
|
||||
const int mousePos = cursorAtMouse.position();
|
||||
QTextCursor cursor;
|
||||
if (m_textEdit) {
|
||||
cursor = m_textEdit->textCursor();
|
||||
} else {
|
||||
cursor = m_plainTextEdit->textCursor();
|
||||
}
|
||||
|
||||
// Check if the user clicked a selected word
|
||||
/* clang-format off */
|
||||
const bool selectedWordClicked = cursor.hasSelection()
|
||||
&& mousePos >= cursor.selectionStart()
|
||||
&& mousePos <= cursor.selectionEnd();
|
||||
/* clang-format on */
|
||||
|
||||
// Get the word under the (mouse-)cursor and see if it is misspelled.
|
||||
// Don't include apostrophes at the start/end of the word in the selection.
|
||||
QTextCursor wordSelectCursor(cursorAtMouse);
|
||||
wordSelectCursor.clearSelection();
|
||||
wordSelectCursor.select(QTextCursor::WordUnderCursor);
|
||||
QString selectedWord = wordSelectCursor.selectedText();
|
||||
|
||||
bool isMouseCursorInsideWord = true;
|
||||
if ((mousePos < wordSelectCursor.selectionStart() || mousePos >= wordSelectCursor.selectionEnd()) //
|
||||
&& (selectedWord.length() > 1)) {
|
||||
isMouseCursorInsideWord = false;
|
||||
}
|
||||
|
||||
// Clear the selection again, we re-select it below (without the apostrophes).
|
||||
wordSelectCursor.setPosition(wordSelectCursor.position() - selectedWord.size());
|
||||
if (selectedWord.startsWith(QLatin1Char('\'')) || selectedWord.startsWith(QLatin1Char('\"'))) {
|
||||
selectedWord = selectedWord.right(selectedWord.size() - 1);
|
||||
wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
|
||||
}
|
||||
if (selectedWord.endsWith(QLatin1Char('\'')) || selectedWord.endsWith(QLatin1Char('\"'))) {
|
||||
selectedWord.chop(1);
|
||||
}
|
||||
|
||||
wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selectedWord.size());
|
||||
|
||||
/* clang-format off */
|
||||
const bool wordIsMisspelled = isMouseCursorInsideWord
|
||||
&& m_highlighter
|
||||
&& m_highlighter->isActive()
|
||||
&& !selectedWord.isEmpty()
|
||||
&& m_highlighter->isWordMisspelled(selectedWord);
|
||||
/* clang-format on */
|
||||
|
||||
// If the user clicked a selected word, do nothing.
|
||||
// If the user clicked somewhere else, move the cursor there.
|
||||
// If the user clicked on a misspelled word, select that word.
|
||||
// Same behavior as in OpenOffice Writer.
|
||||
bool checkBlock = q->isSpellCheckingEnabledForBlock(cursorAtMouse.block().text());
|
||||
if (!selectedWordClicked) {
|
||||
if (wordIsMisspelled && checkBlock) {
|
||||
if (m_textEdit) {
|
||||
m_textEdit->setTextCursor(wordSelectCursor);
|
||||
} else {
|
||||
m_plainTextEdit->setTextCursor(wordSelectCursor);
|
||||
}
|
||||
} else {
|
||||
if (m_textEdit) {
|
||||
m_textEdit->setTextCursor(cursorAtMouse);
|
||||
} else {
|
||||
m_plainTextEdit->setTextCursor(cursorAtMouse);
|
||||
}
|
||||
}
|
||||
if (m_textEdit) {
|
||||
cursor = m_textEdit->textCursor();
|
||||
} else {
|
||||
cursor = m_plainTextEdit->textCursor();
|
||||
}
|
||||
}
|
||||
|
||||
// Use standard context menu for already selected words, correctly spelled
|
||||
// words and words inside quotes.
|
||||
if (!wordIsMisspelled || selectedWordClicked || !checkBlock) {
|
||||
return false;
|
||||
}
|
||||
execSuggestionMenu(event->globalPos(), selectedWord, cursor);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpellCheckDecoratorPrivate::execSuggestionMenu(const QPoint &pos, const QString &selectedWord, const QTextCursor &_cursor)
|
||||
{
|
||||
QTextCursor cursor = _cursor;
|
||||
QMenu menu; // don't use KMenu here we don't want auto management accelerator
|
||||
|
||||
// Add the suggestions to the menu
|
||||
const QStringList reps = m_highlighter->suggestionsForWord(selectedWord, cursor);
|
||||
if (reps.isEmpty()) {
|
||||
QAction *suggestionsAction = menu.addAction(SpellCheckDecorator::tr("No suggestions for %1").arg(selectedWord));
|
||||
suggestionsAction->setEnabled(false);
|
||||
} else {
|
||||
QStringList::const_iterator end(reps.constEnd());
|
||||
for (QStringList::const_iterator it = reps.constBegin(); it != end; ++it) {
|
||||
menu.addAction(*it);
|
||||
}
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
QAction *ignoreAction = menu.addAction(SpellCheckDecorator::tr("Ignore"));
|
||||
QAction *addToDictAction = menu.addAction(SpellCheckDecorator::tr("Add to Dictionary"));
|
||||
// Execute the popup inline
|
||||
const QAction *selectedAction = menu.exec(pos);
|
||||
|
||||
if (selectedAction) {
|
||||
// Fails when we're in the middle of a compose-key sequence
|
||||
// Q_ASSERT(cursor.selectedText() == selectedWord);
|
||||
|
||||
if (selectedAction == ignoreAction) {
|
||||
m_highlighter->ignoreWord(selectedWord);
|
||||
m_highlighter->rehighlight();
|
||||
} else if (selectedAction == addToDictAction) {
|
||||
m_highlighter->addWordToDictionary(selectedWord);
|
||||
m_highlighter->rehighlight();
|
||||
}
|
||||
// Other actions can only be one of the suggested words
|
||||
else {
|
||||
const QString replacement = selectedAction->text();
|
||||
Q_ASSERT(reps.contains(replacement));
|
||||
cursor.insertText(replacement);
|
||||
if (m_textEdit) {
|
||||
m_textEdit->setTextCursor(cursor);
|
||||
} else {
|
||||
m_plainTextEdit->setTextCursor(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpellCheckDecoratorPrivate::createDefaultHighlighter()
|
||||
{
|
||||
if (m_textEdit) {
|
||||
m_highlighter = new Highlighter(m_textEdit);
|
||||
} else {
|
||||
m_highlighter = new Highlighter(m_plainTextEdit);
|
||||
}
|
||||
}
|
||||
|
||||
SpellCheckDecorator::SpellCheckDecorator(QTextEdit *textEdit)
|
||||
: QObject(textEdit)
|
||||
, d(std::make_unique<SpellCheckDecoratorPrivate>(this, textEdit))
|
||||
{
|
||||
}
|
||||
|
||||
SpellCheckDecorator::SpellCheckDecorator(QPlainTextEdit *textEdit)
|
||||
: QObject(textEdit)
|
||||
, d(std::make_unique<SpellCheckDecoratorPrivate>(this, textEdit))
|
||||
{
|
||||
}
|
||||
|
||||
SpellCheckDecorator::~SpellCheckDecorator() = default;
|
||||
|
||||
void SpellCheckDecorator::setHighlighter(Highlighter *highlighter)
|
||||
{
|
||||
d->m_highlighter = highlighter;
|
||||
}
|
||||
|
||||
Highlighter *SpellCheckDecorator::highlighter() const
|
||||
{
|
||||
if (!d->m_highlighter) {
|
||||
d->createDefaultHighlighter();
|
||||
}
|
||||
return d->m_highlighter;
|
||||
}
|
||||
|
||||
bool SpellCheckDecorator::eventFilter(QObject * /*obj*/, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ContextMenu) {
|
||||
return d->onContextMenuEvent(static_cast<QContextMenuEvent *>(event));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SpellCheckDecorator::isSpellCheckingEnabledForBlock(const QString &textBlock) const
|
||||
{
|
||||
Q_UNUSED(textBlock);
|
||||
if (d->m_textEdit) {
|
||||
return d->m_textEdit->isEnabled();
|
||||
} else {
|
||||
return d->m_plainTextEdit->isEnabled();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#include "moc_spellcheckdecorator.cpp"
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* spellcheckdecorator.h
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
#ifndef SPELLCHECKDECORATOR_H
|
||||
#define SPELLCHECKDECORATOR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "sonnetui_export.h"
|
||||
|
||||
class QTextEdit;
|
||||
class QPlainTextEdit;
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellCheckDecoratorPrivate;
|
||||
class Highlighter;
|
||||
|
||||
/**
|
||||
* @class Sonnet::SpellCheckDecorator spellcheckdecorator.h <Sonnet/SpellCheckDecorator>
|
||||
*
|
||||
* @short Connects a Sonnet::Highlighter to a QTextEdit extending the context menu
|
||||
* of the text edit with spell check suggestions
|
||||
* @author Aurélien Gâteau <agateau@kde.org>
|
||||
* @since 5.0
|
||||
**/
|
||||
|
||||
class SONNETUI_EXPORT SpellCheckDecorator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Creates a spell-check decorator.
|
||||
*
|
||||
* @param textEdit the QTextEdit in need of spell-checking. It also is used as the QObject parent for the decorator.
|
||||
*/
|
||||
explicit SpellCheckDecorator(QTextEdit *textEdit);
|
||||
|
||||
/**
|
||||
* Creates a spell-check decorator.
|
||||
*
|
||||
* @param textEdit the QPlainTextEdit in need of spell-checking. It also is used as the QObject parent for the decorator.
|
||||
* @since 5.12
|
||||
*/
|
||||
explicit SpellCheckDecorator(QPlainTextEdit *textEdit);
|
||||
|
||||
~SpellCheckDecorator() override;
|
||||
|
||||
/**
|
||||
* Set a custom highlighter on the decorator.
|
||||
*
|
||||
* SpellCheckDecorator does not take ownership of the new highlighter,
|
||||
* and you must manually delete the old highlighter.
|
||||
*/
|
||||
void setHighlighter(Highlighter *highlighter);
|
||||
|
||||
/**
|
||||
* Returns the hightlighter used by the decorator
|
||||
*/
|
||||
Highlighter *highlighter() const;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
/**
|
||||
* Returns true if the spell checking should be enabled for a given block of text
|
||||
* The default implementation always returns true.
|
||||
*/
|
||||
virtual bool isSpellCheckingEnabledForBlock(const QString &textBlock) const;
|
||||
|
||||
private:
|
||||
friend SpellCheckDecoratorPrivate;
|
||||
const std::unique_ptr<SpellCheckDecoratorPrivate> d;
|
||||
|
||||
Q_DISABLE_COPY(SpellCheckDecorator)
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* SPELLCHECKDECORATOR_H */
|
||||
Reference in New Issue
Block a user