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,139 @@
|
||||
add_library(KF6TextWidgets)
|
||||
add_library(KF6::TextWidgets ALIAS KF6TextWidgets)
|
||||
|
||||
set_target_properties(KF6TextWidgets PROPERTIES
|
||||
VERSION ${KTEXTWIDGETS_VERSION}
|
||||
SOVERSION ${KTEXTWIDGETS_SOVERSION}
|
||||
EXPORT_NAME TextWidgets
|
||||
)
|
||||
|
||||
target_sources(KF6TextWidgets PRIVATE
|
||||
dialogs/klinkdialog.cpp
|
||||
dialogs/klinkdialog_p.h
|
||||
findreplace/kfind.cpp
|
||||
findreplace/kfinddialog.cpp
|
||||
findreplace/kfinddialog.h
|
||||
findreplace/kfinddialog_p.h
|
||||
findreplace/kfind.h
|
||||
findreplace/kfind_p.h
|
||||
findreplace/kreplace.cpp
|
||||
findreplace/kreplacedialog.cpp
|
||||
findreplace/kreplacedialog.h
|
||||
findreplace/kreplace.h
|
||||
widgets/kpluralhandlingspinbox.cpp
|
||||
widgets/kpluralhandlingspinbox.h
|
||||
widgets/krichtextedit.cpp
|
||||
widgets/krichtextedit.h
|
||||
widgets/krichtextedit_p.h
|
||||
widgets/krichtextwidget.cpp
|
||||
widgets/krichtextwidget.h
|
||||
widgets/ktextedit.cpp
|
||||
widgets/ktextedit.h
|
||||
widgets/ktextedit_p.h
|
||||
widgets/nestedlisthelper.cpp
|
||||
widgets/nestedlisthelper_p.h
|
||||
|
||||
)
|
||||
|
||||
ecm_generate_export_header(KF6TextWidgets
|
||||
BASE_NAME KTextWidgets
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
DEPRECATION_VERSIONS 6.6
|
||||
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
|
||||
)
|
||||
|
||||
set(ktextwidgets_INCLUDES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dialogs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/findreplace
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/widgets
|
||||
)
|
||||
target_include_directories(KF6TextWidgets PUBLIC "$<BUILD_INTERFACE:${ktextwidgets_INCLUDES}>"
|
||||
)
|
||||
|
||||
target_include_directories(KF6TextWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KTextWidgets>")
|
||||
|
||||
target_link_libraries(KF6TextWidgets
|
||||
PUBLIC
|
||||
Qt6::Widgets
|
||||
KF6::SonnetUi
|
||||
KF6::I18n
|
||||
PRIVATE
|
||||
KF6::SonnetCore
|
||||
KF6::WidgetsAddons
|
||||
KF6::ColorScheme
|
||||
KF6::Completion
|
||||
KF6::ConfigGui
|
||||
)
|
||||
|
||||
if (Qt6TextToSpeech_FOUND)
|
||||
target_link_libraries(KF6TextWidgets
|
||||
PRIVATE
|
||||
Qt6::TextToSpeech)
|
||||
endif()
|
||||
|
||||
ecm_generate_headers(KTextWidgets_HEADERS
|
||||
HEADER_NAMES
|
||||
KRichTextEdit
|
||||
KRichTextWidget
|
||||
KTextEdit
|
||||
KPluralHandlingSpinBox
|
||||
|
||||
RELATIVE widgets
|
||||
REQUIRED_HEADERS KTextWidgets_HEADERS
|
||||
)
|
||||
ecm_generate_headers(KTextWidgets_HEADERS
|
||||
HEADER_NAMES
|
||||
KFind
|
||||
KFindDialog
|
||||
KReplace
|
||||
KReplaceDialog
|
||||
|
||||
RELATIVE findreplace
|
||||
REQUIRED_HEADERS KTextWidgets_HEADERS
|
||||
)
|
||||
|
||||
install(TARGETS KF6TextWidgets EXPORT KF6TextWidgetsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/ktextwidgets_export.h
|
||||
${KTextWidgets_HEADERS}
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KTextWidgets COMPONENT Devel
|
||||
)
|
||||
|
||||
if (BUILD_DESIGNERPLUGIN)
|
||||
add_subdirectory(designer)
|
||||
endif()
|
||||
|
||||
if (BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6TextWidgets_QCH
|
||||
NAME KTextWidgets
|
||||
BASE_NAME KF6TextWidgets
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${KTextWidgets_HEADERS}
|
||||
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
|
||||
IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
|
||||
LINK_QCHS
|
||||
Qt6Widgets_QCH
|
||||
KF6SonnetUi_QCH
|
||||
KF6I18n_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${ktextwidgets_INCLUDES}
|
||||
BLANK_MACROS
|
||||
KTEXTWIDGETS_EXPORT
|
||||
KTEXTWIDGETS_DEPRECATED_EXPORT
|
||||
KTEXTWIDGETS_DEPRECATED
|
||||
"KTEXTWIDGETS_DEPRECATED_VERSION(x, y, t)"
|
||||
"KTEXTWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
|
||||
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
|
||||
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
|
||||
if [ -n "$lst" ] ; then
|
||||
$EXTRACTRC $lst >> rc.cpp
|
||||
fi
|
||||
|
||||
# Extract strings from all source files.
|
||||
# If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation
|
||||
# system, use $EXTRACT_TR_STRINGS.
|
||||
$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/ktextwidgets6.pot
|
||||
@@ -0,0 +1,34 @@
|
||||
include(ECMAddQtDesignerPlugin)
|
||||
|
||||
ecm_qtdesignerplugin_widget(KRichTextEdit
|
||||
TOOLTIP "Rich Text Editor (KF6)"
|
||||
GROUP "Input (KF6)"
|
||||
)
|
||||
ecm_qtdesignerplugin_widget(KRichTextWidget
|
||||
TOOLTIP "Rich Text Widget (KF6)"
|
||||
GROUP "Input (KF6)"
|
||||
)
|
||||
ecm_qtdesignerplugin_widget(KTextEdit
|
||||
TOOLTIP "Improved QTextEdit (KF6)"
|
||||
WHATSTHIS "An improved version of the QTextEdit with mail or system browser invocation support"
|
||||
GROUP "Input (KF6)"
|
||||
)
|
||||
ecm_qtdesignerplugin_widget(KPluralHandlingSpinBox
|
||||
TOOLTIP "A QSpinBox with plural handling for the suffix (KF6)"
|
||||
WHATSTHIS "A QSpinBox with plural handling for the suffix"
|
||||
GROUP "Input (KF6)"
|
||||
)
|
||||
|
||||
ecm_add_qtdesignerplugin(ktextwidgetswidgets
|
||||
NAME KTextWidgetsWidgets
|
||||
OUTPUT_NAME ktextwidgets6widgets
|
||||
WIDGETS
|
||||
KRichTextEdit
|
||||
KRichTextWidget
|
||||
KTextEdit
|
||||
KPluralHandlingSpinBox
|
||||
LINK_LIBRARIES
|
||||
KF6::TextWidgets
|
||||
INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer"
|
||||
COMPONENT Devel
|
||||
)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
klinkdialog
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmailcom>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "klinkdialog_p.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
/**
|
||||
Private class that helps to provide binary compatibility between releases.
|
||||
@internal
|
||||
*/
|
||||
//@cond PRIVATE
|
||||
class KLinkDialogPrivate
|
||||
{
|
||||
public:
|
||||
QLabel *textLabel = nullptr;
|
||||
QLineEdit *textLineEdit = nullptr;
|
||||
QLabel *linkUrlLabel = nullptr;
|
||||
QLineEdit *linkUrlLineEdit = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
};
|
||||
//@endcond
|
||||
|
||||
KLinkDialog::KLinkDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, d(new KLinkDialogPrivate)
|
||||
{
|
||||
setWindowTitle(i18n("Manage Link"));
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QGridLayout *grid = new QGridLayout;
|
||||
|
||||
d->textLabel = new QLabel(i18n("Link Text:"), this);
|
||||
d->textLineEdit = new QLineEdit(this);
|
||||
d->textLineEdit->setClearButtonEnabled(true);
|
||||
d->linkUrlLabel = new QLabel(i18n("Link URL:"), this);
|
||||
d->linkUrlLineEdit = new QLineEdit(this);
|
||||
d->linkUrlLineEdit->setClearButtonEnabled(true);
|
||||
|
||||
grid->addWidget(d->textLabel, 0, 0);
|
||||
grid->addWidget(d->textLineEdit, 0, 1);
|
||||
grid->addWidget(d->linkUrlLabel, 1, 0);
|
||||
grid->addWidget(d->linkUrlLineEdit, 1, 1);
|
||||
|
||||
layout->addLayout(grid);
|
||||
|
||||
d->buttonBox = new QDialogButtonBox(this);
|
||||
d->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(d->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(d->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
layout->addWidget(d->buttonBox);
|
||||
|
||||
d->textLineEdit->setFocus();
|
||||
d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
connect(d->textLineEdit, &QLineEdit::textChanged, this, &KLinkDialog::slotTextChanged);
|
||||
}
|
||||
|
||||
KLinkDialog::~KLinkDialog()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
void KLinkDialog::slotTextChanged(const QString &text)
|
||||
{
|
||||
d->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.trimmed().isEmpty());
|
||||
}
|
||||
|
||||
void KLinkDialog::setLinkText(const QString &linkText)
|
||||
{
|
||||
d->textLineEdit->setText(linkText);
|
||||
if (!linkText.trimmed().isEmpty()) {
|
||||
d->linkUrlLineEdit->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void KLinkDialog::setLinkUrl(const QString &linkUrl)
|
||||
{
|
||||
d->linkUrlLineEdit->setText(linkUrl);
|
||||
}
|
||||
|
||||
QString KLinkDialog::linkText() const
|
||||
{
|
||||
return d->textLineEdit->text().trimmed();
|
||||
}
|
||||
|
||||
QString KLinkDialog::linkUrl() const
|
||||
{
|
||||
return d->linkUrlLineEdit->text();
|
||||
}
|
||||
|
||||
#include "moc_klinkdialog_p.cpp"
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
klinkdialog
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef KLINKDIALOG_H
|
||||
#define KLINKDIALOG_H
|
||||
|
||||
//@cond PRIVATE
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class KLinkDialogPrivate;
|
||||
class QString;
|
||||
|
||||
/**
|
||||
@short Dialog to allow user to configure a hyperlink.
|
||||
@author Stephen Kelly
|
||||
@since 4.1
|
||||
@internal
|
||||
|
||||
This class provides a dialog to ask the user for a link target url and
|
||||
text.
|
||||
|
||||
The size of the dialog is automatically saved to and restored from the
|
||||
global KDE config file.
|
||||
*/
|
||||
class KLinkDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Create a link dialog.
|
||||
* @param parent Parent widget.
|
||||
*/
|
||||
explicit KLinkDialog(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~KLinkDialog() override;
|
||||
|
||||
/**
|
||||
* Returns the link text shown in the dialog
|
||||
* @param linkText The initial text
|
||||
*/
|
||||
void setLinkText(const QString &linkText);
|
||||
|
||||
/**
|
||||
* Sets the target link url shown in the dialog
|
||||
* @param linkUrl The initial link target url
|
||||
*/
|
||||
void setLinkUrl(const QString &linkUrl);
|
||||
|
||||
/**
|
||||
* Returns the link text entered by the user.
|
||||
* @return The link text
|
||||
*/
|
||||
QString linkText() const;
|
||||
|
||||
/**
|
||||
* Returns the target link url entered by the user.
|
||||
* @return The link url
|
||||
*/
|
||||
QString linkUrl() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void slotTextChanged(const QString &);
|
||||
|
||||
private:
|
||||
//@cond PRIVATE
|
||||
KLinkDialogPrivate *const d;
|
||||
//@endcond
|
||||
};
|
||||
|
||||
//@endcond
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,712 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S .R.Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "kfind.h"
|
||||
#include "kfind_p.h"
|
||||
|
||||
#include "kfinddialog.h"
|
||||
|
||||
#include <KGuiItem>
|
||||
#include <KLocalizedString>
|
||||
#include <KMessageBox>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHash>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
// #define DEBUG_FIND
|
||||
|
||||
static const int INDEX_NOMATCH = -1;
|
||||
|
||||
class KFindNextDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit KFindNextDialog(const QString &pattern, QWidget *parent);
|
||||
|
||||
QPushButton *findButton() const;
|
||||
|
||||
private:
|
||||
QPushButton *m_findButton = nullptr;
|
||||
};
|
||||
|
||||
// Create the dialog.
|
||||
KFindNextDialog::KFindNextDialog(const QString &pattern, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setModal(false);
|
||||
setWindowTitle(i18n("Find Next"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
layout->addWidget(new QLabel(i18n("<qt>Find next occurrence of '<b>%1</b>'?</qt>", pattern), this));
|
||||
|
||||
m_findButton = new QPushButton;
|
||||
KGuiItem::assign(m_findButton, KStandardGuiItem::find());
|
||||
m_findButton->setDefault(true);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->addButton(m_findButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
QPushButton *KFindNextDialog::findButton() const
|
||||
{
|
||||
return m_findButton;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
KFind::KFind(const QString &pattern, long options, QWidget *parent)
|
||||
: KFind(*new KFindPrivate(this), pattern, options, parent)
|
||||
{
|
||||
}
|
||||
|
||||
KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent)
|
||||
: QObject(parent)
|
||||
, d_ptr(&dd)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
d->options = options;
|
||||
d->init(pattern);
|
||||
}
|
||||
|
||||
KFind::KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog)
|
||||
: KFind(*new KFindPrivate(this), pattern, options, parent, findDialog)
|
||||
{
|
||||
}
|
||||
|
||||
KFind::KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog)
|
||||
: QObject(parent)
|
||||
, d_ptr(&dd)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
d->findDialog = findDialog;
|
||||
d->options = options;
|
||||
d->init(pattern);
|
||||
}
|
||||
|
||||
void KFindPrivate::init(const QString &_pattern)
|
||||
{
|
||||
Q_Q(KFind);
|
||||
|
||||
matches = 0;
|
||||
pattern = _pattern;
|
||||
dialog = nullptr;
|
||||
dialogClosed = false;
|
||||
index = INDEX_NOMATCH;
|
||||
lastResult = KFind::NoMatch;
|
||||
|
||||
// TODO: KF6 change this comment once d->regExp is removed
|
||||
// set options and create d->regExp with the right options
|
||||
q->setOptions(options);
|
||||
}
|
||||
|
||||
KFind::~KFind() = default;
|
||||
|
||||
bool KFind::needData() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
// always true when d->text is empty.
|
||||
if (d->options & KFind::FindBackwards)
|
||||
// d->index==-1 and d->lastResult==Match means we haven't answered nomatch yet
|
||||
// This is important in the "replace with a prompt" case.
|
||||
{
|
||||
return (d->index < 0 && d->lastResult != Match);
|
||||
} else
|
||||
// "index over length" test removed: we want to get a nomatch before we set data again
|
||||
// This is important in the "replace with a prompt" case.
|
||||
{
|
||||
return d->index == INDEX_NOMATCH;
|
||||
}
|
||||
}
|
||||
|
||||
void KFind::setData(const QString &data, int startPos)
|
||||
{
|
||||
setData(-1, data, startPos);
|
||||
}
|
||||
|
||||
void KFind::setData(int id, const QString &data, int startPos)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
// cache the data for incremental find
|
||||
if (d->options & KFind::FindIncremental) {
|
||||
if (id != -1) {
|
||||
d->customIds = true;
|
||||
} else {
|
||||
id = d->currentId + 1;
|
||||
}
|
||||
|
||||
Q_ASSERT(id <= d->data.size());
|
||||
|
||||
if (id == d->data.size()) {
|
||||
d->data.append(KFindPrivate::Data(id, data, true));
|
||||
} else {
|
||||
d->data.replace(id, KFindPrivate::Data(id, data, true));
|
||||
}
|
||||
Q_ASSERT(d->data.at(id).text == data);
|
||||
}
|
||||
|
||||
if (!(d->options & KFind::FindIncremental) || needData()) {
|
||||
d->text = data;
|
||||
|
||||
if (startPos != -1) {
|
||||
d->index = startPos;
|
||||
} else if (d->options & KFind::FindBackwards) {
|
||||
d->index = d->text.length();
|
||||
} else {
|
||||
d->index = 0;
|
||||
}
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << "setData: '" << d->text << "' d->index=" << d->index;
|
||||
#endif
|
||||
Q_ASSERT(d->index != INDEX_NOMATCH);
|
||||
d->lastResult = NoMatch;
|
||||
|
||||
d->currentId = id;
|
||||
}
|
||||
}
|
||||
|
||||
QDialog *KFind::findNextDialog(bool create)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
if (!d->dialog && create) {
|
||||
KFindNextDialog *dialog = new KFindNextDialog(d->pattern, parentWidget());
|
||||
connect(dialog->findButton(), &QPushButton::clicked, this, [d]() {
|
||||
d->slotFindNext();
|
||||
});
|
||||
connect(dialog, &QDialog::finished, this, [d]() {
|
||||
d->slotDialogClosed();
|
||||
});
|
||||
d->dialog = dialog;
|
||||
}
|
||||
return d->dialog;
|
||||
}
|
||||
|
||||
KFind::Result KFind::find()
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
Q_ASSERT(d->index != INDEX_NOMATCH || d->patternChanged);
|
||||
|
||||
if (d->lastResult == Match && !d->patternChanged) {
|
||||
// Move on before looking for the next match, _if_ we just found a match
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
d->index--;
|
||||
if (d->index == -1) { // don't call KFind::find with -1, it has a special meaning
|
||||
d->lastResult = NoMatch;
|
||||
return NoMatch;
|
||||
}
|
||||
} else {
|
||||
d->index++;
|
||||
}
|
||||
}
|
||||
d->patternChanged = false;
|
||||
|
||||
if (d->options & KFind::FindIncremental) {
|
||||
// if the current pattern is shorter than the matchedPattern we can
|
||||
// probably look up the match in the incrementalPath
|
||||
if (d->pattern.length() < d->matchedPattern.length()) {
|
||||
KFindPrivate::Match match;
|
||||
if (!d->pattern.isEmpty()) {
|
||||
match = d->incrementalPath.value(d->pattern);
|
||||
} else if (d->emptyMatch) {
|
||||
match = *d->emptyMatch;
|
||||
}
|
||||
QString previousPattern(d->matchedPattern);
|
||||
d->matchedPattern = d->pattern;
|
||||
if (!match.isNull()) {
|
||||
bool clean = true;
|
||||
|
||||
// find the first result backwards on the path that isn't dirty
|
||||
while (d->data.at(match.dataId).dirty == true && !d->pattern.isEmpty()) {
|
||||
d->pattern.truncate(d->pattern.length() - 1);
|
||||
|
||||
match = d->incrementalPath.value(d->pattern);
|
||||
|
||||
clean = false;
|
||||
}
|
||||
|
||||
// remove all matches that lie after the current match
|
||||
while (d->pattern.length() < previousPattern.length()) {
|
||||
d->incrementalPath.remove(previousPattern);
|
||||
previousPattern.truncate(previousPattern.length() - 1);
|
||||
}
|
||||
|
||||
// set the current text, index, etc. to the found match
|
||||
d->text = d->data.at(match.dataId).text;
|
||||
d->index = match.index;
|
||||
d->matchedLength = match.matchedLength;
|
||||
d->currentId = match.dataId;
|
||||
|
||||
// if the result is clean we can return it now
|
||||
if (clean) {
|
||||
if (d->customIds) {
|
||||
Q_EMIT textFoundAtId(d->currentId, d->index, d->matchedLength);
|
||||
} else {
|
||||
Q_EMIT textFound(d->text, d->index, d->matchedLength);
|
||||
}
|
||||
|
||||
d->lastResult = Match;
|
||||
d->matchedPattern = d->pattern;
|
||||
return Match;
|
||||
}
|
||||
}
|
||||
// if we couldn't look up the match, the new pattern isn't a
|
||||
// substring of the matchedPattern, so we start a new search
|
||||
else {
|
||||
d->startNewIncrementalSearch();
|
||||
}
|
||||
}
|
||||
// if the new pattern is longer than the matchedPattern we might be
|
||||
// able to proceed from the last search
|
||||
else if (d->pattern.length() > d->matchedPattern.length()) {
|
||||
// continue from the previous pattern
|
||||
if (d->pattern.startsWith(d->matchedPattern)) {
|
||||
// we can't proceed from the previous position if the previous
|
||||
// position already failed
|
||||
if (d->index == INDEX_NOMATCH) {
|
||||
return NoMatch;
|
||||
}
|
||||
|
||||
QString temp(d->pattern);
|
||||
d->pattern.truncate(d->matchedPattern.length() + 1);
|
||||
d->matchedPattern = temp;
|
||||
}
|
||||
// start a new search
|
||||
else {
|
||||
d->startNewIncrementalSearch();
|
||||
}
|
||||
}
|
||||
// if the new pattern is as long as the matchedPattern, we reset if
|
||||
// they are not equal
|
||||
else if (d->pattern != d->matchedPattern) {
|
||||
d->startNewIncrementalSearch();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << "d->index=" << d->index;
|
||||
#endif
|
||||
do {
|
||||
// if we have multiple data blocks in our cache, walk through these
|
||||
// blocks till we either searched all blocks or we find a match
|
||||
do {
|
||||
// Find the next candidate match.
|
||||
d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, nullptr);
|
||||
|
||||
if (d->options & KFind::FindIncremental) {
|
||||
d->data[d->currentId].dirty = false;
|
||||
}
|
||||
|
||||
if (d->index == -1 && d->currentId < d->data.count() - 1) {
|
||||
d->text = d->data.at(++d->currentId).text;
|
||||
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
d->index = d->text.length();
|
||||
} else {
|
||||
d->index = 0;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (!(d->options & KFind::RegularExpression));
|
||||
|
||||
if (d->index != -1) {
|
||||
// Flexibility: the app can add more rules to validate a possible match
|
||||
if (validateMatch(d->text, d->index, d->matchedLength)) {
|
||||
bool done = true;
|
||||
|
||||
if (d->options & KFind::FindIncremental) {
|
||||
if (d->pattern.isEmpty()) {
|
||||
delete d->emptyMatch;
|
||||
d->emptyMatch = new KFindPrivate::Match(d->currentId, d->index, d->matchedLength);
|
||||
} else {
|
||||
d->incrementalPath.insert(d->pattern, KFindPrivate::Match(d->currentId, d->index, d->matchedLength));
|
||||
}
|
||||
|
||||
if (d->pattern.length() < d->matchedPattern.length()) {
|
||||
d->pattern += QStringView(d->matchedPattern).mid(d->pattern.length(), 1);
|
||||
done = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (done) {
|
||||
d->matches++;
|
||||
// Tell the world about the match we found, in case someone wants to
|
||||
// highlight it.
|
||||
if (d->customIds) {
|
||||
Q_EMIT textFoundAtId(d->currentId, d->index, d->matchedLength);
|
||||
} else {
|
||||
Q_EMIT textFound(d->text, d->index, d->matchedLength);
|
||||
}
|
||||
|
||||
if (!d->dialogClosed) {
|
||||
findNextDialog(true)->show();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << "Match. Next d->index=" << d->index;
|
||||
#endif
|
||||
d->lastResult = Match;
|
||||
return Match;
|
||||
}
|
||||
} else { // Skip match
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
d->index--;
|
||||
} else {
|
||||
d->index++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (d->options & KFind::FindIncremental) {
|
||||
QString temp(d->pattern);
|
||||
temp.truncate(temp.length() - 1);
|
||||
d->pattern = d->matchedPattern;
|
||||
d->matchedPattern = temp;
|
||||
}
|
||||
|
||||
d->index = INDEX_NOMATCH;
|
||||
}
|
||||
} while (d->index != INDEX_NOMATCH);
|
||||
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << "NoMatch. d->index=" << d->index;
|
||||
#endif
|
||||
d->lastResult = NoMatch;
|
||||
return NoMatch;
|
||||
}
|
||||
|
||||
void KFindPrivate::startNewIncrementalSearch()
|
||||
{
|
||||
KFindPrivate::Match *match = emptyMatch;
|
||||
if (match == nullptr) {
|
||||
text.clear();
|
||||
index = 0;
|
||||
currentId = 0;
|
||||
} else {
|
||||
text = data.at(match->dataId).text;
|
||||
index = match->index;
|
||||
currentId = match->dataId;
|
||||
}
|
||||
matchedLength = 0;
|
||||
incrementalPath.clear();
|
||||
delete emptyMatch;
|
||||
emptyMatch = nullptr;
|
||||
matchedPattern = pattern;
|
||||
pattern.clear();
|
||||
}
|
||||
|
||||
static bool isInWord(QChar ch)
|
||||
{
|
||||
return ch.isLetter() || ch.isDigit() || ch == QLatin1Char('_');
|
||||
}
|
||||
|
||||
static bool isWholeWords(const QString &text, int starts, int matchedLength)
|
||||
{
|
||||
if (starts == 0 || !isInWord(text.at(starts - 1))) {
|
||||
const int ends = starts + matchedLength;
|
||||
if (ends == text.length() || !isInWord(text.at(ends))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool matchOk(const QString &text, int index, int matchedLength, long options)
|
||||
{
|
||||
if (options & KFind::WholeWordsOnly) {
|
||||
// Is the match delimited correctly?
|
||||
if (isWholeWords(text, index, matchedLength)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Non-whole-word search: this match is good
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int findRegex(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch)
|
||||
{
|
||||
QString _pattern = pattern;
|
||||
|
||||
// Always enable Unicode support in QRegularExpression
|
||||
QRegularExpression::PatternOptions opts = QRegularExpression::UseUnicodePropertiesOption;
|
||||
// instead of this rudimentary test, add a checkbox to toggle MultilineOption ?
|
||||
if (pattern.startsWith(QLatin1Char('^')) || pattern.endsWith(QLatin1Char('$'))) {
|
||||
opts |= QRegularExpression::MultilineOption;
|
||||
} else if (options & KFind::WholeWordsOnly) { // WholeWordsOnly makes no sense with multiline
|
||||
_pattern = QLatin1String("\\b") + pattern + QLatin1String("\\b");
|
||||
}
|
||||
|
||||
if (!(options & KFind::CaseSensitive)) {
|
||||
opts |= QRegularExpression::CaseInsensitiveOption;
|
||||
}
|
||||
|
||||
QRegularExpression re(_pattern, opts);
|
||||
QRegularExpressionMatch match;
|
||||
if (options & KFind::FindBackwards) {
|
||||
// Backward search, until the beginning of the line...
|
||||
(void)text.lastIndexOf(re, index, &match);
|
||||
} else {
|
||||
// Forward search, until the end of the line...
|
||||
match = re.match(text, index);
|
||||
}
|
||||
|
||||
// index is -1 if no match is found
|
||||
index = match.capturedStart(0);
|
||||
// matchedLength is 0 if no match is found
|
||||
*matchedLength = match.capturedLength(0);
|
||||
|
||||
if (rmatch) {
|
||||
*rmatch = match;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
// static
|
||||
int KFind::find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch)
|
||||
{
|
||||
// Handle regular expressions in the appropriate way.
|
||||
if (options & KFind::RegularExpression) {
|
||||
return findRegex(text, pattern, index, options, matchedLength, rmatch);
|
||||
}
|
||||
|
||||
// In Qt4 QString("aaaaaa").lastIndexOf("a",6) returns -1; we need
|
||||
// to start at text.length() - pattern.length() to give a valid index to QString.
|
||||
if (options & KFind::FindBackwards) {
|
||||
index = qMin(qMax(0, text.length() - pattern.length()), index);
|
||||
}
|
||||
|
||||
Qt::CaseSensitivity caseSensitive = (options & KFind::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
||||
|
||||
if (options & KFind::FindBackwards) {
|
||||
// Backward search, until the beginning of the line...
|
||||
while (index >= 0) {
|
||||
// ...find the next match.
|
||||
index = text.lastIndexOf(pattern, index, caseSensitive);
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (matchOk(text, index, pattern.length(), options)) {
|
||||
break;
|
||||
}
|
||||
index--;
|
||||
// qDebug() << "decrementing:" << index;
|
||||
}
|
||||
} else {
|
||||
// Forward search, until the end of the line...
|
||||
while (index <= text.length()) {
|
||||
// ...find the next match.
|
||||
index = text.indexOf(pattern, index, caseSensitive);
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (matchOk(text, index, pattern.length(), options)) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
if (index > text.length()) { // end of line
|
||||
// qDebug() << "at" << index << "-> not found";
|
||||
index = -1; // not found
|
||||
}
|
||||
}
|
||||
if (index <= -1) {
|
||||
*matchedLength = 0;
|
||||
} else {
|
||||
*matchedLength = pattern.length();
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void KFindPrivate::slotFindNext()
|
||||
{
|
||||
Q_Q(KFind);
|
||||
|
||||
Q_EMIT q->findNext();
|
||||
}
|
||||
|
||||
void KFindPrivate::slotDialogClosed()
|
||||
{
|
||||
Q_Q(KFind);
|
||||
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << " Begin";
|
||||
#endif
|
||||
Q_EMIT q->dialogClosed();
|
||||
dialogClosed = true;
|
||||
#ifdef DEBUG_FIND
|
||||
// qDebug() << " End";
|
||||
#endif
|
||||
}
|
||||
|
||||
void KFind::displayFinalDialog() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
QString message;
|
||||
if (numMatches()) {
|
||||
message = i18np("1 match found.", "%1 matches found.", numMatches());
|
||||
} else {
|
||||
message = i18n("<qt>No matches found for '<b>%1</b>'.</qt>", d->pattern.toHtmlEscaped());
|
||||
}
|
||||
KMessageBox::information(dialogsParent(), message);
|
||||
}
|
||||
|
||||
bool KFind::shouldRestart(bool forceAsking, bool showNumMatches) const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
// Only ask if we did a "find from cursor", otherwise it's pointless.
|
||||
// Well, unless the user can modify the document during a search operation,
|
||||
// hence the force boolean.
|
||||
if (!forceAsking && (d->options & KFind::FromCursor) == 0) {
|
||||
displayFinalDialog();
|
||||
return false;
|
||||
}
|
||||
QString message;
|
||||
if (showNumMatches) {
|
||||
if (numMatches()) {
|
||||
message = i18np("1 match found.", "%1 matches found.", numMatches());
|
||||
} else {
|
||||
message = i18n("No matches found for '<b>%1</b>'.", d->pattern.toHtmlEscaped());
|
||||
}
|
||||
} else {
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
message = i18n("Beginning of document reached.");
|
||||
} else {
|
||||
message = i18n("End of document reached.");
|
||||
}
|
||||
}
|
||||
|
||||
message += QLatin1String("<br><br>"); // can't be in the i18n() of the first if() because of the plural form.
|
||||
// Hope this word puzzle is ok, it's a different sentence
|
||||
message += (d->options & KFind::FindBackwards) ? i18n("Continue from the end?") : i18n("Continue from the beginning?");
|
||||
|
||||
int ret = KMessageBox::questionTwoActions(dialogsParent(),
|
||||
QStringLiteral("<qt>%1</qt>").arg(message),
|
||||
QString(),
|
||||
KStandardGuiItem::cont(),
|
||||
KStandardGuiItem::stop());
|
||||
bool yes = (ret == KMessageBox::PrimaryAction);
|
||||
if (yes) {
|
||||
const_cast<KFindPrivate *>(d)->options &= ~KFind::FromCursor; // clear FromCursor option
|
||||
}
|
||||
return yes;
|
||||
}
|
||||
|
||||
long KFind::options() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
return d->options;
|
||||
}
|
||||
|
||||
void KFind::setOptions(long options)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
d->options = options;
|
||||
}
|
||||
|
||||
void KFind::closeFindNextDialog()
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
if (d->dialog) {
|
||||
d->dialog->deleteLater();
|
||||
d->dialog = nullptr;
|
||||
}
|
||||
d->dialogClosed = true;
|
||||
}
|
||||
|
||||
int KFind::index() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
return d->index;
|
||||
}
|
||||
|
||||
QString KFind::pattern() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
return d->pattern;
|
||||
}
|
||||
|
||||
void KFind::setPattern(const QString &pattern)
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
if (d->pattern != pattern) {
|
||||
d->patternChanged = true;
|
||||
d->matches = 0;
|
||||
}
|
||||
|
||||
d->pattern = pattern;
|
||||
|
||||
// TODO: KF6 change this comment once d->regExp is removed
|
||||
// set the options and rebuild d->regeExp if necessary
|
||||
setOptions(options());
|
||||
}
|
||||
|
||||
int KFind::numMatches() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
return d->matches;
|
||||
}
|
||||
|
||||
void KFind::resetCounts()
|
||||
{
|
||||
Q_D(KFind);
|
||||
|
||||
d->matches = 0;
|
||||
}
|
||||
|
||||
bool KFind::validateMatch(const QString &, int, int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QWidget *KFind::parentWidget() const
|
||||
{
|
||||
return static_cast<QWidget *>(parent());
|
||||
}
|
||||
|
||||
QWidget *KFind::dialogsParent() const
|
||||
{
|
||||
Q_D(const KFind);
|
||||
|
||||
// If the find dialog is still up, it should get the focus when closing a message box
|
||||
// Otherwise, maybe the "find next?" dialog is up
|
||||
// Otherwise, the "view" is the parent.
|
||||
return d->findDialog ? static_cast<QWidget *>(d->findDialog) : (d->dialog ? d->dialog : parentWidget());
|
||||
}
|
||||
|
||||
#include "kfind.moc"
|
||||
#include "moc_kfind.cpp"
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KFIND_H
|
||||
#define KFIND_H
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class QDialog;
|
||||
class KFindPrivate;
|
||||
|
||||
/**
|
||||
* @class KFind kfind.h <KFind>
|
||||
*
|
||||
* @brief A generic implementation of the "find" function.
|
||||
*
|
||||
* @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org>,
|
||||
* Arend van Beelen jr. <arend@auton.nl>
|
||||
*
|
||||
* \b Detail:
|
||||
*
|
||||
* This class includes prompt handling etc. Also provides some
|
||||
* static functions which can be used to create custom behavior
|
||||
* instead of using the class directly.
|
||||
*
|
||||
* \b Example:
|
||||
*
|
||||
* To use the class to implement a complete find feature:
|
||||
*
|
||||
* In the slot connected to the find action, after using KFindDialog:
|
||||
* \code
|
||||
*
|
||||
* // This creates a find-next-prompt dialog if needed.
|
||||
* m_find = new KFind(pattern, options, this);
|
||||
*
|
||||
* // Connect textFound() signal to code which handles highlighting of found text.
|
||||
* connect(m_find, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
|
||||
* slotHighlight(text, matchingIndex, matchedLength);
|
||||
* }));
|
||||
* // Connect findNext signal - called when pressing the button in the dialog
|
||||
* connect(m_find, SIGNAL(findNext()),
|
||||
* this, SLOT(slotFindNext()));
|
||||
* \endcode
|
||||
*
|
||||
* Then initialize the variables determining the "current position"
|
||||
* (to the cursor, if the option FromCursor is set,
|
||||
* to the beginning of the selection if the option SelectedText is set,
|
||||
* and to the beginning of the document otherwise).
|
||||
* Initialize the "end of search" variables as well (end of doc or end of selection).
|
||||
* Swap begin and end if FindBackwards.
|
||||
* Finally, call slotFindNext();
|
||||
*
|
||||
* \code
|
||||
* void slotFindNext()
|
||||
* {
|
||||
* KFind::Result res = KFind::NoMatch;
|
||||
* while (res == KFind::NoMatch && <position not at end>) {
|
||||
* if (m_find->needData())
|
||||
* m_find->setData(<current text fragment>);
|
||||
*
|
||||
* // Let KFind inspect the text fragment, and display a dialog if a match is found
|
||||
* res = m_find->find();
|
||||
*
|
||||
* if (res == KFind::NoMatch) {
|
||||
* <Move to the next text fragment, honoring the FindBackwards setting for the direction>
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* if (res == KFind::NoMatch) // i.e. at end
|
||||
* <Call either m_find->displayFinalDialog(); m_find->deleteLater(); m_find = nullptr;
|
||||
* or if (m_find->shouldRestart()) { reinit (w/o FromCursor); m_find->resetCounts(); slotFindNext(); }
|
||||
* else { m_find->closeFindNextDialog(); }>
|
||||
* }
|
||||
* \endcode
|
||||
*
|
||||
* Don't forget to delete m_find in the destructor of your class,
|
||||
* unless you gave it a parent widget on construction.
|
||||
*
|
||||
* This implementation allows to have a "Find Next" action, which resumes the
|
||||
* search, even if the user closed the "Find Next" dialog.
|
||||
*
|
||||
* A "Find Previous" action can simply switch temporarily the value of
|
||||
* FindBackwards and call slotFindNext() - and reset the value afterwards.
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KFind : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @see SearchOptions
|
||||
*/
|
||||
enum Options {
|
||||
WholeWordsOnly = 1, ///< Match whole words only.
|
||||
FromCursor = 2, ///< Start from current cursor position.
|
||||
SelectedText = 4, ///< Only search selected area.
|
||||
CaseSensitive = 8, ///< Consider case when matching.
|
||||
FindBackwards = 16, ///< Go backwards.
|
||||
RegularExpression = 32, ///< Interpret the pattern as a regular expression.
|
||||
FindIncremental = 64, ///< Find incremental.
|
||||
// Note that KReplaceDialog uses 256 and 512
|
||||
// User extensions can use boolean options above this value.
|
||||
MinimumUserOption = 65536, ///< user options start with this bit
|
||||
};
|
||||
/**
|
||||
* Stores a combination of #Options values.
|
||||
*/
|
||||
Q_DECLARE_FLAGS(SearchOptions, Options)
|
||||
|
||||
/**
|
||||
* Only use this constructor if you don't use KFindDialog, or if
|
||||
* you use it as a modal dialog.
|
||||
*/
|
||||
KFind(const QString &pattern, long options, QWidget *parent);
|
||||
|
||||
/**
|
||||
* This is the recommended constructor if you also use KFindDialog (non-modal).
|
||||
* You should pass the pointer to it here, so that when a message box
|
||||
* appears it has the right parent. Don't worry about deletion, KFind
|
||||
* will notice if the find dialog is closed.
|
||||
*/
|
||||
KFind(const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
|
||||
~KFind() override;
|
||||
|
||||
enum Result {
|
||||
NoMatch,
|
||||
Match,
|
||||
};
|
||||
|
||||
/**
|
||||
* @return true if the application must supply a new text fragment
|
||||
* It also means the last call returned "NoMatch". But by storing this here
|
||||
* the application doesn't have to store it in a member variable (between
|
||||
* calls to slotFindNext()).
|
||||
*/
|
||||
bool needData() const;
|
||||
|
||||
/**
|
||||
* Call this when needData returns true, before calling find().
|
||||
* @param data the text fragment (line)
|
||||
* @param startPos if set, the index at which the search should start.
|
||||
* This is only necessary for the very first call to setData usually,
|
||||
* for the 'find in selection' feature. A value of -1 (the default value)
|
||||
* means "process all the data", i.e. either 0 or data.length()-1 depending
|
||||
* on FindBackwards.
|
||||
*/
|
||||
void setData(const QString &data, int startPos = -1);
|
||||
|
||||
/**
|
||||
* Call this when needData returns true, before calling find(). The use of
|
||||
* ID's is especially useful if you're using the FindIncremental option.
|
||||
* @param id the id of the text fragment
|
||||
* @param data the text fragment (line)
|
||||
* @param startPos if set, the index at which the search should start.
|
||||
* This is only necessary for the very first call to setData usually,
|
||||
* for the 'find in selection' feature. A value of -1 (the default value)
|
||||
* means "process all the data", i.e. either 0 or data.length()-1 depending
|
||||
* on FindBackwards.
|
||||
*/
|
||||
void setData(int id, const QString &data, int startPos = -1);
|
||||
|
||||
/**
|
||||
* Walk the text fragment (e.g. in a text-processor line or spreadsheet
|
||||
* cell ...etc) looking for matches.
|
||||
* For each match, emits the textFound() signal and displays the find-again
|
||||
* dialog to ask if the user wants to find the same text again.
|
||||
*/
|
||||
Result find();
|
||||
|
||||
/**
|
||||
* Return the current options.
|
||||
*
|
||||
* Warning: this is usually the same value as the one passed to the constructor,
|
||||
* but options might change _during_ the replace operation:
|
||||
* e.g. the "All" button resets the PromptOnReplace flag.
|
||||
*
|
||||
* @see KFind::Options
|
||||
*/
|
||||
long options() const;
|
||||
|
||||
/**
|
||||
* Set new options. Usually this is used for setting or clearing the
|
||||
* FindBackwards options.
|
||||
*
|
||||
* @see KFind::Options
|
||||
*/
|
||||
virtual void setOptions(long options);
|
||||
|
||||
/**
|
||||
* @return the pattern we're currently looking for
|
||||
*/
|
||||
QString pattern() const;
|
||||
|
||||
/**
|
||||
* Change the pattern we're looking for
|
||||
*/
|
||||
void setPattern(const QString &pattern);
|
||||
|
||||
/**
|
||||
* Returns the number of matches found (i.e. the number of times the textFound()
|
||||
* signal was emitted).
|
||||
* If 0, can be used in a dialog box to tell the user "no match was found".
|
||||
* The final dialog does so already, unless you used setDisplayFinalDialog(false).
|
||||
*/
|
||||
int numMatches() const;
|
||||
|
||||
/**
|
||||
* Call this to reset the numMatches count
|
||||
* (and the numReplacements count for a KReplace).
|
||||
* Can be useful if reusing the same KReplace for different operations,
|
||||
* or when restarting from the beginning of the document.
|
||||
*/
|
||||
virtual void resetCounts();
|
||||
|
||||
/**
|
||||
* Virtual method, which allows applications to add extra checks for
|
||||
* validating a candidate match. It's only necessary to reimplement this
|
||||
* if the find dialog extension has been used to provide additional
|
||||
* criteria.
|
||||
*
|
||||
* @param text The current text fragment
|
||||
* @param index The starting index where the candidate match was found
|
||||
* @param matchedlength The length of the candidate match
|
||||
*/
|
||||
virtual bool validateMatch(const QString &text, int index, int matchedlength);
|
||||
|
||||
/**
|
||||
* Returns true if we should restart the search from scratch.
|
||||
* Can ask the user, or return false (if we already searched the whole document).
|
||||
*
|
||||
* @param forceAsking set to true if the user modified the document during the
|
||||
* search. In that case it makes sense to restart the search again.
|
||||
*
|
||||
* @param showNumMatches set to true if the dialog should show the number of
|
||||
* matches. Set to false if the application provides a "find previous" action,
|
||||
* in which case the match count will be erroneous when hitting the end,
|
||||
* and we could even be hitting the beginning of the document (so not all
|
||||
* matches have even been seen).
|
||||
*/
|
||||
virtual bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const;
|
||||
|
||||
/**
|
||||
* Search @p text for @p pattern. If a match is found, the length of the matched
|
||||
* string will be stored in @p matchedLength and the index of the matched string
|
||||
* will be returned. If no match is found -1 is returned.
|
||||
*
|
||||
* If the KFind::RegularExpression flag is set, the @p pattern will be iterpreted
|
||||
* as a regular expression (using QRegularExpression).
|
||||
*
|
||||
* @note Unicode support is always enabled (by setting the QRegularExpression::UseUnicodePropertiesOption flag).
|
||||
*
|
||||
* @param text The string to search in
|
||||
* @param pattern The pattern to search for
|
||||
* @param index The index in @p text from which to start the search
|
||||
* @param options The options to use
|
||||
* @param matchedlength If there is a match, its length will be stored in this parameter
|
||||
* @param rmatch If there is a regular expression match (implies that the KFind::RegularExpression
|
||||
* flag is set) and @p rmatch is not a nullptr the match result will be stored
|
||||
* in this QRegularExpressionMatch object
|
||||
* @return The index at which a match was found otherwise -1
|
||||
*
|
||||
* @since 5.70
|
||||
*/
|
||||
static int find(const QString &text, const QString &pattern, int index, long options, int *matchedLength, QRegularExpressionMatch *rmatch);
|
||||
|
||||
/**
|
||||
* Displays the final dialog saying "no match was found", if that was the case.
|
||||
* Call either this or shouldRestart().
|
||||
*/
|
||||
virtual void displayFinalDialog() const;
|
||||
|
||||
/**
|
||||
* Return (or create) the dialog that shows the "find next?" prompt.
|
||||
* Usually you don't need to call this.
|
||||
* One case where it can be useful, is when the user selects the "Find"
|
||||
* menu item while a find operation is under way. In that case, the
|
||||
* program may want to call setActiveWindow() on that dialog.
|
||||
*/
|
||||
QDialog *findNextDialog(bool create = false);
|
||||
|
||||
/**
|
||||
* Close the "find next?" dialog. The application should do this when
|
||||
* the last match was hit. If the application deletes the KFind, then
|
||||
* "find previous" won't be possible anymore.
|
||||
*
|
||||
* IMPORTANT: you should also call this if you are using a non-modal
|
||||
* find dialog, to tell KFind not to pop up its own dialog.
|
||||
*/
|
||||
void closeFindNextDialog();
|
||||
|
||||
/**
|
||||
* @return the current matching index (or -1).
|
||||
* Same as the matchingIndex parameter passed to the textFound() signal.
|
||||
* You usually don't need to use this, except maybe when updating the current data,
|
||||
* so you need to call setData(newData, index()).
|
||||
*/
|
||||
int index() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Connect to this signal to implement highlighting of found text during the find
|
||||
* operation.
|
||||
*
|
||||
* If you've set data with setData(id, text), use the textFoundAtId(int, int, int) signal.
|
||||
*
|
||||
* WARNING: If you're using the FindIncremental option, the text argument
|
||||
* passed by this signal is not necessarily the data last set through
|
||||
* setData(), but can also be an earlier set data block.
|
||||
*
|
||||
* @see setData()
|
||||
*
|
||||
* @since 5.81
|
||||
*/
|
||||
void textFound(const QString &text, int matchingIndex, int matchedLength);
|
||||
|
||||
/**
|
||||
* Connect to this signal to implement highlighting of found text during
|
||||
* the find operation.
|
||||
*
|
||||
* Use this signal if you've set your data with setData(id, text),
|
||||
* otherwise use the textFound(text, matchingIndex, matchedLength) signal.
|
||||
*
|
||||
* WARNING: If you're using the FindIncremental option, the id argument
|
||||
* passed by this signal is not necessarily the id of the data last set
|
||||
* through setData(), but can also be of an earlier set data block.
|
||||
*
|
||||
* @see setData()
|
||||
*
|
||||
* @since 5.81
|
||||
*/
|
||||
void textFoundAtId(int id, int matchingIndex, int matchedLength);
|
||||
|
||||
// ## TODO docu
|
||||
// findprevious will also emit findNext, after temporarily switching the value
|
||||
// of FindBackwards
|
||||
void findNext();
|
||||
|
||||
/**
|
||||
* Emitted when the options have changed.
|
||||
* This can happen e.g. with "Replace All", or if our 'find next' dialog
|
||||
* gets a "find previous" one day.
|
||||
*/
|
||||
void optionsChanged();
|
||||
|
||||
/**
|
||||
* Emitted when the 'find next' dialog is being closed.
|
||||
* Some apps might want to remove the highlighted text when this happens.
|
||||
* Apps without support for "Find Next" can also do m_find->deleteLater()
|
||||
* to terminate the find operation.
|
||||
*/
|
||||
void dialogClosed();
|
||||
|
||||
protected:
|
||||
QWidget *parentWidget() const;
|
||||
QWidget *dialogsParent() const;
|
||||
|
||||
protected:
|
||||
KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent);
|
||||
KTEXTWIDGETS_NO_EXPORT KFind(KFindPrivate &dd, const QString &pattern, long options, QWidget *parent, QWidget *findDialog);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<class KFindPrivate> const d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KFind)
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(KFind::SearchOptions)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KFIND_P_H
|
||||
#define KFIND_P_H
|
||||
|
||||
#include "kfind.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
|
||||
class KFindPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KFind)
|
||||
|
||||
public:
|
||||
KFindPrivate(KFind *qq)
|
||||
: q_ptr(qq)
|
||||
, findDialog(nullptr)
|
||||
, currentId(0)
|
||||
, customIds(false)
|
||||
, patternChanged(false)
|
||||
, matchedPattern(QLatin1String(""))
|
||||
, emptyMatch(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~KFindPrivate()
|
||||
{
|
||||
if (dialog) {
|
||||
dialog->deleteLater();
|
||||
}
|
||||
dialog = nullptr;
|
||||
data.clear();
|
||||
delete emptyMatch;
|
||||
emptyMatch = nullptr;
|
||||
}
|
||||
|
||||
struct Match {
|
||||
Match()
|
||||
: dataId(-1)
|
||||
, index(-1)
|
||||
, matchedLength(-1)
|
||||
{
|
||||
}
|
||||
bool isNull() const
|
||||
{
|
||||
return index == -1;
|
||||
}
|
||||
Match(int _dataId, int _index, int _matchedLength)
|
||||
: dataId(_dataId)
|
||||
, index(_index)
|
||||
, matchedLength(_matchedLength)
|
||||
{
|
||||
Q_ASSERT(index != -1);
|
||||
}
|
||||
|
||||
int dataId;
|
||||
int index;
|
||||
int matchedLength;
|
||||
};
|
||||
|
||||
struct Data {
|
||||
Data()
|
||||
{
|
||||
}
|
||||
Data(int id, const QString &text, bool dirty = false)
|
||||
: text(text)
|
||||
, id(id)
|
||||
, dirty(dirty)
|
||||
{
|
||||
}
|
||||
|
||||
QString text;
|
||||
int id = -1;
|
||||
bool dirty = false;
|
||||
};
|
||||
|
||||
void init(const QString &pattern);
|
||||
void startNewIncrementalSearch();
|
||||
|
||||
void slotFindNext();
|
||||
void slotDialogClosed();
|
||||
|
||||
KFind *const q_ptr;
|
||||
QPointer<QWidget> findDialog;
|
||||
int currentId;
|
||||
bool customIds : 1;
|
||||
bool patternChanged : 1;
|
||||
QString matchedPattern;
|
||||
QHash<QString, Match> incrementalPath;
|
||||
Match *emptyMatch;
|
||||
QList<Data> data; // used like a vector, not like a linked-list
|
||||
|
||||
QString pattern;
|
||||
QDialog *dialog;
|
||||
long options;
|
||||
unsigned matches;
|
||||
|
||||
QString text; // the text set by setData
|
||||
int index;
|
||||
int matchedLength;
|
||||
bool dialogClosed : 1;
|
||||
bool lastResult : 1;
|
||||
};
|
||||
|
||||
#endif // KFIND_P_H
|
||||
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "kfinddialog.h"
|
||||
#include "kfinddialog_p.h"
|
||||
|
||||
#include "kfind.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <KGuiItem>
|
||||
#include <KHistoryComboBox>
|
||||
#include <KLazyLocalizedString>
|
||||
#include <KLocalizedString>
|
||||
#include <KMessageBox>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
KFindDialog::KFindDialog(QWidget *parent, long options, const QStringList &findStrings, bool hasSelection, bool replaceDialog)
|
||||
: KFindDialog(*new KFindDialogPrivate(this), parent, options, findStrings, hasSelection, replaceDialog)
|
||||
{
|
||||
setWindowTitle(i18n("Find Text"));
|
||||
}
|
||||
|
||||
KFindDialog::KFindDialog(KFindDialogPrivate &dd, QWidget *parent, long options, const QStringList &findStrings, bool hasSelection, bool replaceDialog)
|
||||
: QDialog(parent)
|
||||
, d_ptr(&dd)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
d->init(replaceDialog, findStrings, hasSelection);
|
||||
setOptions(options);
|
||||
}
|
||||
|
||||
KFindDialog::~KFindDialog() = default;
|
||||
|
||||
QWidget *KFindDialog::findExtension() const
|
||||
{
|
||||
Q_D(const KFindDialog);
|
||||
|
||||
if (!d->findExtension) {
|
||||
d->findExtension = new QWidget(d->findGrp);
|
||||
d->findLayout->addWidget(d->findExtension, 3, 0, 1, 2);
|
||||
}
|
||||
|
||||
return d->findExtension;
|
||||
}
|
||||
|
||||
QStringList KFindDialog::findHistory() const
|
||||
{
|
||||
Q_D(const KFindDialog);
|
||||
|
||||
return d->find->historyItems();
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::init(bool forReplace, const QStringList &_findStrings, bool hasSelection)
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
// Create common parts of dialog.
|
||||
QVBoxLayout *topLayout = new QVBoxLayout(q);
|
||||
|
||||
findGrp = new QGroupBox(i18nc("@title:group", "Find"), q);
|
||||
findLayout = new QGridLayout(findGrp);
|
||||
|
||||
QLabel *findLabel = new QLabel(i18n("&Text to find:"), findGrp);
|
||||
find = new KHistoryComboBox(findGrp);
|
||||
find->setMaxCount(10);
|
||||
find->setDuplicatesEnabled(false);
|
||||
regExp = new QCheckBox(i18n("Regular e&xpression"), findGrp);
|
||||
regExpItem = new QPushButton(i18nc("@action:button", "&Edit…"), findGrp);
|
||||
regExpItem->setEnabled(false);
|
||||
|
||||
findLayout->addWidget(findLabel, 0, 0);
|
||||
findLayout->addWidget(find, 1, 0, 1, 2);
|
||||
findLayout->addWidget(regExp, 2, 0);
|
||||
findLayout->addWidget(regExpItem, 2, 1);
|
||||
topLayout->addWidget(findGrp);
|
||||
|
||||
replaceGrp = new QGroupBox(i18n("Replace With"), q);
|
||||
replaceLayout = new QGridLayout(replaceGrp);
|
||||
|
||||
QLabel *replaceLabel = new QLabel(i18n("Replace&ment text:"), replaceGrp);
|
||||
replace = new KHistoryComboBox(replaceGrp);
|
||||
replace->setMaxCount(10);
|
||||
replace->setDuplicatesEnabled(false);
|
||||
backRef = new QCheckBox(i18n("Use p&laceholders"), replaceGrp);
|
||||
backRefItem = new QPushButton(i18n("Insert Place&holder"), replaceGrp);
|
||||
backRefItem->setEnabled(false);
|
||||
|
||||
replaceLayout->addWidget(replaceLabel, 0, 0);
|
||||
replaceLayout->addWidget(replace, 1, 0, 1, 2);
|
||||
replaceLayout->addWidget(backRef, 2, 0);
|
||||
replaceLayout->addWidget(backRefItem, 2, 1);
|
||||
topLayout->addWidget(replaceGrp);
|
||||
|
||||
QGroupBox *optionGrp = new QGroupBox(i18n("Options"), q);
|
||||
QGridLayout *optionsLayout = new QGridLayout(optionGrp);
|
||||
|
||||
caseSensitive = new QCheckBox(i18n("C&ase sensitive"), optionGrp);
|
||||
wholeWordsOnly = new QCheckBox(i18n("&Whole words only"), optionGrp);
|
||||
fromCursor = new QCheckBox(i18n("From c&ursor"), optionGrp);
|
||||
findBackwards = new QCheckBox(i18n("Find &backwards"), optionGrp);
|
||||
selectedText = new QCheckBox(i18n("&Selected text"), optionGrp);
|
||||
q->setHasSelection(hasSelection);
|
||||
// If we have a selection, we make 'find in selection' default
|
||||
// and if we don't, then the option has to be unchecked, obviously.
|
||||
selectedText->setChecked(hasSelection);
|
||||
slotSelectedTextToggled(hasSelection);
|
||||
|
||||
promptOnReplace = new QCheckBox(i18n("&Prompt on replace"), optionGrp);
|
||||
promptOnReplace->setChecked(true);
|
||||
|
||||
optionsLayout->addWidget(caseSensitive, 0, 0);
|
||||
optionsLayout->addWidget(wholeWordsOnly, 1, 0);
|
||||
optionsLayout->addWidget(fromCursor, 2, 0);
|
||||
optionsLayout->addWidget(findBackwards, 0, 1);
|
||||
optionsLayout->addWidget(selectedText, 1, 1);
|
||||
optionsLayout->addWidget(promptOnReplace, 2, 1);
|
||||
topLayout->addWidget(optionGrp);
|
||||
|
||||
buttonBox = new QDialogButtonBox(q);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Close);
|
||||
q->connect(buttonBox, &QDialogButtonBox::accepted, q, [this]() {
|
||||
slotOk();
|
||||
});
|
||||
q->connect(buttonBox, &QDialogButtonBox::rejected, q, [this]() {
|
||||
slotReject();
|
||||
});
|
||||
topLayout->addWidget(buttonBox);
|
||||
|
||||
// We delay creation of these until needed.
|
||||
patterns = nullptr;
|
||||
placeholders = nullptr;
|
||||
|
||||
// signals and slots connections
|
||||
q->connect(selectedText, &QCheckBox::toggled, q, [this](bool checked) {
|
||||
slotSelectedTextToggled(checked);
|
||||
});
|
||||
q->connect(regExp, &QCheckBox::toggled, regExpItem, &QWidget::setEnabled);
|
||||
q->connect(backRef, &QCheckBox::toggled, backRefItem, &QWidget::setEnabled);
|
||||
q->connect(regExpItem, &QPushButton::clicked, q, [this]() {
|
||||
showPatterns();
|
||||
});
|
||||
q->connect(backRefItem, &QPushButton::clicked, q, [this]() {
|
||||
showPlaceholders();
|
||||
});
|
||||
|
||||
q->connect(find, &KHistoryComboBox::editTextChanged, q, [this](const QString &text) {
|
||||
textSearchChanged(text);
|
||||
});
|
||||
|
||||
q->connect(regExp, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(backRef, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(caseSensitive, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(wholeWordsOnly, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(fromCursor, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(findBackwards, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(selectedText, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
q->connect(promptOnReplace, &QCheckBox::toggled, q, &KFindDialog::optionsChanged);
|
||||
|
||||
// tab order
|
||||
q->setTabOrder(find, regExp);
|
||||
q->setTabOrder(regExp, regExpItem);
|
||||
q->setTabOrder(regExpItem, replace); // findExtension widgets are inserted in showEvent()
|
||||
q->setTabOrder(replace, backRef);
|
||||
q->setTabOrder(backRef, backRefItem);
|
||||
q->setTabOrder(backRefItem, caseSensitive);
|
||||
q->setTabOrder(caseSensitive, wholeWordsOnly);
|
||||
q->setTabOrder(wholeWordsOnly, fromCursor);
|
||||
q->setTabOrder(fromCursor, findBackwards);
|
||||
q->setTabOrder(findBackwards, selectedText);
|
||||
q->setTabOrder(selectedText, promptOnReplace);
|
||||
|
||||
// buddies
|
||||
findLabel->setBuddy(find);
|
||||
replaceLabel->setBuddy(replace);
|
||||
|
||||
if (!forReplace) {
|
||||
promptOnReplace->hide();
|
||||
replaceGrp->hide();
|
||||
}
|
||||
|
||||
findStrings = _findStrings;
|
||||
find->setFocus();
|
||||
QPushButton *buttonOk = buttonBox->button(QDialogButtonBox::Ok);
|
||||
buttonOk->setEnabled(!q->pattern().isEmpty());
|
||||
|
||||
if (forReplace) {
|
||||
KGuiItem::assign(buttonOk,
|
||||
KGuiItem(i18n("&Replace"),
|
||||
QString(),
|
||||
i18n("Start replace"),
|
||||
i18n("<qt>If you press the <b>Replace</b> button, the text you entered "
|
||||
"above is searched for within the document and any occurrence is "
|
||||
"replaced with the replacement text.</qt>")));
|
||||
} else {
|
||||
KGuiItem::assign(buttonOk,
|
||||
KGuiItem(i18n("&Find"),
|
||||
QStringLiteral("edit-find"),
|
||||
i18n("Start searching"),
|
||||
i18n("<qt>If you press the <b>Find</b> button, the text you entered "
|
||||
"above is searched for within the document.</qt>")));
|
||||
}
|
||||
|
||||
// QWhatsthis texts
|
||||
find->setWhatsThis(i18n("Enter a pattern to search for, or select a previous pattern from the list."));
|
||||
regExp->setWhatsThis(i18n("If enabled, search for a regular expression."));
|
||||
regExpItem->setWhatsThis(i18n("Click here to edit your regular expression using a graphical editor."));
|
||||
replace->setWhatsThis(i18n("Enter a replacement string, or select a previous one from the list."));
|
||||
backRef->setWhatsThis(
|
||||
i18n("<qt>If enabled, any occurrence of <code><b>\\N</b></code>, where "
|
||||
"<code><b>N</b></code> is an integer number, will be replaced with "
|
||||
"the corresponding capture (\"parenthesized substring\") from the "
|
||||
"pattern.<p>To include (a literal <code><b>\\N</b></code> in your "
|
||||
"replacement, put an extra backslash in front of it, like "
|
||||
"<code><b>\\\\N</b></code>.</p></qt>"));
|
||||
backRefItem->setWhatsThis(i18n("Click for a menu of available captures."));
|
||||
wholeWordsOnly->setWhatsThis(i18n("Require word boundaries in both ends of a match to succeed."));
|
||||
fromCursor->setWhatsThis(i18n("Start searching at the current cursor location rather than at the top."));
|
||||
selectedText->setWhatsThis(i18n("Only search within the current selection."));
|
||||
caseSensitive->setWhatsThis(i18n("Perform a case sensitive search: entering the pattern 'Joe' will not match 'joe' or 'JOE', only 'Joe'."));
|
||||
findBackwards->setWhatsThis(i18n("Search backwards."));
|
||||
promptOnReplace->setWhatsThis(i18n("Ask before replacing each match found."));
|
||||
|
||||
textSearchChanged(find->lineEdit()->text());
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::textSearchChanged(const QString &text)
|
||||
{
|
||||
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
|
||||
}
|
||||
|
||||
void KFindDialog::showEvent(QShowEvent *e)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
if (!d->initialShowDone) {
|
||||
d->initialShowDone = true; // only once
|
||||
// qDebug() << "showEvent\n";
|
||||
if (!d->findStrings.isEmpty()) {
|
||||
setFindHistory(d->findStrings);
|
||||
}
|
||||
d->findStrings = QStringList();
|
||||
if (!d->pattern.isEmpty()) {
|
||||
d->find->lineEdit()->setText(d->pattern);
|
||||
d->find->lineEdit()->selectAll();
|
||||
d->pattern.clear();
|
||||
}
|
||||
// maintain a user-friendly tab order
|
||||
if (d->findExtension) {
|
||||
QWidget *prev = d->regExpItem;
|
||||
const auto children = d->findExtension->findChildren<QWidget *>();
|
||||
for (QWidget *child : children) {
|
||||
setTabOrder(prev, child);
|
||||
prev = child;
|
||||
}
|
||||
setTabOrder(prev, d->replace);
|
||||
}
|
||||
}
|
||||
d->find->setFocus();
|
||||
QDialog::showEvent(e);
|
||||
}
|
||||
|
||||
long KFindDialog::options() const
|
||||
{
|
||||
Q_D(const KFindDialog);
|
||||
|
||||
long options = 0;
|
||||
|
||||
if (d->caseSensitive->isChecked()) {
|
||||
options |= KFind::CaseSensitive;
|
||||
}
|
||||
if (d->wholeWordsOnly->isChecked()) {
|
||||
options |= KFind::WholeWordsOnly;
|
||||
}
|
||||
if (d->fromCursor->isChecked()) {
|
||||
options |= KFind::FromCursor;
|
||||
}
|
||||
if (d->findBackwards->isChecked()) {
|
||||
options |= KFind::FindBackwards;
|
||||
}
|
||||
if (d->selectedText->isChecked()) {
|
||||
options |= KFind::SelectedText;
|
||||
}
|
||||
if (d->regExp->isChecked()) {
|
||||
options |= KFind::RegularExpression;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
QString KFindDialog::pattern() const
|
||||
{
|
||||
Q_D(const KFindDialog);
|
||||
|
||||
return d->find->currentText();
|
||||
}
|
||||
|
||||
void KFindDialog::setPattern(const QString &pattern)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
d->find->lineEdit()->setText(pattern);
|
||||
d->find->lineEdit()->selectAll();
|
||||
d->pattern = pattern;
|
||||
// qDebug() << "setPattern " << pattern;
|
||||
}
|
||||
|
||||
void KFindDialog::setFindHistory(const QStringList &strings)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
if (!strings.isEmpty()) {
|
||||
d->find->setHistoryItems(strings, true);
|
||||
d->find->lineEdit()->setText(strings.first());
|
||||
d->find->lineEdit()->selectAll();
|
||||
} else {
|
||||
d->find->clearHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialog::setHasSelection(bool hasSelection)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
if (hasSelection) {
|
||||
d->enabled |= KFind::SelectedText;
|
||||
} else {
|
||||
d->enabled &= ~KFind::SelectedText;
|
||||
}
|
||||
d->selectedText->setEnabled(hasSelection);
|
||||
if (!hasSelection) {
|
||||
d->selectedText->setChecked(false);
|
||||
d->slotSelectedTextToggled(hasSelection);
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::slotSelectedTextToggled(bool selec)
|
||||
{
|
||||
// From cursor doesn't make sense if we have a selection
|
||||
fromCursor->setEnabled(!selec && (enabled & KFind::FromCursor));
|
||||
if (selec) { // uncheck if disabled
|
||||
fromCursor->setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialog::setHasCursor(bool hasCursor)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
if (hasCursor) {
|
||||
d->enabled |= KFind::FromCursor;
|
||||
} else {
|
||||
d->enabled &= ~KFind::FromCursor;
|
||||
}
|
||||
d->fromCursor->setEnabled(hasCursor);
|
||||
d->fromCursor->setChecked(hasCursor && (options() & KFind::FromCursor));
|
||||
}
|
||||
|
||||
void KFindDialog::setSupportsBackwardsFind(bool supports)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
// ########## Shouldn't this hide the checkbox instead?
|
||||
if (supports) {
|
||||
d->enabled |= KFind::FindBackwards;
|
||||
} else {
|
||||
d->enabled &= ~KFind::FindBackwards;
|
||||
}
|
||||
d->findBackwards->setEnabled(supports);
|
||||
d->findBackwards->setChecked(supports && (options() & KFind::FindBackwards));
|
||||
}
|
||||
|
||||
void KFindDialog::setSupportsCaseSensitiveFind(bool supports)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
// ########## This should hide the checkbox instead
|
||||
if (supports) {
|
||||
d->enabled |= KFind::CaseSensitive;
|
||||
} else {
|
||||
d->enabled &= ~KFind::CaseSensitive;
|
||||
}
|
||||
d->caseSensitive->setEnabled(supports);
|
||||
d->caseSensitive->setChecked(supports && (options() & KFind::CaseSensitive));
|
||||
}
|
||||
|
||||
void KFindDialog::setSupportsWholeWordsFind(bool supports)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
// ########## This should hide the checkbox instead
|
||||
if (supports) {
|
||||
d->enabled |= KFind::WholeWordsOnly;
|
||||
} else {
|
||||
d->enabled &= ~KFind::WholeWordsOnly;
|
||||
}
|
||||
d->wholeWordsOnly->setEnabled(supports);
|
||||
d->wholeWordsOnly->setChecked(supports && (options() & KFind::WholeWordsOnly));
|
||||
}
|
||||
|
||||
void KFindDialog::setSupportsRegularExpressionFind(bool supports)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
if (supports) {
|
||||
d->enabled |= KFind::RegularExpression;
|
||||
} else {
|
||||
d->enabled &= ~KFind::RegularExpression;
|
||||
}
|
||||
d->regExp->setEnabled(supports);
|
||||
d->regExp->setChecked(supports && (options() & KFind::RegularExpression));
|
||||
if (!supports) {
|
||||
d->regExpItem->hide();
|
||||
d->regExp->hide();
|
||||
} else {
|
||||
d->regExpItem->show();
|
||||
d->regExp->show();
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialog::setOptions(long options)
|
||||
{
|
||||
Q_D(KFindDialog);
|
||||
|
||||
d->caseSensitive->setChecked((d->enabled & KFind::CaseSensitive) && (options & KFind::CaseSensitive));
|
||||
d->wholeWordsOnly->setChecked((d->enabled & KFind::WholeWordsOnly) && (options & KFind::WholeWordsOnly));
|
||||
d->fromCursor->setChecked((d->enabled & KFind::FromCursor) && (options & KFind::FromCursor));
|
||||
d->findBackwards->setChecked((d->enabled & KFind::FindBackwards) && (options & KFind::FindBackwards));
|
||||
d->selectedText->setChecked((d->enabled & KFind::SelectedText) && (options & KFind::SelectedText));
|
||||
d->regExp->setChecked((d->enabled & KFind::RegularExpression) && (options & KFind::RegularExpression));
|
||||
}
|
||||
|
||||
// Create a popup menu with a list of regular expression terms, to help the user
|
||||
// compose a regular expression search pattern.
|
||||
void KFindDialogPrivate::showPatterns()
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
typedef struct {
|
||||
const KLazyLocalizedString description;
|
||||
const char *regExp;
|
||||
int cursorAdjustment;
|
||||
} Term;
|
||||
static const Term items[] = {
|
||||
{kli18n("Any Character"), ".", 0},
|
||||
{kli18n("Start of Line"), "^", 0},
|
||||
{kli18n("End of Line"), "$", 0},
|
||||
{kli18n("Set of Characters"), "[]", -1},
|
||||
{kli18n("Repeats, Zero or More Times"), "*", 0},
|
||||
{kli18n("Repeats, One or More Times"), "+", 0},
|
||||
{kli18n("Optional"), "?", 0},
|
||||
{kli18n("Escape"), "\\", 0},
|
||||
{kli18n("TAB"), "\\t", 0},
|
||||
{kli18n("Newline"), "\\n", 0},
|
||||
{kli18n("Carriage Return"), "\\r", 0},
|
||||
{kli18n("White Space"), "\\s", 0},
|
||||
{kli18n("Digit"), "\\d", 0},
|
||||
};
|
||||
|
||||
class RegExpAction : public QAction
|
||||
{
|
||||
public:
|
||||
RegExpAction(QObject *parent, const QString &text, const QString ®Exp, int cursor)
|
||||
: QAction(text, parent)
|
||||
, mText(text)
|
||||
, mRegExp(regExp)
|
||||
, mCursor(cursor)
|
||||
{
|
||||
}
|
||||
|
||||
QString text() const
|
||||
{
|
||||
return mText;
|
||||
}
|
||||
QString regExp() const
|
||||
{
|
||||
return mRegExp;
|
||||
}
|
||||
int cursor() const
|
||||
{
|
||||
return mCursor;
|
||||
}
|
||||
|
||||
private:
|
||||
QString mText;
|
||||
QString mRegExp;
|
||||
int mCursor;
|
||||
};
|
||||
|
||||
// Populate the popup menu.
|
||||
if (!patterns) {
|
||||
patterns = new QMenu(q);
|
||||
for (const Term &item : items) {
|
||||
patterns->addAction(new RegExpAction(patterns, item.description.toString(), QLatin1String(item.regExp), item.cursorAdjustment));
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the selection into the edit control.
|
||||
QAction *action = patterns->exec(regExpItem->mapToGlobal(regExpItem->rect().bottomLeft()));
|
||||
if (action) {
|
||||
RegExpAction *regExpAction = static_cast<RegExpAction *>(action);
|
||||
if (regExpAction) {
|
||||
QLineEdit *editor = find->lineEdit();
|
||||
|
||||
editor->insert(regExpAction->regExp());
|
||||
editor->setCursorPosition(editor->cursorPosition() + regExpAction->cursor());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PlaceHolderAction : public QAction
|
||||
{
|
||||
public:
|
||||
PlaceHolderAction(QObject *parent, const QString &text, int id)
|
||||
: QAction(text, parent)
|
||||
, mText(text)
|
||||
, mId(id)
|
||||
{
|
||||
}
|
||||
|
||||
QString text() const
|
||||
{
|
||||
return mText;
|
||||
}
|
||||
int id() const
|
||||
{
|
||||
return mId;
|
||||
}
|
||||
|
||||
private:
|
||||
QString mText;
|
||||
int mId;
|
||||
};
|
||||
|
||||
// Create a popup menu with a list of backreference terms, to help the user
|
||||
// compose a regular expression replacement pattern.
|
||||
void KFindDialogPrivate::showPlaceholders()
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
// Populate the popup menu.
|
||||
if (!placeholders) {
|
||||
placeholders = new QMenu(q);
|
||||
q->connect(placeholders, &QMenu::aboutToShow, q, [this]() {
|
||||
slotPlaceholdersAboutToShow();
|
||||
});
|
||||
}
|
||||
|
||||
// Insert the selection into the edit control.
|
||||
QAction *action = placeholders->exec(backRefItem->mapToGlobal(backRefItem->rect().bottomLeft()));
|
||||
if (action) {
|
||||
PlaceHolderAction *placeHolderAction = static_cast<PlaceHolderAction *>(action);
|
||||
if (placeHolderAction) {
|
||||
QLineEdit *editor = replace->lineEdit();
|
||||
editor->insert(QStringLiteral("\\%1").arg(placeHolderAction->id()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::slotPlaceholdersAboutToShow()
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
placeholders->clear();
|
||||
placeholders->addAction(new PlaceHolderAction(placeholders, i18n("Complete Match"), 0));
|
||||
|
||||
const int n = QRegularExpression(q->pattern(), QRegularExpression::UseUnicodePropertiesOption).captureCount();
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
placeholders->addAction(new PlaceHolderAction(placeholders, i18n("Captured Text (%1)", i), i));
|
||||
}
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::slotOk()
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
// Nothing to find?
|
||||
if (q->pattern().isEmpty()) {
|
||||
KMessageBox::error(q, i18n("You must enter some text to search for."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (regExp->isChecked()) {
|
||||
// Check for a valid regular expression.
|
||||
if (!QRegularExpression(q->pattern(), QRegularExpression::UseUnicodePropertiesOption).isValid()) {
|
||||
KMessageBox::error(q, i18n("Invalid PCRE pattern syntax."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
find->addToHistory(q->pattern());
|
||||
|
||||
if (q->windowModality() != Qt::NonModal) {
|
||||
q->accept();
|
||||
}
|
||||
Q_EMIT q->okClicked();
|
||||
}
|
||||
|
||||
void KFindDialogPrivate::slotReject()
|
||||
{
|
||||
Q_Q(KFindDialog);
|
||||
|
||||
Q_EMIT q->cancelClicked();
|
||||
q->reject();
|
||||
}
|
||||
|
||||
#include "moc_kfinddialog.cpp"
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KFINDDIALOG_H
|
||||
#define KFINDDIALOG_H
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <memory>
|
||||
|
||||
class KFindDialogPrivate;
|
||||
|
||||
/**
|
||||
* @class KFindDialog kfinddialog.h <KFindDialog>
|
||||
*
|
||||
* @brief A generic "find" dialog.
|
||||
*
|
||||
* @author S.R.Haque <srhaque@iee.org>
|
||||
*
|
||||
* \b Detail:
|
||||
*
|
||||
* This widget inherits from KDialog and implements
|
||||
* the following additional functionalities: a find string
|
||||
* object and an area for a user-defined widget to extend the dialog.
|
||||
*
|
||||
* \b Example:
|
||||
*
|
||||
* To use the basic modal find dialog, and then run the search:
|
||||
*
|
||||
* \code
|
||||
* KFindDialog dlg(....)
|
||||
* if (dlg.exec() != QDialog::Accepted)
|
||||
* return;
|
||||
*
|
||||
* // proceed with KFind from here
|
||||
* \endcode
|
||||
*
|
||||
* To create a non-modal find dialog:
|
||||
* \code
|
||||
* if (m_findDialog) {
|
||||
* m_findDialog->activateWindow();
|
||||
* } else {
|
||||
* m_findDialog = new KFindDialog(...);
|
||||
* connect(m_findDialog, &KFindDialog::okClicked, this, [this] {
|
||||
* m_findDialog->close();
|
||||
* delete m_find;
|
||||
* m_find = new KFind(m_findDialog->pattern(), m_findDialog->options(), this);
|
||||
* // ... see KFind documentation for what else should be done here
|
||||
* });
|
||||
* }
|
||||
* \endcode
|
||||
* Don't forget to delete and reset m_findDialog when closed.
|
||||
* (But do NOT delete your KFind object at that point, it's needed for "Find Next".)
|
||||
*
|
||||
* To use your own extensions: see findExtension().
|
||||
*
|
||||
* \image html kfinddialog.png "KFindDialog Widget"
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KFindDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Construct a modal find dialog
|
||||
*
|
||||
* @param parent The parent object of this widget.
|
||||
* @param options A bitfield of the Options to be checked.
|
||||
* @param findStrings The find history, see findHistory()
|
||||
* @param hasSelection Whether a selection exists
|
||||
*/
|
||||
explicit KFindDialog(QWidget *parent = nullptr,
|
||||
long options = 0,
|
||||
const QStringList &findStrings = QStringList(),
|
||||
bool hasSelection = false,
|
||||
bool replaceDialog = false);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KFindDialog() override;
|
||||
|
||||
/**
|
||||
* Provide the list of @p strings to be displayed as the history
|
||||
* of find strings. @p strings might get truncated if it is
|
||||
* too long.
|
||||
*
|
||||
* @param history The find history.
|
||||
* @see findHistory
|
||||
*/
|
||||
void setFindHistory(const QStringList &history);
|
||||
|
||||
/**
|
||||
* Returns the list of history items.
|
||||
*
|
||||
* @see setFindHistory
|
||||
*/
|
||||
QStringList findHistory() const;
|
||||
|
||||
/**
|
||||
* Enable/disable the 'search in selection' option, depending
|
||||
* on whether there actually is a selection.
|
||||
*
|
||||
* @param hasSelection true if a selection exists
|
||||
*/
|
||||
void setHasSelection(bool hasSelection);
|
||||
|
||||
/**
|
||||
* Hide/show the 'from cursor' option, depending
|
||||
* on whether the application implements a cursor.
|
||||
*
|
||||
* @param hasCursor true if the application features a cursor
|
||||
* This is assumed to be the case by default.
|
||||
*/
|
||||
void setHasCursor(bool hasCursor);
|
||||
|
||||
/**
|
||||
* Enable/disable the 'Find backwards' option, depending
|
||||
* on whether the application supports it.
|
||||
*
|
||||
* @param supports true if the application supports backwards find
|
||||
* This is assumed to be the case by default.
|
||||
*/
|
||||
void setSupportsBackwardsFind(bool supports);
|
||||
|
||||
/**
|
||||
* Enable/disable the 'Case sensitive' option, depending
|
||||
* on whether the application supports it.
|
||||
*
|
||||
* @param supports true if the application supports case sensitive find
|
||||
* This is assumed to be the case by default.
|
||||
*/
|
||||
void setSupportsCaseSensitiveFind(bool supports);
|
||||
|
||||
/**
|
||||
* Enable/disable the 'Whole words only' option, depending
|
||||
* on whether the application supports it.
|
||||
*
|
||||
* @param supports true if the application supports whole words only find
|
||||
* This is assumed to be the case by default.
|
||||
*/
|
||||
void setSupportsWholeWordsFind(bool supports);
|
||||
|
||||
/**
|
||||
* Enable/disable the 'Regular expression' option, depending
|
||||
* on whether the application supports it.
|
||||
*
|
||||
* @param supports true if the application supports regular expression find
|
||||
* This is assumed to be the case by default.
|
||||
*/
|
||||
void setSupportsRegularExpressionFind(bool supports);
|
||||
|
||||
/**
|
||||
* Set the options which are checked.
|
||||
*
|
||||
* @param options The setting of the Options.
|
||||
*
|
||||
* @see options()
|
||||
* @see KFind::Options
|
||||
*/
|
||||
void setOptions(long options);
|
||||
|
||||
/**
|
||||
* Returns the state of the options. Disabled options may be returned in
|
||||
* an indeterminate state.
|
||||
*
|
||||
* @see setOptions()
|
||||
* @see KFind::Options
|
||||
*/
|
||||
long options() const;
|
||||
|
||||
/**
|
||||
* Returns the pattern to find.
|
||||
*/
|
||||
QString pattern() const;
|
||||
|
||||
/**
|
||||
* Sets the pattern to find
|
||||
*/
|
||||
void setPattern(const QString &pattern);
|
||||
|
||||
/**
|
||||
* Returns an empty widget which the user may fill with additional UI
|
||||
* elements as required. The widget occupies the width of the dialog,
|
||||
* and is positioned immediately below the regular expression support
|
||||
* widgets for the pattern string.
|
||||
*/
|
||||
QWidget *findExtension() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is sent whenever one of the option checkboxes is toggled.
|
||||
* Call options() to get the new state of the checkboxes.
|
||||
*/
|
||||
void optionsChanged();
|
||||
|
||||
/**
|
||||
* This signal is sent when the user clicks on Ok button.
|
||||
*/
|
||||
void okClicked();
|
||||
|
||||
/**
|
||||
* This signal is sent when the user clicks on Cancel button.
|
||||
*/
|
||||
void cancelClicked();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *) override;
|
||||
|
||||
protected:
|
||||
KTEXTWIDGETS_NO_EXPORT explicit KFindDialog(KFindDialogPrivate &dd,
|
||||
QWidget *parent = nullptr,
|
||||
long options = 0,
|
||||
const QStringList &findStrings = QStringList(),
|
||||
bool hasSelection = false,
|
||||
bool replaceDialog = false);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<class KFindDialogPrivate> const d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KFindDialog)
|
||||
};
|
||||
|
||||
#endif // KFINDDIALOG_H
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KFINDDIALOG_P_H
|
||||
#define KFINDDIALOG_P_H
|
||||
|
||||
#include "kfind.h"
|
||||
#include "kfinddialog.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
class KHistoryComboBox;
|
||||
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QGridLayout;
|
||||
class QGroupBox;
|
||||
class QMenu;
|
||||
class QPushButton;
|
||||
|
||||
class KFindDialogPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KFindDialog)
|
||||
|
||||
public:
|
||||
KFindDialogPrivate(KFindDialog *qq)
|
||||
: q_ptr(qq)
|
||||
, regexpDialog(nullptr)
|
||||
, regexpDialogQueryDone(false)
|
||||
, initialShowDone(false)
|
||||
, enabled(KFind::WholeWordsOnly | KFind::FromCursor | KFind::SelectedText | KFind::CaseSensitive | KFind::FindBackwards | KFind::RegularExpression)
|
||||
, findExtension(nullptr)
|
||||
, buttonBox(nullptr)
|
||||
{
|
||||
}
|
||||
virtual ~KFindDialogPrivate() = default;
|
||||
|
||||
void init(bool forReplace, const QStringList &findStrings, bool hasSelection);
|
||||
|
||||
void slotPlaceholdersAboutToShow();
|
||||
void slotOk();
|
||||
void slotReject();
|
||||
void slotSelectedTextToggled(bool);
|
||||
void showPatterns();
|
||||
void showPlaceholders();
|
||||
void textSearchChanged(const QString &);
|
||||
|
||||
KFindDialog *const q_ptr = nullptr;
|
||||
QDialog *regexpDialog = nullptr;
|
||||
bool regexpDialogQueryDone : 1;
|
||||
bool initialShowDone : 1;
|
||||
long enabled; // uses Options to define which search options are enabled
|
||||
QStringList findStrings;
|
||||
QString pattern;
|
||||
mutable QWidget *findExtension = nullptr;
|
||||
QDialogButtonBox *buttonBox = nullptr;
|
||||
|
||||
QGroupBox *findGrp = nullptr;
|
||||
KHistoryComboBox *find = nullptr;
|
||||
QCheckBox *regExp = nullptr;
|
||||
QPushButton *regExpItem = nullptr;
|
||||
QGridLayout *findLayout = nullptr;
|
||||
|
||||
QCheckBox *wholeWordsOnly = nullptr;
|
||||
QCheckBox *fromCursor = nullptr;
|
||||
QCheckBox *selectedText = nullptr;
|
||||
QCheckBox *caseSensitive = nullptr;
|
||||
QCheckBox *findBackwards = nullptr;
|
||||
|
||||
QMenu *patterns = nullptr;
|
||||
|
||||
// for the replace dialog
|
||||
|
||||
QGroupBox *replaceGrp = nullptr;
|
||||
KHistoryComboBox *replace = nullptr;
|
||||
QCheckBox *backRef = nullptr;
|
||||
QPushButton *backRefItem = nullptr;
|
||||
QGridLayout *replaceLayout = nullptr;
|
||||
|
||||
QCheckBox *promptOnReplace = nullptr;
|
||||
|
||||
QMenu *placeholders = nullptr;
|
||||
};
|
||||
|
||||
#endif // KFINDDIALOG_P_H
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "kreplace.h"
|
||||
|
||||
#include "kfind_p.h"
|
||||
#include "kreplacedialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <KMessageBox>
|
||||
|
||||
//#define DEBUG_REPLACE
|
||||
#define INDEX_NOMATCH -1
|
||||
|
||||
class KReplaceNextDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit KReplaceNextDialog(QWidget *parent);
|
||||
void setLabel(const QString &pattern, const QString &replacement);
|
||||
|
||||
QPushButton *replaceAllButton() const;
|
||||
QPushButton *skipButton() const;
|
||||
QPushButton *replaceButton() const;
|
||||
|
||||
private:
|
||||
QLabel *m_mainLabel = nullptr;
|
||||
QPushButton *m_allButton = nullptr;
|
||||
QPushButton *m_skipButton = nullptr;
|
||||
QPushButton *m_replaceButton = nullptr;
|
||||
};
|
||||
|
||||
KReplaceNextDialog::KReplaceNextDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setModal(false);
|
||||
setWindowTitle(i18n("Replace"));
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
m_mainLabel = new QLabel(this);
|
||||
layout->addWidget(m_mainLabel);
|
||||
|
||||
m_allButton = new QPushButton(i18nc("@action:button Replace all occurrences", "&All"));
|
||||
m_allButton->setObjectName(QStringLiteral("allButton"));
|
||||
m_skipButton = new QPushButton(i18n("&Skip"));
|
||||
m_skipButton->setObjectName(QStringLiteral("skipButton"));
|
||||
m_replaceButton = new QPushButton(i18n("Replace"));
|
||||
m_replaceButton->setObjectName(QStringLiteral("replaceButton"));
|
||||
m_replaceButton->setDefault(true);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->addButton(m_allButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(m_skipButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->addButton(m_replaceButton, QDialogButtonBox::ActionRole);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Close);
|
||||
layout->addWidget(buttonBox);
|
||||
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void KReplaceNextDialog::setLabel(const QString &pattern, const QString &replacement)
|
||||
{
|
||||
m_mainLabel->setText(i18n("Replace '%1' with '%2'?", pattern, replacement));
|
||||
}
|
||||
|
||||
QPushButton *KReplaceNextDialog::replaceAllButton() const
|
||||
{
|
||||
return m_allButton;
|
||||
}
|
||||
|
||||
QPushButton *KReplaceNextDialog::skipButton() const
|
||||
{
|
||||
return m_skipButton;
|
||||
}
|
||||
|
||||
QPushButton *KReplaceNextDialog::replaceButton() const
|
||||
{
|
||||
return m_replaceButton;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
class KReplacePrivate : public KFindPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KReplace)
|
||||
|
||||
public:
|
||||
KReplacePrivate(KReplace *q, const QString &replacement)
|
||||
: KFindPrivate(q)
|
||||
, m_replacement(replacement)
|
||||
{
|
||||
}
|
||||
|
||||
KReplaceNextDialog *nextDialog();
|
||||
void doReplace();
|
||||
|
||||
void slotSkip();
|
||||
void slotReplace();
|
||||
void slotReplaceAll();
|
||||
|
||||
QString m_replacement;
|
||||
int m_replacements = 0;
|
||||
QRegularExpressionMatch m_match;
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent)
|
||||
: KFind(*new KReplacePrivate(this, replacement), pattern, options, parent)
|
||||
{
|
||||
}
|
||||
|
||||
KReplace::KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *dlg)
|
||||
: KFind(*new KReplacePrivate(this, replacement), pattern, options, parent, dlg)
|
||||
{
|
||||
}
|
||||
|
||||
KReplace::~KReplace() = default;
|
||||
|
||||
int KReplace::numReplacements() const
|
||||
{
|
||||
Q_D(const KReplace);
|
||||
|
||||
return d->m_replacements;
|
||||
}
|
||||
|
||||
QDialog *KReplace::replaceNextDialog(bool create)
|
||||
{
|
||||
Q_D(KReplace);
|
||||
|
||||
if (d->dialog || create) {
|
||||
return d->nextDialog();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KReplaceNextDialog *KReplacePrivate::nextDialog()
|
||||
{
|
||||
Q_Q(KReplace);
|
||||
|
||||
if (!dialog) {
|
||||
auto *nextDialog = new KReplaceNextDialog(q->parentWidget());
|
||||
q->connect(nextDialog->replaceAllButton(), &QPushButton::clicked, q, [this]() {
|
||||
slotReplaceAll();
|
||||
});
|
||||
q->connect(nextDialog->skipButton(), &QPushButton::clicked, q, [this]() {
|
||||
slotSkip();
|
||||
});
|
||||
q->connect(nextDialog->replaceButton(), &QPushButton::clicked, q, [this]() {
|
||||
slotReplace();
|
||||
});
|
||||
q->connect(nextDialog, &QDialog::finished, q, [this]() {
|
||||
slotDialogClosed();
|
||||
});
|
||||
dialog = nextDialog;
|
||||
}
|
||||
return static_cast<KReplaceNextDialog *>(dialog);
|
||||
}
|
||||
|
||||
void KReplace::displayFinalDialog() const
|
||||
{
|
||||
Q_D(const KReplace);
|
||||
|
||||
if (!d->m_replacements) {
|
||||
KMessageBox::information(parentWidget(), i18n("No text was replaced."));
|
||||
} else {
|
||||
KMessageBox::information(parentWidget(), i18np("1 replacement done.", "%1 replacements done.", d->m_replacements));
|
||||
}
|
||||
}
|
||||
|
||||
static int replaceHelper(QString &text, const QString &replacement, int index, long options, const QRegularExpressionMatch *match, int length)
|
||||
{
|
||||
QString rep(replacement);
|
||||
if (options & KReplaceDialog::BackReference) {
|
||||
// Handle backreferences
|
||||
if (options & KFind::RegularExpression) { // regex search
|
||||
Q_ASSERT(match);
|
||||
const int capNum = match->regularExpression().captureCount();
|
||||
for (int i = 0; i <= capNum; ++i) {
|
||||
rep.replace(QLatin1String("\\") + QString::number(i), match->captured(i));
|
||||
}
|
||||
} else { // with non-regex search only \0 is supported, replace it with the
|
||||
// right portion of 'text'
|
||||
rep.replace(QLatin1String("\\0"), text.mid(index, length));
|
||||
}
|
||||
}
|
||||
|
||||
// Then replace rep into the text
|
||||
text.replace(index, length, rep);
|
||||
return rep.length();
|
||||
}
|
||||
|
||||
KFind::Result KReplace::replace()
|
||||
{
|
||||
Q_D(KReplace);
|
||||
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "d->index=" << d->index;
|
||||
#endif
|
||||
if (d->index == INDEX_NOMATCH && d->lastResult == Match) {
|
||||
d->lastResult = NoMatch;
|
||||
return NoMatch;
|
||||
}
|
||||
|
||||
do { // this loop is only because validateMatch can fail
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "beginning of loop: d->index=" << d->index;
|
||||
#endif
|
||||
// Find the next match.
|
||||
d->index = KFind::find(d->text, d->pattern, d->index, d->options, &d->matchedLength, d->options & KFind::RegularExpression ? &d->m_match : nullptr);
|
||||
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "KFind::find returned d->index=" << d->index;
|
||||
#endif
|
||||
if (d->index != -1) {
|
||||
// Flexibility: the app can add more rules to validate a possible match
|
||||
if (validateMatch(d->text, d->index, d->matchedLength)) {
|
||||
if (d->options & KReplaceDialog::PromptOnReplace) {
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "PromptOnReplace";
|
||||
#endif
|
||||
// Display accurate initial string and replacement string, they can vary
|
||||
QString matchedText(d->text.mid(d->index, d->matchedLength));
|
||||
QString rep(matchedText);
|
||||
replaceHelper(rep, d->m_replacement, 0, d->options, d->options & KFind::RegularExpression ? &d->m_match : nullptr, d->matchedLength);
|
||||
d->nextDialog()->setLabel(matchedText, rep);
|
||||
d->nextDialog()->show(); // TODO kde5: virtual void showReplaceNextDialog(QString,QString), so that kreplacetest can skip the show()
|
||||
|
||||
// Tell the world about the match we found, in case someone wants to
|
||||
// highlight it.
|
||||
Q_EMIT textFound(d->text, d->index, d->matchedLength);
|
||||
|
||||
d->lastResult = Match;
|
||||
return Match;
|
||||
} else {
|
||||
d->doReplace(); // this moves on too
|
||||
}
|
||||
} else {
|
||||
// not validated -> move on
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
d->index--;
|
||||
} else {
|
||||
d->index++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
d->index = INDEX_NOMATCH; // will exit the loop
|
||||
}
|
||||
} while (d->index != INDEX_NOMATCH);
|
||||
|
||||
d->lastResult = NoMatch;
|
||||
return NoMatch;
|
||||
}
|
||||
|
||||
int KReplace::replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength)
|
||||
{
|
||||
int matchedLength;
|
||||
QRegularExpressionMatch match;
|
||||
index = KFind::find(text, pattern, index, options, &matchedLength, options & KFind::RegularExpression ? &match : nullptr);
|
||||
|
||||
if (index != -1) {
|
||||
*replacedLength = replaceHelper(text, replacement, index, options, options & KFind::RegularExpression ? &match : nullptr, matchedLength);
|
||||
if (options & KFind::FindBackwards) {
|
||||
index--;
|
||||
} else {
|
||||
index += *replacedLength;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void KReplacePrivate::slotReplaceAll()
|
||||
{
|
||||
Q_Q(KReplace);
|
||||
|
||||
doReplace();
|
||||
options &= ~KReplaceDialog::PromptOnReplace;
|
||||
Q_EMIT q->optionsChanged();
|
||||
Q_EMIT q->findNext();
|
||||
}
|
||||
|
||||
void KReplacePrivate::slotSkip()
|
||||
{
|
||||
Q_Q(KReplace);
|
||||
|
||||
if (options & KFind::FindBackwards) {
|
||||
index--;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
if (dialogClosed) {
|
||||
dialog->deleteLater();
|
||||
dialog = nullptr; // hide it again
|
||||
} else {
|
||||
Q_EMIT q->findNext();
|
||||
}
|
||||
}
|
||||
|
||||
void KReplacePrivate::slotReplace()
|
||||
{
|
||||
Q_Q(KReplace);
|
||||
|
||||
doReplace();
|
||||
if (dialogClosed) {
|
||||
dialog->deleteLater();
|
||||
dialog = nullptr; // hide it again
|
||||
} else {
|
||||
Q_EMIT q->findNext();
|
||||
}
|
||||
}
|
||||
|
||||
void KReplacePrivate::doReplace()
|
||||
{
|
||||
Q_Q(KReplace);
|
||||
|
||||
Q_ASSERT(index >= 0);
|
||||
const int replacedLength = replaceHelper(text, m_replacement, index, options, &m_match, matchedLength);
|
||||
|
||||
// Tell the world about the replacement we made, in case someone wants to
|
||||
// highlight it.
|
||||
Q_EMIT q->textReplaced(text, index, replacedLength, matchedLength);
|
||||
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "after replace() signal: d->index=" << d->index << " replacedLength=" << replacedLength;
|
||||
#endif
|
||||
m_replacements++;
|
||||
if (options & KFind::FindBackwards) {
|
||||
Q_ASSERT(index >= 0);
|
||||
index--;
|
||||
} else {
|
||||
index += replacedLength;
|
||||
// when replacing the empty pattern, move on. See also kjs/regexp.cpp for how this should be done for regexps.
|
||||
if (pattern.isEmpty()) {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_REPLACE
|
||||
// qDebug() << "after adjustment: d->index=" << d->index;
|
||||
#endif
|
||||
}
|
||||
|
||||
void KReplace::resetCounts()
|
||||
{
|
||||
Q_D(KReplace);
|
||||
|
||||
KFind::resetCounts();
|
||||
d->m_replacements = 0;
|
||||
}
|
||||
|
||||
bool KReplace::shouldRestart(bool forceAsking, bool showNumMatches) const
|
||||
{
|
||||
Q_D(const KReplace);
|
||||
|
||||
// Only ask if we did a "find from cursor", otherwise it's pointless.
|
||||
// ... Or if the prompt-on-replace option was set.
|
||||
// Well, unless the user can modify the document during a search operation,
|
||||
// hence the force boolean.
|
||||
if (!forceAsking && (d->options & KFind::FromCursor) == 0 && (d->options & KReplaceDialog::PromptOnReplace) == 0) {
|
||||
displayFinalDialog();
|
||||
return false;
|
||||
}
|
||||
QString message;
|
||||
if (showNumMatches) {
|
||||
if (!d->m_replacements) {
|
||||
message = i18n("No text was replaced.");
|
||||
} else {
|
||||
message = i18np("1 replacement done.", "%1 replacements done.", d->m_replacements);
|
||||
}
|
||||
} else {
|
||||
if (d->options & KFind::FindBackwards) {
|
||||
message = i18n("Beginning of document reached.");
|
||||
} else {
|
||||
message = i18n("End of document reached.");
|
||||
}
|
||||
}
|
||||
|
||||
message += QLatin1Char('\n');
|
||||
// Hope this word puzzle is ok, it's a different sentence
|
||||
message +=
|
||||
(d->options & KFind::FindBackwards) ? i18n("Do you want to restart search from the end?") : i18n("Do you want to restart search at the beginning?");
|
||||
|
||||
int ret = KMessageBox::questionTwoActions(parentWidget(),
|
||||
message,
|
||||
QString(),
|
||||
KGuiItem(i18nc("@action:button Restart find & replace", "Restart")),
|
||||
KGuiItem(i18nc("@action:button Stop find & replace", "Stop")));
|
||||
return (ret == KMessageBox::PrimaryAction);
|
||||
}
|
||||
|
||||
void KReplace::closeReplaceNextDialog()
|
||||
{
|
||||
closeFindNextDialog();
|
||||
}
|
||||
|
||||
#include "kreplace.moc"
|
||||
#include "moc_kreplace.cpp"
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KREPLACE_H
|
||||
#define KREPLACE_H
|
||||
|
||||
#include "kfind.h"
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
class KReplacePrivate;
|
||||
|
||||
/**
|
||||
* @class KReplace kreplace.h <KReplace>
|
||||
*
|
||||
* @brief A generic implementation of the "replace" function.
|
||||
*
|
||||
* @author S.R.Haque <srhaque@iee.org>, David Faure <faure@kde.org>
|
||||
*
|
||||
* \b Detail:
|
||||
*
|
||||
* This class includes prompt handling etc. Also provides some
|
||||
* static functions which can be used to create custom behavior
|
||||
* instead of using the class directly.
|
||||
*
|
||||
* \b Example:
|
||||
*
|
||||
* To use the class to implement a complete replace feature:
|
||||
*
|
||||
* In the slot connect to the replace action, after using KReplaceDialog:
|
||||
* \code
|
||||
*
|
||||
* // This creates a replace-on-prompt dialog if needed.
|
||||
* m_replace = new KReplace(pattern, replacement, options, this);
|
||||
*
|
||||
* // Connect signals to code which handles highlighting of found text, and
|
||||
* // on-the-fly replacement.
|
||||
* connect(m_replace, &KFind::textFound, this, [this](const QString &text, int matchingIndex, int matchedLength) {
|
||||
* slotHighlight(text, matchingIndex, matchedLength);
|
||||
* });
|
||||
* // Connect findNext signal - called when pressing the button in the dialog
|
||||
* connect( m_replace, SIGNAL( findNext() ),
|
||||
* this, SLOT( slotReplaceNext() ) );
|
||||
* // Connect to the textReplaced() signal - emitted when a replacement is done
|
||||
* connect( m_replace, &KReplace::textReplaced, this, [this](const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
|
||||
* slotReplace(text, replacementIndex, replacedLength, matchedLength);
|
||||
* });
|
||||
* \endcode
|
||||
* Then initialize the variables determining the "current position"
|
||||
* (to the cursor, if the option FromCursor is set,
|
||||
* to the beginning of the selection if the option SelectedText is set,
|
||||
* and to the beginning of the document otherwise).
|
||||
* Initialize the "end of search" variables as well (end of doc or end of selection).
|
||||
* Swap begin and end if FindBackwards.
|
||||
* Finally, call slotReplaceNext();
|
||||
*
|
||||
* \code
|
||||
* void slotReplaceNext()
|
||||
* {
|
||||
* KFind::Result res = KFind::NoMatch;
|
||||
* while ( res == KFind::NoMatch && <position not at end> ) {
|
||||
* if ( m_replace->needData() )
|
||||
* m_replace->setData( <current text fragment> );
|
||||
*
|
||||
* // Let KReplace inspect the text fragment, and display a dialog if a match is found
|
||||
* res = m_replace->replace();
|
||||
*
|
||||
* if ( res == KFind::NoMatch ) {
|
||||
* <Move to the next text fragment, honoring the FindBackwards setting for the direction>
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* if ( res == KFind::NoMatch ) // i.e. at end
|
||||
* <Call either m_replace->displayFinalDialog(); delete m_replace; m_replace = nullptr;
|
||||
* or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
|
||||
* else { m_replace->closeReplaceNextDialog(); }>
|
||||
* }
|
||||
* \endcode
|
||||
*
|
||||
* Don't forget delete m_find in the destructor of your class,
|
||||
* unless you gave it a parent widget on construction.
|
||||
*
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KReplace : public KFind
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Only use this constructor if you don't use KFindDialog, or if
|
||||
* you use it as a modal dialog.
|
||||
*/
|
||||
KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent = nullptr);
|
||||
/**
|
||||
* This is the recommended constructor if you also use KReplaceDialog (non-modal).
|
||||
* You should pass the pointer to it here, so that when a message box
|
||||
* appears it has the right parent. Don't worry about deletion, KReplace
|
||||
* will notice if the find dialog is closed.
|
||||
*/
|
||||
KReplace(const QString &pattern, const QString &replacement, long options, QWidget *parent, QWidget *replaceDialog);
|
||||
|
||||
~KReplace() override;
|
||||
|
||||
/**
|
||||
* Return the number of replacements made (i.e. the number of times
|
||||
* the textReplaced() signal was emitted).
|
||||
*
|
||||
* Can be used in a dialog box to tell the user how many replacements were made.
|
||||
* The final dialog does so already, unless you used setDisplayFinalDialog(false).
|
||||
*/
|
||||
int numReplacements() const;
|
||||
|
||||
/**
|
||||
* Call this to reset the numMatches & numReplacements counts.
|
||||
* Can be useful if reusing the same KReplace for different operations,
|
||||
* or when restarting from the beginning of the document.
|
||||
*/
|
||||
void resetCounts() override;
|
||||
|
||||
/**
|
||||
* Walk the text fragment (e.g. kwrite line, kspread cell) looking for matches.
|
||||
* For each match, if prompt-on-replace is specified, emits the textFound() signal
|
||||
* and displays the prompt-for-replace dialog before doing the replace.
|
||||
*/
|
||||
Result replace();
|
||||
|
||||
/**
|
||||
* Return (or create) the dialog that shows the "find next?" prompt.
|
||||
* Usually you don't need to call this.
|
||||
* One case where it can be useful, is when the user selects the "Find"
|
||||
* menu item while a find operation is under way. In that case, the
|
||||
* program may want to call setActiveWindow() on that dialog.
|
||||
*/
|
||||
QDialog *replaceNextDialog(bool create = false);
|
||||
|
||||
/**
|
||||
* Close the "replace next?" dialog. The application should do this when
|
||||
* the last match was hit. If the application deletes the KReplace, then
|
||||
* "find previous" won't be possible anymore.
|
||||
*/
|
||||
void closeReplaceNextDialog();
|
||||
|
||||
/**
|
||||
* Searches the given @p text for @p pattern; if a match is found it is replaced
|
||||
* with @p replacement and the index of the replacement string is returned.
|
||||
*
|
||||
* @param text The string to search
|
||||
* @param pattern The pattern to search for
|
||||
* @param replacement The replacement string to insert into the text
|
||||
* @param index The starting index into the string
|
||||
* @param options The options to use
|
||||
* @param replacedLength Output parameter, contains the length of the replaced string
|
||||
* Not always the same as replacement.length(), when backreferences are used
|
||||
* @return The index at which a match was found, or -1 otherwise
|
||||
*/
|
||||
static int replace(QString &text, const QString &pattern, const QString &replacement, int index, long options, int *replacedLength);
|
||||
|
||||
/**
|
||||
* Returns true if we should restart the search from scratch.
|
||||
* Can ask the user, or return false (if we already searched/replaced the
|
||||
* whole document without the PromptOnReplace option).
|
||||
*
|
||||
* @param forceAsking set to true if the user modified the document during the
|
||||
* search. In that case it makes sense to restart the search again.
|
||||
*
|
||||
* @param showNumMatches set to true if the dialog should show the number of
|
||||
* matches. Set to false if the application provides a "find previous" action,
|
||||
* in which case the match count will be erroneous when hitting the end,
|
||||
* and we could even be hitting the beginning of the document (so not all
|
||||
* matches have even been seen).
|
||||
*/
|
||||
bool shouldRestart(bool forceAsking = false, bool showNumMatches = true) const override;
|
||||
|
||||
/**
|
||||
* Displays the final dialog telling the user how many replacements were made.
|
||||
* Call either this or shouldRestart().
|
||||
*/
|
||||
void displayFinalDialog() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Connect to this signal to implement updating of replaced text during the replace
|
||||
* operation.
|
||||
*
|
||||
* Extra care must be taken to properly implement the "no prompt-on-replace" case.
|
||||
* For instance, the textFound() signal isn't emitted in that case (some code
|
||||
* might rely on it), and for performance reasons one should repaint after
|
||||
* replace() ONLY if prompt-on-replace was selected.
|
||||
*
|
||||
* @param text The text, in which the replacement has already been done
|
||||
* @param replacementIndex Starting index of the matched substring
|
||||
* @param replacedLength Length of the replacement string
|
||||
* @param matchedLength Length of the matched string
|
||||
*
|
||||
* @since 5.83
|
||||
*/
|
||||
void textReplaced(const QString &text, int replacementIndex, int replacedLength, int matchedLength);
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KReplace)
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "kreplacedialog.h"
|
||||
#include "kfinddialog_p.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLineEdit>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <KHistoryComboBox>
|
||||
#include <KLocalizedString>
|
||||
#include <KMessageBox>
|
||||
|
||||
/**
|
||||
* we need to insert the strings after the dialog is set
|
||||
* up, otherwise QComboBox will deliver an awful big sizeHint
|
||||
* for long replacement texts.
|
||||
*/
|
||||
class KReplaceDialogPrivate : public KFindDialogPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KReplaceDialog)
|
||||
|
||||
public:
|
||||
explicit KReplaceDialogPrivate(KReplaceDialog *qq)
|
||||
: KFindDialogPrivate(qq)
|
||||
{
|
||||
}
|
||||
|
||||
void slotOk();
|
||||
|
||||
QStringList replaceStrings;
|
||||
mutable QWidget *replaceExtension = nullptr;
|
||||
bool initialShowDone = false;
|
||||
};
|
||||
|
||||
KReplaceDialog::KReplaceDialog(QWidget *parent, long options, const QStringList &findStrings, const QStringList &replaceStrings, bool hasSelection)
|
||||
: KFindDialog(*new KReplaceDialogPrivate(this), parent, options, findStrings, hasSelection, true /*create replace dialog*/)
|
||||
{
|
||||
Q_D(KReplaceDialog);
|
||||
|
||||
d->replaceStrings = replaceStrings;
|
||||
}
|
||||
|
||||
KReplaceDialog::~KReplaceDialog() = default;
|
||||
|
||||
void KReplaceDialog::showEvent(QShowEvent *e)
|
||||
{
|
||||
Q_D(KReplaceDialog);
|
||||
|
||||
if (!d->initialShowDone) {
|
||||
d->initialShowDone = true; // only once
|
||||
|
||||
if (!d->replaceStrings.isEmpty()) {
|
||||
setReplacementHistory(d->replaceStrings);
|
||||
d->replace->lineEdit()->setText(d->replaceStrings[0]);
|
||||
}
|
||||
}
|
||||
|
||||
KFindDialog::showEvent(e);
|
||||
}
|
||||
|
||||
long KReplaceDialog::options() const
|
||||
{
|
||||
Q_D(const KReplaceDialog);
|
||||
|
||||
long options = 0;
|
||||
|
||||
options = KFindDialog::options();
|
||||
if (d->promptOnReplace->isChecked()) {
|
||||
options |= PromptOnReplace;
|
||||
}
|
||||
if (d->backRef->isChecked()) {
|
||||
options |= BackReference;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
QWidget *KReplaceDialog::replaceExtension() const
|
||||
{
|
||||
Q_D(const KReplaceDialog);
|
||||
|
||||
if (!d->replaceExtension) {
|
||||
d->replaceExtension = new QWidget(d->replaceGrp);
|
||||
d->replaceLayout->addWidget(d->replaceExtension, 3, 0, 1, 2);
|
||||
}
|
||||
|
||||
return d->replaceExtension;
|
||||
}
|
||||
|
||||
QString KReplaceDialog::replacement() const
|
||||
{
|
||||
Q_D(const KReplaceDialog);
|
||||
|
||||
return d->replace->currentText();
|
||||
}
|
||||
|
||||
QStringList KReplaceDialog::replacementHistory() const
|
||||
{
|
||||
Q_D(const KReplaceDialog);
|
||||
|
||||
QStringList lst = d->replace->historyItems();
|
||||
// historyItems() doesn't tell us about the case of replacing with an empty string
|
||||
if (d->replace->lineEdit()->text().isEmpty()) {
|
||||
lst.prepend(QString());
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
void KReplaceDialog::setOptions(long options)
|
||||
{
|
||||
Q_D(KReplaceDialog);
|
||||
|
||||
KFindDialog::setOptions(options);
|
||||
d->promptOnReplace->setChecked(options & PromptOnReplace);
|
||||
d->backRef->setChecked(options & BackReference);
|
||||
}
|
||||
|
||||
void KReplaceDialog::setReplacementHistory(const QStringList &strings)
|
||||
{
|
||||
Q_D(KReplaceDialog);
|
||||
|
||||
if (!strings.isEmpty()) {
|
||||
d->replace->setHistoryItems(strings, true);
|
||||
} else {
|
||||
d->replace->clearHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void KReplaceDialogPrivate::slotOk()
|
||||
{
|
||||
Q_Q(KReplaceDialog);
|
||||
|
||||
// If regex and backrefs are enabled, do a sanity check.
|
||||
if (regExp->isChecked() && backRef->isChecked()) {
|
||||
const QRegularExpression re(q->pattern(), QRegularExpression::UseUnicodePropertiesOption);
|
||||
const int caps = re.captureCount();
|
||||
|
||||
const QString rep = q->replacement();
|
||||
const static QRegularExpression check(QStringLiteral("((?:\\\\)+)(\\d+)"));
|
||||
auto iter = check.globalMatch(rep);
|
||||
while (iter.hasNext()) {
|
||||
const QRegularExpressionMatch match = iter.next();
|
||||
if ((match.captured(1).size() % 2) && match.captured(2).toInt() > caps) {
|
||||
KMessageBox::information(q,
|
||||
i18n("Your replacement string is referencing a capture greater than '\\%1', ", caps)
|
||||
+ (caps ? i18np("but your pattern only defines 1 capture.", "but your pattern only defines %1 captures.", caps)
|
||||
: i18n("but your pattern defines no captures."))
|
||||
+ i18n("\nPlease correct."));
|
||||
return; // abort OKing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slotOk();
|
||||
replace->addToHistory(q->replacement());
|
||||
}
|
||||
|
||||
#include "moc_kreplacedialog.cpp"
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2001 S.R. Haque <srhaque@iee.org>.
|
||||
SPDX-FileCopyrightText: 2002 David Faure <david@mandrakesoft.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KREPLACEDIALOG_H
|
||||
#define KREPLACEDIALOG_H
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
#include "kfinddialog.h"
|
||||
|
||||
class KReplaceDialogPrivate;
|
||||
|
||||
/**
|
||||
* @class KReplaceDialog kreplace.h <KReplaceDialog>
|
||||
*
|
||||
* @short A generic "replace" dialog.
|
||||
*
|
||||
* @author S.R.Haque <srhaque@iee.org>
|
||||
*
|
||||
* \b Detail:
|
||||
*
|
||||
* This widget inherits from KFindDialog and implements
|
||||
* the following additional functionalities: a replacement string
|
||||
* object and an area for a user-defined widget to extend the dialog.
|
||||
*
|
||||
* \b Example:
|
||||
*
|
||||
* To use the basic replace dialog:
|
||||
*
|
||||
* \code
|
||||
* \endcode
|
||||
*
|
||||
* To use your own extensions:
|
||||
*
|
||||
* \code
|
||||
* \endcode
|
||||
*
|
||||
* \image html kreplacedialog.png "KReplaceDialog Widget"
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KReplaceDialog : public KFindDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/// Options.
|
||||
|
||||
enum Options {
|
||||
/// Should the user be prompted before the replace operation?
|
||||
PromptOnReplace = 256,
|
||||
BackReference = 512,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a replace dialog.read-only or rather select-only combo box with a
|
||||
* parent object and a name.
|
||||
*
|
||||
* @param parent The parent object of this widget
|
||||
* @param options A bitfield of the Options to be enabled.
|
||||
* @param findStrings A QStringList to insert in the combo box of text to find
|
||||
* @param replaceStrings A QStringList to insert in the combo box of text to
|
||||
* replace with
|
||||
* @param hasSelection Whether a selection exists
|
||||
*/
|
||||
explicit KReplaceDialog(QWidget *parent = nullptr,
|
||||
long options = 0,
|
||||
const QStringList &findStrings = QStringList(),
|
||||
const QStringList &replaceStrings = QStringList(),
|
||||
bool hasSelection = true);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KReplaceDialog() override;
|
||||
|
||||
/**
|
||||
* Provide the list of @p strings to be displayed as the history
|
||||
* of replacement strings. @p strings might get truncated if it is
|
||||
* too long.
|
||||
*
|
||||
* @param history The replacement history.
|
||||
* @see replacementHistory
|
||||
*/
|
||||
void setReplacementHistory(const QStringList &history);
|
||||
|
||||
/**
|
||||
* Returns the list of history items.
|
||||
*
|
||||
* @see setReplacementHistory
|
||||
*/
|
||||
QStringList replacementHistory() const;
|
||||
|
||||
/**
|
||||
* Set the options which are enabled.
|
||||
*
|
||||
* @param options The setting of the Options.
|
||||
*/
|
||||
void setOptions(long options);
|
||||
|
||||
/**
|
||||
* Returns the state of the options. Disabled options may be returned in
|
||||
* an indeterminate state.
|
||||
*
|
||||
* @see setOptions
|
||||
*/
|
||||
long options() const;
|
||||
|
||||
/**
|
||||
* Returns the replacement string.
|
||||
*/
|
||||
QString replacement() const;
|
||||
|
||||
/**
|
||||
* Returns an empty widget which the user may fill with additional UI
|
||||
* elements as required. The widget occupies the width of the dialog,
|
||||
* and is positioned immediately the regular expression support widgets
|
||||
* for the replacement string.
|
||||
*/
|
||||
QWidget *replaceExtension() const;
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *) override;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KReplaceDialog)
|
||||
};
|
||||
|
||||
#endif // KREPLACEDIALOG_H
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2014 Laurent Montel <montel@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "kpluralhandlingspinbox.h"
|
||||
|
||||
#if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(6, 6)
|
||||
|
||||
class KPluralHandlingSpinBoxPrivate
|
||||
{
|
||||
public:
|
||||
KPluralHandlingSpinBoxPrivate(QSpinBox *qq)
|
||||
: q(qq)
|
||||
{
|
||||
QObject::connect(q, qOverload<int>(&QSpinBox::valueChanged), q, [this](int value) {
|
||||
updateSuffix(value);
|
||||
});
|
||||
}
|
||||
|
||||
void updateSuffix(int value)
|
||||
{
|
||||
if (!pluralSuffix.isEmpty()) {
|
||||
KLocalizedString s = pluralSuffix;
|
||||
q->setSuffix(s.subs(value).toString());
|
||||
}
|
||||
}
|
||||
|
||||
QSpinBox *const q;
|
||||
KLocalizedString pluralSuffix;
|
||||
};
|
||||
|
||||
KPluralHandlingSpinBox::KPluralHandlingSpinBox(QWidget *parent)
|
||||
: QSpinBox(parent)
|
||||
, d(new KPluralHandlingSpinBoxPrivate(this))
|
||||
{
|
||||
}
|
||||
|
||||
KPluralHandlingSpinBox::~KPluralHandlingSpinBox() = default;
|
||||
|
||||
void KPluralHandlingSpinBox::setSuffix(const KLocalizedString &suffix)
|
||||
{
|
||||
d->pluralSuffix = suffix;
|
||||
if (suffix.isEmpty()) {
|
||||
QSpinBox::setSuffix(QString());
|
||||
} else {
|
||||
d->updateSuffix(value());
|
||||
}
|
||||
}
|
||||
#include "moc_kpluralhandlingspinbox.cpp"
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2014 Laurent Montel <montel@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef KPLURALHANDLINGSPINBOX_H
|
||||
#define KPLURALHANDLINGSPINBOX_H
|
||||
|
||||
#include <ktextwidgets_export.h>
|
||||
|
||||
#if KTEXTWIDGETS_ENABLE_DEPRECATED_SINCE(6, 6)
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @class KPluralHandlingSpinBox kpluralhandlingspinbox.h <KPluralHandlingSpinBox>
|
||||
*
|
||||
* @brief A QSpinBox with plural handling for the suffix.
|
||||
*
|
||||
* @author Laurent Montel <montel@kde.org>
|
||||
*
|
||||
* @since 5.0
|
||||
* @deprecated since 6.6, use KLocalization::setupSpinBoxFormatString() from KF6::I18n instead,
|
||||
* which is more powerful and does not require inheriting from a specific QSpinBox subclass.
|
||||
*/
|
||||
KTEXTWIDGETS_DEPRECATED_VERSION(6, 6, "use KLocalization::setupSpinBoxFormatString() from KF6::I18n instead")
|
||||
class KTEXTWIDGETS_EXPORT KPluralHandlingSpinBox : public QSpinBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
|
||||
explicit KPluralHandlingSpinBox(QWidget *parent = nullptr);
|
||||
~KPluralHandlingSpinBox() override;
|
||||
|
||||
/**
|
||||
* Sets the suffix to @p suffix.
|
||||
* Use this to add a plural-aware suffix, e.g. by using ki18np("singular", "plural").
|
||||
*/
|
||||
void setSuffix(const KLocalizedString &suffix);
|
||||
|
||||
private:
|
||||
friend class KPluralHandlingSpinBoxPrivate;
|
||||
std::unique_ptr<class KPluralHandlingSpinBoxPrivate> const d;
|
||||
|
||||
Q_DISABLE_COPY(KPluralHandlingSpinBox)
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // KPLURALHANDLINGSPINBOX_H
|
||||
@@ -0,0 +1,661 @@
|
||||
/*
|
||||
krichtextedit
|
||||
SPDX-FileCopyrightText: 2007 Laurent Montel <montel@kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "krichtextedit.h"
|
||||
#include "krichtextedit_p.h"
|
||||
|
||||
// Own includes
|
||||
#include "klinkdialog_p.h"
|
||||
|
||||
// kdelibs includes
|
||||
#include <KColorScheme>
|
||||
#include <KCursor>
|
||||
|
||||
// Qt includes
|
||||
#include <QRegularExpression>
|
||||
|
||||
void KRichTextEditPrivate::activateRichText()
|
||||
{
|
||||
Q_Q(KRichTextEdit);
|
||||
|
||||
if (mMode == KRichTextEdit::Plain) {
|
||||
q->setAcceptRichText(true);
|
||||
mMode = KRichTextEdit::Rich;
|
||||
Q_EMIT q->textModeChanged(mMode);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextEditPrivate::setTextCursor(QTextCursor &cursor)
|
||||
{
|
||||
Q_Q(KRichTextEdit);
|
||||
|
||||
q->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void KRichTextEditPrivate::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
|
||||
{
|
||||
Q_Q(KRichTextEdit);
|
||||
|
||||
QTextCursor cursor = q->textCursor();
|
||||
QTextCursor wordStart(cursor);
|
||||
QTextCursor wordEnd(cursor);
|
||||
|
||||
wordStart.movePosition(QTextCursor::StartOfWord);
|
||||
wordEnd.movePosition(QTextCursor::EndOfWord);
|
||||
|
||||
cursor.beginEditBlock();
|
||||
if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) {
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
}
|
||||
cursor.mergeCharFormat(format);
|
||||
q->mergeCurrentCharFormat(format);
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
|
||||
KRichTextEdit::KRichTextEdit(const QString &text, QWidget *parent)
|
||||
: KRichTextEdit(*new KRichTextEditPrivate(this), text, parent)
|
||||
{
|
||||
}
|
||||
|
||||
KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent)
|
||||
: KTextEdit(dd, text, parent)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->init();
|
||||
}
|
||||
|
||||
KRichTextEdit::KRichTextEdit(QWidget *parent)
|
||||
: KRichTextEdit(*new KRichTextEditPrivate(this), parent)
|
||||
{
|
||||
}
|
||||
|
||||
KRichTextEdit::KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent)
|
||||
: KTextEdit(dd, parent)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->init();
|
||||
}
|
||||
|
||||
KRichTextEdit::~KRichTextEdit() = default;
|
||||
|
||||
//@cond PRIVATE
|
||||
void KRichTextEditPrivate::init()
|
||||
{
|
||||
Q_Q(KRichTextEdit);
|
||||
|
||||
q->setAcceptRichText(false);
|
||||
KCursor::setAutoHideCursor(q, true, true);
|
||||
}
|
||||
//@endcond
|
||||
|
||||
void KRichTextEdit::setListStyle(int _styleIndex)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->nestedListHelper->handleOnBulletType(-_styleIndex);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::indentListMore()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->nestedListHelper->changeIndent(+1);
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::indentListLess()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->nestedListHelper->changeIndent(-1);
|
||||
}
|
||||
|
||||
void KRichTextEdit::insertHorizontalRule()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextBlockFormat bf = cursor.blockFormat();
|
||||
QTextCharFormat cf = cursor.charFormat();
|
||||
|
||||
cursor.beginEditBlock();
|
||||
cursor.insertHtml(QStringLiteral("<hr>"));
|
||||
cursor.insertBlock(bf, cf);
|
||||
cursor.endEditBlock();
|
||||
setTextCursor(cursor);
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::alignLeft()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
setAlignment(Qt::AlignLeft);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::alignCenter()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
setAlignment(Qt::AlignHCenter);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::alignRight()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
setAlignment(Qt::AlignRight);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::alignJustify()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
setAlignment(Qt::AlignJustify);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::makeRightToLeft()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextBlockFormat format;
|
||||
format.setLayoutDirection(Qt::RightToLeft);
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.mergeBlockFormat(format);
|
||||
setTextCursor(cursor);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::makeLeftToRight()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextBlockFormat format;
|
||||
format.setLayoutDirection(Qt::LeftToRight);
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.mergeBlockFormat(format);
|
||||
setTextCursor(cursor);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextBold(bool bold)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontWeight(bold ? QFont::Bold : QFont::Normal);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextItalic(bool italic)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontItalic(italic);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextUnderline(bool underline)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontUnderline(underline);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextStrikeOut(bool strikeOut)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontStrikeOut(strikeOut);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextForegroundColor(const QColor &color)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setForeground(color);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextBackgroundColor(const QColor &color)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setBackground(color);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setFontFamily(const QString &fontFamily)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontFamilies({fontFamily});
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setFontSize(int size)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFontPointSize(size);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setFont(const QFont &font)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setFont(font);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::switchToPlainText()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
if (d->mMode == Rich) {
|
||||
d->mMode = Plain;
|
||||
// TODO: Warn the user about this?
|
||||
auto insertPlainFunc = [this]() {
|
||||
insertPlainTextImplementation();
|
||||
};
|
||||
QMetaObject::invokeMethod(this, insertPlainFunc);
|
||||
setAcceptRichText(false);
|
||||
Q_EMIT textModeChanged(d->mMode);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextEdit::insertPlainTextImplementation()
|
||||
{
|
||||
document()->setPlainText(document()->toPlainText());
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextSuperScript(bool superscript)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setVerticalAlignment(superscript ? QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextSubScript(bool subscript)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setVerticalAlignment(subscript ? QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal);
|
||||
d->mergeFormatOnWordOrSelection(fmt);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::setHeadingLevel(int level)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
const int boundedLevel = qBound(0, 6, level);
|
||||
// Apparently, 5 is maximum for FontSizeAdjustment; otherwise level=1 and
|
||||
// level=2 look the same
|
||||
const int sizeAdjustment = boundedLevel > 0 ? 5 - boundedLevel : 0;
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.beginEditBlock();
|
||||
|
||||
QTextBlockFormat blkfmt;
|
||||
blkfmt.setHeadingLevel(boundedLevel);
|
||||
cursor.mergeBlockFormat(blkfmt);
|
||||
|
||||
QTextCharFormat chrfmt;
|
||||
chrfmt.setFontWeight(boundedLevel > 0 ? QFont::Bold : QFont::Normal);
|
||||
chrfmt.setProperty(QTextFormat::FontSizeAdjustment, sizeAdjustment);
|
||||
// Applying style to the current line or selection
|
||||
QTextCursor selectCursor = cursor;
|
||||
if (selectCursor.hasSelection()) {
|
||||
QTextCursor top = selectCursor;
|
||||
top.setPosition(qMin(top.anchor(), top.position()));
|
||||
top.movePosition(QTextCursor::StartOfBlock);
|
||||
|
||||
QTextCursor bottom = selectCursor;
|
||||
bottom.setPosition(qMax(bottom.anchor(), bottom.position()));
|
||||
bottom.movePosition(QTextCursor::EndOfBlock);
|
||||
|
||||
selectCursor.setPosition(top.position(), QTextCursor::MoveAnchor);
|
||||
selectCursor.setPosition(bottom.position(), QTextCursor::KeepAnchor);
|
||||
} else {
|
||||
selectCursor.select(QTextCursor::BlockUnderCursor);
|
||||
}
|
||||
selectCursor.mergeCharFormat(chrfmt);
|
||||
|
||||
cursor.mergeBlockCharFormat(chrfmt);
|
||||
cursor.endEditBlock();
|
||||
setTextCursor(cursor);
|
||||
setFocus();
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
void KRichTextEdit::enableRichTextMode()
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
d->activateRichText();
|
||||
}
|
||||
|
||||
KRichTextEdit::Mode KRichTextEdit::textMode() const
|
||||
{
|
||||
Q_D(const KRichTextEdit);
|
||||
|
||||
return d->mMode;
|
||||
}
|
||||
|
||||
QString KRichTextEdit::textOrHtml() const
|
||||
{
|
||||
if (textMode() == Rich) {
|
||||
return toCleanHtml();
|
||||
} else {
|
||||
return toPlainText();
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextEdit::setTextOrHtml(const QString &text)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
// might be rich text
|
||||
if (Qt::mightBeRichText(text)) {
|
||||
if (d->mMode == KRichTextEdit::Plain) {
|
||||
d->activateRichText();
|
||||
}
|
||||
setHtml(text);
|
||||
} else {
|
||||
setPlainText(text);
|
||||
}
|
||||
}
|
||||
|
||||
// KF6 TODO: remove constness
|
||||
QString KRichTextEdit::currentLinkText() const
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
selectLinkText(&cursor);
|
||||
return cursor.selectedText();
|
||||
}
|
||||
|
||||
// KF6 TODO: remove constness
|
||||
void KRichTextEdit::selectLinkText() const
|
||||
{
|
||||
Q_D(const KRichTextEdit);
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
selectLinkText(&cursor);
|
||||
// KF6 TODO: remove const_cast
|
||||
const_cast<KRichTextEditPrivate *>(d)->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void KRichTextEdit::selectLinkText(QTextCursor *cursor) const
|
||||
{
|
||||
// If the cursor is on a link, select the text of the link.
|
||||
if (cursor->charFormat().isAnchor()) {
|
||||
QString aHref = cursor->charFormat().anchorHref();
|
||||
|
||||
// Move cursor to start of link
|
||||
while (cursor->charFormat().anchorHref() == aHref) {
|
||||
if (cursor->atStart()) {
|
||||
break;
|
||||
}
|
||||
cursor->setPosition(cursor->position() - 1);
|
||||
}
|
||||
if (cursor->charFormat().anchorHref() != aHref) {
|
||||
cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
|
||||
}
|
||||
|
||||
// Move selection to the end of the link
|
||||
while (cursor->charFormat().anchorHref() == aHref) {
|
||||
if (cursor->atEnd()) {
|
||||
break;
|
||||
}
|
||||
cursor->setPosition(cursor->position() + 1, QTextCursor::KeepAnchor);
|
||||
}
|
||||
if (cursor->charFormat().anchorHref() != aHref) {
|
||||
cursor->setPosition(cursor->position() - 1, QTextCursor::KeepAnchor);
|
||||
}
|
||||
} else if (cursor->hasSelection()) {
|
||||
// Nothing to to. Using the currently selected text as the link text.
|
||||
} else {
|
||||
// Select current word
|
||||
cursor->movePosition(QTextCursor::StartOfWord);
|
||||
cursor->movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
||||
}
|
||||
}
|
||||
|
||||
QString KRichTextEdit::currentLinkUrl() const
|
||||
{
|
||||
return textCursor().charFormat().anchorHref();
|
||||
}
|
||||
|
||||
void KRichTextEdit::updateLink(const QString &linkUrl, const QString &linkText)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
selectLinkText();
|
||||
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.beginEditBlock();
|
||||
|
||||
if (!cursor.hasSelection()) {
|
||||
cursor.select(QTextCursor::WordUnderCursor);
|
||||
}
|
||||
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
// Save original format to create an extra space with the existing char
|
||||
// format for the block
|
||||
const QTextCharFormat originalFormat = format;
|
||||
if (!linkUrl.isEmpty()) {
|
||||
// Add link details
|
||||
format.setAnchor(true);
|
||||
format.setAnchorHref(linkUrl);
|
||||
// Workaround for QTBUG-1814:
|
||||
// Link formatting does not get applied immediately when setAnchor(true)
|
||||
// is called. So the formatting needs to be applied manually.
|
||||
format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
|
||||
format.setUnderlineColor(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
|
||||
format.setForeground(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
|
||||
d->activateRichText();
|
||||
} else {
|
||||
// Remove link details
|
||||
format.setAnchor(false);
|
||||
format.setAnchorHref(QString());
|
||||
// Workaround for QTBUG-1814:
|
||||
// Link formatting does not get removed immediately when setAnchor(false)
|
||||
// is called. So the formatting needs to be applied manually.
|
||||
QTextDocument defaultTextDocument;
|
||||
QTextCharFormat defaultCharFormat = defaultTextDocument.begin().charFormat();
|
||||
|
||||
format.setUnderlineStyle(defaultCharFormat.underlineStyle());
|
||||
format.setUnderlineColor(defaultCharFormat.underlineColor());
|
||||
format.setForeground(defaultCharFormat.foreground());
|
||||
}
|
||||
|
||||
// Insert link text specified in dialog, otherwise write out url.
|
||||
QString _linkText;
|
||||
if (!linkText.isEmpty()) {
|
||||
_linkText = linkText;
|
||||
} else {
|
||||
_linkText = linkUrl;
|
||||
}
|
||||
cursor.insertText(_linkText, format);
|
||||
|
||||
// Insert a space after the link if at the end of the block so that
|
||||
// typing some text after the link does not carry link formatting
|
||||
if (!linkUrl.isEmpty() && cursor.atBlockEnd()) {
|
||||
cursor.setPosition(cursor.selectionEnd());
|
||||
cursor.setCharFormat(originalFormat);
|
||||
cursor.insertText(QStringLiteral(" "));
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
|
||||
void KRichTextEdit::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
Q_D(KRichTextEdit);
|
||||
|
||||
bool handled = false;
|
||||
if (textCursor().currentList()) {
|
||||
handled = d->nestedListHelper->handleKeyPressEvent(event);
|
||||
}
|
||||
|
||||
// If a line was merged with previous (next) one, with different heading level,
|
||||
// the style should also be adjusted accordingly (i.e. merged)
|
||||
if ((event->key() == Qt::Key_Backspace && textCursor().atBlockStart()
|
||||
&& (textCursor().blockFormat().headingLevel() != textCursor().block().previous().blockFormat().headingLevel()))
|
||||
|| (event->key() == Qt::Key_Delete && textCursor().atBlockEnd()
|
||||
&& (textCursor().blockFormat().headingLevel() != textCursor().block().next().blockFormat().headingLevel()))) {
|
||||
QTextCursor cursor = textCursor();
|
||||
cursor.beginEditBlock();
|
||||
if (event->key() == Qt::Key_Delete) {
|
||||
cursor.deleteChar();
|
||||
} else {
|
||||
cursor.deletePreviousChar();
|
||||
}
|
||||
setHeadingLevel(cursor.blockFormat().headingLevel());
|
||||
cursor.endEditBlock();
|
||||
handled = true;
|
||||
}
|
||||
|
||||
const auto prevHeadingLevel = textCursor().blockFormat().headingLevel();
|
||||
if (!handled) {
|
||||
KTextEdit::keyPressEvent(event);
|
||||
}
|
||||
|
||||
// Match the behavior of office suites: newline after header switches to normal text
|
||||
if (event->key() == Qt::Key_Return //
|
||||
&& prevHeadingLevel > 0) {
|
||||
// it should be undoable together with actual "return" keypress
|
||||
textCursor().joinPreviousEditBlock();
|
||||
if (textCursor().atBlockEnd()) {
|
||||
setHeadingLevel(0);
|
||||
} else {
|
||||
setHeadingLevel(prevHeadingLevel);
|
||||
}
|
||||
textCursor().endEditBlock();
|
||||
}
|
||||
|
||||
Q_EMIT cursorPositionChanged();
|
||||
}
|
||||
|
||||
// void KRichTextEdit::dropEvent(QDropEvent *event)
|
||||
// {
|
||||
// int dropSize = event->mimeData()->text().size();
|
||||
//
|
||||
// dropEvent( event );
|
||||
// QTextCursor cursor = textCursor();
|
||||
// int cursorPosition = cursor.position();
|
||||
// cursor.setPosition( cursorPosition - dropSize );
|
||||
// cursor.setPosition( cursorPosition, QTextCursor::KeepAnchor );
|
||||
// setTextCursor( cursor );
|
||||
// d->nestedListHelper->handleAfterDropEvent( event );
|
||||
// }
|
||||
|
||||
bool KRichTextEdit::canIndentList() const
|
||||
{
|
||||
Q_D(const KRichTextEdit);
|
||||
|
||||
return d->nestedListHelper->canIndent();
|
||||
}
|
||||
|
||||
bool KRichTextEdit::canDedentList() const
|
||||
{
|
||||
Q_D(const KRichTextEdit);
|
||||
|
||||
return d->nestedListHelper->canDedent();
|
||||
}
|
||||
|
||||
QString KRichTextEdit::toCleanHtml() const
|
||||
{
|
||||
QString result = toHtml();
|
||||
|
||||
static const QString EMPTYLINEHTML = QLatin1String(
|
||||
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; "
|
||||
"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; \"> </p>");
|
||||
|
||||
// Qt inserts various style properties based on the current mode of the editor (underline,
|
||||
// bold, etc), but only empty paragraphs *also* have qt-paragraph-type set to 'empty'.
|
||||
static const QRegularExpression EMPTYLINEREGEX(QStringLiteral("<p style=\"-qt-paragraph-type:empty;(.*?)</p>"));
|
||||
|
||||
static const QString OLLISTPATTERNQT = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
|
||||
|
||||
static const QString ULLISTPATTERNQT = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px;");
|
||||
|
||||
static const QString ORDEREDLISTHTML = QStringLiteral("<ol style=\"margin-top: 0px; margin-bottom: 0px;");
|
||||
|
||||
static const QString UNORDEREDLISTHTML = QStringLiteral("<ul style=\"margin-top: 0px; margin-bottom: 0px;");
|
||||
|
||||
// fix 1 - empty lines should show as empty lines - MS Outlook treats margin-top:0px; as
|
||||
// a non-existing line.
|
||||
// Although we can simply remove the margin-top style property, we still get unwanted results
|
||||
// if you have three or more empty lines. It's best to replace empty <p> elements with <p> </p>.
|
||||
// replace all occurrences with the new line text
|
||||
result.replace(EMPTYLINEREGEX, EMPTYLINEHTML);
|
||||
|
||||
// fix 2a - ordered lists - MS Outlook treats margin-left:0px; as
|
||||
// a non-existing number; e.g: "1. First item" turns into "First Item"
|
||||
result.replace(OLLISTPATTERNQT, ORDEREDLISTHTML);
|
||||
|
||||
// fix 2b - unordered lists - MS Outlook treats margin-left:0px; as
|
||||
// a non-existing bullet; e.g: "* First bullet" turns into "First Bullet"
|
||||
result.replace(ULLISTPATTERNQT, UNORDEREDLISTHTML);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#include "moc_krichtextedit.cpp"
|
||||
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
krichtextedit.h
|
||||
SPDX-FileCopyrightText: 2007 Laurent Montel <montel@kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef KRICHTEXTEDIT_H
|
||||
#define KRICHTEXTEDIT_H
|
||||
|
||||
#include <ktextedit.h>
|
||||
|
||||
class QKeyEvent;
|
||||
|
||||
class KRichTextEditPrivate;
|
||||
|
||||
#include <ktextwidgets_export.h>
|
||||
|
||||
/**
|
||||
* @class KRichTextEdit krichtextedit.h <KRichTextEdit>
|
||||
*
|
||||
* The KRichTextEdit class provides a widget to edit and display rich text.
|
||||
*
|
||||
* It offers several additional rich text editing functions to KTextEdit and makes
|
||||
* them easier to access including:
|
||||
*
|
||||
* @li Changing fonts, sizes.
|
||||
* @li Font formatting, such as bold, underline, italic, foreground and
|
||||
* background color.
|
||||
* @li Paragraph alignment
|
||||
* @li Ability to edit and remove hyperlinks
|
||||
* @li Nested list handling
|
||||
* @li Simple actions to insert tables. TODO
|
||||
*
|
||||
* The KRichTextEdit can be in two modes: Rich text mode and plain text mode.
|
||||
* Calling functions which modify the format/style of the text will automatically
|
||||
* enable the rich text mode. Rich text mode is sometimes also referred to as
|
||||
* HTML mode.
|
||||
*
|
||||
* Do not call setAcceptRichText() or acceptRichText() yourself. Instead simply
|
||||
* connect to the slots which insert the rich text, use switchToPlainText() or
|
||||
* enableRichTextMode().
|
||||
*
|
||||
* \image html krichtextedit.png "KRichTextEdit Widget"
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KRichTextEdit : public KTextEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* The mode the edit widget is in.
|
||||
*/
|
||||
enum Mode {
|
||||
Plain, ///< Plain text mode
|
||||
Rich, ///< Rich text mode
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a KRichTextEdit object
|
||||
*
|
||||
* @param text The initial text of the text edit, which is interpreted as
|
||||
* HTML.
|
||||
* @param parent The parent widget
|
||||
*/
|
||||
explicit KRichTextEdit(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Constructs a KRichTextEdit object.
|
||||
*
|
||||
* @param parent The parent widget
|
||||
*/
|
||||
explicit KRichTextEdit(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KRichTextEdit() override;
|
||||
|
||||
/**
|
||||
* This enables rich text mode. Nothing is done except changing the internal
|
||||
* mode and allowing rich text pastes.
|
||||
*/
|
||||
void enableRichTextMode();
|
||||
|
||||
/**
|
||||
* @return The current text mode
|
||||
*/
|
||||
Mode textMode() const;
|
||||
|
||||
/**
|
||||
* @return The plain text string if in plain text mode or the HTML code
|
||||
* if in rich text mode. The text is not word-wrapped.
|
||||
*/
|
||||
QString textOrHtml() const;
|
||||
|
||||
/**
|
||||
* Replaces all the content of the text edit with the given string.
|
||||
* If the string is in rich text format, the text is inserted as rich text,
|
||||
* otherwise it is inserted as plain text.
|
||||
*
|
||||
* @param text The text to insert
|
||||
*/
|
||||
void setTextOrHtml(const QString &text);
|
||||
|
||||
/**
|
||||
* Returns the text of the link at the current position or an empty string
|
||||
* if the cursor is not on a link.
|
||||
*
|
||||
* @sa currentLinkUrl
|
||||
* @return The link text
|
||||
*/
|
||||
QString currentLinkText() const;
|
||||
|
||||
/**
|
||||
* Returns the URL target (href) of the link at the current position or an
|
||||
* empty string if the cursor is not on a link.
|
||||
*
|
||||
* @sa currentLinkText
|
||||
* @return The link target URL
|
||||
*/
|
||||
QString currentLinkUrl() const;
|
||||
|
||||
/**
|
||||
* If the cursor is on a link, sets the @a cursor to a selection of the
|
||||
* text of the link. If the @a cursor is not on a link, selects the current word
|
||||
* or existing selection.
|
||||
*
|
||||
* @param cursor The cursor to use to select the text.
|
||||
* @sa updateLink
|
||||
*/
|
||||
void selectLinkText(QTextCursor *cursor) const;
|
||||
|
||||
/**
|
||||
* Convenience function to select the link text using the active cursor.
|
||||
*
|
||||
* @sa selectLinkText
|
||||
*/
|
||||
void selectLinkText() const;
|
||||
|
||||
/**
|
||||
* Replaces the current selection with a hyperlink with the link URL @a linkUrl
|
||||
* and the link text @a linkText.
|
||||
*
|
||||
* @sa selectLinkText
|
||||
* @sa currentLinkUrl
|
||||
* @sa currentLinkText
|
||||
* @param linkUrl The link will get this URL as href (target)
|
||||
* @param linkText The link will get this alternative text, which is the
|
||||
* text displayed in the text edit.
|
||||
*/
|
||||
void updateLink(const QString &linkUrl, const QString &linkText);
|
||||
|
||||
/**
|
||||
* Returns true if the list item at the current position can be indented.
|
||||
*
|
||||
* @sa canDedentList
|
||||
*/
|
||||
bool canIndentList() const;
|
||||
|
||||
/**
|
||||
* Returns true if the list item at the current position can be dedented.
|
||||
*
|
||||
* @sa canIndentList
|
||||
*/
|
||||
bool canDedentList() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
/**
|
||||
* Sets the alignment of the current block to Left Aligned
|
||||
*/
|
||||
void alignLeft();
|
||||
|
||||
/**
|
||||
* Sets the alignment of the current block to Centered
|
||||
*/
|
||||
void alignCenter();
|
||||
|
||||
/**
|
||||
* Sets the alignment of the current block to Right Aligned
|
||||
*/
|
||||
void alignRight();
|
||||
|
||||
/**
|
||||
* Sets the alignment of the current block to Justified
|
||||
*/
|
||||
void alignJustify();
|
||||
|
||||
/**
|
||||
* Sets the direction of the current block to Right-To-Left
|
||||
*
|
||||
* @since 4.6
|
||||
*/
|
||||
void makeRightToLeft();
|
||||
|
||||
/**
|
||||
* Sets the direction of the current block to Left-To-Right
|
||||
*
|
||||
* @since 4.6
|
||||
*/
|
||||
void makeLeftToRight();
|
||||
|
||||
/**
|
||||
* Sets the list style of the current list, or creates a new list using the
|
||||
* current block. The @a _styleindex corresponds to the QTextListFormat::Style
|
||||
*
|
||||
* @param _styleIndex The list will get this style
|
||||
*/
|
||||
void setListStyle(int _styleIndex);
|
||||
|
||||
/**
|
||||
* Increases the nesting level of the current block or selected blocks.
|
||||
*
|
||||
* @sa canIndentList
|
||||
*/
|
||||
void indentListMore();
|
||||
|
||||
/**
|
||||
* Decreases the nesting level of the current block or selected blocks.
|
||||
*
|
||||
* @sa canDedentList
|
||||
*/
|
||||
void indentListLess();
|
||||
|
||||
/**
|
||||
* Sets the current word or selection to the font family @a fontFamily
|
||||
*
|
||||
* @param fontFamily The text's font family will be changed to this one
|
||||
*/
|
||||
void setFontFamily(const QString &fontFamily);
|
||||
|
||||
/**
|
||||
* Sets the current word or selection to the font size @a size
|
||||
*
|
||||
* @param size The text's font will get this size
|
||||
*/
|
||||
void setFontSize(int size);
|
||||
|
||||
/**
|
||||
* Sets the current word or selection to the font @a font
|
||||
*
|
||||
* @param font the font of the text will be set to this font
|
||||
*/
|
||||
void setFont(const QFont &font);
|
||||
|
||||
/**
|
||||
* Toggles the bold formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param bold If true, the text will be set to bold
|
||||
*/
|
||||
void setTextBold(bool bold);
|
||||
|
||||
/**
|
||||
* Toggles the italic formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param italic If true, the text will be set to italic
|
||||
*/
|
||||
void setTextItalic(bool italic);
|
||||
|
||||
/**
|
||||
* Toggles the underline formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param underline If true, the text will be underlined
|
||||
*/
|
||||
void setTextUnderline(bool underline);
|
||||
|
||||
/**
|
||||
* Toggles the strikeout formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param strikeOut If true, the text will be struck out
|
||||
*/
|
||||
void setTextStrikeOut(bool strikeOut);
|
||||
|
||||
/**
|
||||
* Sets the foreground color of the current word or selection to @a color.
|
||||
*
|
||||
* @param color The text will get this background color
|
||||
*/
|
||||
void setTextForegroundColor(const QColor &color);
|
||||
|
||||
/**
|
||||
* Sets the background color of the current word or selection to @a color.
|
||||
*
|
||||
* @param color The text will get this foreground color
|
||||
*/
|
||||
void setTextBackgroundColor(const QColor &color);
|
||||
|
||||
/**
|
||||
* Inserts a horizontal rule below the current block.
|
||||
*/
|
||||
void insertHorizontalRule();
|
||||
|
||||
/**
|
||||
* This will switch the editor to plain text mode.
|
||||
* All rich text formatting will be destroyed.
|
||||
*/
|
||||
void switchToPlainText();
|
||||
|
||||
/**
|
||||
* This will clean some of the bad html produced by the underlying QTextEdit
|
||||
* It walks over all lines and cleans up a bit. Should be improved to produce
|
||||
* our own Html.
|
||||
*/
|
||||
QString toCleanHtml() const;
|
||||
|
||||
/**
|
||||
* Toggles the superscript formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param superscript If true, the text will be set to superscript
|
||||
*/
|
||||
void setTextSuperScript(bool superscript);
|
||||
|
||||
/**
|
||||
* Toggles the subscript formatting of the current word or selection at the current
|
||||
* cursor position.
|
||||
*
|
||||
* @param subscript If true, the text will be set to subscript
|
||||
*/
|
||||
void setTextSubScript(bool subscript);
|
||||
|
||||
/**
|
||||
* Sets the heading level of a current block or selection
|
||||
*
|
||||
* @param level Heading level (value should be between 0 and 6)
|
||||
* (0 is "normal text", 1 is the largest heading, 6 is the smallest one)
|
||||
*
|
||||
* @since 5.70
|
||||
*/
|
||||
void setHeadingLevel(int level);
|
||||
|
||||
/**
|
||||
* @since 4.10
|
||||
* Because of binary compatibility constraints, insertPlainText
|
||||
* is not virtual. Therefore it must dynamically detect and call this slot.
|
||||
*/
|
||||
void insertPlainTextImplementation();
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
/**
|
||||
* Emitted whenever the text mode is changed.
|
||||
*
|
||||
* @param mode The new text mode
|
||||
*/
|
||||
void textModeChanged(KRichTextEdit::Mode mode);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Reimplemented.
|
||||
* Catches key press events. Used to handle some key presses on lists.
|
||||
*/
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
protected:
|
||||
KTEXTWIDGETS_NO_EXPORT KRichTextEdit(KRichTextEditPrivate &dd, const QString &text, QWidget *parent);
|
||||
KTEXTWIDGETS_NO_EXPORT KRichTextEdit(KRichTextEditPrivate &dd, QWidget *parent);
|
||||
|
||||
private:
|
||||
//@cond PRIVATE
|
||||
Q_DECLARE_PRIVATE(KRichTextEdit)
|
||||
//@endcond
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
krichtextedit
|
||||
SPDX-FileCopyrightText: 2007 Laurent Montel <montel@kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef KRICHTEXTEDIT_P_H
|
||||
#define KRICHTEXTEDIT_P_H
|
||||
|
||||
#include "ktextedit_p.h"
|
||||
#include "nestedlisthelper_p.h"
|
||||
|
||||
class KRichTextEditPrivate : public KTextEditPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KRichTextEdit)
|
||||
|
||||
public:
|
||||
explicit KRichTextEditPrivate(KRichTextEdit *qq)
|
||||
: KTextEditPrivate(qq)
|
||||
, nestedListHelper(new NestedListHelper(qq))
|
||||
{
|
||||
}
|
||||
|
||||
~KRichTextEditPrivate() override
|
||||
{
|
||||
delete nestedListHelper;
|
||||
}
|
||||
|
||||
//
|
||||
// Normal functions
|
||||
//
|
||||
|
||||
// If the text under the cursor is a link, the cursor's selection is set to
|
||||
// the complete link text. Otherwise selects the current word if there is no
|
||||
// selection.
|
||||
void selectLinkText() const;
|
||||
|
||||
void init();
|
||||
|
||||
// Switches to rich text mode and emits the mode changed signal if the
|
||||
// mode really changed.
|
||||
void activateRichText();
|
||||
|
||||
// Applies formatting to the current word if there is no selection.
|
||||
void mergeFormatOnWordOrSelection(const QTextCharFormat &format);
|
||||
|
||||
void setTextCursor(QTextCursor &cursor);
|
||||
|
||||
// Data members
|
||||
KRichTextEdit::Mode mMode = KRichTextEdit::Plain;
|
||||
|
||||
NestedListHelper *nestedListHelper;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "krichtextwidget.h"
|
||||
|
||||
#include "krichtextedit_p.h"
|
||||
|
||||
// KDE includes
|
||||
#include <KColorScheme>
|
||||
#include <KFontAction>
|
||||
#include <KFontSizeAction>
|
||||
#include <KLocalizedString>
|
||||
#include <ktoggleaction.h>
|
||||
|
||||
// Qt includes
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QColorDialog>
|
||||
#include <QTextList>
|
||||
|
||||
#include "klinkdialog_p.h"
|
||||
|
||||
// TODO: Add i18n context
|
||||
|
||||
/**
|
||||
Private class that helps to provide binary compatibility between releases.
|
||||
@internal
|
||||
*/
|
||||
//@cond PRIVATE
|
||||
class KRichTextWidgetPrivate : public KRichTextEditPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KRichTextWidget)
|
||||
|
||||
public:
|
||||
KRichTextWidgetPrivate(KRichTextWidget *qq)
|
||||
: KRichTextEditPrivate(qq)
|
||||
{
|
||||
}
|
||||
|
||||
QList<QAction *> richTextActionList;
|
||||
QTextCharFormat painterFormat;
|
||||
|
||||
KRichTextWidget::RichTextSupport richTextSupport;
|
||||
|
||||
bool painterActive = false;
|
||||
|
||||
bool richTextEnabled = false;
|
||||
KToggleAction *enableRichText = nullptr;
|
||||
|
||||
QAction *action_text_foreground_color = nullptr;
|
||||
QAction *action_text_background_color = nullptr;
|
||||
|
||||
KToggleAction *action_text_bold = nullptr;
|
||||
KToggleAction *action_text_italic = nullptr;
|
||||
KToggleAction *action_text_underline = nullptr;
|
||||
KToggleAction *action_text_strikeout = nullptr;
|
||||
|
||||
KFontAction *action_font_family = nullptr;
|
||||
KFontSizeAction *action_font_size = nullptr;
|
||||
|
||||
KSelectAction *action_list_style = nullptr;
|
||||
QAction *action_list_indent = nullptr;
|
||||
QAction *action_list_dedent = nullptr;
|
||||
|
||||
QAction *action_manage_link = nullptr;
|
||||
QAction *action_insert_horizontal_rule = nullptr;
|
||||
QAction *action_format_painter = nullptr;
|
||||
QAction *action_to_plain_text = nullptr;
|
||||
|
||||
KToggleAction *action_align_left = nullptr;
|
||||
KToggleAction *action_align_right = nullptr;
|
||||
KToggleAction *action_align_center = nullptr;
|
||||
KToggleAction *action_align_justify = nullptr;
|
||||
|
||||
KToggleAction *action_direction_ltr = nullptr;
|
||||
KToggleAction *action_direction_rtl = nullptr;
|
||||
|
||||
KToggleAction *action_text_superscript = nullptr;
|
||||
KToggleAction *action_text_subscript = nullptr;
|
||||
|
||||
KSelectAction *action_heading_level = nullptr;
|
||||
|
||||
//
|
||||
// Normal functions
|
||||
//
|
||||
void init();
|
||||
|
||||
//
|
||||
// Slots
|
||||
//
|
||||
|
||||
/**
|
||||
* @brief Opens a dialog to allow the user to select a foreground color.
|
||||
*/
|
||||
void _k_setTextForegroundColor();
|
||||
|
||||
/**
|
||||
* @brief Opens a dialog to allow the user to select a background color.
|
||||
*/
|
||||
void _k_setTextBackgroundColor();
|
||||
|
||||
/**
|
||||
* Opens a dialog which lets the user turn the currently selected text into
|
||||
* a link.
|
||||
* If no text is selected, the word under the cursor will be taken.
|
||||
* If the cursor is already over a link, the user can edit that link.
|
||||
*
|
||||
*/
|
||||
void _k_manageLink();
|
||||
|
||||
/**
|
||||
* Activates a format painter to allow the user to copy font/text formatting
|
||||
* to different parts of the document.
|
||||
*
|
||||
*/
|
||||
void _k_formatPainter(bool active);
|
||||
|
||||
/**
|
||||
* @brief Update actions relating to text format (bold, size etc.).
|
||||
*/
|
||||
void updateCharFormatActions(const QTextCharFormat &format);
|
||||
|
||||
/**
|
||||
* Update actions not covered by text formatting, such as alignment,
|
||||
* list style and level.
|
||||
*/
|
||||
void updateMiscActions();
|
||||
|
||||
/**
|
||||
* Change the style of the current list or create a new list with the style given by @a index.
|
||||
*/
|
||||
void _k_setListStyle(int index);
|
||||
|
||||
/**
|
||||
* Change the heading level of a current line to a level given by @a level
|
||||
*/
|
||||
void _k_setHeadingLevel(int level);
|
||||
};
|
||||
//@endcond
|
||||
|
||||
void KRichTextWidgetPrivate::init()
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
q->setRichTextSupport(KRichTextWidget::FullSupport);
|
||||
}
|
||||
|
||||
KRichTextWidget::KRichTextWidget(QWidget *parent)
|
||||
: KRichTextEdit(*new KRichTextWidgetPrivate(this), parent)
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
d->init();
|
||||
}
|
||||
|
||||
KRichTextWidget::KRichTextWidget(const QString &text, QWidget *parent)
|
||||
: KRichTextEdit(*new KRichTextWidgetPrivate(this), text, parent)
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
d->init();
|
||||
}
|
||||
|
||||
KRichTextWidget::~KRichTextWidget() = default;
|
||||
|
||||
KRichTextWidget::RichTextSupport KRichTextWidget::richTextSupport() const
|
||||
{
|
||||
Q_D(const KRichTextWidget);
|
||||
|
||||
return d->richTextSupport;
|
||||
}
|
||||
|
||||
void KRichTextWidget::setRichTextSupport(const KRichTextWidget::RichTextSupport &support)
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
d->richTextSupport = support;
|
||||
}
|
||||
|
||||
QList<QAction *> KRichTextWidget::createActions()
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
// Note to maintainers: If adding new functionality here, make sure to disconnect
|
||||
// and delete actions which should not be supported.
|
||||
//
|
||||
// New Actions need to be added to the following places:
|
||||
// - possibly the RichTextSupportValues enum
|
||||
// - the API documentation for createActions()
|
||||
// - this function
|
||||
// - the action needs to be added to the private class as a member
|
||||
// - the constructor of the private class
|
||||
// - depending on the action, some slot that changes the toggle state when
|
||||
// appropriate, such as updateCharFormatActions or updateMiscActions.
|
||||
|
||||
// The list of actions currently supported is also stored internally.
|
||||
// This is used to disable all actions at once in setActionsEnabled.
|
||||
d->richTextActionList.clear();
|
||||
|
||||
if (d->richTextSupport & SupportTextForegroundColor) {
|
||||
// Foreground Color
|
||||
d->action_text_foreground_color = new QAction(QIcon::fromTheme(QStringLiteral("format-stroke-color")), i18nc("@action", "Text &Color…"), this);
|
||||
d->action_text_foreground_color->setIconText(i18nc("@label stroke color", "Color"));
|
||||
d->richTextActionList.append((d->action_text_foreground_color));
|
||||
d->action_text_foreground_color->setObjectName(QStringLiteral("format_text_foreground_color"));
|
||||
connect(d->action_text_foreground_color, &QAction::triggered, this, [this]() {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_setTextForegroundColor();
|
||||
});
|
||||
} else {
|
||||
d->action_text_foreground_color = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportTextBackgroundColor) {
|
||||
// Background Color
|
||||
d->action_text_background_color = new QAction(QIcon::fromTheme(QStringLiteral("format-fill-color")), i18nc("@action", "Text &Highlight…"), this);
|
||||
d->richTextActionList.append((d->action_text_background_color));
|
||||
d->action_text_background_color->setObjectName(QStringLiteral("format_text_background_color"));
|
||||
connect(d->action_text_background_color, &QAction::triggered, this, [this]() {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_setTextBackgroundColor();
|
||||
});
|
||||
} else {
|
||||
d->action_text_background_color = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportFontFamily) {
|
||||
// Font Family
|
||||
d->action_font_family = new KFontAction(i18nc("@action", "&Font"), this);
|
||||
d->richTextActionList.append((d->action_font_family));
|
||||
d->action_font_family->setObjectName(QStringLiteral("format_font_family"));
|
||||
connect(d->action_font_family, &KSelectAction::textTriggered, this, &KRichTextWidget::setFontFamily);
|
||||
} else {
|
||||
d->action_font_family = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportFontSize) {
|
||||
// Font Size
|
||||
d->action_font_size = new KFontSizeAction(i18nc("@action", "Font &Size"), this);
|
||||
d->richTextActionList.append((d->action_font_size));
|
||||
d->action_font_size->setObjectName(QStringLiteral("format_font_size"));
|
||||
connect(d->action_font_size, &KFontSizeAction::fontSizeChanged, this, &KRichTextEdit::setFontSize);
|
||||
} else {
|
||||
d->action_font_size = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportBold) {
|
||||
d->action_text_bold = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-bold")), i18nc("@action boldify selected text", "&Bold"), this);
|
||||
QFont bold;
|
||||
bold.setBold(true);
|
||||
d->action_text_bold->setFont(bold);
|
||||
d->richTextActionList.append((d->action_text_bold));
|
||||
d->action_text_bold->setObjectName(QStringLiteral("format_text_bold"));
|
||||
d->action_text_bold->setShortcut(Qt::CTRL | Qt::Key_B);
|
||||
connect(d->action_text_bold, &QAction::triggered, this, &KRichTextEdit::setTextBold);
|
||||
} else {
|
||||
d->action_text_bold = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportItalic) {
|
||||
d->action_text_italic =
|
||||
new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-italic")), i18nc("@action italicize selected text", "&Italic"), this);
|
||||
QFont italic;
|
||||
italic.setItalic(true);
|
||||
d->action_text_italic->setFont(italic);
|
||||
d->richTextActionList.append((d->action_text_italic));
|
||||
d->action_text_italic->setObjectName(QStringLiteral("format_text_italic"));
|
||||
d->action_text_italic->setShortcut(Qt::CTRL | Qt::Key_I);
|
||||
connect(d->action_text_italic, &QAction::triggered, this, &KRichTextEdit::setTextItalic);
|
||||
} else {
|
||||
d->action_text_italic = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportUnderline) {
|
||||
d->action_text_underline =
|
||||
new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-underline")), i18nc("@action underline selected text", "&Underline"), this);
|
||||
QFont underline;
|
||||
underline.setUnderline(true);
|
||||
d->action_text_underline->setFont(underline);
|
||||
d->richTextActionList.append((d->action_text_underline));
|
||||
d->action_text_underline->setObjectName(QStringLiteral("format_text_underline"));
|
||||
d->action_text_underline->setShortcut(Qt::CTRL | Qt::Key_U);
|
||||
connect(d->action_text_underline, &QAction::triggered, this, &KRichTextEdit::setTextUnderline);
|
||||
} else {
|
||||
d->action_text_underline = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportStrikeOut) {
|
||||
d->action_text_strikeout = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-strikethrough")), i18nc("@action", "&Strike Out"), this);
|
||||
QFont strikeout;
|
||||
strikeout.setStrikeOut(true);
|
||||
d->action_text_strikeout->setFont(strikeout);
|
||||
d->richTextActionList.append((d->action_text_strikeout));
|
||||
d->action_text_strikeout->setObjectName(QStringLiteral("format_text_strikeout"));
|
||||
d->action_text_strikeout->setShortcut(Qt::CTRL | Qt::Key_L);
|
||||
connect(d->action_text_strikeout, &QAction::triggered, this, &KRichTextEdit::setTextStrikeOut);
|
||||
} else {
|
||||
d->action_text_strikeout = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportAlignment) {
|
||||
// Alignment
|
||||
d->action_align_left = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-left")), i18nc("@action", "Align &Left"), this);
|
||||
d->action_align_left->setIconText(i18nc("@label left justify", "Left"));
|
||||
d->richTextActionList.append((d->action_align_left));
|
||||
d->action_align_left->setObjectName(QStringLiteral("format_align_left"));
|
||||
connect(d->action_align_left, &QAction::triggered, this, &KRichTextEdit::alignLeft);
|
||||
|
||||
d->action_align_center = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-center")), i18nc("@action", "Align &Center"), this);
|
||||
d->action_align_center->setIconText(i18nc("@label center justify", "Center"));
|
||||
d->richTextActionList.append((d->action_align_center));
|
||||
d->action_align_center->setObjectName(QStringLiteral("format_align_center"));
|
||||
connect(d->action_align_center, &QAction::triggered, this, &KRichTextEdit::alignCenter);
|
||||
|
||||
d->action_align_right = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-right")), i18nc("@action", "Align &Right"), this);
|
||||
d->action_align_right->setIconText(i18nc("@label right justify", "Right"));
|
||||
d->richTextActionList.append((d->action_align_right));
|
||||
d->action_align_right->setObjectName(QStringLiteral("format_align_right"));
|
||||
connect(d->action_align_right, &QAction::triggered, this, &KRichTextEdit::alignRight);
|
||||
|
||||
d->action_align_justify = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-fill")), i18nc("@action", "&Justify"), this);
|
||||
d->action_align_justify->setIconText(i18nc("@label justify fill", "Justify"));
|
||||
d->richTextActionList.append((d->action_align_justify));
|
||||
d->action_align_justify->setObjectName(QStringLiteral("format_align_justify"));
|
||||
connect(d->action_align_justify, &QAction::triggered, this, &KRichTextEdit::alignJustify);
|
||||
|
||||
QActionGroup *alignmentGroup = new QActionGroup(this);
|
||||
alignmentGroup->addAction(d->action_align_left);
|
||||
alignmentGroup->addAction(d->action_align_center);
|
||||
alignmentGroup->addAction(d->action_align_right);
|
||||
alignmentGroup->addAction(d->action_align_justify);
|
||||
} else {
|
||||
d->action_align_left = nullptr;
|
||||
d->action_align_center = nullptr;
|
||||
d->action_align_right = nullptr;
|
||||
d->action_align_justify = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportDirection) {
|
||||
d->action_direction_ltr = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-direction-ltr")), i18nc("@action", "Left-to-Right"), this);
|
||||
d->action_direction_ltr->setIconText(i18nc("@label left-to-right", "Left-to-Right"));
|
||||
d->richTextActionList.append(d->action_direction_ltr);
|
||||
d->action_direction_ltr->setObjectName(QStringLiteral("direction_ltr"));
|
||||
connect(d->action_direction_ltr, &QAction::triggered, this, &KRichTextEdit::makeLeftToRight);
|
||||
|
||||
d->action_direction_rtl = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-direction-rtl")), i18nc("@action", "Right-to-Left"), this);
|
||||
d->action_direction_rtl->setIconText(i18nc("@label right-to-left", "Right-to-Left"));
|
||||
d->richTextActionList.append(d->action_direction_rtl);
|
||||
d->action_direction_rtl->setObjectName(QStringLiteral("direction_rtl"));
|
||||
connect(d->action_direction_rtl, &QAction::triggered, this, &KRichTextEdit::makeRightToLeft);
|
||||
|
||||
QActionGroup *directionGroup = new QActionGroup(this);
|
||||
directionGroup->addAction(d->action_direction_ltr);
|
||||
directionGroup->addAction(d->action_direction_rtl);
|
||||
} else {
|
||||
d->action_direction_ltr = nullptr;
|
||||
d->action_direction_rtl = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportChangeListStyle) {
|
||||
d->action_list_style = new KSelectAction(QIcon::fromTheme(QStringLiteral("format-list-unordered")), i18nc("@title:menu", "List Style"), this);
|
||||
QStringList listStyles;
|
||||
/* clang-format off */
|
||||
listStyles << i18nc("@item:inmenu no list style", "None")
|
||||
<< i18nc("@item:inmenu disc list style", "Disc")
|
||||
<< i18nc("@item:inmenu circle list style", "Circle")
|
||||
<< i18nc("@item:inmenu square list style", "Square")
|
||||
<< i18nc("@item:inmenu numbered lists", "123")
|
||||
<< i18nc("@item:inmenu lowercase abc lists", "abc")
|
||||
<< i18nc("@item:inmenu uppercase abc lists", "ABC")
|
||||
<< i18nc("@item:inmenu lower case roman numerals", "i ii iii")
|
||||
<< i18nc("@item:inmenu upper case roman numerals", "I II III");
|
||||
/* clang-format on */
|
||||
|
||||
d->action_list_style->setItems(listStyles);
|
||||
d->action_list_style->setCurrentItem(0);
|
||||
d->richTextActionList.append((d->action_list_style));
|
||||
d->action_list_style->setObjectName(QStringLiteral("format_list_style"));
|
||||
|
||||
connect(d->action_list_style, &KSelectAction::indexTriggered, this, [this](int style) {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_setListStyle(style);
|
||||
});
|
||||
connect(d->action_list_style, &QAction::triggered, this, [d]() {
|
||||
d->updateMiscActions();
|
||||
});
|
||||
} else {
|
||||
d->action_list_style = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportIndentLists) {
|
||||
d->action_list_indent = new QAction(QIcon::fromTheme(QStringLiteral("format-indent-more")), i18nc("@action", "Increase Indent"), this);
|
||||
d->richTextActionList.append((d->action_list_indent));
|
||||
d->action_list_indent->setObjectName(QStringLiteral("format_list_indent_more"));
|
||||
connect(d->action_list_indent, &QAction::triggered, this, &KRichTextEdit::indentListMore);
|
||||
connect(d->action_list_indent, &QAction::triggered, this, [d]() {
|
||||
d->updateMiscActions();
|
||||
});
|
||||
} else {
|
||||
d->action_list_indent = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportDedentLists) {
|
||||
d->action_list_dedent = new QAction(QIcon::fromTheme(QStringLiteral("format-indent-less")), i18nc("@action", "Decrease Indent"), this);
|
||||
d->richTextActionList.append((d->action_list_dedent));
|
||||
d->action_list_dedent->setObjectName(QStringLiteral("format_list_indent_less"));
|
||||
connect(d->action_list_dedent, &QAction::triggered, this, &KRichTextEdit::indentListLess);
|
||||
connect(d->action_list_dedent, &QAction::triggered, this, [d]() {
|
||||
d->updateMiscActions();
|
||||
});
|
||||
} else {
|
||||
d->action_list_dedent = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportRuleLine) {
|
||||
d->action_insert_horizontal_rule = new QAction(QIcon::fromTheme(QStringLiteral("insert-horizontal-rule")), i18nc("@action", "Insert Rule Line"), this);
|
||||
d->richTextActionList.append((d->action_insert_horizontal_rule));
|
||||
d->action_insert_horizontal_rule->setObjectName(QStringLiteral("insert_horizontal_rule"));
|
||||
connect(d->action_insert_horizontal_rule, &QAction::triggered, this, &KRichTextEdit::insertHorizontalRule);
|
||||
} else {
|
||||
d->action_insert_horizontal_rule = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportHyperlinks) {
|
||||
d->action_manage_link = new QAction(QIcon::fromTheme(QStringLiteral("insert-link")), i18nc("@action", "Link"), this);
|
||||
d->richTextActionList.append((d->action_manage_link));
|
||||
d->action_manage_link->setObjectName(QStringLiteral("manage_link"));
|
||||
connect(d->action_manage_link, &QAction::triggered, this, [this]() {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_manageLink();
|
||||
});
|
||||
} else {
|
||||
d->action_manage_link = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportFormatPainting) {
|
||||
d->action_format_painter = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-brush")), i18nc("@action", "Format Painter"), this);
|
||||
d->richTextActionList.append((d->action_format_painter));
|
||||
d->action_format_painter->setObjectName(QStringLiteral("format_painter"));
|
||||
connect(d->action_format_painter, &QAction::toggled, this, [this](bool state) {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_formatPainter(state);
|
||||
});
|
||||
} else {
|
||||
d->action_format_painter = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportToPlainText) {
|
||||
d->action_to_plain_text = new KToggleAction(i18nc("@action", "To Plain Text"), this);
|
||||
d->richTextActionList.append((d->action_to_plain_text));
|
||||
d->action_to_plain_text->setObjectName(QStringLiteral("action_to_plain_text"));
|
||||
connect(d->action_to_plain_text, &QAction::triggered, this, &KRichTextEdit::switchToPlainText);
|
||||
} else {
|
||||
d->action_to_plain_text = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportSuperScriptAndSubScript) {
|
||||
d->action_text_subscript = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-subscript")), i18nc("@action", "Subscript"), this);
|
||||
d->richTextActionList.append((d->action_text_subscript));
|
||||
d->action_text_subscript->setObjectName(QStringLiteral("format_text_subscript"));
|
||||
connect(d->action_text_subscript, &QAction::triggered, this, &KRichTextEdit::setTextSubScript);
|
||||
|
||||
d->action_text_superscript = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-superscript")), i18nc("@action", "Superscript"), this);
|
||||
d->richTextActionList.append((d->action_text_superscript));
|
||||
d->action_text_superscript->setObjectName(QStringLiteral("format_text_superscript"));
|
||||
connect(d->action_text_superscript, &QAction::triggered, this, &KRichTextEdit::setTextSuperScript);
|
||||
} else {
|
||||
d->action_text_subscript = nullptr;
|
||||
|
||||
d->action_text_superscript = nullptr;
|
||||
}
|
||||
|
||||
if (d->richTextSupport & SupportHeading) {
|
||||
// TODO: an icon maybe?
|
||||
d->action_heading_level = new KSelectAction(i18nc("@title:menu", "Heading Level"), this);
|
||||
const QStringList headingLevels = {i18nc("@item:inmenu no heading", "Basic text"),
|
||||
i18nc("@item:inmenu heading level 1 (largest)", "Title"),
|
||||
i18nc("@item:inmenu heading level 2", "Subtitle"),
|
||||
i18nc("@item:inmenu heading level 3", "Section"),
|
||||
i18nc("@item:inmenu heading level 4", "Subsection"),
|
||||
i18nc("@item:inmenu heading level 5", "Paragraph"),
|
||||
i18nc("@item:inmenu heading level 6 (smallest)", "Subparagraph")};
|
||||
|
||||
d->action_heading_level->setItems(headingLevels);
|
||||
d->action_heading_level->setCurrentItem(0);
|
||||
d->richTextActionList.append(d->action_heading_level);
|
||||
d->action_heading_level->setObjectName(QStringLiteral("format_heading_level"));
|
||||
connect(d->action_heading_level, &KSelectAction::indexTriggered, this, [this](int level) {
|
||||
Q_D(KRichTextWidget);
|
||||
d->_k_setHeadingLevel(level);
|
||||
});
|
||||
} else {
|
||||
d->action_heading_level = nullptr;
|
||||
}
|
||||
|
||||
disconnect(this, &QTextEdit::currentCharFormatChanged, this, nullptr);
|
||||
disconnect(this, &QTextEdit::cursorPositionChanged, this, nullptr);
|
||||
connect(this, &QTextEdit::currentCharFormatChanged, this, [d](const QTextCharFormat &format) {
|
||||
d->updateCharFormatActions(format);
|
||||
});
|
||||
connect(this, &QTextEdit::cursorPositionChanged, this, [d]() {
|
||||
d->updateMiscActions();
|
||||
});
|
||||
|
||||
d->updateMiscActions();
|
||||
d->updateCharFormatActions(currentCharFormat());
|
||||
|
||||
return d->richTextActionList;
|
||||
}
|
||||
|
||||
void KRichTextWidget::setActionsEnabled(bool enabled)
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
for (QAction *action : std::as_const(d->richTextActionList)) {
|
||||
action->setEnabled(enabled);
|
||||
}
|
||||
d->richTextEnabled = enabled;
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_setListStyle(int index)
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
q->setListStyle(index);
|
||||
updateMiscActions();
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_setHeadingLevel(int level)
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
q->setHeadingLevel(level);
|
||||
updateMiscActions();
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::updateCharFormatActions(const QTextCharFormat &format)
|
||||
{
|
||||
QFont f = format.font();
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportFontFamily) {
|
||||
action_font_family->setFont(f.family());
|
||||
}
|
||||
if (richTextSupport & KRichTextWidget::SupportFontSize) {
|
||||
if (f.pointSize() > 0) {
|
||||
action_font_size->setFontSize(f.pointSize());
|
||||
}
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportBold) {
|
||||
action_text_bold->setChecked(f.bold());
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportItalic) {
|
||||
action_text_italic->setChecked(f.italic());
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportUnderline) {
|
||||
action_text_underline->setChecked(f.underline());
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportStrikeOut) {
|
||||
action_text_strikeout->setChecked(f.strikeOut());
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportSuperScriptAndSubScript) {
|
||||
QTextCharFormat::VerticalAlignment vAlign = format.verticalAlignment();
|
||||
action_text_superscript->setChecked(vAlign == QTextCharFormat::AlignSuperScript);
|
||||
action_text_subscript->setChecked(vAlign == QTextCharFormat::AlignSubScript);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::updateMiscActions()
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportAlignment) {
|
||||
Qt::Alignment a = q->alignment();
|
||||
if (a & Qt::AlignLeft) {
|
||||
action_align_left->setChecked(true);
|
||||
} else if (a & Qt::AlignHCenter) {
|
||||
action_align_center->setChecked(true);
|
||||
} else if (a & Qt::AlignRight) {
|
||||
action_align_right->setChecked(true);
|
||||
} else if (a & Qt::AlignJustify) {
|
||||
action_align_justify->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportChangeListStyle) {
|
||||
if (q->textCursor().currentList()) {
|
||||
action_list_style->setCurrentItem(-q->textCursor().currentList()->format().style());
|
||||
} else {
|
||||
action_list_style->setCurrentItem(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportIndentLists) {
|
||||
if (richTextEnabled) {
|
||||
action_list_indent->setEnabled(q->canIndentList());
|
||||
} else {
|
||||
action_list_indent->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportDedentLists) {
|
||||
if (richTextEnabled) {
|
||||
action_list_dedent->setEnabled(q->canDedentList());
|
||||
} else {
|
||||
action_list_dedent->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportDirection) {
|
||||
const Qt::LayoutDirection direction = q->textCursor().blockFormat().layoutDirection();
|
||||
action_direction_ltr->setChecked(direction == Qt::LeftToRight);
|
||||
action_direction_rtl->setChecked(direction == Qt::RightToLeft);
|
||||
}
|
||||
|
||||
if (richTextSupport & KRichTextWidget::SupportHeading) {
|
||||
action_heading_level->setCurrentItem(q->textCursor().blockFormat().headingLevel());
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_setTextForegroundColor()
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
const QColor currentColor = q->textColor();
|
||||
const QColor defaultColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
|
||||
|
||||
const QColor selectedColor = QColorDialog::getColor(currentColor.isValid() ? currentColor : defaultColor, q);
|
||||
|
||||
if (!selectedColor.isValid() && !currentColor.isValid()) {
|
||||
q->setTextForegroundColor(defaultColor);
|
||||
} else if (selectedColor.isValid()) {
|
||||
q->setTextForegroundColor(selectedColor);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_setTextBackgroundColor()
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
QTextCharFormat fmt = q->textCursor().charFormat();
|
||||
const QColor currentColor = fmt.background().color();
|
||||
const QColor defaultColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
|
||||
|
||||
const QColor selectedColor = QColorDialog::getColor(currentColor.isValid() ? currentColor : defaultColor, q);
|
||||
|
||||
if (!selectedColor.isValid() && !currentColor.isValid()) {
|
||||
q->setTextBackgroundColor(defaultColor);
|
||||
} else if (selectedColor.isValid()) {
|
||||
q->setTextBackgroundColor(selectedColor);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_manageLink()
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
q->selectLinkText();
|
||||
KLinkDialog *linkDialog = new KLinkDialog(q);
|
||||
linkDialog->setLinkText(q->currentLinkText());
|
||||
linkDialog->setLinkUrl(q->currentLinkUrl());
|
||||
linkDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QObject::connect(linkDialog, &QDialog::accepted, linkDialog, [linkDialog, this]() {
|
||||
Q_Q(KRichTextWidget);
|
||||
q->updateLink(linkDialog->linkUrl(), linkDialog->linkText());
|
||||
});
|
||||
|
||||
linkDialog->show();
|
||||
}
|
||||
|
||||
void KRichTextWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
if (d->painterActive) {
|
||||
// If the painter is active, paint the selection with the
|
||||
// correct format.
|
||||
if (textCursor().hasSelection()) {
|
||||
QTextCursor c = textCursor();
|
||||
c.setCharFormat(d->painterFormat);
|
||||
setTextCursor(c);
|
||||
}
|
||||
d->painterActive = false;
|
||||
d->action_format_painter->setChecked(false);
|
||||
}
|
||||
KRichTextEdit::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void KRichTextWidgetPrivate::_k_formatPainter(bool active)
|
||||
{
|
||||
Q_Q(KRichTextWidget);
|
||||
|
||||
if (active) {
|
||||
painterFormat = q->currentCharFormat();
|
||||
painterActive = true;
|
||||
q->viewport()->setCursor(QCursor(QIcon::fromTheme(QStringLiteral("draw-brush")).pixmap(32, 32), 0, 32));
|
||||
} else {
|
||||
painterFormat = QTextCharFormat();
|
||||
painterActive = false;
|
||||
q->viewport()->setCursor(Qt::IBeamCursor);
|
||||
}
|
||||
}
|
||||
|
||||
void KRichTextWidget::updateActionStates()
|
||||
{
|
||||
Q_D(KRichTextWidget);
|
||||
|
||||
d->updateMiscActions();
|
||||
d->updateCharFormatActions(currentCharFormat());
|
||||
}
|
||||
|
||||
#include "moc_krichtextwidget.cpp"
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KRICHTEXTWIDGET_H
|
||||
#define KRICHTEXTWIDGET_H
|
||||
|
||||
#include "krichtextedit.h"
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
class QAction;
|
||||
class KRichTextWidgetPrivate;
|
||||
|
||||
/**
|
||||
* @class KRichTextWidget krichtextwidget.h <KRichTextWidget>
|
||||
*
|
||||
* @brief A KRichTextEdit with common actions
|
||||
*
|
||||
* This class implements common actions which are often used with KRichTextEdit.
|
||||
* All you need to do is to call createActions(), and the actions will be
|
||||
* added to your KXMLGUIWindow. Remember to also add the chosen actions to
|
||||
* your application ui.rc file.
|
||||
*
|
||||
* See the KRichTextWidget::RichTextSupportValues enum for an overview of
|
||||
* supported actions.
|
||||
*
|
||||
* @author Stephen Kelly <steveire@gmail.com>
|
||||
* @author Thomas McGuire <thomas.mcguire@gmx.net>
|
||||
*
|
||||
* \image html krichtextedit.png "KRichTextWidget Widget"
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KRichTextWidget : public KRichTextEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(RichTextSupport richTextSupport READ richTextSupport WRITE setRichTextSupport)
|
||||
public:
|
||||
/**
|
||||
* These flags describe what actions will be created by createActions() after
|
||||
* passing a combination of these flags to setRichTextSupport().
|
||||
* @see RichTextSupport
|
||||
*/
|
||||
enum RichTextSupportValues {
|
||||
/**
|
||||
* No rich text support at all, no actions will be created. Do not use
|
||||
* in combination with other flags.
|
||||
*/
|
||||
DisableRichText = 0x00,
|
||||
|
||||
/**
|
||||
* Action to format the selected text as bold. If no text is selected,
|
||||
* the word under the cursor is formatted bold.
|
||||
* This is a KToggleAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportBold = 0x01,
|
||||
|
||||
/**
|
||||
* Action to format the selected text as italic. If no text is selected,
|
||||
* the word under the cursor is formatted italic.
|
||||
* This is a KToggleAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportItalic = 0x02,
|
||||
|
||||
/**
|
||||
* Action to underline the selected text. If no text is selected,
|
||||
* the word under the cursor is underlined.
|
||||
* This is a KToggleAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportUnderline = 0x04,
|
||||
|
||||
/**
|
||||
* Action to strike out the selected text. If no text is selected,
|
||||
* the word under the cursor is struck out.
|
||||
* This is a KToggleAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportStrikeOut = 0x08,
|
||||
|
||||
/**
|
||||
* Action to change the font family of the currently selected text. If
|
||||
* no text is selected, the font family of the word under the cursor is
|
||||
* changed.
|
||||
* Displayed as a combobox when inserted into the toolbar.
|
||||
* This is a KFontAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportFontFamily = 0x10,
|
||||
|
||||
/**
|
||||
* Action to change the font size of the currently selected text. If no
|
||||
* text is selected, the font size of the word under the cursor is changed.
|
||||
* Displayed as a combobox when inserted into the toolbar.
|
||||
* This is a KFontSizeAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportFontSize = 0x20,
|
||||
|
||||
/**
|
||||
* Action to change the text color of the currently selected text. If no
|
||||
* text is selected, the text color of the word under the cursor is
|
||||
* changed.
|
||||
* Opens a QColorDialog to select the color.
|
||||
*/
|
||||
SupportTextForegroundColor = 0x40,
|
||||
|
||||
/**
|
||||
* Action to change the background color of the currently selected text. If no
|
||||
* text is selected, the background color of the word under the cursor is
|
||||
* changed.
|
||||
* Opens a QColorDialog to select the color.
|
||||
*/
|
||||
SupportTextBackgroundColor = 0x80,
|
||||
|
||||
/**
|
||||
* A combination of all the flags above.
|
||||
* Includes all actions that change the format of the text.
|
||||
*/
|
||||
FullTextFormattingSupport = 0xff,
|
||||
|
||||
/**
|
||||
* Action to make the current line a list element, change the
|
||||
* list style or remove list formatting.
|
||||
* Displayed as a combobox when inserted into a toolbar.
|
||||
* This is a KSelectAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportChangeListStyle = 0x100,
|
||||
|
||||
/**
|
||||
* Action to increase the current list nesting level. This makes it
|
||||
* possible to create nested lists.
|
||||
*/
|
||||
SupportIndentLists = 0x200,
|
||||
|
||||
/**
|
||||
* Action to decrease the current list nesting level.
|
||||
*/
|
||||
SupportDedentLists = 0x400,
|
||||
|
||||
/**
|
||||
* All of the three list actions above.
|
||||
* Includes all list-related actions.
|
||||
*/
|
||||
FullListSupport = 0xf00,
|
||||
|
||||
// Not implemented yet.
|
||||
// SupportCreateTables = 0x1000,
|
||||
// SupportChangeCellMargin = 0x2000,
|
||||
// SupportChangeCellPadding = 0x4000,
|
||||
// SupportChangeTableBorderWidth = 0x8000,
|
||||
// SupportChangeTableBorderColor = 0x10000,
|
||||
// SupportChangeTableBorderStyle = 0x20000,
|
||||
// SupportChangeCellBackground = 0x40000,
|
||||
// SupportCellFillPatterns = 0x80000,
|
||||
//
|
||||
// FullTableSupport = 0xff000,
|
||||
|
||||
/**
|
||||
* Actions to align the current paragraph left, righ, center or justify.
|
||||
* These actions are KToogleActions. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportAlignment = 0x100000,
|
||||
|
||||
// Not yet implemented SupportImages = 0x200000,
|
||||
|
||||
/**
|
||||
* Action to insert a horizontal line.
|
||||
*/
|
||||
SupportRuleLine = 0x400000,
|
||||
|
||||
/**
|
||||
* Action to convert the current text to a hyperlink. If no text is selected,
|
||||
* the word under the cursor is converted.
|
||||
* This action opens a dialog where the user can enter the link target.
|
||||
*/
|
||||
SupportHyperlinks = 0x800000,
|
||||
|
||||
/**
|
||||
* Action to make the mouse cursor a format painter. The user can select
|
||||
* text with that painter. The selected text gets the same format as the
|
||||
* text that was previously selected.
|
||||
*/
|
||||
SupportFormatPainting = 0x1000000,
|
||||
|
||||
/**
|
||||
* Action to change the text of the whole text edit to plain text.
|
||||
* All rich text formatting will get lost.
|
||||
*/
|
||||
SupportToPlainText = 0x2000000,
|
||||
|
||||
/**
|
||||
* Actions to format text as superscript or subscript. If no text is selected,
|
||||
* the word under the cursor is formatted as selected.
|
||||
* This is a KToggleAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*/
|
||||
SupportSuperScriptAndSubScript = 0x4000000,
|
||||
|
||||
// SupportChangeParagraphSpacing = 0x200000,
|
||||
|
||||
/**
|
||||
* Action to change direction of text to Right-To-Left or Left-To-Right.
|
||||
*/
|
||||
SupportDirection = 0x8000000,
|
||||
|
||||
/**
|
||||
* Action to make the current line a heading (up to six levels,
|
||||
* corresponding to HTML h1...h6 tags)
|
||||
* Displayed as a combobox when inserted into a toolbar.
|
||||
* This is a KSelectAction. The status is automatically updated when
|
||||
* the text cursor is moved.
|
||||
*
|
||||
* @since 5.70
|
||||
*/
|
||||
SupportHeading = 0x10000000,
|
||||
|
||||
/**
|
||||
* Includes all above actions for full rich text support
|
||||
*/
|
||||
FullSupport = 0xffffffff,
|
||||
};
|
||||
/**
|
||||
* Stores a combination of #RichTextSupportValues values.
|
||||
*/
|
||||
Q_DECLARE_FLAGS(RichTextSupport, RichTextSupportValues)
|
||||
Q_FLAG(RichTextSupport)
|
||||
|
||||
/**
|
||||
* @brief Constructor
|
||||
* @param parent the parent widget
|
||||
*/
|
||||
explicit KRichTextWidget(QWidget *parent);
|
||||
|
||||
/**
|
||||
* Constructs a KRichTextWidget object
|
||||
*
|
||||
* @param text The initial text of the text edit, which is interpreted as
|
||||
* HTML.
|
||||
* @param parent The parent widget
|
||||
*/
|
||||
explicit KRichTextWidget(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~KRichTextWidget() override;
|
||||
|
||||
/**
|
||||
* @brief Creates the actions and adds them to the given action collection.
|
||||
*
|
||||
* Call this before calling setupGUI() in your application, but after
|
||||
* calling setRichTextSupport().
|
||||
*
|
||||
* The XML file of your KXmlGuiWindow needs to have the action names in
|
||||
* them, so that the actions actually appear in the menu and in the toolbars.
|
||||
*
|
||||
* Below is a list of actions that are created,depending on the supported rich text
|
||||
* subset set by setRichTextSupport(). The list contains action names.
|
||||
* Those names need to be the same in your XML file.
|
||||
*
|
||||
* See the KRichTextWidget::RichTextSupportValues enum documentation for a
|
||||
* detailed explanation of each action.
|
||||
*
|
||||
* <table>
|
||||
* <tr><td><b>XML Name</b></td><td><b>RichTextSupportValues flag</b></td></tr>
|
||||
* <tr><td>format_text_foreground_color</td><td>SupportTextForegroundColor</td></tr>
|
||||
* <tr><td>format_text_background_color</td><td>SupportTextBackgroundColor</td></tr>
|
||||
* <tr><td>format_font_family</td><td>SupportFontFamily</td></tr>
|
||||
* <tr><td>format_font_size</td><td>SupportFontSize</td></tr>
|
||||
* <tr><td>format_text_bold</td><td>SupportBold</td></tr>
|
||||
* <tr><td>format_text_italic</td><td>SupportItalic</td></tr>
|
||||
* <tr><td>format_text_underline</td><td>SupportUnderline</td></tr>
|
||||
* <tr><td>format_text_strikeout</td><td>SupportStrikeOut</td></tr>
|
||||
* <tr><td>format_align_left</td><td>SupportAlignment</td></tr>
|
||||
* <tr><td>format_align_center</td><td>SupportAlignment</td></tr>
|
||||
* <tr><td>format_align_right</td><td>SupportAlignment</td></tr>
|
||||
* <tr><td>format_align_justify</td><td>SupportAlignment</td></tr>
|
||||
* <tr><td>direction_ltr</td><td>SupportDirection</td></tr>
|
||||
* <tr><td>direction_rtl</td><td>SupportDirection</td></tr>
|
||||
* <tr><td>format_list_style</td><td>SupportChangeListStyle</td></tr>
|
||||
* <tr><td>format_list_indent_more</td><td>SupportIndentLists</td></tr>
|
||||
* <tr><td>format_list_indent_less</td><td>SupportDedentLists</td></tr>
|
||||
* <tr><td>insert_horizontal_rule</td><td>SupportRuleLine</td></tr>
|
||||
* <tr><td>manage_link</td><td>SupportHyperlinks</td></tr>
|
||||
* <tr><td>format_painter</td><td>SupportFormatPainting</td></tr>
|
||||
* <tr><td>action_to_plain_text</td><td>SupportToPlainText</td></tr>
|
||||
* <tr><td>format_text_subscript & format_text_superscript</td><td>SupportSuperScriptAndSubScript</td></tr>
|
||||
* <tr><td>format_heading_level</td><td>SupportHeading</td></tr>
|
||||
* </table>
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
virtual QList<QAction *> createActions();
|
||||
|
||||
/**
|
||||
* @brief Sets the supported rich text subset available.
|
||||
*
|
||||
* The default is KRichTextWidget::FullSupport and will be set in the
|
||||
* constructor.
|
||||
*
|
||||
* You need to call createActions() afterwards.
|
||||
*
|
||||
* @param support The supported subset.
|
||||
*/
|
||||
void setRichTextSupport(const KRichTextWidget::RichTextSupport &support);
|
||||
|
||||
/**
|
||||
* @brief Returns the supported rich text subset available.
|
||||
* @return The supported subset.
|
||||
*/
|
||||
RichTextSupport richTextSupport() const;
|
||||
|
||||
/**
|
||||
* Tells KRichTextWidget to update the state of the actions created by
|
||||
* createActions().
|
||||
* This is normally automatically done, but there might be a few cases where
|
||||
* you'll need to manually call this function.
|
||||
*
|
||||
* Call this function only after calling createActions().
|
||||
*/
|
||||
void updateActionStates();
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
/**
|
||||
* Disables or enables all of the actions created by
|
||||
* createActions().
|
||||
* This may be useful in cases where rich text mode may be set on or off.
|
||||
*
|
||||
* @param enabled Whether to enable or disable the actions.
|
||||
*/
|
||||
void setActionsEnabled(bool enabled);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Reimplemented.
|
||||
* Catches mouse release events. Used to know when a selection has been completed.
|
||||
*/
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
//@cond PRIVATE
|
||||
Q_DECLARE_PRIVATE(KRichTextWidget)
|
||||
//@endcond
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(KRichTextWidget::RichTextSupport)
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KTEXTEDIT_H
|
||||
#define KTEXTEDIT_H
|
||||
|
||||
#include "ktextwidgets_export.h"
|
||||
|
||||
#include <QTextEdit>
|
||||
#include <memory>
|
||||
#include <sonnet/highlighter.h>
|
||||
|
||||
namespace Sonnet
|
||||
{
|
||||
class SpellCheckDecorator;
|
||||
}
|
||||
|
||||
class KTextEditPrivate;
|
||||
|
||||
/**
|
||||
* @class KTextEdit ktextedit.h <KTextEdit>
|
||||
*
|
||||
* @short A KDE'ified QTextEdit
|
||||
*
|
||||
* This is just a little subclass of QTextEdit, implementing
|
||||
* some standard KDE features, like cursor auto-hiding, configurable
|
||||
* wheelscrolling (fast-scroll or zoom), spell checking and deleting of entire
|
||||
* words with Ctrl-Backspace or Ctrl-Delete.
|
||||
*
|
||||
* This text edit provides two ways of spell checking: background checking,
|
||||
* which will mark incorrectly spelled words red, and a spell check dialog,
|
||||
* which lets the user check and correct all incorrectly spelled words.
|
||||
*
|
||||
* Basic rule: whenever you want to use QTextEdit, use KTextEdit!
|
||||
*
|
||||
* \image html ktextedit.png "KTextEdit Widget"
|
||||
*
|
||||
* @see QTextEdit
|
||||
* @author Carsten Pfeiffer <pfeiffer@kde.org>
|
||||
*/
|
||||
class KTEXTWIDGETS_EXPORT KTextEdit : public QTextEdit // krazy:exclude=qclasses
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool checkSpellingEnabled READ checkSpellingEnabled WRITE setCheckSpellingEnabled)
|
||||
Q_PROPERTY(QString spellCheckingLanguage READ spellCheckingLanguage WRITE setSpellCheckingLanguage)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs a KTextEdit object. See QTextEdit::QTextEdit
|
||||
* for details.
|
||||
*/
|
||||
explicit KTextEdit(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Constructs a KTextEdit object. See QTextEdit::QTextEdit
|
||||
* for details.
|
||||
*/
|
||||
explicit KTextEdit(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Destroys the KTextEdit object.
|
||||
*/
|
||||
~KTextEdit() override;
|
||||
|
||||
/**
|
||||
* Reimplemented to set a proper "deactivated" background color.
|
||||
*/
|
||||
virtual void setReadOnly(bool readOnly);
|
||||
|
||||
/**
|
||||
* Turns background spell checking for this text edit on or off.
|
||||
* Note that spell checking is only available in read-writable KTextEdits.
|
||||
*
|
||||
* Enabling spell checking will set back the current highlighter to the one
|
||||
* returned by createHighlighter().
|
||||
*
|
||||
* @see checkSpellingEnabled()
|
||||
* @see isReadOnly()
|
||||
* @see setReadOnly()
|
||||
*/
|
||||
virtual void setCheckSpellingEnabled(bool check);
|
||||
|
||||
/**
|
||||
* Returns true if background spell checking is enabled for this text edit.
|
||||
* Note that it even returns true if this is a read-only KTextEdit,
|
||||
* where spell checking is actually disabled.
|
||||
* By default spell checking is disabled.
|
||||
*
|
||||
* @see setCheckSpellingEnabled()
|
||||
*/
|
||||
virtual bool checkSpellingEnabled() const;
|
||||
|
||||
/**
|
||||
* Returns true if the given paragraph or block should be spellcheck.
|
||||
* For example, a mail client does not want to check quoted text, and
|
||||
* would return false here (by checking whether the block starts with a
|
||||
* quote sign).
|
||||
*
|
||||
* Always returns true by default.
|
||||
*
|
||||
*/
|
||||
virtual bool shouldBlockBeSpellChecked(const QString &block) const;
|
||||
|
||||
/**
|
||||
* Selects the characters at the specified position. Any previous
|
||||
* selection will be lost. The cursor is moved to the first character
|
||||
* of the new selection.
|
||||
*
|
||||
* @param length The length of the selection, in number of characters
|
||||
* @param pos The position of the first character of the selection
|
||||
*/
|
||||
void highlightWord(int length, int pos);
|
||||
|
||||
/**
|
||||
* Allows to create a specific highlighter if reimplemented.
|
||||
*
|
||||
* By default, it creates a normal highlighter, based on the config
|
||||
* file given to setSpellCheckingConfigFileName().
|
||||
*
|
||||
* This highlighter is set each time spell checking is toggled on by
|
||||
* calling setCheckSpellingEnabled(), but can later be overridden by calling
|
||||
* setHighlighter().
|
||||
*
|
||||
* @see setHighlighter()
|
||||
* @see highlighter()
|
||||
* @see setSpellCheckingConfigFileName()
|
||||
*/
|
||||
virtual void createHighlighter();
|
||||
|
||||
/**
|
||||
* Returns the current highlighter, which is 0 if spell checking is disabled.
|
||||
* The default highlighter is the one created by createHighlighter(), but
|
||||
* might be overridden by setHighlighter().
|
||||
*
|
||||
* @see setHighlighter()
|
||||
* @see createHighlighter()
|
||||
*/
|
||||
Sonnet::Highlighter *highlighter() const;
|
||||
|
||||
/**
|
||||
* Sets a custom background spell highlighter for this text edit.
|
||||
* Normally, the highlighter returned by createHighlighter() will be
|
||||
* used to detect and highlight incorrectly spelled words, but this
|
||||
* function allows to set a custom highlighter.
|
||||
*
|
||||
* This has to be called after enabling spell checking with
|
||||
* setCheckSpellingEnabled(), otherwise it has no effect.
|
||||
*
|
||||
* Ownership is transferred to the KTextEdit
|
||||
*
|
||||
* @see highlighter()
|
||||
* @see createHighlighter()
|
||||
* @param highLighter the new highlighter which will be used now
|
||||
*/
|
||||
void setHighlighter(Sonnet::Highlighter *_highLighter);
|
||||
|
||||
/**
|
||||
* Return standard KTextEdit popupMenu
|
||||
* @since 4.1
|
||||
*/
|
||||
virtual QMenu *mousePopupMenu();
|
||||
|
||||
/**
|
||||
* Enable find replace action.
|
||||
* @since 4.1
|
||||
*/
|
||||
void enableFindReplace(bool enabled);
|
||||
|
||||
/**
|
||||
* @return the spell checking language which was set by
|
||||
* setSpellCheckingLanguage(), the spellcheck dialog or the spellcheck
|
||||
* config dialog, or an empty string if that has never been called.
|
||||
* @since 4.2
|
||||
*/
|
||||
const QString &spellCheckingLanguage() const;
|
||||
|
||||
/**
|
||||
* @since 4.10
|
||||
*/
|
||||
void showTabAction(bool show);
|
||||
|
||||
/**
|
||||
* @since 4.10
|
||||
*/
|
||||
void showAutoCorrectButton(bool show);
|
||||
|
||||
/**
|
||||
* @since 4.10
|
||||
* create a modal spellcheck dialogbox and spellCheckingFinished signal we sent when
|
||||
* we finish spell checking or spellCheckingCanceled signal when we cancel spell checking
|
||||
*/
|
||||
void forceSpellChecking();
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* emit signal when we activate or not autospellchecking
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
void checkSpellingChanged(bool);
|
||||
|
||||
/**
|
||||
* Signal sends when spell checking is finished/stopped/completed
|
||||
* @since 4.1
|
||||
*/
|
||||
void spellCheckStatus(const QString &);
|
||||
|
||||
/**
|
||||
* Emitted when the user changes the language in the spellcheck dialog
|
||||
* shown by checkSpelling() or when calling setSpellCheckingLanguage().
|
||||
*
|
||||
* @param language the new language the user selected
|
||||
* @since 4.1
|
||||
*/
|
||||
void languageChanged(const QString &language);
|
||||
|
||||
/**
|
||||
* Emitted before the context menu is displayed.
|
||||
*
|
||||
* The signal allows you to add your own entries into the
|
||||
* the context menu that is created on demand.
|
||||
*
|
||||
* NOTE: Do not store the pointer to the QMenu
|
||||
* provided through since it is created and deleted
|
||||
* on demand.
|
||||
*
|
||||
* @param p the context menu about to be displayed
|
||||
* @since 4.5
|
||||
*/
|
||||
void aboutToShowContextMenu(QMenu *menu);
|
||||
|
||||
/**
|
||||
* @since 4.10
|
||||
*/
|
||||
void spellCheckerAutoCorrect(const QString ¤tWord, const QString &autoCorrectWord);
|
||||
|
||||
/**
|
||||
* signal spellCheckingFinished is sent when we finish spell check or we click on "Terminate" button in sonnet dialogbox
|
||||
* @since 4.10
|
||||
*/
|
||||
void spellCheckingFinished();
|
||||
|
||||
/**
|
||||
* signal spellCheckingCanceled is sent when we cancel spell checking.
|
||||
* @since 4.10
|
||||
*/
|
||||
void spellCheckingCanceled();
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
/**
|
||||
* Set the spell check language which will be used for highlighting spelling
|
||||
* mistakes and for the spellcheck dialog.
|
||||
* The languageChanged() signal will be emitted when the new language is
|
||||
* different from the old one.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
void setSpellCheckingLanguage(const QString &language);
|
||||
|
||||
/**
|
||||
* Show a dialog to check the spelling. The spellCheckStatus() signal
|
||||
* will be emitted when the spell checking dialog is closed.
|
||||
*/
|
||||
void checkSpelling();
|
||||
|
||||
/**
|
||||
* Opens a Sonnet::ConfigDialog for this text edit.
|
||||
* The spellcheck language of the config dialog is set to the current spellcheck
|
||||
* language of the textedit. If the user changes the language in that dialog,
|
||||
* the languageChanged() signal is emitted.
|
||||
*
|
||||
* @param configFileName The file which is used to store and load the config
|
||||
* settings
|
||||
* @param windowIcon the icon which is used for the titlebar of the spell dialog
|
||||
* window. Can be empty, then no icon is set.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
void showSpellConfigDialog(const QString &windowIcon = QString());
|
||||
|
||||
/**
|
||||
* Create replace dialogbox
|
||||
* @since 4.1
|
||||
*/
|
||||
void replace();
|
||||
|
||||
/**
|
||||
* Add custom spell checker decorator
|
||||
* @since 5.11
|
||||
*/
|
||||
void addTextDecorator(Sonnet::SpellCheckDecorator *decorator);
|
||||
|
||||
/**
|
||||
* @brief clearDecorator clear the spellcheckerdecorator
|
||||
* @since 5.11
|
||||
*/
|
||||
void clearDecorator();
|
||||
|
||||
protected Q_SLOTS:
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
void slotDoReplace();
|
||||
void slotReplaceNext();
|
||||
void slotDoFind();
|
||||
void slotFind();
|
||||
void slotFindNext();
|
||||
/**
|
||||
* @since 5.11
|
||||
*/
|
||||
void slotFindPrevious();
|
||||
void slotReplace();
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
void slotSpeakText();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Reimplemented to catch "delete word" shortcut events.
|
||||
*/
|
||||
bool event(QEvent *) override;
|
||||
|
||||
/**
|
||||
* Reimplemented for internal reasons
|
||||
*/
|
||||
void keyPressEvent(QKeyEvent *) override;
|
||||
|
||||
/**
|
||||
* Reimplemented to instantiate a KDictSpellingHighlighter, if
|
||||
* spellchecking is enabled.
|
||||
*/
|
||||
void focusInEvent(QFocusEvent *) override;
|
||||
|
||||
/**
|
||||
* Deletes a word backwards from the current cursor position,
|
||||
* if available.
|
||||
*/
|
||||
virtual void deleteWordBack();
|
||||
|
||||
/**
|
||||
* Deletes a word forwards from the current cursor position,
|
||||
* if available.
|
||||
*/
|
||||
virtual void deleteWordForward();
|
||||
|
||||
/**
|
||||
* Reimplemented from QTextEdit to add spelling related items
|
||||
* when appropriate.
|
||||
*/
|
||||
void contextMenuEvent(QContextMenuEvent *) override;
|
||||
|
||||
protected:
|
||||
KTEXTWIDGETS_NO_EXPORT KTextEdit(KTextEditPrivate &dd, const QString &text, QWidget *parent);
|
||||
KTEXTWIDGETS_NO_EXPORT KTextEdit(KTextEditPrivate &dd, QWidget *parent);
|
||||
|
||||
protected:
|
||||
std::unique_ptr<class KTextEditPrivate> const d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KTextEdit)
|
||||
};
|
||||
|
||||
#endif // KTEXTEDIT_H
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2002 Carsten Pfeiffer <pfeiffer@kde.org>
|
||||
SPDX-FileCopyrightText: 2005 Michael Brade <brade@kde.org>
|
||||
SPDX-FileCopyrightText: 2012 Laurent Montel <montel@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KTEXTEDIT_P_H
|
||||
#define KTEXTEDIT_P_H
|
||||
|
||||
#include "kfind.h"
|
||||
#include "kfinddialog.h"
|
||||
#include "kreplace.h"
|
||||
#include "kreplacedialog.h"
|
||||
|
||||
#include <Sonnet/SpellCheckDecorator>
|
||||
#include <Sonnet/Speller>
|
||||
|
||||
#include <QSettings>
|
||||
#include <QTextDocumentFragment>
|
||||
#ifdef HAVE_SPEECH
|
||||
#include <QTextToSpeech>
|
||||
#endif
|
||||
|
||||
class KTextEditPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KTextEdit)
|
||||
|
||||
public:
|
||||
explicit KTextEditPrivate(KTextEdit *qq)
|
||||
: q_ptr(qq)
|
||||
, customPalette(false)
|
||||
, spellCheckingEnabled(false)
|
||||
, findReplaceEnabled(true)
|
||||
, showTabAction(true)
|
||||
, showAutoCorrectionButton(false)
|
||||
{
|
||||
// Check the default sonnet settings to see if spellchecking should be enabled.
|
||||
QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet"));
|
||||
spellCheckingEnabled = settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool();
|
||||
}
|
||||
|
||||
virtual ~KTextEditPrivate()
|
||||
{
|
||||
delete decorator;
|
||||
delete findDlg;
|
||||
delete find;
|
||||
delete replace;
|
||||
delete repDlg;
|
||||
delete speller;
|
||||
#ifdef HAVE_SPEECH
|
||||
delete textToSpeech;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we should/should not consume a key used as a shortcut.
|
||||
* This makes it possible to handle shortcuts in the focused widget before any
|
||||
* window-global QAction is triggered.
|
||||
*/
|
||||
bool overrideShortcut(const QKeyEvent *e);
|
||||
/**
|
||||
* Actually handle a shortcut event.
|
||||
*/
|
||||
bool handleShortcut(const QKeyEvent *e);
|
||||
|
||||
void spellCheckerMisspelling(const QString &text, int pos);
|
||||
void spellCheckerCorrected(const QString &, int, const QString &);
|
||||
void spellCheckerAutoCorrect(const QString &, const QString &);
|
||||
void spellCheckerCanceled();
|
||||
void spellCheckerFinished();
|
||||
void toggleAutoSpellCheck();
|
||||
|
||||
void slotFindHighlight(const QString &text, int matchingIndex, int matchingLength);
|
||||
void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength);
|
||||
|
||||
/**
|
||||
* Similar to QTextEdit::clear(), only that it is possible to undo this
|
||||
* action.
|
||||
*/
|
||||
void undoableClear();
|
||||
|
||||
void slotAllowTab();
|
||||
void menuActivated(QAction *action);
|
||||
|
||||
void init();
|
||||
|
||||
void checkSpelling(bool force);
|
||||
|
||||
KTextEdit *const q_ptr;
|
||||
QAction *autoSpellCheckAction;
|
||||
QAction *allowTab;
|
||||
QAction *spellCheckAction;
|
||||
QMenu *languagesMenu = nullptr;
|
||||
bool customPalette : 1;
|
||||
|
||||
bool spellCheckingEnabled : 1;
|
||||
bool findReplaceEnabled : 1;
|
||||
bool showTabAction : 1;
|
||||
bool showAutoCorrectionButton : 1;
|
||||
QTextDocumentFragment originalDoc;
|
||||
QString spellCheckingLanguage;
|
||||
Sonnet::SpellCheckDecorator *decorator = nullptr;
|
||||
Sonnet::Speller *speller = nullptr;
|
||||
KFindDialog *findDlg = nullptr;
|
||||
KFind *find = nullptr;
|
||||
KReplaceDialog *repDlg = nullptr;
|
||||
KReplace *replace = nullptr;
|
||||
#ifdef HAVE_SPEECH
|
||||
QTextToSpeech *textToSpeech = nullptr;
|
||||
#endif
|
||||
|
||||
int findIndex = 0;
|
||||
int repIndex = 0;
|
||||
int lastReplacedPosition = -1;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
Nested list helper
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include "nestedlisthelper_p.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QTextBlock>
|
||||
#include <QTextCursor>
|
||||
#include <QTextList>
|
||||
|
||||
#include "ktextedit.h"
|
||||
|
||||
NestedListHelper::NestedListHelper(QTextEdit *te)
|
||||
: textEdit(te)
|
||||
{
|
||||
}
|
||||
|
||||
NestedListHelper::~NestedListHelper()
|
||||
{
|
||||
}
|
||||
|
||||
bool NestedListHelper::handleKeyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
if (!cursor.currentList()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Backspace && !cursor.hasSelection() && cursor.atBlockStart() && canDedent()) {
|
||||
changeIndent(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Return && !cursor.hasSelection() && cursor.block().text().isEmpty() && canDedent()) {
|
||||
changeIndent(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Tab && (cursor.atBlockStart() || cursor.hasSelection()) && canIndent()) {
|
||||
changeIndent(+1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NestedListHelper::canIndent() const
|
||||
{
|
||||
const QTextCursor cursor = topOfSelection();
|
||||
const QTextBlock block = cursor.block();
|
||||
if (!block.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (!block.textList()) {
|
||||
return true;
|
||||
}
|
||||
const QTextBlock prevBlock = block.previous();
|
||||
if (!prevBlock.textList()) {
|
||||
return false;
|
||||
}
|
||||
return block.textList()->format().indent() <= prevBlock.textList()->format().indent();
|
||||
}
|
||||
|
||||
bool NestedListHelper::canDedent() const
|
||||
{
|
||||
const QTextCursor cursor = bottomOfSelection();
|
||||
const QTextBlock block = cursor.block();
|
||||
if (!block.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (!block.textList() || block.textList()->format().indent() <= 0) {
|
||||
return false;
|
||||
}
|
||||
const QTextBlock nextBlock = block.next();
|
||||
if (!nextBlock.textList()) {
|
||||
return true;
|
||||
}
|
||||
return block.textList()->format().indent() >= nextBlock.textList()->format().indent();
|
||||
}
|
||||
|
||||
bool NestedListHelper::handleAfterDropEvent(QDropEvent *dropEvent)
|
||||
{
|
||||
Q_UNUSED(dropEvent);
|
||||
QTextCursor cursor = topOfSelection();
|
||||
|
||||
QTextBlock droppedBlock = cursor.block();
|
||||
int firstDroppedItemIndent = droppedBlock.textList()->format().indent();
|
||||
|
||||
int minimumIndent = droppedBlock.previous().textList()->format().indent();
|
||||
|
||||
if (firstDroppedItemIndent < minimumIndent) {
|
||||
cursor = QTextCursor(droppedBlock);
|
||||
QTextListFormat fmt = droppedBlock.textList()->format();
|
||||
fmt.setIndent(minimumIndent);
|
||||
QTextList *list = cursor.createList(fmt);
|
||||
|
||||
int endOfDrop = bottomOfSelection().position();
|
||||
while (droppedBlock.next().position() < endOfDrop) {
|
||||
droppedBlock = droppedBlock.next();
|
||||
if (droppedBlock.textList()->format().indent() != firstDroppedItemIndent) {
|
||||
// new list?
|
||||
}
|
||||
list->add(droppedBlock);
|
||||
}
|
||||
// list.add( droppedBlock );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NestedListHelper::processList(QTextList *list)
|
||||
{
|
||||
QTextBlock block = list->item(0);
|
||||
int thisListIndent = list->format().indent();
|
||||
|
||||
QTextCursor cursor = QTextCursor(block);
|
||||
list = cursor.createList(list->format());
|
||||
bool processingSubList = false;
|
||||
while (block.next().textList() != nullptr) {
|
||||
block = block.next();
|
||||
|
||||
QTextList *nextList = block.textList();
|
||||
int nextItemIndent = nextList->format().indent();
|
||||
if (nextItemIndent < thisListIndent) {
|
||||
return;
|
||||
} else if (nextItemIndent > thisListIndent) {
|
||||
if (processingSubList) {
|
||||
continue;
|
||||
}
|
||||
processingSubList = true;
|
||||
processList(nextList);
|
||||
} else {
|
||||
processingSubList = false;
|
||||
list->add(block);
|
||||
}
|
||||
}
|
||||
// delete nextList;
|
||||
// nextList = 0;
|
||||
}
|
||||
|
||||
void NestedListHelper::reformatList(QTextBlock block)
|
||||
{
|
||||
if (block.textList()) {
|
||||
int minimumIndent = block.textList()->format().indent();
|
||||
|
||||
// Start at the top of the list
|
||||
while (block.previous().textList() != nullptr) {
|
||||
if (block.previous().textList()->format().indent() < minimumIndent) {
|
||||
break;
|
||||
}
|
||||
block = block.previous();
|
||||
}
|
||||
|
||||
processList(block.textList());
|
||||
}
|
||||
}
|
||||
|
||||
void NestedListHelper::reformatList()
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
reformatList(cursor.block());
|
||||
}
|
||||
|
||||
QTextCursor NestedListHelper::topOfSelection() const
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.setPosition(qMin(cursor.position(), cursor.anchor()));
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
QTextCursor NestedListHelper::bottomOfSelection() const
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
|
||||
if (cursor.hasSelection()) {
|
||||
cursor.setPosition(qMax(cursor.position(), cursor.anchor()));
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void NestedListHelper::changeIndent(int delta)
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
cursor.beginEditBlock();
|
||||
|
||||
const int top = qMin(cursor.position(), cursor.anchor());
|
||||
const int bottom = qMax(cursor.position(), cursor.anchor());
|
||||
|
||||
// A reformatList should be called on the block inside selection
|
||||
// with the lowest indentation level
|
||||
int minIndentPosition;
|
||||
int minIndent = -1;
|
||||
|
||||
// Changing indentation of all blocks between top and bottom
|
||||
cursor.setPosition(top);
|
||||
do {
|
||||
QTextList *list = cursor.currentList();
|
||||
// Setting up listFormat
|
||||
QTextListFormat listFmt;
|
||||
if (!list) {
|
||||
if (delta > 0) {
|
||||
// No list, we're increasing indentation -> create a new one
|
||||
listFmt.setStyle(QTextListFormat::ListDisc);
|
||||
listFmt.setIndent(delta);
|
||||
}
|
||||
// else do nothing
|
||||
} else {
|
||||
const int newIndent = list->format().indent() + delta;
|
||||
if (newIndent > 0) {
|
||||
listFmt = list->format();
|
||||
listFmt.setIndent(newIndent);
|
||||
} else {
|
||||
listFmt.setIndent(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (listFmt.indent() > 0) {
|
||||
// This block belongs to a list: here we create a new one
|
||||
// for each block, and then let reformatList() sort it out
|
||||
cursor.createList(listFmt);
|
||||
if (minIndent == -1 || minIndent > listFmt.indent()) {
|
||||
minIndent = listFmt.indent();
|
||||
minIndentPosition = cursor.block().position();
|
||||
}
|
||||
} else {
|
||||
// If the block belonged to a list, remove it from there
|
||||
if (list) {
|
||||
list->remove(cursor.block());
|
||||
}
|
||||
// The removal does not change the indentation, we need to do it explicitly
|
||||
QTextBlockFormat blkFmt;
|
||||
blkFmt.setIndent(0);
|
||||
cursor.mergeBlockFormat(blkFmt);
|
||||
}
|
||||
if (!cursor.block().next().isValid()) {
|
||||
break;
|
||||
}
|
||||
cursor.movePosition(QTextCursor::NextBlock);
|
||||
} while (cursor.position() < bottom);
|
||||
// Reformatting the whole list
|
||||
if (minIndent != -1) {
|
||||
cursor.setPosition(minIndentPosition);
|
||||
reformatList(cursor.block());
|
||||
}
|
||||
cursor.setPosition(top);
|
||||
reformatList(cursor.block());
|
||||
cursor.endEditBlock();
|
||||
}
|
||||
|
||||
void NestedListHelper::handleOnBulletType(int styleIndex)
|
||||
{
|
||||
QTextCursor cursor = textEdit->textCursor();
|
||||
if (styleIndex != 0) {
|
||||
QTextListFormat::Style style = static_cast<QTextListFormat::Style>(styleIndex);
|
||||
QTextList *currentList = cursor.currentList();
|
||||
QTextListFormat listFmt;
|
||||
|
||||
cursor.beginEditBlock();
|
||||
|
||||
if (currentList) {
|
||||
listFmt = currentList->format();
|
||||
listFmt.setStyle(style);
|
||||
currentList->setFormat(listFmt);
|
||||
} else {
|
||||
listFmt.setStyle(style);
|
||||
cursor.createList(listFmt);
|
||||
}
|
||||
|
||||
cursor.endEditBlock();
|
||||
} else {
|
||||
QTextBlockFormat bfmt;
|
||||
bfmt.setObjectIndex(-1);
|
||||
cursor.setBlockFormat(bfmt);
|
||||
}
|
||||
|
||||
reformatList();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
Nested list helper
|
||||
SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef NESTEDLISTHELPER_H
|
||||
#define NESTEDLISTHELPER_H
|
||||
|
||||
//@cond PRIVATE
|
||||
|
||||
class QTextEdit;
|
||||
|
||||
class QKeyEvent;
|
||||
class QDropEvent;
|
||||
class QTextCursor;
|
||||
class QTextList;
|
||||
class QTextBlock;
|
||||
|
||||
/**
|
||||
*
|
||||
* @short Helper class for automatic handling of nested lists in a text edit
|
||||
*
|
||||
*
|
||||
* @author Stephen Kelly
|
||||
* @since 4.1
|
||||
* @internal
|
||||
*/
|
||||
class NestedListHelper
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a helper
|
||||
*
|
||||
* @param te The text edit object to handle lists in.
|
||||
*/
|
||||
explicit NestedListHelper(QTextEdit *te);
|
||||
|
||||
/**
|
||||
* Destructor
|
||||
*/
|
||||
~NestedListHelper();
|
||||
|
||||
/**
|
||||
*
|
||||
* Handles a key press before it is processed by the text edit widget.
|
||||
*
|
||||
* This includes:
|
||||
* 1. Backspace at the beginning of a line decreases nesting level
|
||||
* 2. Return at the empty list element decreases nesting level
|
||||
* 3. Tab at the beginning of a line OR with a multi-line selection
|
||||
* increases nesting level
|
||||
*
|
||||
* @param event The event to be handled
|
||||
* @return Whether the event was completely handled by this method.
|
||||
*/
|
||||
bool handleKeyPressEvent(QKeyEvent *event);
|
||||
|
||||
bool handleAfterDropEvent(QDropEvent *event);
|
||||
|
||||
/**
|
||||
* Changes the indent (nesting level) on a current list item or selection
|
||||
* by the value @p delta (typically, +1 or -1)
|
||||
*/
|
||||
void changeIndent(int delta);
|
||||
|
||||
/**
|
||||
* Changes the style of the current list or creates a new list with
|
||||
* the specified style.
|
||||
*
|
||||
* @param styleIndex The QTextListStyle of the list.
|
||||
*/
|
||||
void handleOnBulletType(int styleIndex);
|
||||
|
||||
/**
|
||||
* @brief Check whether the current item in the list may be indented.
|
||||
*
|
||||
* An list item must have an item above it on the same or greater level
|
||||
* if it can be indented.
|
||||
*
|
||||
* Also, a block which is currently part of a list can be indented.
|
||||
*
|
||||
* @sa canDedent
|
||||
*
|
||||
* @return Whether the item can be indented.
|
||||
*/
|
||||
bool canIndent() const;
|
||||
|
||||
/**
|
||||
* \brief Check whether the current item in the list may be dedented.
|
||||
*
|
||||
* An item may be dedented if it is part of a list.
|
||||
* The next item must be at the same or lesser level.
|
||||
*
|
||||
* @sa canIndent
|
||||
*
|
||||
* @return Whether the item can be dedented.
|
||||
*/
|
||||
bool canDedent() const;
|
||||
|
||||
private:
|
||||
QTextCursor topOfSelection() const;
|
||||
QTextCursor bottomOfSelection() const;
|
||||
void processList(QTextList *list);
|
||||
void reformatList(QTextBlock block);
|
||||
void reformatList();
|
||||
|
||||
QTextEdit *const textEdit;
|
||||
};
|
||||
|
||||
//@endcond
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user