richtextdialog: implemented basic html tag editing
Another step in moving away from the WYSIWYG rich text dialog.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "richtext.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -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("<br>"), 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("</%1>").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<QPushButton*>(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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user