From ceeb4912939c3640d2a476cf6455dc7d295a62d1 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 11 Jul 2020 01:44:48 +1000 Subject: [PATCH] richtextdialog: implemented basic html tag editing Another step in moving away from the WYSIWYG rich text dialog. --- app/dialog/richtext/richtext.cpp | 235 +++++++++++++++++++++++++++---- app/dialog/richtext/richtext.h | 15 +- 2 files changed, 225 insertions(+), 25 deletions(-) diff --git a/app/dialog/richtext/richtext.cpp b/app/dialog/richtext/richtext.cpp index 1b49c649a..40f2baabd 100644 --- a/app/dialog/richtext/richtext.cpp +++ b/app/dialog/richtext/richtext.cpp @@ -20,6 +20,7 @@ #include "richtext.h" +#include #include #include #include @@ -37,13 +38,13 @@ RichTextDialog::RichTextDialog(QString start, QWidget* parent) : // Create toolbar QHBoxLayout* toolbar_layout = new QHBoxLayout(); - bold_btn_ = CreateToolbarButton(tr("B"), tr("Bold")); + bold_btn_ = CreateToolbarButton(tr("B"), tr("Bold"), {QStringLiteral("b"), QStringLiteral("strong")}); toolbar_layout->addWidget(bold_btn_); - italic_btn_ = CreateToolbarButton(tr("I"), tr("Italic")); + italic_btn_ = CreateToolbarButton(tr("I"), tr("Italic"), {QStringLiteral("i"), QStringLiteral("em")}); toolbar_layout->addWidget(italic_btn_); - underline_btn_ = CreateToolbarButton(tr("U"), tr("Underline")); + underline_btn_ = CreateToolbarButton(tr("U"), tr("Underline"), {QStringLiteral("u")}); toolbar_layout->addWidget(underline_btn_); - strikeout_btn_ = CreateToolbarButton(tr("S"), tr("Strikethrough")); + strikeout_btn_ = CreateToolbarButton(tr("S"), tr("Strikethrough"), {QStringLiteral("strike")}); toolbar_layout->addWidget(strikeout_btn_); font_combo_ = new QFontComboBox(); font_combo_->setToolTip(tr("Font Family")); @@ -56,13 +57,13 @@ RichTextDialog::RichTextDialog(QString start, QWidget* parent) : toolbar_layout->addStretch(); - left_align_btn_ = CreateToolbarButton(tr("L"), tr("Left Align")); + left_align_btn_ = CreateToolbarButton(tr("L"), tr("Left Align"), {}); toolbar_layout->addWidget(left_align_btn_); - center_align_btn_ = CreateToolbarButton(tr("C"), tr("Center Align")); + center_align_btn_ = CreateToolbarButton(tr("C"), tr("Center Align"), {}); toolbar_layout->addWidget(center_align_btn_); - right_align_btn_ = CreateToolbarButton(tr("R"), tr("Right Align")); + right_align_btn_ = CreateToolbarButton(tr("R"), tr("Right Align"), {}); toolbar_layout->addWidget(right_align_btn_); - justify_align_btn_ = CreateToolbarButton(tr("J"), tr("Justify Align")); + justify_align_btn_ = CreateToolbarButton(tr("J"), tr("Justify Align"), {}); toolbar_layout->addWidget(justify_align_btn_); layout->addLayout(toolbar_layout); @@ -70,7 +71,7 @@ RichTextDialog::RichTextDialog(QString start, QWidget* parent) : // Create text edit widget text_edit_ = new QTextEdit(); text_edit_->setWordWrapMode(QTextOption::NoWrap); - //connect(text_edit_, &QTextEdit::cursorPositionChanged, this, &RichTextDialog::UpdateButtons); + connect(text_edit_, &QTextEdit::cursorPositionChanged, this, &RichTextDialog::UpdateButtons); start.replace(QStringLiteral("
"), QStringLiteral("\n")); text_edit_->document()->setPlainText(start); layout->addWidget(text_edit_); @@ -83,16 +84,6 @@ RichTextDialog::RichTextDialog(QString start, QWidget* parent) : // Connect font buttons /* - connect(bold_btn_, &QPushButton::clicked, this, [this](bool e){ - text_edit_->setFontWeight(e ? QFont::Bold : QFont::Normal); - }); - connect(italic_btn_, &QPushButton::clicked, text_edit_, &QTextEdit::setFontItalic); - connect(underline_btn_, &QPushButton::clicked, text_edit_, &QTextEdit::setFontUnderline); - connect(strikeout_btn_, &QPushButton::clicked, this, [this](bool e){ - QFont current_font = text_edit_->currentFont(); - current_font.setStrikeOut(e); - text_edit_->setCurrentFont(current_font); - }); connect(size_slider_, &FloatSlider::ValueChanged, text_edit_, &QTextEdit::setFontPointSize); connect(left_align_btn_, &QPushButton::clicked, this, [this](){ text_edit_->setAlignment(Qt::AlignLeft); @@ -118,22 +109,218 @@ RichTextDialog::RichTextDialog(QString start, QWidget* parent) : */ } -QPushButton *RichTextDialog::CreateToolbarButton(const QString& label, const QString& tooltip) +QPushButton *RichTextDialog::CreateToolbarButton(const QString& label, const QString& tooltip, const QStringList &tags) { QPushButton* btn = new QPushButton(label); btn->setCheckable(true); btn->setToolTip(tooltip); btn->setFixedWidth(btn->sizeHint().height()); + + if (!tags.isEmpty()) { + btn->setProperty("tag", tags); + connect(btn, &QPushButton::clicked, this, &RichTextDialog::TagButtonToggled); + } + return btn; } +int SnapPositionOutsideTags(const QString& text, int pos) +{ + // Look for closest opening bracket before position + int opening_bracket_pos = text.lastIndexOf('<', pos - text.size() -1); + + // Look for closest closing bracket before position + int closing_bracket_pos = text.indexOf('>', opening_bracket_pos); + + if (opening_bracket_pos > -1 && closing_bracket_pos >= pos) { + // Must be inside an angle bracket, snap to closest position outside of bracket + closing_bracket_pos++; + + if (pos - opening_bracket_pos < closing_bracket_pos - pos) { + // Closer to opening bracket pos + return opening_bracket_pos; + } else { + return closing_bracket_pos; + } + } + + return pos; +} + +void RichTextDialog::SetTags(const QStringList &t, bool enabled) +{ + QString s = text_edit_->toPlainText(); + + int selection_start, selection_end; + + { + QTextCursor c = text_edit_->textCursor(); + + if (c.hasSelection()) { + selection_start = SnapPositionOutsideTags(s, c.selectionStart()); + selection_end = SnapPositionOutsideTags(s, c.selectionEnd()); + + c.clearSelection(); + c.setPosition(selection_start, QTextCursor::MoveAnchor); + c.setPosition(selection_end, QTextCursor::KeepAnchor); + } else { + selection_start = SnapPositionOutsideTags(s, c.position()); + selection_end = selection_start; + c.setPosition(selection_start, QTextCursor::MoveAnchor); + } + + text_edit_->setTextCursor(c); + } + + QString open_tag = CreateOpeningTag(t.first()); + QString close_tag = CreateClosingTag(t.first()); + + // Insert tags + QString new_text; + + if (!enabled) { + std::swap(open_tag, close_tag); + } + + bool open_tag_cancels_out = !QString::compare(s.mid(selection_start - close_tag.size(), close_tag.size()), close_tag, Qt::CaseInsensitive); + bool close_tag_cancels_out = !QString::compare(s.mid(selection_end, open_tag.size()), open_tag, Qt::CaseInsensitive); + + QString selected_text = text_edit_->textCursor().selectedText(); + + if (open_tag_cancels_out && close_tag_cancels_out) { + + // Both tags cancel each other out, simply remove + selection_start -= close_tag.size(); + + QTextCursor c = text_edit_->textCursor(); + c.clearSelection(); + c.setPosition(selection_start, QTextCursor::MoveAnchor); + c.setPosition(selection_end + open_tag.size(), QTextCursor::KeepAnchor); + text_edit_->setTextCursor(c); + + selection_end -= close_tag.size(); + + new_text = selected_text; + + } else if (open_tag_cancels_out) { + + // Open tag cancels out, shift close tag rather than inserting new tags + selection_start -= close_tag.size(); + + QTextCursor c = text_edit_->textCursor(); + c.clearSelection(); + c.setPosition(selection_start, QTextCursor::MoveAnchor); + c.setPosition(selection_end, QTextCursor::KeepAnchor); + text_edit_->setTextCursor(c); + + selection_end -= close_tag.size(); + + new_text = selected_text; + new_text.append(close_tag); + + } else if (close_tag_cancels_out) { + + // Close tag cancels out, shift open tag rather than inserting new tags + selection_end += open_tag.size(); + + QTextCursor c = text_edit_->textCursor(); + c.clearSelection(); + c.setPosition(selection_start, QTextCursor::MoveAnchor); + c.setPosition(selection_end, QTextCursor::KeepAnchor); + text_edit_->setTextCursor(c); + + selection_start += open_tag.size(); + + new_text = open_tag; + new_text.append(selected_text); + + } else { + // Nothing is cancelled out, simply insert tags + new_text = QStringLiteral("%1%2%3").arg(open_tag, + selected_text, + close_tag); + + selection_start += open_tag.size(); + selection_end += open_tag.size(); + } + + text_edit_->insertPlainText(new_text); + + text_edit_->setFocus(); + + { + // Re-select text + QTextCursor c = text_edit_->textCursor(); + + c.clearSelection(); + c.setPosition(selection_start, QTextCursor::MoveAnchor); + c.setPosition(selection_end, QTextCursor::KeepAnchor); + text_edit_->setTextCursor(c); + } +} + +QString RichTextDialog::CreateOpeningTag(const QString &s) +{ + return QStringLiteral("<%1>").arg(s); +} + +QString RichTextDialog::CreateClosingTag(const QString &s) +{ + return QStringLiteral("").arg(s); +} + +void RichTextDialog::UpdateTagButton(QPushButton *btn, + const QString &text, + int cursor_pos) +{ + QStringList tags = btn->property("tag").toStringList(); + foreach (const QString& t, tags) { + QString opening = CreateOpeningTag(t); + QString closing = CreateClosingTag(t); + + int opening_index = text.lastIndexOf(opening, + cursor_pos - text.size() - 1, + Qt::CaseInsensitive); + int closing_index = text.indexOf(closing, + opening_index, + Qt::CaseInsensitive); + + if (opening_index > -1 && closing_index + closing.size() > cursor_pos) { + btn->setChecked(true); + btn->setProperty("foundtag", t); + return; + } + } + + btn->setChecked(false); + btn->setProperty("foundtag", QVariant()); +} + +void RichTextDialog::TagButtonToggled(bool checked) +{ + QPushButton* src = static_cast(sender()); + QStringList tags; + + if (src->property("foundtag").isNull()) { + tags = src->property("tag").toStringList(); + } else { + tags = QStringList({src->property("foundtag").toString()}); + } + + SetTags(tags, checked); +} + void RichTextDialog::UpdateButtons() { + QString text = text_edit_->toPlainText(); + int cursor_pos = text_edit_->textCursor().position(); + + UpdateTagButton(bold_btn_, text, cursor_pos); + UpdateTagButton(italic_btn_, text, cursor_pos); + UpdateTagButton(underline_btn_, text, cursor_pos); + UpdateTagButton(strikeout_btn_, text, cursor_pos); + /* - bold_btn_->setChecked(text_edit_->fontWeight() > QFont::Normal); - italic_btn_->setChecked(text_edit_->fontItalic()); - underline_btn_->setChecked(text_edit_->fontUnderline()); - strikeout_btn_->setChecked(text_edit_->currentFont().strikeOut()); // Update font family font_combo_->blockSignals(true); diff --git a/app/dialog/richtext/richtext.h b/app/dialog/richtext/richtext.h index aface3d04..b3514df4b 100644 --- a/app/dialog/richtext/richtext.h +++ b/app/dialog/richtext/richtext.h @@ -47,7 +47,18 @@ public: } private: - QPushButton* CreateToolbarButton(const QString &label, const QString &tooltip); + QPushButton* CreateToolbarButton(const QString &label, + const QString &tooltip, + const QStringList& tags); + + void SetTags(const QStringList& t, bool enabled); + + static QString CreateOpeningTag(const QString& s); + static QString CreateClosingTag(const QString& s); + + static void UpdateTagButton(QPushButton* btn, + const QString &text, + int cursor_pos); QFontDatabase font_db_; @@ -65,6 +76,8 @@ private: QPushButton* justify_align_btn_; private slots: + void TagButtonToggled(bool checked); + void UpdateButtons(); };