1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Designer of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include "richtexteditor_p.h"
30#include "htmlhighlighter_p.h"
31#include "iconselector_p.h"
32#include "ui_addlinkdialog.h"
33
34#include "iconloader_p.h"
35
36#include <QtDesigner/abstractformeditor.h>
37#include <QtDesigner/abstractsettings.h>
38
39#include <QtCore/qlist.h>
40#include <QtCore/qmap.h>
41#include <QtCore/qpointer.h>
42#include <QtCore/qxmlstream.h>
43
44#include <QtWidgets/qaction.h>
45#include <QtWidgets/qcolordialog.h>
46#include <QtWidgets/qcombobox.h>
47#include <QtGui/qfontdatabase.h>
48#include <QtGui/qtextcursor.h>
49#include <QtGui/qpainter.h>
50#include <QtGui/qicon.h>
51#include <QtWidgets/qmenu.h>
52#include <QtGui/qevent.h>
53#include <QtWidgets/qtabwidget.h>
54#include <QtGui/qtextobject.h>
55#include <QtGui/qtextdocument.h>
56#include <QtWidgets/qtoolbar.h>
57#include <QtWidgets/qtoolbutton.h>
58#include <QtWidgets/qboxlayout.h>
59#include <QtWidgets/qpushbutton.h>
60#include <QtWidgets/qdialogbuttonbox.h>
61
62QT_BEGIN_NAMESPACE
63
64static const char RichTextDialogGroupC[] = "RichTextDialog";
65static const char GeometryKeyC[] = "Geometry";
66static const char TabKeyC[] = "Tab";
67
68const bool simplifyRichTextDefault = true;
69
70namespace qdesigner_internal {
71
72// Richtext simplification filter helpers: Elements to be discarded
73static inline bool filterElement(const QStringRef &name)
74{
75 return name != QStringLiteral("meta") && name != QStringLiteral("style");
76}
77
78// Richtext simplification filter helpers: Filter attributes of elements
79static inline void filterAttributes(const QStringRef &name,
80 QXmlStreamAttributes *atts,
81 bool *paragraphAlignmentFound)
82{
83 if (atts->isEmpty())
84 return;
85
86 // No style attributes for <body>
87 if (name == QStringLiteral("body")) {
88 atts->clear();
89 return;
90 }
91
92 // Clean out everything except 'align' for 'p'
93 if (name == QStringLiteral("p")) {
94 for (auto it = atts->begin(); it != atts->end(); ) {
95 if (it->name() == QStringLiteral("align")) {
96 ++it;
97 *paragraphAlignmentFound = true;
98 } else {
99 it = atts->erase(pos: it);
100 }
101 }
102 return;
103 }
104}
105
106// Richtext simplification filter helpers: Check for blank QStringRef.
107static inline bool isWhiteSpace(const QStringRef &in)
108{
109 const int count = in.size();
110 for (int i = 0; i < count; i++)
111 if (!in.at(i).isSpace())
112 return false;
113 return true;
114}
115
116// Richtext simplification filter: Remove hard-coded font settings,
117// <style> elements, <p> attributes other than 'align' and
118// and unnecessary meta-information.
119QString simplifyRichTextFilter(const QString &in, bool *isPlainTextPtr = nullptr)
120{
121 unsigned elementCount = 0;
122 bool paragraphAlignmentFound = false;
123 QString out;
124 QXmlStreamReader reader(in);
125 QXmlStreamWriter writer(&out);
126 writer.setAutoFormatting(false);
127 writer.setAutoFormattingIndent(0);
128
129 while (!reader.atEnd()) {
130 switch (reader.readNext()) {
131 case QXmlStreamReader::StartElement:
132 elementCount++;
133 if (filterElement(name: reader.name())) {
134 const auto name = reader.name();
135 QXmlStreamAttributes attributes = reader.attributes();
136 filterAttributes(name, atts: &attributes, paragraphAlignmentFound: &paragraphAlignmentFound);
137 writer.writeStartElement(qualifiedName: name.toString());
138 if (!attributes.isEmpty())
139 writer.writeAttributes(attributes);
140 } else {
141 reader.readElementText(); // Skip away all nested elements and characters.
142 }
143 break;
144 case QXmlStreamReader::Characters:
145 if (!isWhiteSpace(in: reader.text()))
146 writer.writeCharacters(text: reader.text().toString());
147 break;
148 case QXmlStreamReader::EndElement:
149 writer.writeEndElement();
150 break;
151 default:
152 break;
153 }
154 }
155 // Check for plain text (no spans, just <html><head><body><p>)
156 if (isPlainTextPtr)
157 *isPlainTextPtr = !paragraphAlignmentFound && elementCount == 4u; //
158 return out;
159}
160
161class RichTextEditor : public QTextEdit
162{
163 Q_OBJECT
164public:
165 explicit RichTextEditor(QWidget *parent = nullptr);
166 void setDefaultFont(QFont font);
167
168 QToolBar *createToolBar(QDesignerFormEditorInterface *core, QWidget *parent = nullptr);
169
170 bool simplifyRichText() const { return m_simplifyRichText; }
171
172public slots:
173 void setFontBold(bool b);
174 void setFontPointSize(double);
175 void setText(const QString &text);
176 void setSimplifyRichText(bool v);
177 QString text(Qt::TextFormat format) const;
178
179signals:
180 void stateChanged();
181 void simplifyRichTextChanged(bool);
182
183private:
184 bool m_simplifyRichText;
185};
186
187class AddLinkDialog : public QDialog
188{
189 Q_OBJECT
190
191public:
192 AddLinkDialog(RichTextEditor *editor, QWidget *parent = nullptr);
193 ~AddLinkDialog() override;
194
195 int showDialog();
196
197public slots:
198 void accept() override;
199
200private:
201 RichTextEditor *m_editor;
202 Ui::AddLinkDialog *m_ui;
203};
204
205AddLinkDialog::AddLinkDialog(RichTextEditor *editor, QWidget *parent) :
206 QDialog(parent),
207 m_ui(new Ui::AddLinkDialog)
208{
209 m_ui->setupUi(this);
210
211 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
212
213 m_editor = editor;
214}
215
216AddLinkDialog::~AddLinkDialog()
217{
218 delete m_ui;
219}
220
221int AddLinkDialog::showDialog()
222{
223 // Set initial focus
224 const QTextCursor cursor = m_editor->textCursor();
225 if (cursor.hasSelection()) {
226 m_ui->titleInput->setText(cursor.selectedText());
227 m_ui->urlInput->setFocus();
228 } else {
229 m_ui->titleInput->setFocus();
230 }
231
232 return exec();
233}
234
235void AddLinkDialog::accept()
236{
237 const QString title = m_ui->titleInput->text();
238 const QString url = m_ui->urlInput->text();
239
240 if (!title.isEmpty()) {
241 QString html = QStringLiteral("<a href=\"");
242 html += url;
243 html += QStringLiteral("\">");
244 html += title;
245 html += QStringLiteral("</a>");
246
247 m_editor->insertHtml(text: html);
248 }
249
250 m_ui->titleInput->clear();
251 m_ui->urlInput->clear();
252
253 QDialog::accept();
254}
255
256class HtmlTextEdit : public QTextEdit
257{
258 Q_OBJECT
259
260public:
261 HtmlTextEdit(QWidget *parent = nullptr)
262 : QTextEdit(parent)
263 {}
264
265 void contextMenuEvent(QContextMenuEvent *event) override;
266
267private slots:
268 void actionTriggered(QAction *action);
269};
270
271void HtmlTextEdit::contextMenuEvent(QContextMenuEvent *event)
272{
273 QMenu *menu = createStandardContextMenu();
274 QMenu *htmlMenu = new QMenu(tr(s: "Insert HTML entity"), menu);
275
276 typedef struct {
277 const char *text;
278 const char *entity;
279 } Entry;
280
281 const Entry entries[] = {
282 { .text: "&&amp; (&&)", .entity: "&amp;" },
283 { .text: "&&nbsp;", .entity: "&nbsp;" },
284 { .text: "&&lt; (<)", .entity: "&lt;" },
285 { .text: "&&gt; (>)", .entity: "&gt;" },
286 { .text: "&&copy; (Copyright)", .entity: "&copy;" },
287 { .text: "&&reg; (Trade Mark)", .entity: "&reg;" },
288 };
289
290 for (const Entry &e : entries) {
291 QAction *entityAction = new QAction(QLatin1String(e.text),
292 htmlMenu);
293 entityAction->setData(QLatin1String(e.entity));
294 htmlMenu->addAction(action: entityAction);
295 }
296
297 menu->addMenu(menu: htmlMenu);
298 connect(sender: htmlMenu, signal: &QMenu::triggered, receiver: this, slot: &HtmlTextEdit::actionTriggered);
299 menu->exec(pos: event->globalPos());
300 delete menu;
301}
302
303void HtmlTextEdit::actionTriggered(QAction *action)
304{
305 insertPlainText(text: action->data().toString());
306}
307
308class ColorAction : public QAction
309{
310 Q_OBJECT
311
312public:
313 ColorAction(QObject *parent);
314
315 const QColor& color() const { return m_color; }
316 void setColor(const QColor &color);
317
318signals:
319 void colorChanged(const QColor &color);
320
321private slots:
322 void chooseColor();
323
324private:
325 QColor m_color;
326};
327
328ColorAction::ColorAction(QObject *parent):
329 QAction(parent)
330{
331 setText(tr(s: "Text Color"));
332 setColor(Qt::black);
333 connect(sender: this, signal: &QAction::triggered, receiver: this, slot: &ColorAction::chooseColor);
334}
335
336void ColorAction::setColor(const QColor &color)
337{
338 if (color == m_color)
339 return;
340 m_color = color;
341 QPixmap pix(24, 24);
342 QPainter painter(&pix);
343 painter.setRenderHint(hint: QPainter::Antialiasing, on: false);
344 painter.fillRect(pix.rect(), color: m_color);
345 painter.setPen(m_color.darker());
346 painter.drawRect(r: pix.rect().adjusted(xp1: 0, yp1: 0, xp2: -1, yp2: -1));
347 setIcon(pix);
348}
349
350void ColorAction::chooseColor()
351{
352 const QColor col = QColorDialog::getColor(initial: m_color, parent: nullptr);
353 if (col.isValid() && col != m_color) {
354 setColor(col);
355 emit colorChanged(color: m_color);
356 }
357}
358
359class RichTextEditorToolBar : public QToolBar
360{
361 Q_OBJECT
362public:
363 RichTextEditorToolBar(QDesignerFormEditorInterface *core,
364 RichTextEditor *editor,
365 QWidget *parent = nullptr);
366
367public slots:
368 void updateActions();
369
370private slots:
371 void alignmentActionTriggered(QAction *action);
372 void sizeInputActivated(const QString &size);
373 void colorChanged(const QColor &color);
374 void setVAlignSuper(bool super);
375 void setVAlignSub(bool sub);
376 void insertLink();
377 void insertImage();
378 void layoutDirectionChanged();
379
380private:
381 QAction *m_bold_action;
382 QAction *m_italic_action;
383 QAction *m_underline_action;
384 QAction *m_valign_sup_action;
385 QAction *m_valign_sub_action;
386 QAction *m_align_left_action;
387 QAction *m_align_center_action;
388 QAction *m_align_right_action;
389 QAction *m_align_justify_action;
390 QAction *m_layoutDirectionAction;
391 QAction *m_link_action;
392 QAction *m_image_action;
393 QAction *m_simplify_richtext_action;
394 ColorAction *m_color_action;
395 QComboBox *m_font_size_input;
396
397 QDesignerFormEditorInterface *m_core;
398 QPointer<RichTextEditor> m_editor;
399};
400
401static QAction *createCheckableAction(const QIcon &icon, const QString &text,
402 QObject *receiver, const char *slot,
403 QObject *parent = nullptr)
404{
405 QAction *result = new QAction(parent);
406 result->setIcon(icon);
407 result->setText(text);
408 result->setCheckable(true);
409 result->setChecked(false);
410 if (slot)
411 QObject::connect(sender: result, SIGNAL(triggered(bool)), receiver, member: slot);
412 return result;
413}
414
415RichTextEditorToolBar::RichTextEditorToolBar(QDesignerFormEditorInterface *core,
416 RichTextEditor *editor,
417 QWidget *parent) :
418 QToolBar(parent),
419 m_link_action(new QAction(this)),
420 m_image_action(new QAction(this)),
421 m_color_action(new ColorAction(this)),
422 m_font_size_input(new QComboBox),
423 m_core(core),
424 m_editor(editor)
425{
426 // Font size combo box
427 m_font_size_input->setEditable(false);
428 const auto font_sizes = QFontDatabase::standardSizes();
429 for (int font_size : font_sizes)
430 m_font_size_input->addItem(atext: QString::number(font_size));
431
432 connect(sender: m_font_size_input, signal: &QComboBox::textActivated,
433 receiver: this, slot: &RichTextEditorToolBar::sizeInputActivated);
434 addWidget(widget: m_font_size_input);
435
436 addSeparator();
437
438 // Bold, italic and underline buttons
439
440 m_bold_action = createCheckableAction(
441 icon: createIconSet(QStringLiteral("textbold.png")),
442 text: tr(s: "Bold"), receiver: editor, SLOT(setFontBold(bool)), parent: this);
443 m_bold_action->setShortcut(tr(s: "CTRL+B"));
444 addAction(action: m_bold_action);
445
446 m_italic_action = createCheckableAction(
447 icon: createIconSet(QStringLiteral("textitalic.png")),
448 text: tr(s: "Italic"), receiver: editor, SLOT(setFontItalic(bool)), parent: this);
449 m_italic_action->setShortcut(tr(s: "CTRL+I"));
450 addAction(action: m_italic_action);
451
452 m_underline_action = createCheckableAction(
453 icon: createIconSet(QStringLiteral("textunder.png")),
454 text: tr(s: "Underline"), receiver: editor, SLOT(setFontUnderline(bool)), parent: this);
455 m_underline_action->setShortcut(tr(s: "CTRL+U"));
456 addAction(action: m_underline_action);
457
458 addSeparator();
459
460 // Left, center, right and justified alignment buttons
461
462 QActionGroup *alignment_group = new QActionGroup(this);
463 connect(sender: alignment_group, signal: &QActionGroup::triggered,
464 receiver: this, slot: &RichTextEditorToolBar::alignmentActionTriggered);
465
466 m_align_left_action = createCheckableAction(
467 icon: createIconSet(QStringLiteral("textleft.png")),
468 text: tr(s: "Left Align"), receiver: editor, slot: nullptr, parent: alignment_group);
469 addAction(action: m_align_left_action);
470
471 m_align_center_action = createCheckableAction(
472 icon: createIconSet(QStringLiteral("textcenter.png")),
473 text: tr(s: "Center"), receiver: editor, slot: nullptr, parent: alignment_group);
474 addAction(action: m_align_center_action);
475
476 m_align_right_action = createCheckableAction(
477 icon: createIconSet(QStringLiteral("textright.png")),
478 text: tr(s: "Right Align"), receiver: editor, slot: nullptr, parent: alignment_group);
479 addAction(action: m_align_right_action);
480
481 m_align_justify_action = createCheckableAction(
482 icon: createIconSet(QStringLiteral("textjustify.png")),
483 text: tr(s: "Justify"), receiver: editor, slot: nullptr, parent: alignment_group);
484 addAction(action: m_align_justify_action);
485
486 m_layoutDirectionAction = createCheckableAction(
487 icon: createIconSet(QStringLiteral("righttoleft.png")),
488 text: tr(s: "Right to Left"), receiver: this, SLOT(layoutDirectionChanged()));
489 addAction(action: m_layoutDirectionAction);
490
491 addSeparator();
492
493 // Superscript and subscript buttons
494
495 m_valign_sup_action = createCheckableAction(
496 icon: createIconSet(QStringLiteral("textsuperscript.png")),
497 text: tr(s: "Superscript"),
498 receiver: this, SLOT(setVAlignSuper(bool)), parent: this);
499 addAction(action: m_valign_sup_action);
500
501 m_valign_sub_action = createCheckableAction(
502 icon: createIconSet(QStringLiteral("textsubscript.png")),
503 text: tr(s: "Subscript"),
504 receiver: this, SLOT(setVAlignSub(bool)), parent: this);
505 addAction(action: m_valign_sub_action);
506
507 addSeparator();
508
509 // Insert hyperlink and image buttons
510
511 m_link_action->setIcon(createIconSet(QStringLiteral("textanchor.png")));
512 m_link_action->setText(tr(s: "Insert &Link"));
513 connect(sender: m_link_action, signal: &QAction::triggered, receiver: this, slot: &RichTextEditorToolBar::insertLink);
514 addAction(action: m_link_action);
515
516 m_image_action->setIcon(createIconSet(QStringLiteral("insertimage.png")));
517 m_image_action->setText(tr(s: "Insert &Image"));
518 connect(sender: m_image_action, signal: &QAction::triggered, receiver: this, slot: &RichTextEditorToolBar::insertImage);
519 addAction(action: m_image_action);
520
521 addSeparator();
522
523 // Text color button
524 connect(sender: m_color_action, signal: &ColorAction::colorChanged,
525 receiver: this, slot: &RichTextEditorToolBar::colorChanged);
526 addAction(action: m_color_action);
527
528 addSeparator();
529
530 // Simplify rich text
531 m_simplify_richtext_action
532 = createCheckableAction(icon: createIconSet(QStringLiteral("simplifyrichtext.png")),
533 text: tr(s: "Simplify Rich Text"), receiver: m_editor, SLOT(setSimplifyRichText(bool)));
534 m_simplify_richtext_action->setChecked(m_editor->simplifyRichText());
535 connect(sender: m_editor.data(), signal: &RichTextEditor::simplifyRichTextChanged,
536 receiver: m_simplify_richtext_action, slot: &QAction::setChecked);
537 addAction(action: m_simplify_richtext_action);
538
539 connect(sender: editor, signal: &QTextEdit::textChanged, receiver: this, slot: &RichTextEditorToolBar::updateActions);
540 connect(sender: editor, signal: &RichTextEditor::stateChanged, receiver: this, slot: &RichTextEditorToolBar::updateActions);
541
542 updateActions();
543}
544
545void RichTextEditorToolBar::alignmentActionTriggered(QAction *action)
546{
547 Qt::Alignment new_alignment;
548
549 if (action == m_align_left_action) {
550 new_alignment = Qt::AlignLeft;
551 } else if (action == m_align_center_action) {
552 new_alignment = Qt::AlignCenter;
553 } else if (action == m_align_right_action) {
554 new_alignment = Qt::AlignRight;
555 } else {
556 new_alignment = Qt::AlignJustify;
557 }
558
559 m_editor->setAlignment(new_alignment);
560}
561
562void RichTextEditorToolBar::colorChanged(const QColor &color)
563{
564 m_editor->setTextColor(color);
565 m_editor->setFocus();
566}
567
568void RichTextEditorToolBar::sizeInputActivated(const QString &size)
569{
570 bool ok;
571 int i = size.toInt(ok: &ok);
572 if (!ok)
573 return;
574
575 m_editor->setFontPointSize(i);
576 m_editor->setFocus();
577}
578
579void RichTextEditorToolBar::setVAlignSuper(bool super)
580{
581 const QTextCharFormat::VerticalAlignment align = super ?
582 QTextCharFormat::AlignSuperScript : QTextCharFormat::AlignNormal;
583
584 QTextCharFormat charFormat = m_editor->currentCharFormat();
585 charFormat.setVerticalAlignment(align);
586 m_editor->setCurrentCharFormat(charFormat);
587
588 m_valign_sub_action->setChecked(false);
589}
590
591void RichTextEditorToolBar::setVAlignSub(bool sub)
592{
593 const QTextCharFormat::VerticalAlignment align = sub ?
594 QTextCharFormat::AlignSubScript : QTextCharFormat::AlignNormal;
595
596 QTextCharFormat charFormat = m_editor->currentCharFormat();
597 charFormat.setVerticalAlignment(align);
598 m_editor->setCurrentCharFormat(charFormat);
599
600 m_valign_sup_action->setChecked(false);
601}
602
603void RichTextEditorToolBar::insertLink()
604{
605 AddLinkDialog linkDialog(m_editor, this);
606 linkDialog.showDialog();
607 m_editor->setFocus();
608}
609
610void RichTextEditorToolBar::insertImage()
611{
612 const QString path = IconSelector::choosePixmapResource(core: m_core, resourceModel: m_core->resourceModel(), oldPath: QString(), parent: this);
613 if (!path.isEmpty())
614 m_editor->insertHtml(QStringLiteral("<img src=\"") + path + QStringLiteral("\"/>"));
615}
616
617void RichTextEditorToolBar::layoutDirectionChanged()
618{
619 QTextCursor cursor = m_editor->textCursor();
620 QTextBlock block = cursor.block();
621 if (block.isValid()) {
622 QTextBlockFormat format = block.blockFormat();
623 const Qt::LayoutDirection newDirection = m_layoutDirectionAction->isChecked() ? Qt::RightToLeft : Qt::LeftToRight;
624 if (format.layoutDirection() != newDirection) {
625 format.setLayoutDirection(newDirection);
626 cursor.setBlockFormat(format);
627 }
628 }
629}
630
631void RichTextEditorToolBar::updateActions()
632{
633 if (m_editor == nullptr) {
634 setEnabled(false);
635 return;
636 }
637
638 const Qt::Alignment alignment = m_editor->alignment();
639 const QTextCursor cursor = m_editor->textCursor();
640 const QTextCharFormat charFormat = cursor.charFormat();
641 const QFont font = charFormat.font();
642 const QTextCharFormat::VerticalAlignment valign =
643 charFormat.verticalAlignment();
644 const bool superScript = valign == QTextCharFormat::AlignSuperScript;
645 const bool subScript = valign == QTextCharFormat::AlignSubScript;
646
647 if (alignment & Qt::AlignLeft) {
648 m_align_left_action->setChecked(true);
649 } else if (alignment & Qt::AlignRight) {
650 m_align_right_action->setChecked(true);
651 } else if (alignment & Qt::AlignHCenter) {
652 m_align_center_action->setChecked(true);
653 } else {
654 m_align_justify_action->setChecked(true);
655 }
656 m_layoutDirectionAction->setChecked(cursor.blockFormat().layoutDirection() == Qt::RightToLeft);
657
658 m_bold_action->setChecked(font.bold());
659 m_italic_action->setChecked(font.italic());
660 m_underline_action->setChecked(font.underline());
661 m_valign_sup_action->setChecked(superScript);
662 m_valign_sub_action->setChecked(subScript);
663
664 const int size = font.pointSize();
665 const int idx = m_font_size_input->findText(text: QString::number(size));
666 if (idx != -1)
667 m_font_size_input->setCurrentIndex(idx);
668
669 m_color_action->setColor(m_editor->textColor());
670}
671
672RichTextEditor::RichTextEditor(QWidget *parent)
673 : QTextEdit(parent), m_simplifyRichText(simplifyRichTextDefault)
674{
675 connect(sender: this, signal: &RichTextEditor::currentCharFormatChanged,
676 receiver: this, slot: &RichTextEditor::stateChanged);
677 connect(sender: this, signal: &RichTextEditor::cursorPositionChanged,
678 receiver: this, slot: &RichTextEditor::stateChanged);
679}
680
681QToolBar *RichTextEditor::createToolBar(QDesignerFormEditorInterface *core, QWidget *parent)
682{
683 return new RichTextEditorToolBar(core, this, parent);
684}
685
686void RichTextEditor::setFontBold(bool b)
687{
688 if (b)
689 setFontWeight(QFont::Bold);
690 else
691 setFontWeight(QFont::Normal);
692}
693
694void RichTextEditor::setFontPointSize(double d)
695{
696 QTextEdit::setFontPointSize(qreal(d));
697}
698
699void RichTextEditor::setText(const QString &text)
700{
701
702 if (Qt::mightBeRichText(text))
703 setHtml(text);
704 else
705 setPlainText(text);
706}
707
708void RichTextEditor::setSimplifyRichText(bool v)
709{
710 if (v != m_simplifyRichText) {
711 m_simplifyRichText = v;
712 emit simplifyRichTextChanged(v);
713 }
714}
715
716void RichTextEditor::setDefaultFont(QFont font)
717{
718 // Some default fonts on Windows have a default size of 7.8,
719 // which results in complicated rich text generated by toHtml().
720 // Use an integer value.
721 const int pointSize = qRound(d: font.pointSizeF());
722 if (pointSize > 0 && !qFuzzyCompare(p1: qreal(pointSize), p2: font.pointSizeF())) {
723 font.setPointSize(pointSize);
724 }
725
726 document()->setDefaultFont(font);
727 if (font.pointSize() > 0)
728 setFontPointSize(font.pointSize());
729 else
730 setFontPointSize(QFontInfo(font).pointSize());
731 emit textChanged();
732}
733
734QString RichTextEditor::text(Qt::TextFormat format) const
735{
736 switch (format) {
737 case Qt::PlainText:
738 return toPlainText();
739 case Qt::RichText:
740 return m_simplifyRichText ? simplifyRichTextFilter(in: toHtml()) : toHtml();
741 default:
742 break;
743 }
744 const QString html = toHtml();
745 bool isPlainText;
746 const QString simplifiedHtml = simplifyRichTextFilter(in: html, isPlainTextPtr: &isPlainText);
747 if (isPlainText)
748 return toPlainText();
749 return m_simplifyRichText ? simplifiedHtml : html;
750}
751
752RichTextEditorDialog::RichTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent) :
753 QDialog(parent),
754 m_editor(new RichTextEditor()),
755 m_text_edit(new HtmlTextEdit),
756 m_tab_widget(new QTabWidget),
757 m_state(Clean),
758 m_core(core),
759 m_initialTab(RichTextIndex)
760{
761 setWindowTitle(tr(s: "Edit text"));
762 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
763
764 // Read settings
765 const QDesignerSettingsInterface *settings = core->settingsManager();
766 const QString rootKey = QLatin1String(RichTextDialogGroupC) + QLatin1Char('/');
767 const QByteArray lastGeometry = settings->value(key: rootKey + QLatin1String(GeometryKeyC)).toByteArray();
768 const int initialTab = settings->value(key: rootKey + QLatin1String(TabKeyC), defaultValue: QVariant(m_initialTab)).toInt();
769 if (initialTab == RichTextIndex || initialTab == SourceIndex)
770 m_initialTab = initialTab;
771
772 m_text_edit->setAcceptRichText(false);
773 new HtmlHighlighter(m_text_edit);
774
775 connect(sender: m_editor, signal: &QTextEdit::textChanged, receiver: this, slot: &RichTextEditorDialog::richTextChanged);
776 connect(sender: m_editor, signal: &RichTextEditor::simplifyRichTextChanged,
777 receiver: this, slot: &RichTextEditorDialog::richTextChanged);
778 connect(sender: m_text_edit, signal: &QTextEdit::textChanged, receiver: this, slot: &RichTextEditorDialog::sourceChanged);
779
780 // The toolbar needs to be created after the RichTextEditor
781 QToolBar *tool_bar = m_editor->createToolBar(core);
782 tool_bar->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Minimum);
783
784 QWidget *rich_edit = new QWidget;
785 QVBoxLayout *rich_edit_layout = new QVBoxLayout(rich_edit);
786 rich_edit_layout->addWidget(tool_bar);
787 rich_edit_layout->addWidget(m_editor);
788
789 QWidget *plain_edit = new QWidget;
790 QVBoxLayout *plain_edit_layout = new QVBoxLayout(plain_edit);
791 plain_edit_layout->addWidget(m_text_edit);
792
793 m_tab_widget->setTabPosition(QTabWidget::South);
794 m_tab_widget->addTab(widget: rich_edit, tr(s: "Rich Text"));
795 m_tab_widget->addTab(widget: plain_edit, tr(s: "Source"));
796 connect(sender: m_tab_widget, signal: &QTabWidget::currentChanged,
797 receiver: this, slot: &RichTextEditorDialog::tabIndexChanged);
798
799 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
800 QPushButton *ok_button = buttonBox->button(which: QDialogButtonBox::Ok);
801 ok_button->setText(tr(s: "&OK"));
802 ok_button->setDefault(true);
803 buttonBox->button(which: QDialogButtonBox::Cancel)->setText(tr(s: "&Cancel"));
804 connect(sender: buttonBox, signal: &QDialogButtonBox::accepted, receiver: this, slot: &QDialog::accept);
805 connect(sender: buttonBox, signal: &QDialogButtonBox::rejected, receiver: this, slot: &QDialog::reject);
806
807 QVBoxLayout *layout = new QVBoxLayout(this);
808 layout->addWidget(m_tab_widget);
809 layout->addWidget(buttonBox);
810
811 if (!lastGeometry.isEmpty())
812 restoreGeometry(geometry: lastGeometry);
813}
814
815RichTextEditorDialog::~RichTextEditorDialog()
816{
817 QDesignerSettingsInterface *settings = m_core->settingsManager();
818 settings->beginGroup(prefix: QLatin1String(RichTextDialogGroupC));
819
820 settings->setValue(key: QLatin1String(GeometryKeyC), value: saveGeometry());
821 settings->setValue(key: QLatin1String(TabKeyC), value: m_tab_widget->currentIndex());
822 settings->endGroup();
823}
824
825int RichTextEditorDialog::showDialog()
826{
827 m_tab_widget->setCurrentIndex(m_initialTab);
828 switch (m_initialTab) {
829 case RichTextIndex:
830 m_editor->selectAll();
831 m_editor->setFocus();
832 break;
833 case SourceIndex:
834 m_text_edit->selectAll();
835 m_text_edit->setFocus();
836 break;
837 }
838 return exec();
839}
840
841void RichTextEditorDialog::setDefaultFont(const QFont &font)
842{
843 m_editor->setDefaultFont(font);
844}
845
846void RichTextEditorDialog::setText(const QString &text)
847{
848 // Generally simplify rich text unless verbose text is found.
849 const bool isSimplifiedRichText = !text.startsWith(QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"));
850 m_editor->setSimplifyRichText(isSimplifiedRichText);
851 m_editor->setText(text);
852 m_text_edit->setPlainText(text);
853 m_state = Clean;
854}
855
856QString RichTextEditorDialog::text(Qt::TextFormat format) const
857{
858 // In autotext mode, if the user has changed the source, use that
859 if (format == Qt::AutoText && (m_state == Clean || m_state == SourceChanged))
860 return m_text_edit->toPlainText();
861 // If the plain text HTML editor is selected, first copy its contents over
862 // to the rich text editor so that it is converted to Qt-HTML or actual
863 // plain text.
864 if (m_tab_widget->currentIndex() == SourceIndex && m_state == SourceChanged)
865 m_editor->setHtml(m_text_edit->toPlainText());
866 return m_editor->text(format);
867}
868
869void RichTextEditorDialog::tabIndexChanged(int newIndex)
870{
871 // Anything changed, is there a need for a conversion?
872 if (newIndex == SourceIndex && m_state != RichTextChanged)
873 return;
874 if (newIndex == RichTextIndex && m_state != SourceChanged)
875 return;
876 const State oldState = m_state;
877 // Remember the cursor position, since it is invalidated by setPlainText
878 QTextEdit *new_edit = (newIndex == SourceIndex) ? m_text_edit : m_editor;
879 const int position = new_edit->textCursor().position();
880
881 if (newIndex == SourceIndex)
882 m_text_edit->setPlainText(m_editor->text(format: Qt::RichText));
883 else
884 m_editor->setHtml(m_text_edit->toPlainText());
885
886 QTextCursor cursor = new_edit->textCursor();
887 cursor.movePosition(op: QTextCursor::End);
888 if (cursor.position() > position) {
889 cursor.setPosition(pos: position);
890 }
891 new_edit->setTextCursor(cursor);
892 m_state = oldState; // Changed is triggered by setting the text
893}
894
895void RichTextEditorDialog::richTextChanged()
896{
897 m_state = RichTextChanged;
898}
899
900void RichTextEditorDialog::sourceChanged()
901{
902 m_state = SourceChanged;
903}
904
905} // namespace qdesigner_internal
906
907QT_END_NAMESPACE
908
909#include "richtexteditor.moc"
910

source code of qttools/src/designer/src/lib/shared/richtexteditor.cpp