// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "richtexteditor_p.h"
#include "htmlhighlighter_p.h"
#include "iconselector_p.h"
#include "ui_addlinkdialog.h"

#include "iconloader_p.h"

#include <QtDesigner/abstractformeditor.h>
#include <QtDesigner/abstractsettings.h>

#include <QtWidgets/qcolordialog.h>
#include <QtWidgets/qcombobox.h>
#include <QtWidgets/qmenu.h>
#include <QtWidgets/qtabwidget.h>
#include <QtWidgets/qtoolbar.h>
#include <QtWidgets/qtoolbutton.h>
#include <QtWidgets/qboxlayout.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qdialogbuttonbox.h>

#include <QtGui/qaction.h>
#include <QtGui/qactiongroup.h>
#include <QtGui/qevent.h>
#include <QtGui/qfontdatabase.h>
#include <QtGui/qicon.h>
#include <QtGui/qpainter.h>
#include <QtGui/qtextcursor.h>
#include <QtGui/qtextdocument.h>
#include <QtGui/qtextobject.h>

#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
#include <QtCore/qpointer.h>
#include <QtCore/qxmlstream.h>

#include <algorithm>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

static constexpr auto RichTextDialogGroupC = "RichTextDialog"_L1;
static constexpr auto GeometryKeyC = "Geometry"_L1;
static constexpr auto TabKeyC = "Tab"_L1;

const bool simplifyRichTextDefault = true;

namespace qdesigner_internal {

// Richtext simplification filter helpers: Elements to be discarded
static inline bool filterElement(QStringView name)
{
    return name != "meta"_L1 && name != "style"_L1;
}

// Richtext simplification filter helpers: Filter attributes of elements
static inline void filterAttributes(QStringView name,
                                    QXmlStreamAttributes *atts,
                                    bool *paragraphAlignmentFound)
{
    if (atts->isEmpty())
        return;

     // No style attributes for <body>
    if (name == "body"_L1) {
        atts->clear();
        return;
    }

    // Clean out everything except 'align' for 'p'
    if (name == "p"_L1) {
        for (auto it = atts->begin(); it != atts->end(); ) {
            if (it->name() == "align"_L1) {
                ++it;
                *paragraphAlignmentFound = true;
            } else {
                it = atts->erase(it);
            }
        }
        return;
    }
}

// Richtext simplification filter helpers: Check for blank QStringView.
static inline bool isWhiteSpace(QStringView in)
{
    return std::all_of(in.cbegin(), in.cend(),
                       [](QChar c) { return c.isSpace(); });
}

// Richtext simplification filter: Remove hard-coded font settings,
// <style> elements, <p> attributes other than 'align' and
// and unnecessary meta-information.
QString simplifyRichTextFilter(const QString &in, bool *isPlainTextPtr = nullptr)
{
    unsigned elementCount = 0;
    bool paragraphAlignmentFound = false;
    QString out;
    QXmlStreamReader reader(in);
    QXmlStreamWriter writer(&out);
    writer.setAutoFormatting(false);
    writer.setAutoFormattingIndent(0);

    while (!reader.atEnd()) {
        switch (reader.readNext()) {
        case QXmlStreamReader::StartElement:
            elementCount++;
            if (filterElement(reader.name())) {
                const auto name = reader.name();
                QXmlStreamAttributes attributes = reader.attributes();
                filterAttributes(name, &attributes, &paragraphAlignmentFound);
                writer.writeStartElement(name.toString());
                if (!attributes.isEmpty())
                    writer.writeAttributes(attributes);
            } else {
                reader.readElementText(); // Skip away all nested elements and characters.
            }
            break;
        case QXmlStreamReader::Characters:
            if (!isWhiteSpace(reader.text()))
                writer.writeCharacters(reader.text().toString());
            break;
        case QXmlStreamReader::EndElement:
            writer.writeEndElement();
            break;
        default:
            break;
        }
    }
    // Check for plain text (no spans, just <html><head><body><p>)
    if (isPlainTextPtr)
        *isPlainTextPtr = !paragraphAlignmentFound && elementCount == 4u; //
    return out;
}

class RichTextEditor : public QTextEdit
{
    Q_OBJECT
public:
    explicit RichTextEditor(QWidget *parent = nullptr);
    void setDefaultFont(QFont font);

    QToolBar *createToolBar(QDesignerFormEditorInterface *core, QWidget *parent = nullptr);

    QString text(Qt::TextFormat format) const;

    bool simplifyRichText() const      { return m_simplifyRichText; }

public slots:
    void setFontBold(bool b);
    void setFontPointSize(double);
    void setText(const QString &text);
    void setSimplifyRichText(bool v);

signals:
    void stateChanged();
    void simplifyRichTextChanged(bool);

private:
    bool m_simplifyRichText;
};

class AddLinkDialog : public QDialog
{
    Q_OBJECT

public:
    AddLinkDialog(RichTextEditor *editor, QWidget *parent = nullptr);
    ~AddLinkDialog() override;

    int showDialog();

public slots:
    void accept() override;

private:
    RichTextEditor *m_editor;
    QT_PREPEND_NAMESPACE(Ui)::AddLinkDialog *m_ui;
};

AddLinkDialog::AddLinkDialog(RichTextEditor *editor, QWidget *parent) :
    QDialog(parent),
    m_ui(new QT_PREPEND_NAMESPACE(Ui)::AddLinkDialog)
{
    m_ui->setupUi(this);

    m_editor = editor;
}

AddLinkDialog::~AddLinkDialog()
{
    delete m_ui;
}

int AddLinkDialog::showDialog()
{
    // Set initial focus
    const QTextCursor cursor = m_editor->textCursor();
    if (cursor.hasSelection()) {
        m_ui->titleInput->setText(cursor.selectedText());
        m_ui->urlInput->setFocus();
    } else {
        m_ui->titleInput->setFocus();
    }

    return exec();
}

void AddLinkDialog::accept()
{
    const QString title = m_ui->titleInput->text();
    const QString url = m_ui->urlInput->text();

    if (!title.isEmpty()) {
        const QString html = "<a href=\""_L1 + url + "\">"_L1 + title + "</a>"_L1;
        m_editor->insertHtml(html);
    }

    m_ui->titleInput->clear();
    m_ui->urlInput->clear();

    QDialog::accept();
}

class HtmlTextEdit : public QTextEdit
{
    Q_OBJECT

public:
    HtmlTextEdit(QWidget *parent = nullptr)
        : QTextEdit(parent)
    {}

    void contextMenuEvent(QContextMenuEvent *event) override;

private slots:
    void actionTriggered(QAction *action);
};

void HtmlTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();
    QMenu *htmlMenu = new QMenu(tr("Insert HTML entity"), menu);

    typedef struct {
        const char *text;
        const char *entity;
    } Entry;

    const Entry entries[] = {
        { "&&amp; (&&)", "&amp;" },
        { "&&nbsp;", "&nbsp;" },
        { "&&lt; (<)", "&lt;" },
        { "&&gt; (>)", "&gt;" },
        { "&&copy; (Copyright)", "&copy;" },
        { "&&reg; (Trade Mark)", "&reg;" },
    };

    for (const Entry &e : entries) {
        QAction *entityAction = new QAction(QLatin1StringView(e.text),
                                            htmlMenu);
        entityAction->setData(QLatin1StringView(e.entity));
        htmlMenu->addAction(entityAction);
    }

    menu->addMenu(htmlMenu);
    connect(htmlMenu, &QMenu::triggered, this, &HtmlTextEdit::actionTriggered);
    menu->exec(event->globalPos());
    delete menu;
}

void HtmlTextEdit::actionTriggered(QAction *action)
{
    insertPlainText(action->data().toString());
}

class ColorAction : public QAction
{
    Q_OBJECT

public:
    ColorAction(QObject *parent);

    const QColor& color() const { return m_color; }
    void setColor(const QColor &color);

signals:
    void colorChanged(const QColor &color);

private slots:
    void chooseColor();

private:
    QColor m_color;
};

ColorAction::ColorAction(QObject *parent):
    QAction(parent)
{
    setText(tr("Text Color"));
    setColor(Qt::black);
    connect(this, &QAction::triggered, this, &ColorAction::chooseColor);
}

void ColorAction::setColor(const QColor &color)
{
    if (color == m_color)
        return;
    m_color = color;
    QPixmap pix(24, 24);
    QPainter painter(&pix);
    painter.setRenderHint(QPainter::Antialiasing, false);
    painter.fillRect(pix.rect(), m_color);
    painter.setPen(m_color.darker());
    painter.drawRect(pix.rect().adjusted(0, 0, -1, -1));
    setIcon(pix);
}

void ColorAction::chooseColor()
{
    const QColor col = QColorDialog::getColor(m_color, nullptr);
    if (col.isValid() && col != m_color) {
        setColor(col);
        emit colorChanged(m_color);
    }
}

class RichTextEditorToolBar : public QToolBar
{
    Q_OBJECT
public:
    RichTextEditorToolBar(QDesignerFormEditorInterface *core,
                          RichTextEditor *editor,
                          QWidget *parent = nullptr);

public slots:
    void updateActions();

private slots:
    void alignmentActionTriggered(QAction *action);
    void sizeInputActivated(const QString &size);
    void colorChanged(const QColor &color);
    void setVAlignSuper(bool super);
    void setVAlignSub(bool sub);
    void insertLink();
    void insertImage();
    void layoutDirectionChanged();

private:
    QAction *m_bold_action;
    QAction *m_italic_action;
    QAction *m_underline_action;
    QAction *m_valign_sup_action;
    QAction *m_valign_sub_action;
    QAction *m_align_left_action;
    QAction *m_align_center_action;
    QAction *m_align_right_action;
    QAction *m_align_justify_action;
    QAction *m_layoutDirectionAction;
    QAction *m_link_action;
    QAction *m_image_action;
    QAction *m_simplify_richtext_action;
    ColorAction *m_color_action;
    QComboBox *m_font_size_input;

    QDesignerFormEditorInterface *m_core;
    QPointer<RichTextEditor> m_editor;
};

static QAction *createCheckableAction(const QIcon &icon, const QString &text,
                                      QObject *parent = nullptr)
{
    QAction *result = new QAction(parent);
    result->setIcon(icon);
    result->setText(text);
    result->setCheckable(true);
    result->setChecked(false);
    return result;
}

RichTextEditorToolBar::RichTextEditorToolBar(QDesignerFormEditorInterface *core,
                                             RichTextEditor *editor,
                                             QWidget *parent) :
    QToolBar(parent),
    m_link_action(new QAction(this)),
    m_image_action(new QAction(this)),
    m_color_action(new ColorAction(this)),
    m_font_size_input(new QComboBox),
    m_core(core),
    m_editor(editor)
{
    // Font size combo box
    m_font_size_input->setEditable(false);
    const auto font_sizes = QFontDatabase::standardSizes();
    for (int font_size : font_sizes)
        m_font_size_input->addItem(QString::number(font_size));

    connect(m_font_size_input, &QComboBox::textActivated,
            this, &RichTextEditorToolBar::sizeInputActivated);
    addWidget(m_font_size_input);

    addSeparator();

    // Bold, italic and underline buttons

    m_bold_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatTextBold,
                          "textbold.png"_L1), tr("Bold"), this);
    connect(m_bold_action, &QAction::triggered, editor, &RichTextEditor::setFontBold);
    m_bold_action->setShortcut(tr("CTRL+B"));
    addAction(m_bold_action);

    m_italic_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatTextItalic,
                          "textitalic.png"_L1), tr("Italic"), this);
    connect(m_italic_action, &QAction::triggered, editor, &RichTextEditor::setFontItalic);
    m_italic_action->setShortcut(tr("CTRL+I"));
    addAction(m_italic_action);

    m_underline_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatTextUnderline,
                          "textunder.png"_L1), tr("Underline"), this);
    connect(m_underline_action, &QAction::triggered, editor, &RichTextEditor::setFontUnderline);
    m_underline_action->setShortcut(tr("CTRL+U"));
    addAction(m_underline_action);

    addSeparator();

    // Left, center, right and justified alignment buttons

    QActionGroup *alignment_group = new QActionGroup(this);
    connect(alignment_group, &QActionGroup::triggered,
            this, &RichTextEditorToolBar::alignmentActionTriggered);

    m_align_left_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatJustifyLeft,
                          "textleft.png"_L1), tr("Left Align"), alignment_group);
    addAction(m_align_left_action);

    m_align_center_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatJustifyCenter,
                          "textcenter.png"_L1), tr("Center"), alignment_group);
    addAction(m_align_center_action);

    m_align_right_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatJustifyRight,
                          "textright.png"_L1), tr("Right Align"), alignment_group);
    addAction(m_align_right_action);

    m_align_justify_action = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatJustifyFill,
                          "textjustify.png"_L1), tr("Justify"), alignment_group);
    addAction(m_align_justify_action);

    m_layoutDirectionAction = createCheckableAction(
            createIconSet(QIcon::ThemeIcon::FormatTextDirectionRtl,
                          "righttoleft.png"_L1), tr("Right to Left"));
    connect(m_layoutDirectionAction, &QAction::triggered,
            this, &RichTextEditorToolBar::layoutDirectionChanged);
    addAction(m_layoutDirectionAction);

    addSeparator();

    // Superscript and subscript buttons

    m_valign_sup_action = createCheckableAction(
            createIconSet("textsuperscript.png"_L1), tr("Superscript"), this);
    connect(m_valign_sup_action, &QAction::triggered,
            this, &RichTextEditorToolBar::setVAlignSuper);
    addAction(m_valign_sup_action);

    m_valign_sub_action = createCheckableAction(
            createIconSet("textsubscript.png"_L1), tr("Subscript"), this);
    connect(m_valign_sub_action, &QAction::triggered,
            this, &RichTextEditorToolBar::setVAlignSub);
    addAction(m_valign_sub_action);

    addSeparator();

    // Insert hyperlink and image buttons

    m_link_action->setIcon(createIconSet("textanchor.png"_L1));
    m_link_action->setText(tr("Insert &Link"));
    connect(m_link_action, &QAction::triggered, this, &RichTextEditorToolBar::insertLink);
    addAction(m_link_action);

    m_image_action->setIcon(createIconSet("insertimage.png"_L1));
    m_image_action->setText(tr("Insert &Image"));
    connect(m_image_action, &QAction::triggered, this, &RichTextEditorToolBar::insertImage);
    addAction(m_image_action);

    addSeparator();

    // Text color button
    connect(m_color_action, &ColorAction::colorChanged,
            this, &RichTextEditorToolBar::colorChanged);
    addAction(m_color_action);

    addSeparator();

    // Simplify rich text
    m_simplify_richtext_action
        = createCheckableAction(createIconSet("simplifyrichtext.png"_L1), tr("Simplify Rich Text"));
    connect(m_simplify_richtext_action, &QAction::triggered,
            m_editor, &RichTextEditor::setSimplifyRichText);
    m_simplify_richtext_action->setChecked(m_editor->simplifyRichText());
    connect(m_editor.data(), &RichTextEditor::simplifyRichTextChanged,
            m_simplify_richtext_action, &QAction::setChecked);
    addAction(m_simplify_richtext_action);

    connect(editor, &QTextEdit::textChanged, this, &RichTextEditorToolBar::updateActions);
    connect(editor, &RichTextEditor::stateChanged, this, &RichTextEditorToolBar::updateActions);

    updateActions();
}

void RichTextEditorToolBar::alignmentActionTriggered(QAction *action)
{
    Qt::Alignment new_alignment;

    if (action == m_align_left_action) {
        new_alignment = Qt::AlignLeft;
    } else if (action == m_align_center_action) {
        new_alignment = Qt::AlignCenter;
    } else if (action == m_align_right_action) {
        new_alignment = Qt::AlignRight;
    } else {
        new_alignment = Qt::AlignJustify;
    }

    m_editor->setAlignment(new_alignment);
}

void RichTextEditorToolBar::colorChanged(const QColor &color)
{
    m_editor->setTextColor(color);
    m_editor->setFocus();
}

void RichTextEditorToolBar::sizeInputActivated(const QString &size)
{
    bool ok;
    int i = size.toInt(&ok);
    if (!ok)
        return;

    m_editor->setFontPointSize(i);
    m_editor->setFocus();
}

void RichTextEditorToolBar::setVAlignSuper(bool super)
{
    const QTextCharFormat::VerticalAlignment align = super ?
        QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal;

    QTextCharFormat charFormat = m_editor->currentCharFormat();
    charFormat.setVerticalAlignment(align);
    m_editor->setCurrentCharFormat(charFormat);

    m_valign_sub_action->setChecked(false);
}

void RichTextEditorToolBar::setVAlignSub(bool sub)
{
    const QTextCharFormat::VerticalAlignment align = sub ?
        QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal;

    QTextCharFormat charFormat = m_editor->currentCharFormat();
    charFormat.setVerticalAlignment(align);
    m_editor->setCurrentCharFormat(charFormat);

    m_valign_sup_action->setChecked(false);
}

void RichTextEditorToolBar::insertLink()
{
    AddLinkDialog linkDialog(m_editor, this);
    linkDialog.showDialog();
    m_editor->setFocus();
}

void RichTextEditorToolBar::insertImage()
{
    const QString path = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), QString(), this);
    if (!path.isEmpty())
        m_editor->insertHtml(QStringLiteral("<img src=\"") + path + QStringLiteral("\"/>"));
}

void RichTextEditorToolBar::layoutDirectionChanged()
{
    QTextCursor cursor = m_editor->textCursor();
    QTextBlock block = cursor.block();
    if (block.isValid()) {
        QTextBlockFormat format = block.blockFormat();
        const Qt::LayoutDirection newDirection = m_layoutDirectionAction->isChecked() ? Qt::RightToLeft : Qt::LeftToRight;
        if (format.layoutDirection() != newDirection) {
            format.setLayoutDirection(newDirection);
            cursor.setBlockFormat(format);
        }
    }
}

void RichTextEditorToolBar::updateActions()
{
    if (m_editor == nullptr) {
        setEnabled(false);
        return;
    }

    const Qt::Alignment alignment = m_editor->alignment();
    const QTextCursor cursor = m_editor->textCursor();
    const QTextCharFormat charFormat = cursor.charFormat();
    const QFont font = charFormat.font();
    const QTextCharFormat::VerticalAlignment valign =
        charFormat.verticalAlignment();
    const bool superScript = valign == QTextCharFormat::AlignSuperScript;
    const bool subScript = valign == QTextCharFormat::AlignSubScript;

    if (alignment & Qt::AlignLeft) {
        m_align_left_action->setChecked(true);
    } else if (alignment & Qt::AlignRight) {
        m_align_right_action->setChecked(true);
    } else if (alignment & Qt::AlignHCenter) {
        m_align_center_action->setChecked(true);
    } else {
        m_align_justify_action->setChecked(true);
    }
    m_layoutDirectionAction->setChecked(cursor.blockFormat().layoutDirection() == Qt::RightToLeft);

    m_bold_action->setChecked(font.bold());
    m_italic_action->setChecked(font.italic());
    m_underline_action->setChecked(font.underline());
    m_valign_sup_action->setChecked(superScript);
    m_valign_sub_action->setChecked(subScript);

    const int size = font.pointSize();
    const int idx = m_font_size_input->findText(QString::number(size));
    if (idx != -1)
        m_font_size_input->setCurrentIndex(idx);

    m_color_action->setColor(m_editor->textColor());
}

RichTextEditor::RichTextEditor(QWidget *parent)
    : QTextEdit(parent), m_simplifyRichText(simplifyRichTextDefault)
{
    connect(this, &RichTextEditor::currentCharFormatChanged,
            this, &RichTextEditor::stateChanged);
    connect(this, &RichTextEditor::cursorPositionChanged,
            this, &RichTextEditor::stateChanged);
}

QToolBar *RichTextEditor::createToolBar(QDesignerFormEditorInterface *core, QWidget *parent)
{
    return new RichTextEditorToolBar(core, this, parent);
}

void RichTextEditor::setFontBold(bool b)
{
    if (b)
        setFontWeight(QFont::Bold);
    else
        setFontWeight(QFont::Normal);
}

void RichTextEditor::setFontPointSize(double d)
{
    QTextEdit::setFontPointSize(qreal(d));
}

void RichTextEditor::setText(const QString &text)
{

    if (Qt::mightBeRichText(text))
        setHtml(text);
    else
        setPlainText(text);
}

void RichTextEditor::setSimplifyRichText(bool v)
{
    if (v != m_simplifyRichText) {
        m_simplifyRichText = v;
        emit simplifyRichTextChanged(v);
    }
}

void RichTextEditor::setDefaultFont(QFont font)
{
    // Some default fonts on Windows have a default size of 7.8,
    // which results in complicated rich text generated by toHtml().
    // Use an integer value.
    const int pointSize = qRound(font.pointSizeF());
    if (pointSize > 0 && !qFuzzyCompare(qreal(pointSize), font.pointSizeF())) {
        font.setPointSize(pointSize);
    }

    document()->setDefaultFont(font);
    if (font.pointSize() > 0)
        setFontPointSize(font.pointSize());
    else
        setFontPointSize(QFontInfo(font).pointSize());
    emit textChanged();
}

QString RichTextEditor::text(Qt::TextFormat format) const
{
    switch (format) {
    case Qt::PlainText:
        return toPlainText();
    case Qt::RichText:
        return m_simplifyRichText ? simplifyRichTextFilter(toHtml()) : toHtml();
    default:
        break;
    }
    const QString html = toHtml();
    bool isPlainText;
    const QString simplifiedHtml = simplifyRichTextFilter(html, &isPlainText);
    if (isPlainText)
        return toPlainText();
    return m_simplifyRichText ? simplifiedHtml : html;
}

RichTextEditorDialog::RichTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent)  :
    QDialog(parent),
    m_editor(new RichTextEditor()),
    m_text_edit(new HtmlTextEdit),
    m_tab_widget(new QTabWidget),
    m_state(Clean),
    m_core(core),
    m_initialTab(RichTextIndex)
{
    setWindowTitle(tr("Edit text"));

    // Read settings
    const QDesignerSettingsInterface *settings = core->settingsManager();
    const QString rootKey = RichTextDialogGroupC + u'/';
    const QByteArray lastGeometry = settings->value(rootKey + GeometryKeyC).toByteArray();
    const int initialTab = settings->value(rootKey + TabKeyC, QVariant(m_initialTab)).toInt();
    if (initialTab == RichTextIndex || initialTab == SourceIndex)
        m_initialTab = initialTab;

    m_text_edit->setAcceptRichText(false);
    new HtmlHighlighter(m_text_edit);

    connect(m_editor, &QTextEdit::textChanged, this, &RichTextEditorDialog::richTextChanged);
    connect(m_editor, &RichTextEditor::simplifyRichTextChanged,
            this, &RichTextEditorDialog::richTextChanged);
    connect(m_text_edit, &QTextEdit::textChanged, this, &RichTextEditorDialog::sourceChanged);

    // The toolbar needs to be created after the RichTextEditor
    QToolBar *tool_bar = m_editor->createToolBar(core);
    tool_bar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);

    QWidget *rich_edit = new QWidget;
    QVBoxLayout *rich_edit_layout = new QVBoxLayout(rich_edit);
    rich_edit_layout->addWidget(tool_bar);
    rich_edit_layout->addWidget(m_editor);

    QWidget *plain_edit = new QWidget;
    QVBoxLayout *plain_edit_layout = new QVBoxLayout(plain_edit);
    plain_edit_layout->addWidget(m_text_edit);

    m_tab_widget->setTabPosition(QTabWidget::South);
    m_tab_widget->addTab(rich_edit, tr("Rich Text"));
    m_tab_widget->addTab(plain_edit, tr("Source"));
    connect(m_tab_widget, &QTabWidget::currentChanged,
            this, &RichTextEditorDialog::tabIndexChanged);

    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
    QPushButton *ok_button = buttonBox->button(QDialogButtonBox::Ok);
    ok_button->setText(tr("&OK"));
    ok_button->setDefault(true);
    buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("&Cancel"));
    connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);

    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(m_tab_widget);
    layout->addWidget(buttonBox);

    if (!lastGeometry.isEmpty())
        restoreGeometry(lastGeometry);
}

RichTextEditorDialog::~RichTextEditorDialog()
{
    QDesignerSettingsInterface *settings = m_core->settingsManager();
    settings->beginGroup(RichTextDialogGroupC);

    settings->setValue(GeometryKeyC, saveGeometry());
    settings->setValue(TabKeyC, m_tab_widget->currentIndex());
    settings->endGroup();
}

int RichTextEditorDialog::showDialog()
{
    m_tab_widget->setCurrentIndex(m_initialTab);
    switch (m_initialTab) {
    case RichTextIndex:
        m_editor->selectAll();
        m_editor->setFocus();
        break;
    case SourceIndex:
        m_text_edit->selectAll();
        m_text_edit->setFocus();
        break;
    }
    return exec();
}

void RichTextEditorDialog::setDefaultFont(const QFont &font)
{
    m_editor->setDefaultFont(font);
}

void RichTextEditorDialog::setText(const QString &text)
{
    // Generally simplify rich text unless verbose text is found.
    const bool isSimplifiedRichText = !text.startsWith(QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"));
    m_editor->setSimplifyRichText(isSimplifiedRichText);
    m_editor->setText(text);
    m_text_edit->setPlainText(text);
    m_state = Clean;
}

QString RichTextEditorDialog::text(Qt::TextFormat format) const
{
    // In autotext mode, if the user has changed the source, use that
    if (format == Qt::AutoText && (m_state == Clean || m_state == SourceChanged))
        return m_text_edit->toPlainText();
    // If the plain text HTML editor is selected, first copy its contents over
    // to the rich text editor so that it is converted to Qt-HTML or actual
    // plain text.
    if (m_tab_widget->currentIndex() == SourceIndex && m_state == SourceChanged)
        m_editor->setHtml(m_text_edit->toPlainText());
    return m_editor->text(format);
}

void RichTextEditorDialog::tabIndexChanged(int newIndex)
{
    // Anything changed, is there a need for a conversion?
    if (newIndex == SourceIndex && m_state != RichTextChanged)
        return;
    if (newIndex == RichTextIndex && m_state != SourceChanged)
        return;
    const State oldState = m_state;
    // Remember the cursor position, since it is invalidated by setPlainText
    QTextEdit *new_edit = (newIndex == SourceIndex) ? m_text_edit : m_editor;
    const int position = new_edit->textCursor().position();

    if (newIndex == SourceIndex)
        m_text_edit->setPlainText(m_editor->text(Qt::RichText));
    else
        m_editor->setHtml(m_text_edit->toPlainText());

    QTextCursor cursor = new_edit->textCursor();
    cursor.movePosition(QTextCursor::End);
    if (cursor.position() > position) {
        cursor.setPosition(position);
    }
    new_edit->setTextCursor(cursor);
    m_state = oldState; // Changed is triggered by setting the text
}

void RichTextEditorDialog::richTextChanged()
{
    m_state = RichTextChanged;
}

void RichTextEditorDialog::sourceChanged()
{
    m_state = SourceChanged;
}

} // namespace qdesigner_internal

QT_END_NAMESPACE

#include "richtexteditor.moc"
