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, ¶graphAlignmentFound);
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 { "&& (&&)", "&" },
283 { "& ", " " },
284 { "&< (<)", "<" },
285 { "&> (>)", ">" },
286 { "&© (Copyright)", "©" },
287 { "&® (Trade Mark)", "®" },
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