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:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -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; \">&nbsp;</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>&nbsp;</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 &currentWord, 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