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 
62 QT_BEGIN_NAMESPACE
63 
64 static const char RichTextDialogGroupC[] = "RichTextDialog";
65 static const char GeometryKeyC[] = "Geometry";
66 static const char TabKeyC[] = "Tab";
67 
68 const bool simplifyRichTextDefault = true;
69 
70 namespace qdesigner_internal {
71 
72 // Richtext simplification filter helpers: Elements to be discarded
filterElement(const QStringRef & name)73 static 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
filterAttributes(const QStringRef & name,QXmlStreamAttributes * atts,bool * paragraphAlignmentFound)79 static 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(it);
100             }
101         }
102         return;
103     }
104 }
105 
106 // Richtext simplification filter helpers: Check for blank QStringRef.
isWhiteSpace(const QStringRef & in)107 static 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.
simplifyRichTextFilter(const QString & in,bool * isPlainTextPtr=nullptr)119 QString 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(reader.name())) {
134                 const auto name = reader.name();
135                 QXmlStreamAttributes attributes = reader.attributes();
136                 filterAttributes(name, &attributes, &paragraphAlignmentFound);
137                 writer.writeStartElement(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(reader.text()))
146                 writer.writeCharacters(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 
161 class RichTextEditor : public QTextEdit
162 {
163     Q_OBJECT
164 public:
165     explicit RichTextEditor(QWidget *parent = nullptr);
166     void setDefaultFont(QFont font);
167 
168     QToolBar *createToolBar(QDesignerFormEditorInterface *core, QWidget *parent = nullptr);
169 
simplifyRichText() const170     bool simplifyRichText() const      { return m_simplifyRichText; }
171 
172 public 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 
179 signals:
180     void stateChanged();
181     void simplifyRichTextChanged(bool);
182 
183 private:
184     bool m_simplifyRichText;
185 };
186 
187 class AddLinkDialog : public QDialog
188 {
189     Q_OBJECT
190 
191 public:
192     AddLinkDialog(RichTextEditor *editor, QWidget *parent = nullptr);
193     ~AddLinkDialog() override;
194 
195     int showDialog();
196 
197 public slots:
198     void accept() override;
199 
200 private:
201     RichTextEditor *m_editor;
202     Ui::AddLinkDialog *m_ui;
203 };
204 
AddLinkDialog(RichTextEditor * editor,QWidget * parent)205 AddLinkDialog::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 
~AddLinkDialog()216 AddLinkDialog::~AddLinkDialog()
217 {
218     delete m_ui;
219 }
220 
showDialog()221 int 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 
accept()235 void 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(html);
248     }
249 
250     m_ui->titleInput->clear();
251     m_ui->urlInput->clear();
252 
253     QDialog::accept();
254 }
255 
256 class HtmlTextEdit : public QTextEdit
257 {
258     Q_OBJECT
259 
260 public:
HtmlTextEdit(QWidget * parent=nullptr)261     HtmlTextEdit(QWidget *parent = nullptr)
262         : QTextEdit(parent)
263     {}
264 
265     void contextMenuEvent(QContextMenuEvent *event) override;
266 
267 private slots:
268     void actionTriggered(QAction *action);
269 };
270 
contextMenuEvent(QContextMenuEvent * event)271 void HtmlTextEdit::contextMenuEvent(QContextMenuEvent *event)
272 {
273     QMenu *menu = createStandardContextMenu();
274     QMenu *htmlMenu = new QMenu(tr("Insert HTML entity"), menu);
275 
276     typedef struct {
277         const char *text;
278         const char *entity;
279     } Entry;
280 
281     const Entry entries[] = {
282         { "&&amp; (&&)", "&amp;" },
283         { "&&nbsp;", "&nbsp;" },
284         { "&&lt; (<)", "&lt;" },
285         { "&&gt; (>)", "&gt;" },
286         { "&&copy; (Copyright)", "&copy;" },
287         { "&&reg; (Trade Mark)", "&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(entityAction);
295     }
296 
297     menu->addMenu(htmlMenu);
298     connect(htmlMenu, &QMenu::triggered, this, &HtmlTextEdit::actionTriggered);
299     menu->exec(event->globalPos());
300     delete menu;
301 }
302 
actionTriggered(QAction * action)303 void HtmlTextEdit::actionTriggered(QAction *action)
304 {
305     insertPlainText(action->data().toString());
306 }
307 
308 class ColorAction : public QAction
309 {
310     Q_OBJECT
311 
312 public:
313     ColorAction(QObject *parent);
314 
color() const315     const QColor& color() const { return m_color; }
316     void setColor(const QColor &color);
317 
318 signals:
319     void colorChanged(const QColor &color);
320 
321 private slots:
322     void chooseColor();
323 
324 private:
325     QColor m_color;
326 };
327 
ColorAction(QObject * parent)328 ColorAction::ColorAction(QObject *parent):
329     QAction(parent)
330 {
331     setText(tr("Text Color"));
332     setColor(Qt::black);
333     connect(this, &QAction::triggered, this, &ColorAction::chooseColor);
334 }
335 
setColor(const QColor & color)336 void 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(QPainter::Antialiasing, false);
344     painter.fillRect(pix.rect(), m_color);
345     painter.setPen(m_color.darker());
346     painter.drawRect(pix.rect().adjusted(0, 0, -1, -1));
347     setIcon(pix);
348 }
349 
chooseColor()350 void ColorAction::chooseColor()
351 {
352     const QColor col = QColorDialog::getColor(m_color, nullptr);
353     if (col.isValid() && col != m_color) {
354         setColor(col);
355         emit colorChanged(m_color);
356     }
357 }
358 
359 class RichTextEditorToolBar : public QToolBar
360 {
361     Q_OBJECT
362 public:
363     RichTextEditorToolBar(QDesignerFormEditorInterface *core,
364                           RichTextEditor *editor,
365                           QWidget *parent = nullptr);
366 
367 public slots:
368     void updateActions();
369 
370 private 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 
380 private:
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 
createCheckableAction(const QIcon & icon,const QString & text,QObject * receiver,const char * slot,QObject * parent=nullptr)401 static 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(result, SIGNAL(triggered(bool)), receiver, slot);
412     return result;
413 }
414 
RichTextEditorToolBar(QDesignerFormEditorInterface * core,RichTextEditor * editor,QWidget * parent)415 RichTextEditorToolBar::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(QString::number(font_size));
431 
432     connect(m_font_size_input, &QComboBox::textActivated,
433             this, &RichTextEditorToolBar::sizeInputActivated);
434     addWidget(m_font_size_input);
435 
436     addSeparator();
437 
438     // Bold, italic and underline buttons
439 
440     m_bold_action = createCheckableAction(
441             createIconSet(QStringLiteral("textbold.png")),
442             tr("Bold"), editor, SLOT(setFontBold(bool)), this);
443     m_bold_action->setShortcut(tr("CTRL+B"));
444     addAction(m_bold_action);
445 
446     m_italic_action = createCheckableAction(
447             createIconSet(QStringLiteral("textitalic.png")),
448             tr("Italic"), editor, SLOT(setFontItalic(bool)), this);
449     m_italic_action->setShortcut(tr("CTRL+I"));
450     addAction(m_italic_action);
451 
452     m_underline_action = createCheckableAction(
453             createIconSet(QStringLiteral("textunder.png")),
454             tr("Underline"), editor, SLOT(setFontUnderline(bool)), this);
455     m_underline_action->setShortcut(tr("CTRL+U"));
456     addAction(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(alignment_group, &QActionGroup::triggered,
464             this, &RichTextEditorToolBar::alignmentActionTriggered);
465 
466     m_align_left_action = createCheckableAction(
467             createIconSet(QStringLiteral("textleft.png")),
468             tr("Left Align"), editor, nullptr, alignment_group);
469     addAction(m_align_left_action);
470 
471     m_align_center_action = createCheckableAction(
472             createIconSet(QStringLiteral("textcenter.png")),
473             tr("Center"), editor, nullptr, alignment_group);
474     addAction(m_align_center_action);
475 
476     m_align_right_action = createCheckableAction(
477             createIconSet(QStringLiteral("textright.png")),
478             tr("Right Align"), editor, nullptr, alignment_group);
479     addAction(m_align_right_action);
480 
481     m_align_justify_action = createCheckableAction(
482             createIconSet(QStringLiteral("textjustify.png")),
483             tr("Justify"), editor, nullptr, alignment_group);
484     addAction(m_align_justify_action);
485 
486     m_layoutDirectionAction = createCheckableAction(
487             createIconSet(QStringLiteral("righttoleft.png")),
488             tr("Right to Left"), this, SLOT(layoutDirectionChanged()));
489     addAction(m_layoutDirectionAction);
490 
491     addSeparator();
492 
493     // Superscript and subscript buttons
494 
495     m_valign_sup_action = createCheckableAction(
496             createIconSet(QStringLiteral("textsuperscript.png")),
497             tr("Superscript"),
498             this, SLOT(setVAlignSuper(bool)), this);
499     addAction(m_valign_sup_action);
500 
501     m_valign_sub_action = createCheckableAction(
502             createIconSet(QStringLiteral("textsubscript.png")),
503             tr("Subscript"),
504             this, SLOT(setVAlignSub(bool)), this);
505     addAction(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("Insert &Link"));
513     connect(m_link_action, &QAction::triggered, this, &RichTextEditorToolBar::insertLink);
514     addAction(m_link_action);
515 
516     m_image_action->setIcon(createIconSet(QStringLiteral("insertimage.png")));
517     m_image_action->setText(tr("Insert &Image"));
518     connect(m_image_action, &QAction::triggered, this, &RichTextEditorToolBar::insertImage);
519     addAction(m_image_action);
520 
521     addSeparator();
522 
523     // Text color button
524     connect(m_color_action, &ColorAction::colorChanged,
525             this, &RichTextEditorToolBar::colorChanged);
526     addAction(m_color_action);
527 
528     addSeparator();
529 
530     // Simplify rich text
531     m_simplify_richtext_action
532         = createCheckableAction(createIconSet(QStringLiteral("simplifyrichtext.png")),
533                                 tr("Simplify Rich Text"), m_editor, SLOT(setSimplifyRichText(bool)));
534     m_simplify_richtext_action->setChecked(m_editor->simplifyRichText());
535     connect(m_editor.data(), &RichTextEditor::simplifyRichTextChanged,
536             m_simplify_richtext_action, &QAction::setChecked);
537     addAction(m_simplify_richtext_action);
538 
539     connect(editor, &QTextEdit::textChanged, this, &RichTextEditorToolBar::updateActions);
540     connect(editor, &RichTextEditor::stateChanged, this, &RichTextEditorToolBar::updateActions);
541 
542     updateActions();
543 }
544 
alignmentActionTriggered(QAction * action)545 void 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 
colorChanged(const QColor & color)562 void RichTextEditorToolBar::colorChanged(const QColor &color)
563 {
564     m_editor->setTextColor(color);
565     m_editor->setFocus();
566 }
567 
sizeInputActivated(const QString & size)568 void RichTextEditorToolBar::sizeInputActivated(const QString &size)
569 {
570     bool ok;
571     int i = size.toInt(&ok);
572     if (!ok)
573         return;
574 
575     m_editor->setFontPointSize(i);
576     m_editor->setFocus();
577 }
578 
setVAlignSuper(bool super)579 void 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 
setVAlignSub(bool sub)591 void 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 
insertLink()603 void RichTextEditorToolBar::insertLink()
604 {
605     AddLinkDialog linkDialog(m_editor, this);
606     linkDialog.showDialog();
607     m_editor->setFocus();
608 }
609 
insertImage()610 void RichTextEditorToolBar::insertImage()
611 {
612     const QString path = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), QString(), this);
613     if (!path.isEmpty())
614         m_editor->insertHtml(QStringLiteral("<img src=\"") + path + QStringLiteral("\"/>"));
615 }
616 
layoutDirectionChanged()617 void 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 
updateActions()631 void 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(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 
RichTextEditor(QWidget * parent)672 RichTextEditor::RichTextEditor(QWidget *parent)
673     : QTextEdit(parent), m_simplifyRichText(simplifyRichTextDefault)
674 {
675     connect(this, &RichTextEditor::currentCharFormatChanged,
676             this, &RichTextEditor::stateChanged);
677     connect(this, &RichTextEditor::cursorPositionChanged,
678             this, &RichTextEditor::stateChanged);
679 }
680 
createToolBar(QDesignerFormEditorInterface * core,QWidget * parent)681 QToolBar *RichTextEditor::createToolBar(QDesignerFormEditorInterface *core, QWidget *parent)
682 {
683     return new RichTextEditorToolBar(core, this, parent);
684 }
685 
setFontBold(bool b)686 void RichTextEditor::setFontBold(bool b)
687 {
688     if (b)
689         setFontWeight(QFont::Bold);
690     else
691         setFontWeight(QFont::Normal);
692 }
693 
setFontPointSize(double d)694 void RichTextEditor::setFontPointSize(double d)
695 {
696     QTextEdit::setFontPointSize(qreal(d));
697 }
698 
setText(const QString & text)699 void RichTextEditor::setText(const QString &text)
700 {
701 
702     if (Qt::mightBeRichText(text))
703         setHtml(text);
704     else
705         setPlainText(text);
706 }
707 
setSimplifyRichText(bool v)708 void RichTextEditor::setSimplifyRichText(bool v)
709 {
710     if (v != m_simplifyRichText) {
711         m_simplifyRichText = v;
712         emit simplifyRichTextChanged(v);
713     }
714 }
715 
setDefaultFont(QFont font)716 void 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(font.pointSizeF());
722     if (pointSize > 0 && !qFuzzyCompare(qreal(pointSize), 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 
text(Qt::TextFormat format) const734 QString 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(toHtml()) : toHtml();
741     default:
742         break;
743     }
744     const QString html = toHtml();
745     bool isPlainText;
746     const QString simplifiedHtml = simplifyRichTextFilter(html, &isPlainText);
747     if (isPlainText)
748         return toPlainText();
749     return m_simplifyRichText ? simplifiedHtml : html;
750 }
751 
RichTextEditorDialog(QDesignerFormEditorInterface * core,QWidget * parent)752 RichTextEditorDialog::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("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(rootKey + QLatin1String(GeometryKeyC)).toByteArray();
768     const int initialTab = settings->value(rootKey + QLatin1String(TabKeyC), 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(m_editor, &QTextEdit::textChanged, this, &RichTextEditorDialog::richTextChanged);
776     connect(m_editor, &RichTextEditor::simplifyRichTextChanged,
777             this, &RichTextEditorDialog::richTextChanged);
778     connect(m_text_edit, &QTextEdit::textChanged, this, &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(QSizePolicy::Expanding, 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(rich_edit, tr("Rich Text"));
795     m_tab_widget->addTab(plain_edit, tr("Source"));
796     connect(m_tab_widget, &QTabWidget::currentChanged,
797             this, &RichTextEditorDialog::tabIndexChanged);
798 
799     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
800     QPushButton *ok_button = buttonBox->button(QDialogButtonBox::Ok);
801     ok_button->setText(tr("&OK"));
802     ok_button->setDefault(true);
803     buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("&Cancel"));
804     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
805     connect(buttonBox, &QDialogButtonBox::rejected, this, &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(lastGeometry);
813 }
814 
~RichTextEditorDialog()815 RichTextEditorDialog::~RichTextEditorDialog()
816 {
817     QDesignerSettingsInterface *settings = m_core->settingsManager();
818     settings->beginGroup(QLatin1String(RichTextDialogGroupC));
819 
820     settings->setValue(QLatin1String(GeometryKeyC), saveGeometry());
821     settings->setValue(QLatin1String(TabKeyC), m_tab_widget->currentIndex());
822     settings->endGroup();
823 }
824 
showDialog()825 int 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 
setDefaultFont(const QFont & font)841 void RichTextEditorDialog::setDefaultFont(const QFont &font)
842 {
843     m_editor->setDefaultFont(font);
844 }
845 
setText(const QString & text)846 void 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 
text(Qt::TextFormat format) const856 QString 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 
tabIndexChanged(int newIndex)869 void 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(Qt::RichText));
883     else
884         m_editor->setHtml(m_text_edit->toPlainText());
885 
886     QTextCursor cursor = new_edit->textCursor();
887     cursor.movePosition(QTextCursor::End);
888     if (cursor.position() > position) {
889         cursor.setPosition(position);
890     }
891     new_edit->setTextCursor(cursor);
892     m_state = oldState; // Changed is triggered by setting the text
893 }
894 
richTextChanged()895 void RichTextEditorDialog::richTextChanged()
896 {
897     m_state = RichTextChanged;
898 }
899 
sourceChanged()900 void RichTextEditorDialog::sourceChanged()
901 {
902     m_state = SourceChanged;
903 }
904 
905 } // namespace qdesigner_internal
906 
907 QT_END_NAMESPACE
908 
909 #include "richtexteditor.moc"
910