QGIS API Documentation 4.3.0-Master (0de80482b60)
Loading...
Searching...
No Matches
qgsrichtexteditor.cpp
Go to the documentation of this file.
1/****************************************************************************
2**
3** Copyright (C) 2013 Jiří Procházka (Hobrasoft)
4** Contact: http://www.hobrasoft.cz/
5**
6** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
7** Contact: http://www.qt-project.org/legal
8**
9** This library is free software; you can redistribute it and/or
10** modify it under the terms of the GNU Lesser General Public
11** License as published by the Free Software Foundation; either
12** version 2.1 of the License, or (at your option) any later version.
13**
14** $QT_BEGIN_LICENSE:LGPL$
15** GNU Lesser General Public License Usage
16** This file is under the terms of the GNU Lesser General Public License
17** version 2.1 as published by the Free Software Foundation and appearing
18** in the file LICENSE.LGPL included in the packaging of this file.
19** Please review the following information to ensure the
20** GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Digia gives you certain additional
24** rights. These rights are described in the Digia Qt LGPL Exception
25** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
26**
27** $QT_END_LICENSE$
28**
29****************************************************************************/
30
31#include "qgsrichtexteditor.h"
32
33#include "qgscodeeditor.h"
34#include "qgscodeeditorhtml.h"
35#include "qgscodeeditorwidget.h"
36#include "qgscolorbutton.h"
37#include "qgsgui.h"
38#include "qgsguiutils.h"
40
41#include <QApplication>
42#include <QBuffer>
43#include <QClipboard>
44#include <QComboBox>
45#include <QFileDialog>
46#include <QFontDatabase>
47#include <QImageReader>
48#include <QInputDialog>
49#include <QMenu>
50#include <QMessageBox>
51#include <QMimeData>
52#include <QNetworkRequest>
53#include <QSettings>
54#include <QString>
55#include <QTextList>
56#include <QToolButton>
57#include <QUrl>
58#include <QtDebug>
59
60#include "moc_qgsrichtexteditor.cpp"
61
62using namespace Qt::StringLiterals;
63
65 : QWidget( parent )
66{
67 setupUi( this );
68
69 mMonospaceFontFamily = QgsCodeEditor::getMonospaceFont().family();
70
71 QVBoxLayout *sourceLayout = new QVBoxLayout();
72 sourceLayout->setContentsMargins( 0, 0, 0, 0 );
73 mSourceEdit = new QgsCodeEditorHTML();
74 QgsCodeEditorWidget *codeEditorWidget = new QgsCodeEditorWidget( mSourceEdit );
75 sourceLayout->addWidget( codeEditorWidget );
76 mPageSourceEdit->setLayout( sourceLayout );
77
79
80 connect( mTextEdit, &QTextEdit::currentCharFormatChanged, this, &QgsRichTextEditor::slotCurrentCharFormatChanged );
81 connect( mTextEdit, &QTextEdit::cursorPositionChanged, this, &QgsRichTextEditor::slotCursorPositionChanged );
82
83 // undo & redo
84 mActionUndo->setShortcut( QKeySequence::Undo );
85 mActionRedo->setShortcut( QKeySequence::Redo );
86
87 connect( mTextEdit->document(), &QTextDocument::undoAvailable, mActionUndo, &QAction::setEnabled );
88 connect( mTextEdit->document(), &QTextDocument::redoAvailable, mActionRedo, &QAction::setEnabled );
89
90 mActionUndo->setEnabled( mTextEdit->document()->isUndoAvailable() );
91 mActionRedo->setEnabled( mTextEdit->document()->isRedoAvailable() );
92
93 connect( mActionUndo, &QAction::triggered, mTextEdit, &QTextEdit::undo );
94 connect( mActionRedo, &QAction::triggered, mTextEdit, &QTextEdit::redo );
95
96 // cut, copy & paste
97 mActionCut->setShortcut( QKeySequence::Cut );
98 mActionCopy->setShortcut( QKeySequence::Copy );
99 mActionPaste->setShortcut( QKeySequence::Paste );
100
101 mActionCut->setEnabled( false );
102 mActionCopy->setEnabled( false );
103
104 connect( mActionCut, &QAction::triggered, mTextEdit, &QTextEdit::cut );
105 connect( mActionCopy, &QAction::triggered, mTextEdit, &QTextEdit::copy );
106 connect( mActionPaste, &QAction::triggered, mTextEdit, &QTextEdit::paste );
107
108 connect( mTextEdit, &QTextEdit::copyAvailable, mActionCut, &QAction::setEnabled );
109 connect( mTextEdit, &QTextEdit::copyAvailable, mActionCopy, &QAction::setEnabled );
110
111#ifndef QT_NO_CLIPBOARD
112 connect( QApplication::clipboard(), &QClipboard::dataChanged, this, &QgsRichTextEditor::slotClipboardDataChanged );
113#endif
114
115 // link
116 mActionInsertLink->setShortcut( QKeySequence( u"CTRL+L"_s ) );
117 connect( mActionInsertLink, &QAction::triggered, this, &QgsRichTextEditor::textLink );
118 connect( mActionInsertImageFromUrl, &QAction::triggered, this, &QgsRichTextEditor::insertImageFromUrl );
119
120 // bold, italic & underline
121 mActionBold->setShortcut( QKeySequence( u"CTRL+B"_s ) );
122 mActionItalic->setShortcut( QKeySequence( u"CTRL+I"_s ) );
123 mActionUnderline->setShortcut( QKeySequence( u"CTRL+U"_s ) );
124
125 connect( mActionBold, &QAction::triggered, this, &QgsRichTextEditor::textBold );
126 connect( mActionItalic, &QAction::triggered, this, &QgsRichTextEditor::textItalic );
127 connect( mActionUnderline, &QAction::triggered, this, &QgsRichTextEditor::textUnderline );
128 connect( mActionStrikeOut, &QAction::triggered, this, &QgsRichTextEditor::textStrikeout );
129
130 // lists
131 mActionBulletList->setShortcut( QKeySequence( u"CTRL+-"_s ) );
132 mActionOrderedList->setShortcut( QKeySequence( u"CTRL+="_s ) );
133 connect( mActionBulletList, &QAction::triggered, this, &QgsRichTextEditor::listBullet );
134 connect( mActionOrderedList, &QAction::triggered, this, &QgsRichTextEditor::listOrdered );
135
136 // indentation
137 mActionDecreaseIndent->setShortcut( QKeySequence( u"CTRL+,"_s ) );
138 mActionIncreaseIndent->setShortcut( QKeySequence( u"CTRL+."_s ) );
139 connect( mActionIncreaseIndent, &QAction::triggered, this, &QgsRichTextEditor::increaseIndentation );
140 connect( mActionDecreaseIndent, &QAction::triggered, this, &QgsRichTextEditor::decreaseIndentation );
141
142 connect( mActionEditSource, &QAction::toggled, this, &QgsRichTextEditor::editSource );
143
144 // images
145 connect( mActionInsertImage, &QAction::triggered, this, &QgsRichTextEditor::insertImage );
146
148
149 fontChanged( mTextEdit->font() );
150
151 connect( mTextEdit, &QTextEdit::textChanged, this, &QgsRichTextEditor::textChanged );
152 connect( mSourceEdit, &QgsCodeEditorHTML::textChanged, this, &QgsRichTextEditor::textChanged );
153}
154
156{
157 mMode = mode;
158
159 delete mFontSizeCombo;
160 mFontSizeCombo = nullptr;
161 delete mParagraphStyleCombo;
162 mParagraphStyleCombo = nullptr;
163 delete mForeColorButton;
164 mForeColorButton = nullptr;
165 delete mBackColorButton;
166 mBackColorButton = nullptr;
167
168 mToolBar->clear();
169
170 // paragraph formatting
171 mParagraphStyleCombo = new QComboBox();
172 mParagraphStyleCombo->addItem( tr( "Standard" ), ParagraphStandard );
173 mParagraphStyleCombo->addItem( tr( "Heading 1" ), ParagraphHeading1 );
174 mParagraphStyleCombo->addItem( tr( "Heading 2" ), ParagraphHeading2 );
175 mParagraphStyleCombo->addItem( tr( "Heading 3" ), ParagraphHeading3 );
176 mParagraphStyleCombo->addItem( tr( "Heading 4" ), ParagraphHeading4 );
177 mParagraphStyleCombo->addItem( tr( "Monospace" ), ParagraphMonospace );
178
179 connect( mParagraphStyleCombo, qOverload<int>( &QComboBox::activated ), this, &QgsRichTextEditor::textStyle );
180 if ( mode == Mode::QTextDocument )
181 {
182 mToolBar->addWidget( mParagraphStyleCombo );
183 }
184
185 mToolBar->addAction( mActionUndo );
186 mToolBar->addAction( mActionRedo );
187 mToolBar->addAction( mActionCut );
188 mToolBar->addAction( mActionCopy );
189 mToolBar->addAction( mActionPaste );
190
191 // font size
192 mFontSizeCombo = new QComboBox();
193 mFontSizeCombo->setEditable( true );
194
195 const QList<int> sizes = QFontDatabase::standardSizes();
196 for ( const int size : sizes )
197 mFontSizeCombo->addItem( QString::number( size ), size );
198
199 mFontSizeCombo->setCurrentIndex( mFontSizeCombo->findData( QApplication::font().pointSize() ) );
200 connect( mFontSizeCombo, &QComboBox::textActivated, this, &QgsRichTextEditor::textSize );
201
202 if ( mode != Mode::PlainText )
203 {
204 mToolBar->addSeparator();
205 mToolBar->addWidget( mFontSizeCombo );
206 mToolBar->addAction( mActionBold );
207 mToolBar->addAction( mActionItalic );
208 mToolBar->addAction( mActionUnderline );
209 mToolBar->addAction( mActionStrikeOut );
210
211 mToolBar->addSeparator();
212 }
213
214 // text foreground color
215 mForeColorButton = new QgsColorButton();
216 mForeColorButton->setAllowOpacity( false );
217 mForeColorButton->setColorDialogTitle( tr( "Foreground Color" ) );
218 mForeColorButton->setColor( palette().windowText().color() );
219 mForeColorButton->setShowNoColor( false );
220 mForeColorButton->setToolTip( tr( "Foreground color" ) );
221 mForeColorButton->setMinimumWidth( QFontMetrics( font() ).horizontalAdvance( u"x"_s ) * 10 );
222 mForeColorButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
223
224 connect( mForeColorButton, &QgsColorButton::colorChanged, this, &QgsRichTextEditor::textFgColor );
225
226 if ( mode != Mode::PlainText )
227 {
228 mToolBar->addWidget( mForeColorButton );
229 }
230
231 // text background color
232 mBackColorButton = new QgsColorButton();
233 mBackColorButton->setAllowOpacity( false );
234 mBackColorButton->setColorDialogTitle( tr( "Background Color" ) );
235 mBackColorButton->setToolTip( tr( "Background color" ) );
236 mBackColorButton->setShowNull( true, tr( "No Background Color" ) );
237 mBackColorButton->setToNull();
238 mBackColorButton->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed );
239 mBackColorButton->setMinimumWidth( QFontMetrics( font() ).horizontalAdvance( u"x"_s ) * 10 );
240 connect( mBackColorButton, &QgsColorButton::colorChanged, this, &QgsRichTextEditor::textBgColor );
241
242 if ( mode == Mode::QTextDocument )
243 {
244 mToolBar->addWidget( mBackColorButton );
245 mToolBar->addAction( mActionBulletList );
246 mToolBar->addAction( mActionOrderedList );
247 mToolBar->addAction( mActionDecreaseIndent );
248 mToolBar->addAction( mActionIncreaseIndent );
249 mToolBar->addSeparator();
250 mToolBar->addAction( mActionInsertLink );
251 QToolButton *insertImageButton = new QToolButton( mToolBar );
252 insertImageButton->setDefaultAction( mActionInsertImage );
253 insertImageButton->setPopupMode( QToolButton::MenuButtonPopup );
254 QMenu *insertImageMenu = new QMenu( insertImageButton );
255 insertImageMenu->addAction( mActionInsertImageFromUrl );
256 insertImageButton->setMenu( insertImageMenu );
257 mToolBar->addWidget( insertImageButton );
258 }
259 if ( mode != Mode::PlainText )
260 {
261 mToolBar->addAction( mActionEditSource );
262 }
263
264 QMenu *menu = new QMenu( this );
265 if ( mode != Mode::PlainText )
266 {
267 QAction *removeFormat = new QAction( tr( "Remove Character Formatting" ), menu );
268 removeFormat->setShortcut( QKeySequence( u"CTRL+M"_s ) );
269 connect( removeFormat, &QAction::triggered, this, &QgsRichTextEditor::textRemoveFormat );
270 mTextEdit->addAction( removeFormat );
271
272 QAction *removeAllFormat = new QAction( tr( "Remove all Formatting" ), menu );
273 connect( removeAllFormat, &QAction::triggered, this, &QgsRichTextEditor::textRemoveAllFormat );
274 mTextEdit->addAction( removeAllFormat );
275 menu->addAction( removeAllFormat );
276 menu->addAction( removeFormat );
277 }
278
279 QAction *clearText = new QAction( tr( "Clear all Content" ), menu );
280 connect( clearText, &QAction::triggered, this, &QgsRichTextEditor::clearSource );
281 mTextEdit->addAction( clearText );
282
283 menu->addAction( clearText );
284
285 QToolButton *menuButton = new QToolButton( mToolBar );
286 menuButton->setMenu( menu );
287 menuButton->setPopupMode( QToolButton::InstantPopup );
288 menuButton->setToolTip( tr( "Advanced Options" ) );
289 menuButton->setText( u"…"_s );
290
291 QWidget *spacer = new QWidget();
292 spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
293 mToolBar->addWidget( spacer );
294 mToolBar->addWidget( menuButton );
295
296 if ( mode == Mode::PlainText )
297 {
298 editSource( false );
299 }
300}
301
303{
304 switch ( mStackedWidget->currentIndex() )
305 {
306 case 0:
307 return mTextEdit->toPlainText();
308
309 case 1:
310 // go via text edit to remove html from text...
311 mTextEdit->setText( mSourceEdit->text() );
312 return mTextEdit->toPlainText();
313
314 default:
315 break;
316 }
317 return QString();
318}
319
321{
322 switch ( mStackedWidget->currentIndex() )
323 {
324 case 0:
325 return mTextEdit->toHtml();
326
327 case 1:
328 return mSourceEdit->text();
329
330 default:
331 break;
332 }
333 return QString();
334}
335
336void QgsRichTextEditor::editSource( bool enabled )
337{
338 if ( enabled && mStackedWidget->currentIndex() == 0 )
339 {
340 mSourceEdit->setText( mTextEdit->toHtml() );
341 mStackedWidget->setCurrentIndex( 1 );
342 }
343 else if ( !enabled && mStackedWidget->currentIndex() == 1 )
344 {
345 mTextEdit->setHtml( mSourceEdit->text() );
346 mStackedWidget->setCurrentIndex( 0 );
347 mSourceEdit->clear();
348 }
349
350 // disable formatting actions when in html edit mode
351 mFontSizeCombo->setEnabled( !enabled );
352 mParagraphStyleCombo->setEnabled( !enabled );
353 mForeColorButton->setEnabled( !enabled );
354 mBackColorButton->setEnabled( !enabled );
355 mActionUndo->setEnabled( !enabled );
356 mActionRedo->setEnabled( !enabled );
357 mActionCut->setEnabled( !enabled );
358 mActionCopy->setEnabled( !enabled );
359 mActionPaste->setEnabled( !enabled );
360 mActionInsertLink->setEnabled( !enabled );
361 mActionBold->setEnabled( !enabled );
362 mActionItalic->setEnabled( !enabled );
363 mActionUnderline->setEnabled( !enabled );
364 mActionStrikeOut->setEnabled( !enabled );
365 mActionBulletList->setEnabled( !enabled );
366 mActionOrderedList->setEnabled( !enabled );
367 mActionDecreaseIndent->setEnabled( !enabled );
368 mActionIncreaseIndent->setEnabled( !enabled );
369 mActionInsertImage->setEnabled( !enabled );
370}
371
373{
374 mTextEdit->clear();
375}
376
377void QgsRichTextEditor::textRemoveFormat()
378{
379 QTextCharFormat format;
380 format.setFontWeight( QFont::Normal );
381 format.setFontUnderline( false );
382 format.setFontStrikeOut( false );
383 format.setFontItalic( false );
384 format.setFontPointSize( 9 );
385
386 mActionBold->setChecked( false );
387 mActionUnderline->setChecked( false );
388 mActionItalic->setChecked( false );
389 mActionStrikeOut->setChecked( false );
390 mFontSizeCombo->setCurrentIndex( mFontSizeCombo->findData( 9 ) );
391
392 format.clearBackground();
393
394 mergeFormatOnWordOrSelection( format );
395}
396
397void QgsRichTextEditor::textRemoveAllFormat()
398{
399 mActionBold->setChecked( false );
400 mActionUnderline->setChecked( false );
401 mActionItalic->setChecked( false );
402 mActionStrikeOut->setChecked( false );
403 mFontSizeCombo->setCurrentIndex( mFontSizeCombo->findData( 9 ) );
404 const QString text = mTextEdit->toPlainText();
405 mTextEdit->setPlainText( text );
406}
407
408void QgsRichTextEditor::textBold()
409{
410 QTextCharFormat format;
411 format.setFontWeight( mActionBold->isChecked() ? QFont::Bold : QFont::Normal );
412 mergeFormatOnWordOrSelection( format );
413}
414
416{
417 mTextEdit->setFocus( Qt::TabFocusReason );
418}
419
420void QgsRichTextEditor::textUnderline()
421{
422 QTextCharFormat format;
423 format.setFontUnderline( mActionUnderline->isChecked() );
424 mergeFormatOnWordOrSelection( format );
425}
426
427void QgsRichTextEditor::textItalic()
428{
429 QTextCharFormat format;
430 format.setFontItalic( mActionItalic->isChecked() );
431 mergeFormatOnWordOrSelection( format );
432}
433
434void QgsRichTextEditor::textStrikeout()
435{
436 QTextCharFormat format;
437 format.setFontStrikeOut( mActionStrikeOut->isChecked() );
438 mergeFormatOnWordOrSelection( format );
439}
440
441void QgsRichTextEditor::textSize( const QString &p )
442{
443 const qreal pointSize = p.toDouble();
444 if ( p.toFloat() > 0 )
445 {
446 QTextCharFormat format;
447 format.setFontPointSize( pointSize );
448 mergeFormatOnWordOrSelection( format );
449 }
450}
451
452void QgsRichTextEditor::textLink( bool checked )
453{
454 bool unlink = false;
455 QTextCharFormat format;
456 if ( checked )
457 {
458 const QString url = mTextEdit->currentCharFormat().anchorHref();
459 bool ok;
460 const QString newUrl = QInputDialog::getText( this, tr( "Create a Link" ), tr( "Link URL:" ), QLineEdit::Normal, url, &ok );
461 if ( ok )
462 {
463 format.setAnchor( true );
464 format.setAnchorHref( newUrl );
465 format.setForeground( palette().color( QPalette::Link ) );
466 format.setFontUnderline( true );
467 }
468 else
469 {
470 unlink = true;
471 }
472 }
473 else
474 {
475 unlink = true;
476 }
477 if ( unlink )
478 {
479 format.setAnchor( false );
480 format.setForeground( palette().color( QPalette::Text ) );
481 format.setFontUnderline( false );
482 }
483 mergeFormatOnWordOrSelection( format );
484}
485
486void QgsRichTextEditor::textStyle( int index )
487{
488 QTextCursor cursor = mTextEdit->textCursor();
489 cursor.beginEditBlock();
490
491 // standard
492 if ( !cursor.hasSelection() )
493 {
494 cursor.select( QTextCursor::BlockUnderCursor );
495 }
496 QTextCharFormat format;
497 cursor.setCharFormat( format );
498 mTextEdit->setCurrentCharFormat( format );
499
500 const ParagraphItems style = static_cast<ParagraphItems>( mParagraphStyleCombo->itemData( index ).toInt() );
501
502 switch ( style )
503 {
504 case QgsRichTextEditor::ParagraphStandard:
505 break;
506
507 case QgsRichTextEditor::ParagraphHeading1:
508 format.setFontPointSize( mFontSizeH1 );
509 format.setFontWeight( QFont::Bold );
510 break;
511
512 case QgsRichTextEditor::ParagraphHeading2:
513 format.setFontPointSize( mFontSizeH2 );
514 format.setFontWeight( QFont::Bold );
515 format.setFontItalic( true );
516 break;
517
518 case QgsRichTextEditor::ParagraphHeading3:
519 format.setFontPointSize( mFontSizeH3 );
520 format.setFontWeight( QFont::Bold );
521 break;
522
523 case QgsRichTextEditor::ParagraphHeading4:
524 format.setFontPointSize( mFontSizeH4 );
525 format.setFontWeight( QFont::Bold );
526 format.setFontItalic( true );
527 break;
528
529 case QgsRichTextEditor::ParagraphMonospace:
530 {
531 format = cursor.charFormat();
532 format.setFontFamily( mMonospaceFontFamily );
533 format.setFontStyleHint( QFont::Monospace );
534 format.setFontFixedPitch( true );
535 break;
536 }
537 }
538
539 cursor.setCharFormat( format );
540 mTextEdit->setCurrentCharFormat( format );
541
542 cursor.endEditBlock();
543}
544
545void QgsRichTextEditor::textFgColor()
546{
547 QTextCharFormat format;
548 format.setForeground( mForeColorButton->color() );
549 mergeFormatOnWordOrSelection( format );
550}
551
552void QgsRichTextEditor::textBgColor()
553{
554 QTextCharFormat format;
555 const QColor col = mBackColorButton->color();
556 if ( col.isValid() )
557 {
558 format.setBackground( col );
559 }
560 else
561 {
562 format.clearBackground();
563 }
564 mergeFormatOnWordOrSelection( format );
565}
566
567void QgsRichTextEditor::listBullet( bool checked )
568{
569 if ( checked )
570 {
571 mActionOrderedList->setChecked( false );
572 }
573 list( checked, QTextListFormat::ListDisc );
574}
575
576void QgsRichTextEditor::listOrdered( bool checked )
577{
578 if ( checked )
579 {
580 mActionBulletList->setChecked( false );
581 }
582 list( checked, QTextListFormat::ListDecimal );
583}
584
585void QgsRichTextEditor::list( bool checked, QTextListFormat::Style style )
586{
587 QTextCursor cursor = mTextEdit->textCursor();
588 cursor.beginEditBlock();
589 if ( !checked )
590 {
591 const QTextBlockFormat originalFormat = cursor.blockFormat();
592 QTextBlockFormat format;
593 format.setIndent( originalFormat.indent() );
594 cursor.setBlockFormat( format );
595 }
596 else
597 {
598 QTextListFormat listFormat;
599 if ( cursor.currentList() )
600 {
601 listFormat = cursor.currentList()->format();
602 }
603 listFormat.setStyle( style );
604 cursor.createList( listFormat );
605 }
606 cursor.endEditBlock();
607}
608
609void QgsRichTextEditor::mergeFormatOnWordOrSelection( const QTextCharFormat &format )
610{
611 QTextCursor cursor = mTextEdit->textCursor();
612 if ( !cursor.hasSelection() )
613 {
614 cursor.select( QTextCursor::WordUnderCursor );
615 }
616 cursor.mergeCharFormat( format );
617 mTextEdit->mergeCurrentCharFormat( format );
618 mTextEdit->setFocus( Qt::TabFocusReason );
619}
620
621void QgsRichTextEditor::slotCursorPositionChanged()
622{
623 QTextList *l = mTextEdit->textCursor().currentList();
624 if ( mLastBlockList && ( l == mLastBlockList || ( l && mLastBlockList && l->format().style() == mLastBlockList->format().style() ) ) )
625 {
626 return;
627 }
628 mLastBlockList = l;
629 if ( l )
630 {
631 const QTextListFormat listFormat = l->format();
632 if ( listFormat.style() == QTextListFormat::ListDisc )
633 {
634 mActionBulletList->setChecked( true );
635 mActionOrderedList->setChecked( false );
636 }
637 else if ( listFormat.style() == QTextListFormat::ListDecimal )
638 {
639 mActionBulletList->setChecked( false );
640 mActionOrderedList->setChecked( true );
641 }
642 else
643 {
644 mActionBulletList->setChecked( false );
645 mActionOrderedList->setChecked( false );
646 }
647 }
648 else
649 {
650 mActionBulletList->setChecked( false );
651 mActionOrderedList->setChecked( false );
652 }
653}
654
655void QgsRichTextEditor::fontChanged( const QFont &f )
656{
657 mFontSizeCombo->setCurrentIndex( mFontSizeCombo->findData( f.pointSize() ) );
658 mActionBold->setChecked( f.bold() );
659 mActionItalic->setChecked( f.italic() );
660 mActionUnderline->setChecked( f.underline() );
661 mActionStrikeOut->setChecked( f.strikeOut() );
662 if ( f.bold() && f.pointSize() == mFontSizeH1 )
663 {
664 mParagraphStyleCombo->setCurrentIndex( ParagraphHeading1 );
665 }
666 else if ( f.bold() && f.pointSize() == mFontSizeH2 )
667 {
668 mParagraphStyleCombo->setCurrentIndex( ParagraphHeading2 );
669 }
670 else if ( f.bold() && f.pointSize() == mFontSizeH3 )
671 {
672 mParagraphStyleCombo->setCurrentIndex( ParagraphHeading3 );
673 }
674 else if ( f.bold() && f.pointSize() == mFontSizeH4 )
675 {
676 mParagraphStyleCombo->setCurrentIndex( ParagraphHeading4 );
677 }
678 else
679 {
680 if ( f.fixedPitch() && f.family() == mMonospaceFontFamily )
681 {
682 mParagraphStyleCombo->setCurrentIndex( ParagraphMonospace );
683 }
684 else
685 {
686 mParagraphStyleCombo->setCurrentIndex( ParagraphStandard );
687 }
688 }
689 if ( mTextEdit->textCursor().currentList() )
690 {
691 const QTextListFormat listFormat = mTextEdit->textCursor().currentList()->format();
692 if ( listFormat.style() == QTextListFormat::ListDisc )
693 {
694 mActionBulletList->setChecked( true );
695 mActionOrderedList->setChecked( false );
696 }
697 else if ( listFormat.style() == QTextListFormat::ListDecimal )
698 {
699 mActionBulletList->setChecked( false );
700 mActionOrderedList->setChecked( true );
701 }
702 else
703 {
704 mActionBulletList->setChecked( false );
705 mActionOrderedList->setChecked( false );
706 }
707 }
708 else
709 {
710 mActionBulletList->setChecked( false );
711 mActionOrderedList->setChecked( false );
712 }
713}
714
715void QgsRichTextEditor::fgColorChanged( const QColor &c )
716{
717 whileBlocking( mForeColorButton )->setColor( c );
718}
719
720void QgsRichTextEditor::bgColorChanged( const QColor &c )
721{
722 if ( c.isValid() )
723 whileBlocking( mBackColorButton )->setColor( c );
724 else
725 whileBlocking( mBackColorButton )->setToNull();
726}
727
728void QgsRichTextEditor::slotCurrentCharFormatChanged( const QTextCharFormat &format )
729{
730 fontChanged( format.font() );
731 bgColorChanged( ( format.background().isOpaque() ) ? format.background().color() : QColor() );
732 fgColorChanged( ( format.foreground().isOpaque() ) ? format.foreground().color() : palette().windowText().color() );
733 mActionInsertLink->setChecked( format.isAnchor() );
734}
735
736void QgsRichTextEditor::slotClipboardDataChanged()
737{
738#ifndef QT_NO_CLIPBOARD
739 if ( const QMimeData *md = QApplication::clipboard()->mimeData() )
740 mActionPaste->setEnabled( md->hasText() );
741#endif
742}
743
744void QgsRichTextEditor::increaseIndentation()
745{
746 indent( +1 );
747}
748
749void QgsRichTextEditor::decreaseIndentation()
750{
751 indent( -1 );
752}
753
754void QgsRichTextEditor::indent( int delta )
755{
756 QTextCursor cursor = mTextEdit->textCursor();
757 cursor.beginEditBlock();
758 QTextBlockFormat format = cursor.blockFormat();
759 const int indent = format.indent();
760 if ( indent + delta >= 0 )
761 {
762 format.setIndent( indent + delta );
763 }
764 cursor.setBlockFormat( format );
765 cursor.endEditBlock();
766}
767
768void QgsRichTextEditor::setText( const QString &text )
769{
770 if ( text.isEmpty() )
771 {
772 mTextEdit->setPlainText( text );
773 mSourceEdit->clear();
774 return;
775 }
776
777 const thread_local QRegularExpression sIsHtmlRx( u"^\\s*<"_s );
778 if ( sIsHtmlRx.match( text ).hasMatch() )
779 {
780 mTextEdit->setHtml( text );
781 mSourceEdit->setText( text );
782 }
783 else
784 {
785 mTextEdit->setPlainText( text );
786 mSourceEdit->setText( text );
787 }
788}
789
790void QgsRichTextEditor::insertImage()
791{
792 const QSettings s;
793 const QString attdir = s.value( u"general/filedialog-path"_s ).toString();
794 const QString file
795 = QFileDialog::getOpenFileName( this, tr( "Select an image" ), attdir, tr( "All images (*.jpg *.jpeg *.png *.bmp *.gif);;JPEG (*.jpg *.jpeg);;PNG (*.png);;BMP (*.bmp);;GIF (*.gif);;All files (*)" ) );
796 if ( file.isEmpty() )
797 return;
798
799 QImageReader reader( file );
800 // Honour any EXIF orientation
801 reader.setAutoTransform( true );
802 const QImage image = reader.read();
803
804 mTextEdit->dropImage( image, QFileInfo( file ).suffix().toUpper().toLocal8Bit().data() );
805}
806
807void QgsRichTextEditor::insertImageFromUrl()
808{
809 bool ok = false;
810 const QString entered = QInputDialog::getText( this, tr( "Image from URL" ), tr( "Enter the URL of an image:" ), QLineEdit::Normal, QString(), &ok );
811 if ( !ok || entered.trimmed().isEmpty() )
812 return;
813
814 const QUrl url( entered.trimmed() );
815 if ( !url.isValid() || !( url.scheme().compare( "http"_L1, Qt::CaseInsensitive ) == 0 || url.scheme().compare( "https"_L1, Qt::CaseInsensitive ) == 0 ) )
816 {
817 QMessageBox::warning( this, tr( "Image from URL" ), tr( "Please enter a valid http:// or https:// URL." ) );
818 return;
819 }
820
821 QgsTemporaryCursorOverride busyCursor( Qt::WaitCursor );
822
823 QNetworkRequest request( url );
824 const QgsNetworkReplyContent content = QgsNetworkAccessManager::instance()->blockingGet( request );
825 if ( content.error() != QNetworkReply::NoError || content.content().isEmpty() )
826 {
827 busyCursor.release();
828 QMessageBox::warning( this, tr( "Image from URL" ), tr( "Failed to download the image: %1" ).arg( content.errorString() ) );
829 return;
830 }
831
832 QByteArray data = content.content();
833 QBuffer buffer( &data );
834 buffer.open( QIODevice::ReadOnly );
835 QImageReader reader;
836 // Honour any EXIF orientation
837 reader.setAutoTransform( true );
838 reader.setDevice( &buffer );
839 // Take the format from the payload rather than the URL
840 const QByteArray format = reader.format().isEmpty() ? QByteArray( "PNG" ) : reader.format().toUpper();
841 const QImage image = reader.read();
842 busyCursor.release();
843 if ( image.isNull() )
844 {
845 QMessageBox::warning( this, tr( "Image from URL" ), tr( "The URL did not return an image." ) );
846 return;
847 }
848
849 mTextEdit->dropImage( image, QString::fromLatin1( format ) );
850}
@ MainWindowToolbar
Main window toolbar icons.
Definition qgis.h:7084
A HTML editor based on QScintilla2.
A widget which wraps a QgsCodeEditor in additional functionality.
void setText(const QString &text) override
static QFont getMonospaceFont()
Returns the monospaced font to use for code editors.
A cross platform button subclass for selecting colors.
void colorChanged(const QColor &color)
Emitted whenever a new color is set for the button.
static QSize iconSize(Qgis::UserInterfaceIconType iconType=Qgis::UserInterfaceIconType::MainWindowToolbar)
Returns the desired size for user interface icons.
Definition qgsgui.cpp:509
static QgsNetworkReplyContent blockingGet(QNetworkRequest &request, const QString &authCfg=QString(), bool forceRefresh=false, QgsFeedback *feedback=nullptr, Qgis::NetworkRequestFlags flags=Qgis::NetworkRequestFlags())
Posts a GET request to obtain the contents of the target request and returns a new QgsNetworkReplyCon...
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
QString errorString() const
Returns the error text for the reply, or an empty string if no error was encountered.
QByteArray content() const
Returns the reply content.
QNetworkReply::NetworkError error() const
Returns the reply's error message, or QNetworkReply::NoError if no error was encountered.
QgsRichTextEditor(QWidget *parent=nullptr)
Constructor for QgsRichTextEditor, with the specified parent widget.
QString toPlainText() const
Returns the widget's content as a plain text string.
Mode mode() const
Returns the widget's mode, which defines which formatting options are exposed in the widget.
void setMode(Mode mode)
Sets the widget's mode, which defines which formatting options are exposed in the widget.
@ QTextDocument
Default mode, exposes the Qt supported HTML/CSS subset.
@ PlainText
Plain text mode.
QString toHtml() const
Returns the widget's content as a HTML string.
void textChanged()
Emitted when the text contents are changed.
void clearSource()
Clears the current text from the widget.
void focusInEvent(QFocusEvent *event) override
void setText(const QString &text)
Sets the text to show in the widget.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition qgis.h:7451