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, ¶graphAlignmentFound);
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 { "&& (&&)", "&" },
301 { "& ", " " },
302 { "&< (<)", "<" },
303 { "&> (>)", ">" },
304 { "&© (Copyright)", "©" },
305 { "&® (Trade Mark)", "®" },
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