QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgslayoutitemhtml.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitemhtml.cpp
3 ------------------------------------------------------------
4 begin : October 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgslayoutitemhtml.h"
17#include "qgslayoutframe.h"
18#include "qgslayout.h"
20#include "qgsmessagelog.h"
21#include "qgsexpression.h"
22#include "qgslogger.h"
24#include "qgsvectorlayer.h"
25#include "qgsproject.h"
26#include "qgsdistancearea.h"
27#include "qgsjsonutils.h"
28#include "qgsmapsettings.h"
29#include "qgswebpage.h"
30#include "qgswebframe.h"
31#include "qgslayoutitemmap.h"
32
33#include <QCoreApplication>
34#include <QPainter>
35#include <QImage>
36#include <QNetworkReply>
37#include <QThread>
38#include <QUrl>
39
40// clazy:excludeall=lambda-in-connect
41
43 : QgsLayoutMultiFrame( layout )
44{
45 mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits();
46
47 // only possible on the main thread!
48 if ( QThread::currentThread() == QApplication::instance()->thread() )
49 {
50 mWebPage = std::make_unique< QgsWebPage >();
51 }
52 else
53 {
54 QgsMessageLog::logMessage( QObject::tr( "Cannot load HTML content in background threads" ) );
55 }
56
57 if ( mWebPage )
58 {
59 mWebPage->setIdentifier( tr( "Layout HTML item" ) );
60 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
61 mWebPage->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
62
63 //This makes the background transparent. Found on http://blog.qt.digia.com/blog/2009/06/30/transparent-qwebview-or-qwebpage/
64 QPalette palette = mWebPage->palette();
65 palette.setBrush( QPalette::Base, Qt::transparent );
66 mWebPage->setPalette( palette );
67
68 mWebPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
69 }
70
71 //a html item added to a layout needs to have the initial expression context set,
72 //otherwise fields in the html aren't correctly evaluated until atlas preview feature changes (#9457)
73 setExpressionContext( mLayout->reportContext().feature(), mLayout->reportContext().layer() );
74
75 connect( &mLayout->reportContext(), &QgsLayoutReportContext::changed, this, &QgsLayoutItemHtml::refreshExpressionContext );
76
77 mFetcher = new QgsNetworkContentFetcher();
78}
79
81{
82 mFetcher->deleteLater();
83}
84
86{
88}
89
91{
92 return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemHtml.svg" ) );
93}
94
96{
97 return new QgsLayoutItemHtml( layout );
98}
99
100void QgsLayoutItemHtml::setUrl( const QUrl &url )
101{
102 if ( !mWebPage )
103 {
104 return;
105 }
106
107 mUrl = url;
108 loadHtml( true );
109 emit changed();
110}
111
112void QgsLayoutItemHtml::setHtml( const QString &html )
113{
114 mHtml = html;
115 //TODO - this signal should be emitted, but without changing the signal which sets the html
116 //to an equivalent of editingFinished it causes a lot of problems. Need to investigate
117 //ways of doing this using QScintilla widgets.
118 //emit changed();
119}
120
121void QgsLayoutItemHtml::setEvaluateExpressions( bool evaluateExpressions )
122{
123 mEvaluateExpressions = evaluateExpressions;
124 loadHtml( true );
125 emit changed();
126}
127
128void QgsLayoutItemHtml::loadHtml( const bool useCache, const QgsExpressionContext *context )
129{
130 if ( !mWebPage )
131 {
132 return;
133 }
134
135 const QgsExpressionContext scopedContext = createExpressionContext();
136 const QgsExpressionContext *evalContext = context ? context : &scopedContext;
137
138 QString loadedHtml;
139 switch ( mContentMode )
140 {
142 {
143
144 QString currentUrl = mUrl.toString();
145
146 //data defined url set?
147 bool ok = false;
148 currentUrl = mDataDefinedProperties.valueAsString( QgsLayoutObject::SourceUrl, *evalContext, currentUrl, &ok );
149 if ( ok )
150 {
151 currentUrl = currentUrl.trimmed();
152 QgsDebugMsg( QStringLiteral( "exprVal Source Url:%1" ).arg( currentUrl ) );
153 }
154 if ( currentUrl.isEmpty() )
155 {
156 return;
157 }
158 if ( !( useCache && currentUrl == mLastFetchedUrl ) )
159 {
160 loadedHtml = fetchHtml( QUrl( currentUrl ) );
161 mLastFetchedUrl = currentUrl;
162 }
163 else
164 {
165 loadedHtml = mFetchedHtml;
166 }
167
168 break;
169 }
171 loadedHtml = mHtml;
172 break;
173 }
174
175 //evaluate expressions
176 if ( mEvaluateExpressions )
177 {
178 loadedHtml = QgsExpression::replaceExpressionText( loadedHtml, evalContext, &mDistanceArea );
179 }
180
181 bool loaded = false;
182
183 QEventLoop loop;
184 connect( mWebPage.get(), &QWebPage::loadFinished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
185 connect( mFetcher, &QgsNetworkContentFetcher::finished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
186
187 //reset page size. otherwise viewport size increases but never decreases again
188 mWebPage->setViewportSize( QSize( maxFrameWidth() * mHtmlUnitsToLayoutUnits, 0 ) );
189
190 //set html, using the specified url as base if in Url mode or the project file if in manual mode
191 const QUrl baseUrl = mContentMode == QgsLayoutItemHtml::Url ?
192 QUrl( mActualFetchedUrl ) :
193 QUrl::fromLocalFile( mLayout->project()->absoluteFilePath() );
194
195 mWebPage->mainFrame()->setHtml( loadedHtml, baseUrl );
196
197 //set user stylesheet
198 QWebSettings *settings = mWebPage->settings();
199 if ( mEnableUserStylesheet && ! mUserStylesheet.isEmpty() )
200 {
201 QByteArray ba;
202 ba.append( mUserStylesheet.toUtf8() );
203 const QUrl cssFileURL = QUrl( QString( "data:text/css;charset=utf-8;base64," + ba.toBase64() ) );
204 settings->setUserStyleSheetUrl( cssFileURL );
205 }
206 else
207 {
208 settings->setUserStyleSheetUrl( QUrl() );
209 }
210
211 if ( !loaded )
212 loop.exec( QEventLoop::ExcludeUserInputEvents );
213
214 //inject JSON feature
215 if ( !mAtlasFeatureJSON.isEmpty() )
216 {
217 JavascriptExecutorLoop jsLoop;
218
219 mWebPage->mainFrame()->addToJavaScriptWindowObject( QStringLiteral( "loop" ), &jsLoop );
220 mWebPage->mainFrame()->evaluateJavaScript( QStringLiteral( "if ( typeof setFeature === \"function\" ) { try{ setFeature(%1); } catch (err) { loop.reportError(err.message); } }; loop.done();" ).arg( mAtlasFeatureJSON ) );
221
222 jsLoop.execIfNotDone();
223 }
224
226 //trigger a repaint
227 emit contentsChanged();
228}
229
230double QgsLayoutItemHtml::maxFrameWidth() const
231{
232 double maxWidth = 0;
234 {
235 maxWidth = std::max( maxWidth, static_cast< double >( frame->boundingRect().width() ) );
236 }
237
238 return maxWidth;
239}
240
242{
243 if ( frameCount() < 1 )
244 return;
245
246 if ( !mWebPage )
247 return;
248
249 QSize contentsSize = mWebPage->mainFrame()->contentsSize();
250
251 //find maximum frame width
252 const double maxWidth = maxFrameWidth();
253 //set content width to match maximum frame width
254 contentsSize.setWidth( maxWidth * mHtmlUnitsToLayoutUnits );
255
256 mWebPage->setViewportSize( contentsSize );
257 mSize.setWidth( contentsSize.width() / mHtmlUnitsToLayoutUnits );
258 mSize.setHeight( contentsSize.height() / mHtmlUnitsToLayoutUnits );
259 if ( contentsSize.isValid() )
260 {
261 renderCachedImage();
262 }
264 emit changed();
265}
266
267void QgsLayoutItemHtml::renderCachedImage()
268{
269 if ( !mWebPage )
270 return;
271
272 //render page to cache image
273 mRenderedPage = QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
274 if ( mRenderedPage.isNull() )
275 {
276 return;
277 }
278 mRenderedPage.fill( Qt::transparent );
279 QPainter painter;
280 painter.begin( &mRenderedPage );
281 mWebPage->mainFrame()->render( &painter );
282 painter.end();
283}
284
285QString QgsLayoutItemHtml::fetchHtml( const QUrl &url )
286{
287 //pause until HTML fetch
288 bool loaded = false;
289 QEventLoop loop;
290 connect( mFetcher, &QgsNetworkContentFetcher::finished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
291 mFetcher->fetchContent( url );
292
293 if ( !loaded )
294 loop.exec( QEventLoop::ExcludeUserInputEvents );
295
296 mFetchedHtml = mFetcher->contentAsString();
297 mActualFetchedUrl = mFetcher->reply()->url().toString();
298 return mFetchedHtml;
299}
300
302{
303 return mSize;
304}
305
306void QgsLayoutItemHtml::render( QgsLayoutItemRenderContext &context, const QRectF &renderExtent, const int )
307{
308 if ( !mWebPage )
309 return;
310
311 QPainter *painter = context.renderContext().painter();
312 const QgsScopedQPainterState painterState( painter );
313 // painter is scaled to dots, so scale back to layout units
314 painter->scale( context.renderContext().scaleFactor() / mHtmlUnitsToLayoutUnits, context.renderContext().scaleFactor() / mHtmlUnitsToLayoutUnits );
315 painter->translate( 0.0, -renderExtent.top() * mHtmlUnitsToLayoutUnits );
316 mWebPage->mainFrame()->render( painter, QRegion( renderExtent.left(), renderExtent.top() * mHtmlUnitsToLayoutUnits, renderExtent.width() * mHtmlUnitsToLayoutUnits, renderExtent.height() * mHtmlUnitsToLayoutUnits ) );
317}
318
319double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
320{
321 if ( !mLayout )
322 {
323 return 1.0;
324 }
325
326 return mLayout->convertToLayoutUnits( QgsLayoutMeasurement( mLayout->renderContext().dpi() / 72.0, QgsUnitTypes::LayoutMillimeters ) ); //webkit seems to assume a standard dpi of 96
327}
328
329bool candidateSort( QPair<int, int> c1, QPair<int, int> c2 )
330{
331 if ( c1.second < c2.second )
332 return true;
333 else if ( c1.second > c2.second )
334 return false;
335 else if ( c1.first > c2.first )
336 return true;
337 else
338 return false;
339}
340
342{
343 if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
344 {
345 return yPos;
346 }
347
348 //convert yPos to pixels
349 const int idealPos = yPos * htmlUnitsToLayoutUnits();
350
351 //if ideal break pos is past end of page, there's nothing we need to do
352 if ( idealPos >= mRenderedPage.height() )
353 {
354 return yPos;
355 }
356
357 const int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
358
359 //loop through all lines just before ideal break location, up to max distance
360 //of maxSearchDistance
361 int changes = 0;
362 QRgb currentColor;
363 bool currentPixelTransparent = false;
364 bool previousPixelTransparent = false;
365 QRgb pixelColor;
366 QList< QPair<int, int> > candidates;
367 const int minRow = std::max( idealPos - maxSearchDistance, 0 );
368 for ( int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
369 {
370 changes = 0;
371 currentColor = qRgba( 0, 0, 0, 0 );
372 //check all pixels in this line
373 for ( int col = 0; col < mRenderedPage.width(); ++col )
374 {
375 //count how many times the pixels change color in this row
376 //eventually, we select a row to break at with the minimum number of color changes
377 //since this is likely a line break, or gap between table cells, etc
378 //but very unlikely to be midway through a text line or picture
379 pixelColor = mRenderedPage.pixel( col, candidateRow );
380 currentPixelTransparent = qAlpha( pixelColor ) == 0;
381 if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
382 {
383 //color has changed
384 currentColor = pixelColor;
385 changes++;
386 }
387 previousPixelTransparent = currentPixelTransparent;
388 }
389 candidates.append( qMakePair( candidateRow, changes ) );
390 }
391
392 //sort candidate rows by number of changes ascending, row number descending
393 std::sort( candidates.begin(), candidates.end(), candidateSort );
394 //first candidate is now the largest row with smallest number of changes
395
396 //OK, now take the mid point of the best candidate position
397 //we do this so that the spacing between text lines is likely to be split in half
398 //otherwise the html will be broken immediately above a line of text, which
399 //looks a little messy
400 const int maxCandidateRow = candidates[0].first;
401 int minCandidateRow = maxCandidateRow + 1;
402 const int minCandidateChanges = candidates[0].second;
403
404 QList< QPair<int, int> >::iterator it;
405 for ( it = candidates.begin(); it != candidates.end(); ++it )
406 {
407 if ( ( *it ).second != minCandidateChanges || ( *it ).first != minCandidateRow - 1 )
408 {
409 //no longer in a consecutive block of rows of minimum pixel color changes
410 //so return the row mid-way through the block
411 //first converting back to mm
412 return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
413 }
414 minCandidateRow = ( *it ).first;
415 }
416
417 //above loop didn't work for some reason
418 //return first candidate converted to mm
419 return candidates[0].first / htmlUnitsToLayoutUnits();
420}
421
422void QgsLayoutItemHtml::setUseSmartBreaks( bool useSmartBreaks )
423{
424 mUseSmartBreaks = useSmartBreaks;
426 emit changed();
427}
428
429void QgsLayoutItemHtml::setMaxBreakDistance( double maxBreakDistance )
430{
431 mMaxBreakDistance = maxBreakDistance;
433 emit changed();
434}
435
436void QgsLayoutItemHtml::setUserStylesheet( const QString &stylesheet )
437{
438 mUserStylesheet = stylesheet;
439 //TODO - this signal should be emitted, but without changing the signal which sets the css
440 //to an equivalent of editingFinished it causes a lot of problems. Need to investigate
441 //ways of doing this using QScintilla widgets.
442 //emit changed();
443}
444
445void QgsLayoutItemHtml::setUserStylesheetEnabled( const bool stylesheetEnabled )
446{
447 if ( mEnableUserStylesheet != stylesheetEnabled )
448 {
449 mEnableUserStylesheet = stylesheetEnabled;
450 loadHtml( true );
451 emit changed();
452 }
453}
454
456{
457 return tr( "<HTML frame>" );
458}
459
460bool QgsLayoutItemHtml::writePropertiesToElement( QDomElement &htmlElem, QDomDocument &, const QgsReadWriteContext & ) const
461{
462 htmlElem.setAttribute( QStringLiteral( "contentMode" ), QString::number( static_cast< int >( mContentMode ) ) );
463 htmlElem.setAttribute( QStringLiteral( "url" ), mUrl.toString() );
464 htmlElem.setAttribute( QStringLiteral( "html" ), mHtml );
465 htmlElem.setAttribute( QStringLiteral( "evaluateExpressions" ), mEvaluateExpressions ? "true" : "false" );
466 htmlElem.setAttribute( QStringLiteral( "useSmartBreaks" ), mUseSmartBreaks ? "true" : "false" );
467 htmlElem.setAttribute( QStringLiteral( "maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
468 htmlElem.setAttribute( QStringLiteral( "stylesheet" ), mUserStylesheet );
469 htmlElem.setAttribute( QStringLiteral( "stylesheetEnabled" ), mEnableUserStylesheet ? "true" : "false" );
470 return true;
471}
472
473bool QgsLayoutItemHtml::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & )
474{
475 bool contentModeOK;
476 mContentMode = static_cast< QgsLayoutItemHtml::ContentMode >( itemElem.attribute( QStringLiteral( "contentMode" ) ).toInt( &contentModeOK ) );
477 if ( !contentModeOK )
478 {
479 mContentMode = QgsLayoutItemHtml::Url;
480 }
481 mEvaluateExpressions = itemElem.attribute( QStringLiteral( "evaluateExpressions" ), QStringLiteral( "true" ) ) == QLatin1String( "true" );
482 mUseSmartBreaks = itemElem.attribute( QStringLiteral( "useSmartBreaks" ), QStringLiteral( "true" ) ) == QLatin1String( "true" );
483 mMaxBreakDistance = itemElem.attribute( QStringLiteral( "maxBreakDistance" ), QStringLiteral( "10" ) ).toDouble();
484 mHtml = itemElem.attribute( QStringLiteral( "html" ) );
485 mUserStylesheet = itemElem.attribute( QStringLiteral( "stylesheet" ) );
486 mEnableUserStylesheet = itemElem.attribute( QStringLiteral( "stylesheetEnabled" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
487
488 //finally load the set url
489 const QString urlString = itemElem.attribute( QStringLiteral( "url" ) );
490 if ( !urlString.isEmpty() )
491 {
492 mUrl = urlString;
493 }
494 loadHtml( true );
495
496 //since frames had to be created before, we need to emit a changed signal to refresh the widget
497 emit changed();
498 return true;
499}
500
501void QgsLayoutItemHtml::setExpressionContext( const QgsFeature &feature, QgsVectorLayer *layer )
502{
503 mExpressionFeature = feature;
504 mExpressionLayer = layer;
505
506 //setup distance area conversion
507 if ( layer )
508 {
509 mDistanceArea.setSourceCrs( layer->crs(), mLayout->project()->transformContext() );
510 }
511 else if ( mLayout )
512 {
513 //set to composition's mapsettings' crs
514 QgsLayoutItemMap *referenceMap = mLayout->referenceMap();
515 if ( referenceMap )
516 mDistanceArea.setSourceCrs( referenceMap->crs(), mLayout->project()->transformContext() );
517 }
518 if ( mLayout )
519 {
520 mDistanceArea.setEllipsoid( mLayout->project()->ellipsoid() );
521 }
522
523 if ( feature.isValid() )
524 {
525 // create JSON representation of feature
526 QgsJsonExporter exporter( layer );
527 exporter.setIncludeRelated( true );
528 mAtlasFeatureJSON = exporter.exportFeature( feature );
529 }
530 else
531 {
532 mAtlasFeatureJSON.clear();
533 }
534}
535
536void QgsLayoutItemHtml::refreshExpressionContext()
537{
538 QgsVectorLayer *vl = nullptr;
539 QgsFeature feature;
540
541 if ( mLayout )
542 {
543 vl = mLayout->reportContext().layer();
544 feature = mLayout->reportContext().feature();
545 }
546
547 setExpressionContext( feature, vl );
548 loadHtml( true );
549}
550
552{
554
555 //updates data defined properties and redraws item to match
556 if ( property == QgsLayoutObject::SourceUrl || property == QgsLayoutObject::AllProperties )
557 {
558 loadHtml( true, &context );
559 }
560}
561
562//JavascriptExecutorLoop
564
565void JavascriptExecutorLoop::done()
566{
567 mDone = true;
568 quit();
569}
570
571void JavascriptExecutorLoop::execIfNotDone()
572{
573 if ( !mDone )
574 exec( QEventLoop::ExcludeUserInputEvents );
575
576 // gross, but nothing else works, so f*** it.. it's not worth spending a day trying to find a non-hacky way
577 // to force the web page to update following the js execution
578 for ( int i = 0; i < 100; i++ )
579 qApp->processEvents();
580}
581
582void JavascriptExecutorLoop::reportError( const QString &error )
583{
584 mDone = true;
585 QgsMessageLog::logMessage( tr( "HTML setFeature function error: %1" ).arg( error ), tr( "Layout" ) );
586 quit();
587}
588
The QWebSettings class is a collection of stubs to mimic the API of a QWebSettings on systems where Q...
Definition: qgswebpage.h:43
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
static QString replaceExpressionText(const QString &action, const QgsExpressionContext *context, const QgsDistanceArea *distanceArea=nullptr)
This function replaces each expression between [% and %] in the string with the result of its evaluat...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:219
Handles exporting QgsFeature features to GeoJSON features.
Definition: qgsjsonutils.h:41
Base class for frame items, which form a layout multiframe item.
A layout multiframe subclass for HTML content.
int type() const override
Returns unique multiframe type id.
QSizeF totalSize() const override
Returns the total size of the multiframe's content, in layout units.
void setUrl(const QUrl &url)
Sets the url for content to display in the item when the item is using the QgsLayoutItemHtml::Url mod...
ContentMode
Source modes for the HTML content to render in the item.
@ ManualHtml
HTML content is manually set for the item.
@ Url
Using this mode item fetches its content via a url.
void setEvaluateExpressions(bool evaluateExpressions)
Sets whether the html item will evaluate QGIS expressions prior to rendering the HTML content.
bool readPropertiesFromElement(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets multiframe state from a DOM element.
QString html() const
Returns the HTML source displayed in the item if the item is using the QgsLayoutItemHtml::ManualHtml ...
~QgsLayoutItemHtml() override
double maxBreakDistance() const
Returns the maximum distance allowed when calculating where to place page breaks in the html.
static QgsLayoutItemHtml * create(QgsLayout *layout)
Returns a new QgsLayoutItemHtml for the specified parent layout.
bool evaluateExpressions() const
Returns whether html item will evaluate QGIS expressions prior to rendering the HTML content.
double findNearbyPageBreak(double yPos) override
Finds the optimal position to break a frame at.
QUrl url() const
Returns the URL of the content displayed in the item if the item is using the QgsLayoutItemHtml::Url ...
void setMaxBreakDistance(double distance)
Sets the maximum distance allowed when calculating where to place page breaks in the html.
void setUserStylesheetEnabled(bool enabled)
Sets whether user stylesheets are enabled for the HTML content.
void setHtml(const QString &html)
Sets the html to display in the item when the item is using the QgsLayoutItemHtml::ManualHtml mode.
QString displayName() const override
Returns the multiframe display name.
void setUseSmartBreaks(bool useSmartBreaks)
Sets whether the html item should use smart breaks.
void recalculateFrameSizes() override
Recalculates the frame sizes for the current viewport dimensions.
void setUserStylesheet(const QString &stylesheet)
Sets the user stylesheet CSS rules to use while rendering the HTML content.
QIcon icon() const override
Returns the item's icon.
void refreshDataDefinedProperty(QgsLayoutObject::DataDefinedProperty property=QgsLayoutObject::AllProperties) override
bool writePropertiesToElement(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores multiframe state within an XML DOM element.
QgsLayoutItemHtml(QgsLayout *layout)
Constructor for QgsLayoutItemHtml, with the specified parent layout.
void loadHtml(bool useCache=false, const QgsExpressionContext *context=nullptr)
Reloads the html source from the url and redraws the item.
void render(QgsLayoutItemRenderContext &context, const QRectF &renderExtent, int frameIndex) override
Renders a portion of the multiframe's content into a render context.
bool useSmartBreaks() const
Returns whether html item is using smart breaks.
Layout graphical items for displaying a map.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
@ LayoutHtml
Html multiframe item.
Contains settings and helpers relating to a render of a QgsLayoutItem.
Definition: qgslayoutitem.h:45
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
Definition: qgslayoutitem.h:72
This class provides a method of storing measurements for use in QGIS layouts using a variety of diffe...
Abstract base class for layout items with the ability to distribute the content to several frames (Qg...
int frameCount() const
Returns the number of frames associated with this multiframe.
void contentsChanged()
Emitted when the contents of the multi frame have changed and the frames must be redrawn.
QgsLayoutFrame * frame(int index) const
Returns the child frame at a specified index from the multiframe.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QList< QgsLayoutFrame * > mFrameItems
virtual void recalculateFrameSizes()
Recalculates the portion of the multiframe item which is shown in each of its component frames.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
void changed()
Emitted when the object's properties change.
QPointer< QgsLayout > mLayout
DataDefinedProperty
Data defined properties for different item types.
@ AllProperties
All properties for item.
@ SourceUrl
Html source url.
void changed()
Emitted certain settings in the context is changed, e.g.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
Definition: qgslayout.h:51
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
HTTP network content fetcher.
void finished()
Emitted when content has loaded.
QNetworkReply * reply()
Returns a reference to the network reply.
QString contentAsString() const
Returns the fetched content as a string.
void fetchContent(const QUrl &url, const QString &authcfg=QString())
Fetches content from a remote URL and handles redirects.
The class is used as a container of context for various read/write operations on other objects.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
Scoped object for saving and restoring a QPainter object's state.
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
Represents a vector layer which manages a vector based data sets.
bool candidateSort(QPair< int, int > c1, QPair< int, int > c2)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38