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,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