QGIS API Documentation  3.23.0-Master (22c16f2067)
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 
100 void 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 
112 void 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 
121 void QgsLayoutItemHtml::setEvaluateExpressions( bool evaluateExpressions )
122 {
123  mEvaluateExpressions = evaluateExpressions;
124  loadHtml( true );
125  emit changed();
126 }
127 
128 void 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 
230 double QgsLayoutItemHtml::maxFrameWidth() const
231 {
232  double maxWidth = 0;
233  for ( QgsLayoutFrame *frame : mFrameItems )
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 
267 void 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 
285 QString 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 
306 void 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 
319 double 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 
329 bool 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 
422 void QgsLayoutItemHtml::setUseSmartBreaks( bool useSmartBreaks )
423 {
424  mUseSmartBreaks = useSmartBreaks;
426  emit changed();
427 }
428 
429 void QgsLayoutItemHtml::setMaxBreakDistance( double maxBreakDistance )
430 {
431  mMaxBreakDistance = maxBreakDistance;
433  emit changed();
434 }
435 
436 void 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 
445 void 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 
460 bool 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 
473 bool 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 
501 void 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 
536 void 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 
565 void JavascriptExecutorLoop::done()
566 {
567  mDone = true;
568  quit();
569 }
570 
571 void 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 
582 void 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:191
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