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