diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 85e0c67ec9e..f28f5733ec5 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -588,6 +588,13 @@ extend_qtc_plugin(QmlDesigner texttool/texttool.cpp texttool/texttool.h ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/richtexteditor + SOURCES + hyperlinkdialog.cpp hyperlinkdialog.h hyperlinkdialog.ui + richtexteditor.cpp richtexteditor.h hyperlinkdialog.ui +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/timelineeditor SOURCES diff --git a/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.cpp b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.cpp new file mode 100644 index 00000000000..82d24f6839e --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "hyperlinkdialog.h" +#include "ui_hyperlinkdialog.h" + +#include + +namespace QmlDesigner { + + +HyperlinkDialog::HyperlinkDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::HyperlinkDialog) +{ + ui->setupUi(this); + connect (ui->linkEdit, &QLineEdit::textChanged, [this] () { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!(ui->linkEdit->text().isEmpty())); + }); +} + +HyperlinkDialog::~HyperlinkDialog() +{ + delete ui; +} + +QString HyperlinkDialog::getLink() const +{ + return ui->linkEdit->text().trimmed(); +} + +void HyperlinkDialog::setLink(const QString &link) +{ + ui->linkEdit->setText(link); +} + +QString HyperlinkDialog::getAnchor() const +{ + return ui->anchorEdit->text().trimmed(); +} + +void HyperlinkDialog::setAnchor(const QString &anchor) +{ + ui->anchorEdit->setText(anchor); +} + +} diff --git a/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.h b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.h new file mode 100644 index 00000000000..dcf8759e492 --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include + +namespace QmlDesigner { + +namespace Ui { +class HyperlinkDialog; +} + +class HyperlinkDialog : public QDialog +{ + Q_OBJECT + +public: + explicit HyperlinkDialog(QWidget *parent = nullptr); + ~HyperlinkDialog(); + + QString getLink() const; + void setLink(const QString &link); + + QString getAnchor() const; + void setAnchor(const QString &anchor); + +private: + Ui::HyperlinkDialog *ui; +}; + +} diff --git a/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.ui b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.ui new file mode 100644 index 00000000000..7906a1fccb1 --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/hyperlinkdialog.ui @@ -0,0 +1,88 @@ + + + QmlDesigner::HyperlinkDialog + + + + 0 + 0 + 403 + 156 + + + + Dialog + + + + + + + + Link + + + + + + + + + + Anchor + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QmlDesigner::HyperlinkDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QmlDesigner::HyperlinkDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.cpp b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.cpp new file mode 100644 index 00000000000..66d8ca70d0d --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.cpp @@ -0,0 +1,684 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "richtexteditor.h" +#include "ui_richtexteditor.h" +#include "hyperlinkdialog.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace QmlDesigner { + +template +class FontWidgetActions : public QWidgetAction { +public: + FontWidgetActions(QObject *parent = nullptr) + : QWidgetAction(parent) {} + + ~FontWidgetActions () override {} + + void setInitializer(std::function func) + { + m_initializer = func; + } + + QList createdWidgets() + { + return QWidgetAction::createdWidgets(); + } + +protected: + QWidget *createWidget(QWidget *parent) override + { + T *w = new T(parent); + if (m_initializer) + m_initializer(w); + return w; + } + + void deleteWidget(QWidget *widget) override + { + widget->deleteLater(); + } + +private: + std::function m_initializer; +}; + +static void cursorEditBlock(QTextCursor& cursor, std::function f) { + cursor.beginEditBlock(); + f(); + cursor.endEditBlock(); +} + +RichTextEditor::RichTextEditor(QWidget *parent) + : QWidget(parent) + , ui(new Ui::RichTextEditor) + , m_linkDialog(new HyperlinkDialog(this)) +{ + ui->setupUi(this); + ui->textEdit->setTextInteractionFlags(Qt::TextEditorInteraction | Qt::LinksAccessibleByMouse); + ui->tableBar->setVisible(false); + + setupEditActions(); + setupTextActions(); + setupHyperlinkActions(); + setupAlignActions(); + setupListActions(); + setupFontActions(); + setupTableActions(); + + connect(ui->textEdit, &QTextEdit::currentCharFormatChanged, + this, &RichTextEditor::currentCharFormatChanged); + connect(ui->textEdit, &QTextEdit::cursorPositionChanged, + this, &RichTextEditor::cursorPositionChanged); + connect(m_linkDialog, &QDialog::accepted, [this]() { + QTextCharFormat oldFormat = ui->textEdit->textCursor().charFormat(); + + QTextCursor tcursor = ui->textEdit->textCursor(); + QTextCharFormat charFormat = tcursor.charFormat(); + + charFormat.setForeground(QApplication::palette().color(QPalette::Link)); + charFormat.setFontUnderline(true); + + QString link = m_linkDialog->getLink(); + QString anchor = m_linkDialog->getAnchor(); + + if (anchor.isEmpty()) + anchor = link; + + charFormat.setAnchor(true); + charFormat.setAnchorHref(link); + charFormat.setAnchorNames(QStringList(anchor)); + + tcursor.insertText(anchor, charFormat); + + tcursor.insertText(" ", oldFormat); + + m_linkDialog->hide(); + }); + + ui->textEdit->setFocus(); + m_linkDialog->hide(); +} + +RichTextEditor::~RichTextEditor() +{ +} + +void RichTextEditor::setPlainText(const QString &text) +{ + ui->textEdit->setPlainText(text); +} + +QString RichTextEditor::plainText() const +{ + return ui->textEdit->toPlainText(); +} + +void RichTextEditor::setRichText(const QString &text) +{ + ui->textEdit->setHtml(text); +} + +void RichTextEditor::setTabChangesFocus(bool change) +{ + ui->textEdit->setTabChangesFocus(change); +} + +QPixmap RichTextEditor::getIcon(const QString &iconName) +{ + const QString fontName = "qtds_propertyIconFont.ttf"; + + return Utils::StyleHelper::getIconFromIconFont(fontName, iconName, 36, 36); +} + +QString RichTextEditor::richText() const +{ + return ui->textEdit->toHtml(); +} + +void RichTextEditor::currentCharFormatChanged(const QTextCharFormat &format) +{ + fontChanged(format.font()); + colorChanged(format.foreground().color()); +} + +void RichTextEditor::cursorPositionChanged() +{ + alignmentChanged(ui->textEdit->alignment()); + styleChanged(ui->textEdit->textCursor()); + tableChanged(ui->textEdit->textCursor()); +} + +void RichTextEditor::mergeFormatOnWordOrSelection(const QTextCharFormat &format) +{ + QTextCursor cursor = ui->textEdit->textCursor(); + if (!cursor.hasSelection()) + cursor.select(QTextCursor::WordUnderCursor); + cursor.mergeCharFormat(format); + ui->textEdit->mergeCurrentCharFormat(format); +} + +void RichTextEditor::fontChanged(const QFont &f) +{ + for (QWidget* w: m_fontNameAction->createdWidgets() ) { + QFontComboBox* box = qobject_cast(w); + if (box) + box->setCurrentFont(f); + } + for (QWidget* w: m_fontSizeAction->createdWidgets() ) { + QComboBox* box = qobject_cast(w); + if (box) + box->setCurrentText(QString::number(f.pointSize())); + } + + m_actionTextBold->setChecked(f.bold()); + m_actionTextItalic->setChecked(f.italic()); + m_actionTextUnderline->setChecked(f.underline()); +} + +void RichTextEditor::colorChanged(const QColor &c) +{ + QPixmap colorBox(ui->tableBar->iconSize()); + colorBox.fill(c); + m_actionTextColor->setIcon(colorBox); +} + +void RichTextEditor::alignmentChanged(Qt::Alignment a) +{ + if (a & Qt::AlignLeft) + m_actionAlignLeft->setChecked(true); + else if (a & Qt::AlignHCenter) + m_actionAlignCenter->setChecked(true); + else if (a & Qt::AlignRight) + m_actionAlignRight->setChecked(true); + else if (a & Qt::AlignJustify) + m_actionAlignJustify->setChecked(true); +} + +void RichTextEditor::styleChanged(const QTextCursor &cursor) +{ + if (!m_actionBulletList || !m_actionNumberedList) return; + + QTextList *currentList = cursor.currentList(); + + if (currentList) { + if (currentList->format().style() == QTextListFormat::ListDisc) { + m_actionBulletList->setChecked(true); + m_actionNumberedList->setChecked(false); + } + else if (currentList->format().style() == QTextListFormat::ListDecimal) { + m_actionBulletList->setChecked(false); + m_actionNumberedList->setChecked(true); + } + else { + m_actionBulletList->setChecked(false); + m_actionNumberedList->setChecked(false); + } + } + else { + m_actionBulletList->setChecked(false); + m_actionNumberedList->setChecked(false); + } +} + +void RichTextEditor::tableChanged(const QTextCursor &cursor) +{ + if (!m_actionTableSettings) return; + + QTextTable *currentTable = cursor.currentTable(); + + if (currentTable) { + m_actionTableSettings->setChecked(true); + ui->tableBar->setVisible(true); + + setTableActionsActive(true); + } + else { + setTableActionsActive(false); + } +} + +void RichTextEditor::setupEditActions() +{ + const QIcon undoIcon(getIcon("\u005F")); + QAction *actionUndo = ui->toolBar->addAction(undoIcon, tr("&Undo"), ui->textEdit, &QTextEdit::undo); + actionUndo->setShortcut(QKeySequence::Undo); + connect(ui->textEdit->document(), &QTextDocument::undoAvailable, + actionUndo, &QAction::setEnabled); + + const QIcon redoIcon(getIcon("\u0050")); + QAction *actionRedo = ui->toolBar->addAction(redoIcon, tr("&Redo"), ui->textEdit, &QTextEdit::redo); + actionRedo->setShortcut(QKeySequence::Redo); + connect(ui->textEdit->document(), &QTextDocument::redoAvailable, + actionRedo, &QAction::setEnabled); + + actionUndo->setEnabled(ui->textEdit->document()->isUndoAvailable()); + actionRedo->setEnabled(ui->textEdit->document()->isRedoAvailable()); + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupTextActions() +{ + const QIcon boldIcon(getIcon("\u004B")); + m_actionTextBold = ui->toolBar->addAction(boldIcon, tr("&Bold"), + [this](bool checked) { + QTextCharFormat fmt; + fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal); + mergeFormatOnWordOrSelection(fmt); + }); + m_actionTextBold->setShortcut(Qt::CTRL + Qt::Key_B); + QFont bold; + bold.setBold(true); + m_actionTextBold->setFont(bold); + m_actionTextBold->setCheckable(true); + + const QIcon italicIcon(getIcon("\u004C")); + m_actionTextItalic = ui->toolBar->addAction(italicIcon, tr("&Italic"), + [this](bool checked) { + QTextCharFormat fmt; + fmt.setFontItalic(checked); + mergeFormatOnWordOrSelection(fmt); + }); + m_actionTextItalic->setShortcut(Qt::CTRL + Qt::Key_I); + QFont italic; + italic.setItalic(true); + m_actionTextItalic->setFont(italic); + m_actionTextItalic->setCheckable(true); + + const QIcon underlineIcon(getIcon("\u004E")); + m_actionTextUnderline = ui->toolBar->addAction(underlineIcon, tr("&Underline"), + [this](bool checked) { + QTextCharFormat fmt; + fmt.setFontUnderline(checked); + mergeFormatOnWordOrSelection(fmt); + }); + m_actionTextUnderline->setShortcut(Qt::CTRL + Qt::Key_U); + QFont underline; + underline.setUnderline(true); + m_actionTextUnderline->setFont(underline); + m_actionTextUnderline->setCheckable(true); + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupHyperlinkActions() +{ + const QIcon bulletIcon(getIcon("\u0022")); + m_actionHyperlink = ui->toolBar->addAction(bulletIcon, tr("Hyperlink Settings"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + QTextCharFormat linkFormat = cursor.charFormat(); + if (linkFormat.isAnchor()) { + m_linkDialog->setLink(linkFormat.anchorHref()); + m_linkDialog->setAnchor(linkFormat.anchorName()); + } + else { + m_linkDialog->setLink("http://"); + m_linkDialog->setAnchor(""); + } + + m_linkDialog->show(); + }); + m_actionHyperlink->setCheckable(false); + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupAlignActions() +{ + const QIcon leftIcon(getIcon("\u0056")); + m_actionAlignLeft = ui->toolBar->addAction(leftIcon, tr("&Left"), [this]() { ui->textEdit->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute); }); + m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); + m_actionAlignLeft->setCheckable(true); + m_actionAlignLeft->setPriority(QAction::LowPriority); + + const QIcon centerIcon(getIcon("\u0055")); + m_actionAlignCenter = ui->toolBar->addAction(centerIcon, tr("C&enter"), [this]() { ui->textEdit->setAlignment(Qt::AlignHCenter); }); + m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); + m_actionAlignCenter->setCheckable(true); + m_actionAlignCenter->setPriority(QAction::LowPriority); + + const QIcon rightIcon(getIcon("\u0058")); + m_actionAlignRight = ui->toolBar->addAction(rightIcon, tr("&Right"), [this]() { ui->textEdit->setAlignment(Qt::AlignRight | Qt::AlignAbsolute); }); + m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); + m_actionAlignRight->setCheckable(true); + m_actionAlignRight->setPriority(QAction::LowPriority); + + const QIcon fillIcon(getIcon("\u005B")); + m_actionAlignJustify = ui->toolBar->addAction(fillIcon, tr("&Justify"), [this]() { ui->textEdit->setAlignment(Qt::AlignJustify); }); + m_actionAlignJustify->setShortcut(Qt::CTRL + Qt::Key_J); + m_actionAlignJustify->setCheckable(true); + m_actionAlignJustify->setPriority(QAction::LowPriority); + + // Make sure the alignLeft is always left of the alignRight + QActionGroup *alignGroup = new QActionGroup(ui->toolBar); + + if (QApplication::isLeftToRight()) { + alignGroup->addAction(m_actionAlignLeft); + alignGroup->addAction(m_actionAlignCenter); + alignGroup->addAction(m_actionAlignRight); + } else { + alignGroup->addAction(m_actionAlignRight); + alignGroup->addAction(m_actionAlignCenter); + alignGroup->addAction(m_actionAlignLeft); + } + alignGroup->addAction(m_actionAlignJustify); + + ui->toolBar->addActions(alignGroup->actions()); + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupListActions() +{ + const QIcon bulletIcon(getIcon("\u005A")); + m_actionBulletList = ui->toolBar->addAction(bulletIcon, tr("Bullet List"), [this](bool checked) { + if (checked) { + m_actionNumberedList->setChecked(false); + textStyle(QTextListFormat::ListDisc); + } + else if (!m_actionNumberedList->isChecked()) { + textStyle(QTextListFormat::ListStyleUndefined); + } + }); + m_actionBulletList->setCheckable(true); + + const QIcon numberedIcon(getIcon("\u005C")); + m_actionNumberedList = ui->toolBar->addAction(numberedIcon, tr("Numbered List"), [this](bool checked) { + if (checked) { + m_actionBulletList->setChecked(false); + textStyle(QTextListFormat::ListDecimal); + } + else if (!m_actionBulletList->isChecked()) { + textStyle(QTextListFormat::ListStyleUndefined); + } + }); + m_actionNumberedList->setCheckable(true); + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupFontActions() +{ + QPixmap colorBox(ui->tableBar->iconSize()); + colorBox.fill(ui->textEdit->textColor()); + + m_actionTextColor = ui->toolBar->addAction(colorBox, tr("&Color..."), [this]() { + QColor col = QColorDialog::getColor(ui->textEdit->textColor(), this); + if (!col.isValid()) + return; + QTextCharFormat fmt; + fmt.setForeground(col); + mergeFormatOnWordOrSelection(fmt); + colorChanged(col); + }); + + m_fontNameAction = new FontWidgetActions(this); + m_fontNameAction->setInitializer([this](QFontComboBox *w) { + if (!w) return; + + w->setCurrentIndex(w->findText(ui->textEdit->currentCharFormat().font().family())); + connect(w, QOverload::of(&QComboBox::activated), [this](const QString &f) { + QTextCharFormat fmt; + fmt.setFontFamily(f); + mergeFormatOnWordOrSelection(fmt); + }); + }); + + m_fontNameAction->setDefaultWidget(new QFontComboBox); + ui->toolBar->addAction(m_fontNameAction); + + m_fontSizeAction = new FontWidgetActions(this); + m_fontSizeAction->setInitializer([this](QComboBox *w) { + if (!w) return; + + w->setEditable(true); + + const QList standardSizes = QFontDatabase::standardSizes(); + foreach (int size, standardSizes) + w->addItem(QString::number(size)); + w->setCurrentText(QString::number(ui->textEdit->currentCharFormat().font().pointSize())); + connect(w, QOverload::of(&QComboBox::activated), [this](const QString &p) { + qreal pointSize = p.toDouble(); + if (pointSize > 0.0) { + QTextCharFormat fmt; + fmt.setFontPointSize(pointSize); + mergeFormatOnWordOrSelection(fmt); + } + }); + }); + + m_fontSizeAction->setDefaultWidget(new QComboBox); + ui->toolBar->addAction(m_fontSizeAction); + + + ui->toolBar->addSeparator(); +} + +void RichTextEditor::setupTableActions() +{ + const QIcon tableIcon(getIcon("\u0028")); + m_actionTableSettings = ui->toolBar->addAction(tableIcon, tr("&Table Settings"), [this](bool checked) { + ui->tableBar->setVisible(checked); + }); + m_actionTableSettings->setShortcut(Qt::CTRL + Qt::Key_T); + m_actionTableSettings->setCheckable(true); + m_actionTableSettings->setPriority(QAction::LowPriority); + +//table bar: + + const QIcon createTableIcon(getIcon("\u0028")); + m_actionCreateTable = ui->tableBar->addAction(createTableIcon, tr("Create Table"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + cursorEditBlock(cursor, [&] () { + cursor.insertTable(1,1); + }); + }); + m_actionCreateTable->setCheckable(false); + + const QIcon removeTableIcon(getIcon("\u003D")); + m_actionRemoveTable = ui->tableBar->addAction(removeTableIcon, tr("Remove Table"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = ui->textEdit->textCursor().currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->removeRows(0, currentTable->rows()); + }); + } + }); + m_actionRemoveTable->setCheckable(false); + + ui->tableBar->addSeparator(); + + const QIcon addRowIcon(getIcon("\u0026")); //addRowAfter + m_actionAddRow = ui->tableBar->addAction(addRowIcon, tr("Add Row"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = ui->textEdit->textCursor().currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->insertRows(currentTable->cellAt(cursor).row()+1, 1); + }); + } + }); + m_actionAddRow->setCheckable(false); + + const QIcon addColumnIcon(getIcon("\u0023")); //addColumnAfter + m_actionAddColumn = ui->tableBar->addAction(addColumnIcon, tr("Add Column"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = ui->textEdit->textCursor().currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->insertColumns(currentTable->cellAt(cursor).column()+1, 1); + }); + } + }); + m_actionAddColumn->setCheckable(false); + + const QIcon removeRowIcon(getIcon("\u003C")); + m_actionRemoveRow = ui->tableBar->addAction(removeRowIcon, tr("Remove Row"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = cursor.currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->insertColumns(currentTable->cellAt(cursor).column()+1, 1); + + int firstRow = 0; + int numRows = 0; + int firstColumn = 0; + int numColumns = 0; + + if (cursor.hasSelection()) + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + + if (numRows < 1) + currentTable->removeRows(currentTable->cellAt(cursor).row(), 1); + else + currentTable->removeRows(firstRow, numRows); + }); + } + }); + m_actionRemoveRow->setCheckable(false); + + const QIcon removeColumnIcon(getIcon("\u003B")); + m_actionRemoveColumn = ui->tableBar->addAction(removeColumnIcon, tr("Remove Column"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = cursor.currentTable()) { + cursorEditBlock(cursor, [&] () { + int firstRow = 0; + int numRows = 0; + int firstColumn = 0; + int numColumns = 0; + + if (cursor.hasSelection()) + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + + if (numColumns < 1) + currentTable->removeColumns(currentTable->cellAt(cursor).column(), 1); + else + currentTable->removeColumns(firstColumn, numColumns); + }); + } + }); + m_actionRemoveColumn->setCheckable(false); + + ui->tableBar->addSeparator(); + + const QIcon mergeCellsIcon(getIcon("\u004F")); + m_actionMergeCells = ui->tableBar->addAction(mergeCellsIcon, tr("Merge Cells"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = cursor.currentTable()) { + if (cursor.hasSelection()) { + cursorEditBlock(cursor, [&] () { + currentTable->mergeCells(cursor); + }); + } + } + }); + m_actionMergeCells->setCheckable(false); + + const QIcon splitRowIcon(getIcon("\u0052")); + m_actionSplitRow = ui->tableBar->addAction(splitRowIcon, tr("Split Row"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = cursor.currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->splitCell(currentTable->cellAt(cursor).row(), + currentTable->cellAt(cursor).column(), + 2, 1); + }); + } + }); + m_actionSplitRow->setCheckable(false); + + const QIcon splitColumnIcon(getIcon("\u0051")); + m_actionSplitColumn = ui->tableBar->addAction(splitRowIcon, tr("Split Column"), [this]() { + QTextCursor cursor = ui->textEdit->textCursor(); + if (QTextTable *currentTable = cursor.currentTable()) { + cursorEditBlock(cursor, [&] () { + currentTable->splitCell(currentTable->cellAt(cursor).row(), + currentTable->cellAt(cursor).column(), + 1, 2); + }); + } + }); + m_actionSplitColumn->setCheckable(false); +} + +void RichTextEditor::textStyle(QTextListFormat::Style style) +{ + QTextCursor cursor = ui->textEdit->textCursor(); + cursorEditBlock(cursor, [&] () { + if (style != QTextListFormat::ListStyleUndefined) { + QTextBlockFormat blockFmt = cursor.blockFormat(); + QTextListFormat listFmt; + + if (cursor.currentList()) { + listFmt = cursor.currentList()->format(); + } else { + listFmt.setIndent(blockFmt.indent() + 1); + blockFmt.setIndent(0); + cursor.setBlockFormat(blockFmt); + } + + listFmt.setStyle(style); + + cursor.createList(listFmt); + } else { + QTextList* currentList = cursor.currentList(); + QTextBlock currentBlock = cursor.block(); + currentList->remove(currentBlock); + + QTextBlockFormat blockFormat = cursor.blockFormat(); + blockFormat.setIndent(0); + cursor.setBlockFormat(blockFormat); + } + }); +} + +void RichTextEditor::setTableActionsActive(bool active) +{ + m_actionCreateTable->setEnabled(!active); + m_actionRemoveTable->setEnabled(active); + + m_actionAddRow->setEnabled(active); + m_actionAddColumn->setEnabled(active); + m_actionRemoveRow->setEnabled(active); + m_actionRemoveColumn->setEnabled(active); + + m_actionMergeCells->setEnabled(active); + m_actionSplitRow->setEnabled(active); + m_actionSplitColumn->setEnabled(active); +} + +} diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.h b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.h new file mode 100644 index 00000000000..3b9ff7864ca --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +namespace Ui { +class RichTextEditor; +} + +template +class FontWidgetActions; + +class HyperlinkDialog; + +class RichTextEditor : public QWidget +{ + Q_OBJECT + +public: + explicit RichTextEditor(QWidget *parent = nullptr); + ~RichTextEditor(); + + void setPlainText(const QString &text); + QString plainText() const; + + void setRichText(const QString &text); + QString richText() const; + + void setTabChangesFocus(bool change); + +private slots: + void currentCharFormatChanged(const QTextCharFormat &format); + void cursorPositionChanged(); + +private: + QPixmap getIcon(const QString &iconName); + void mergeFormatOnWordOrSelection(const QTextCharFormat &format); + + void fontChanged(const QFont &f); + void colorChanged(const QColor &c); + void alignmentChanged(Qt::Alignment a); + void styleChanged(const QTextCursor &cursor); + void tableChanged(const QTextCursor &cursor); + + void setupEditActions(); + void setupTextActions(); + void setupHyperlinkActions(); + void setupAlignActions(); + void setupListActions(); + void setupFontActions(); + void setupTableActions(); + + void textStyle(QTextListFormat::Style style); + + void setTableActionsActive(bool active); //switches between "has table/has no table" ui setup + +private: + QScopedPointer ui; + QPointer m_linkDialog; + + QAction *m_actionTextBold; + QAction *m_actionTextItalic; + QAction *m_actionTextUnderline; + + QAction *m_actionHyperlink; + + QAction *m_actionAlignLeft; + QAction *m_actionAlignCenter; + QAction *m_actionAlignRight; + QAction *m_actionAlignJustify; + + QAction *m_actionTextColor; + + QAction *m_actionBulletList; + QAction *m_actionNumberedList; + + QAction *m_actionTableSettings; + + QAction *m_actionCreateTable; + QAction *m_actionRemoveTable; + + QAction *m_actionAddRow; + QAction *m_actionAddColumn; + QAction *m_actionRemoveRow; + QAction *m_actionRemoveColumn; + + QAction *m_actionMergeCells; + QAction *m_actionSplitRow; + QAction *m_actionSplitColumn; + + QPointer> m_fontNameAction; + QPointer> m_fontSizeAction; +}; + +} //namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri new file mode 100644 index 00000000000..68b6dbe0268 --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.pri @@ -0,0 +1,8 @@ +HEADERS += $$PWD/richtexteditor.h +HEADERS += $$PWD/hyperlinkdialog.h + +SOURCES += $$PWD/richtexteditor.cpp +SOURCES += $$PWD/hyperlinkdialog.cpp + +FORMS += $$PWD/richtexteditor.ui +FORMS += $$PWD/hyperlinkdialog.ui diff --git a/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.ui b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.ui new file mode 100644 index 00000000000..25a7b5d244a --- /dev/null +++ b/src/plugins/qmldesigner/components/richtexteditor/richtexteditor.ui @@ -0,0 +1,50 @@ + + + QmlDesigner::RichTextEditor + + + + 0 + 0 + 428 + 283 + + + + + 0 + 5 + + + + Form + + + + + + + 36 + 36 + + + + + + + + + 36 + 36 + + + + + + + + + + + + diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index ea017e3ceff..0095aa8f10b 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -31,6 +31,7 @@ include(components/connectioneditor/connectioneditor.pri) include(components/curveeditor/curveeditor.pri) include(components/bindingeditor/bindingeditor.pri) include(components/annotationeditor/annotationeditor.pri) +include(components/richtexteditor/richtexteditor.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 3c434788cd9..63fa8a07432 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -746,6 +746,12 @@ Project { "pathtool/pathtool.h", "pathtool/pathtoolview.cpp", "pathtool/pathtoolview.h", + "richtexteditor/hyperlinkdialog.cpp", + "richtexteditor/hyperlinkdialog.h", + "richtexteditor/hyperlinkdialog.ui", + "richtexteditor/richtexteditor.cpp", + "richtexteditor/richtexteditor.h", + "richtexteditor/richtexteditor.ui", "sourcetool/sourcetool.cpp", "sourcetool/sourcetool.h", "texttool/textedititem.cpp",