Branch data Line data Source code
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"
19 : : #include "qgsnetworkaccessmanager.h"
20 : : #include "qgsmessagelog.h"
21 : : #include "qgsexpression.h"
22 : : #include "qgslogger.h"
23 : : #include "qgsnetworkcontentfetcher.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 : :
42 : 0 : QgsLayoutItemHtml::QgsLayoutItemHtml( QgsLayout *layout )
43 : 0 : : QgsLayoutMultiFrame( layout )
44 : 0 : {
45 : 0 : mHtmlUnitsToLayoutUnits = htmlUnitsToLayoutUnits();
46 : :
47 : : // only possible on the main thread!
48 : 0 : if ( QThread::currentThread() == QApplication::instance()->thread() )
49 : : {
50 : 0 : mWebPage = std::make_unique< QgsWebPage >();
51 : 0 : }
52 : : else
53 : : {
54 : 0 : QgsMessageLog::logMessage( QObject::tr( "Cannot load HTML content in background threads" ) );
55 : : }
56 : :
57 : 0 : if ( mWebPage )
58 : : {
59 : 0 : mWebPage->setIdentifier( tr( "Layout HTML item" ) );
60 : 0 : mWebPage->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
61 : 0 : 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 : 0 : QPalette palette = mWebPage->palette();
65 : 0 : palette.setBrush( QPalette::Base, Qt::transparent );
66 : 0 : mWebPage->setPalette( palette );
67 : :
68 : 0 : mWebPage->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
69 : 0 : }
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 : 0 : setExpressionContext( mLayout->reportContext().feature(), mLayout->reportContext().layer() );
74 : :
75 : 0 : connect( &mLayout->reportContext(), &QgsLayoutReportContext::changed, this, &QgsLayoutItemHtml::refreshExpressionContext );
76 : :
77 : 0 : mFetcher = new QgsNetworkContentFetcher();
78 : 0 : }
79 : :
80 : 0 : QgsLayoutItemHtml::~QgsLayoutItemHtml()
81 : 0 : {
82 : 0 : mFetcher->deleteLater();
83 : 0 : }
84 : :
85 : 0 : int QgsLayoutItemHtml::type() const
86 : : {
87 : 0 : return QgsLayoutItemRegistry::LayoutHtml;
88 : : }
89 : :
90 : 0 : QIcon QgsLayoutItemHtml::icon() const
91 : : {
92 : 0 : return QgsApplication::getThemeIcon( QStringLiteral( "/mLayoutItemHtml.svg" ) );
93 : 0 : }
94 : :
95 : 0 : QgsLayoutItemHtml *QgsLayoutItemHtml::create( QgsLayout *layout )
96 : : {
97 : 0 : return new QgsLayoutItemHtml( layout );
98 : 0 : }
99 : :
100 : 0 : void QgsLayoutItemHtml::setUrl( const QUrl &url )
101 : : {
102 : 0 : if ( !mWebPage )
103 : : {
104 : 0 : return;
105 : : }
106 : :
107 : 0 : mUrl = url;
108 : 0 : loadHtml( true );
109 : 0 : emit changed();
110 : 0 : }
111 : :
112 : 0 : void QgsLayoutItemHtml::setHtml( const QString &html )
113 : : {
114 : 0 : 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 : 0 : }
120 : :
121 : 0 : void QgsLayoutItemHtml::setEvaluateExpressions( bool evaluateExpressions )
122 : : {
123 : 0 : mEvaluateExpressions = evaluateExpressions;
124 : 0 : loadHtml( true );
125 : 0 : emit changed();
126 : 0 : }
127 : :
128 : 0 : void QgsLayoutItemHtml::loadHtml( const bool useCache, const QgsExpressionContext *context )
129 : : {
130 : 0 : if ( !mWebPage )
131 : : {
132 : 0 : return;
133 : : }
134 : :
135 : 0 : QgsExpressionContext scopedContext = createExpressionContext();
136 : 0 : const QgsExpressionContext *evalContext = context ? context : &scopedContext;
137 : :
138 : 0 : QString loadedHtml;
139 : 0 : switch ( mContentMode )
140 : : {
141 : : case QgsLayoutItemHtml::Url:
142 : : {
143 : :
144 : 0 : QString currentUrl = mUrl.toString();
145 : :
146 : : //data defined url set?
147 : 0 : bool ok = false;
148 : 0 : currentUrl = mDataDefinedProperties.valueAsString( QgsLayoutObject::SourceUrl, *evalContext, currentUrl, &ok );
149 : 0 : if ( ok )
150 : : {
151 : 0 : currentUrl = currentUrl.trimmed();
152 : 0 : QgsDebugMsg( QStringLiteral( "exprVal Source Url:%1" ).arg( currentUrl ) );
153 : 0 : }
154 : 0 : if ( currentUrl.isEmpty() )
155 : : {
156 : 0 : return;
157 : : }
158 : 0 : if ( !( useCache && currentUrl == mLastFetchedUrl ) )
159 : : {
160 : 0 : loadedHtml = fetchHtml( QUrl( currentUrl ) );
161 : 0 : mLastFetchedUrl = currentUrl;
162 : 0 : }
163 : : else
164 : : {
165 : 0 : loadedHtml = mFetchedHtml;
166 : : }
167 : :
168 : 0 : break;
169 : 0 : }
170 : : case QgsLayoutItemHtml::ManualHtml:
171 : 0 : loadedHtml = mHtml;
172 : 0 : break;
173 : : }
174 : :
175 : : //evaluate expressions
176 : 0 : if ( mEvaluateExpressions )
177 : : {
178 : 0 : loadedHtml = QgsExpression::replaceExpressionText( loadedHtml, evalContext, &mDistanceArea );
179 : 0 : }
180 : :
181 : 0 : bool loaded = false;
182 : :
183 : 0 : QEventLoop loop;
184 : 0 : connect( mWebPage.get(), &QWebPage::loadFinished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
185 : 0 : 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 : 0 : 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 : 0 : const QUrl baseUrl = mContentMode == QgsLayoutItemHtml::Url ?
192 : 0 : QUrl( mActualFetchedUrl ) :
193 : 0 : QUrl::fromLocalFile( mLayout->project()->absoluteFilePath() );
194 : :
195 : 0 : mWebPage->mainFrame()->setHtml( loadedHtml, baseUrl );
196 : :
197 : : //set user stylesheet
198 : 0 : QWebSettings *settings = mWebPage->settings();
199 : 0 : if ( mEnableUserStylesheet && ! mUserStylesheet.isEmpty() )
200 : : {
201 : 0 : QByteArray ba;
202 : 0 : ba.append( mUserStylesheet.toUtf8() );
203 : 0 : QUrl cssFileURL = QUrl( QString( "data:text/css;charset=utf-8;base64," + ba.toBase64() ) );
204 : 0 : settings->setUserStyleSheetUrl( cssFileURL );
205 : 0 : }
206 : : else
207 : : {
208 : 0 : settings->setUserStyleSheetUrl( QUrl() );
209 : : }
210 : :
211 : 0 : if ( !loaded )
212 : 0 : loop.exec( QEventLoop::ExcludeUserInputEvents );
213 : :
214 : : //inject JSON feature
215 : 0 : if ( !mAtlasFeatureJSON.isEmpty() )
216 : : {
217 : 0 : JavascriptExecutorLoop jsLoop;
218 : :
219 : 0 : mWebPage->mainFrame()->addToJavaScriptWindowObject( QStringLiteral( "loop" ), &jsLoop );
220 : 0 : mWebPage->mainFrame()->evaluateJavaScript( QStringLiteral( "if ( typeof setFeature === \"function\" ) { try{ setFeature(%1); } catch (err) { loop.reportError(err.message); } }; loop.done();" ).arg( mAtlasFeatureJSON ) );
221 : :
222 : 0 : jsLoop.execIfNotDone();
223 : 0 : }
224 : :
225 : 0 : recalculateFrameSizes();
226 : : //trigger a repaint
227 : 0 : emit contentsChanged();
228 : 0 : }
229 : :
230 : 0 : double QgsLayoutItemHtml::maxFrameWidth() const
231 : : {
232 : 0 : double maxWidth = 0;
233 : 0 : for ( QgsLayoutFrame *frame : mFrameItems )
234 : : {
235 : 0 : maxWidth = std::max( maxWidth, static_cast< double >( frame->boundingRect().width() ) );
236 : : }
237 : :
238 : 0 : return maxWidth;
239 : : }
240 : :
241 : 0 : void QgsLayoutItemHtml::recalculateFrameSizes()
242 : : {
243 : 0 : if ( frameCount() < 1 )
244 : 0 : return;
245 : 0 :
246 : 0 : if ( !mWebPage )
247 : 0 : return;
248 : :
249 : 0 : QSize contentsSize = mWebPage->mainFrame()->contentsSize();
250 : 0 :
251 : : //find maximum frame width
252 : 0 : double maxWidth = maxFrameWidth();
253 : : //set content width to match maximum frame width
254 : 0 : contentsSize.setWidth( maxWidth * mHtmlUnitsToLayoutUnits );
255 : :
256 : 0 : mWebPage->setViewportSize( contentsSize );
257 : 0 : mSize.setWidth( contentsSize.width() / mHtmlUnitsToLayoutUnits );
258 : 0 : mSize.setHeight( contentsSize.height() / mHtmlUnitsToLayoutUnits );
259 : 0 : if ( contentsSize.isValid() )
260 : : {
261 : 0 : renderCachedImage();
262 : 0 : }
263 : 0 : QgsLayoutMultiFrame::recalculateFrameSizes();
264 : 0 : emit changed();
265 : 0 : }
266 : :
267 : 0 : void QgsLayoutItemHtml::renderCachedImage()
268 : : {
269 : 0 : if ( !mWebPage )
270 : 0 : return;
271 : :
272 : : //render page to cache image
273 : 0 : mRenderedPage = QImage( mWebPage->viewportSize(), QImage::Format_ARGB32 );
274 : 0 : if ( mRenderedPage.isNull() )
275 : : {
276 : 0 : return;
277 : : }
278 : 0 : mRenderedPage.fill( Qt::transparent );
279 : 0 : QPainter painter;
280 : 0 : painter.begin( &mRenderedPage );
281 : 0 : mWebPage->mainFrame()->render( &painter );
282 : 0 : painter.end();
283 : 0 : }
284 : :
285 : 0 : QString QgsLayoutItemHtml::fetchHtml( const QUrl &url )
286 : : {
287 : : //pause until HTML fetch
288 : 0 : bool loaded = false;
289 : 0 : QEventLoop loop;
290 : 0 : connect( mFetcher, &QgsNetworkContentFetcher::finished, &loop, [&loaded, &loop ] { loaded = true; loop.quit(); } );
291 : 0 : mFetcher->fetchContent( url );
292 : :
293 : 0 : if ( !loaded )
294 : 0 : loop.exec( QEventLoop::ExcludeUserInputEvents );
295 : :
296 : 0 : mFetchedHtml = mFetcher->contentAsString();
297 : 0 : mActualFetchedUrl = mFetcher->reply()->url().toString();
298 : 0 : return mFetchedHtml;
299 : 0 : }
300 : :
301 : 0 : QSizeF QgsLayoutItemHtml::totalSize() const
302 : : {
303 : 0 : return mSize;
304 : : }
305 : :
306 : 0 : void QgsLayoutItemHtml::render( QgsLayoutItemRenderContext &context, const QRectF &renderExtent, const int )
307 : : {
308 : 0 : if ( !mWebPage )
309 : 0 : return;
310 : :
311 : 0 : QPainter *painter = context.renderContext().painter();
312 : 0 : QgsScopedQPainterState painterState( painter );
313 : : // painter is scaled to dots, so scale back to layout units
314 : 0 : painter->scale( context.renderContext().scaleFactor() / mHtmlUnitsToLayoutUnits, context.renderContext().scaleFactor() / mHtmlUnitsToLayoutUnits );
315 : 0 : painter->translate( 0.0, -renderExtent.top() * mHtmlUnitsToLayoutUnits );
316 : 0 : mWebPage->mainFrame()->render( painter, QRegion( renderExtent.left(), renderExtent.top() * mHtmlUnitsToLayoutUnits, renderExtent.width() * mHtmlUnitsToLayoutUnits, renderExtent.height() * mHtmlUnitsToLayoutUnits ) );
317 : 0 : }
318 : :
319 : 0 : double QgsLayoutItemHtml::htmlUnitsToLayoutUnits()
320 : : {
321 : 0 : if ( !mLayout )
322 : : {
323 : 0 : return 1.0;
324 : : }
325 : :
326 : 0 : return mLayout->convertToLayoutUnits( QgsLayoutMeasurement( mLayout->renderContext().dpi() / 72.0, QgsUnitTypes::LayoutMillimeters ) ); //webkit seems to assume a standard dpi of 96
327 : 0 : }
328 : :
329 : 0 : bool candidateSort( QPair<int, int> c1, QPair<int, int> c2 )
330 : : {
331 : 0 : if ( c1.second < c2.second )
332 : 0 : return true;
333 : 0 : else if ( c1.second > c2.second )
334 : 0 : return false;
335 : 0 : else if ( c1.first > c2.first )
336 : 0 : return true;
337 : : else
338 : 0 : return false;
339 : 0 : }
340 : :
341 : 0 : double QgsLayoutItemHtml::findNearbyPageBreak( double yPos )
342 : : {
343 : 0 : if ( !mWebPage || mRenderedPage.isNull() || !mUseSmartBreaks )
344 : : {
345 : 0 : return yPos;
346 : : }
347 : :
348 : : //convert yPos to pixels
349 : 0 : int idealPos = yPos * htmlUnitsToLayoutUnits();
350 : :
351 : : //if ideal break pos is past end of page, there's nothing we need to do
352 : 0 : if ( idealPos >= mRenderedPage.height() )
353 : : {
354 : 0 : return yPos;
355 : : }
356 : :
357 : 0 : int maxSearchDistance = mMaxBreakDistance * htmlUnitsToLayoutUnits();
358 : :
359 : : //loop through all lines just before ideal break location, up to max distance
360 : : //of maxSearchDistance
361 : 0 : int changes = 0;
362 : : QRgb currentColor;
363 : 0 : bool currentPixelTransparent = false;
364 : 0 : bool previousPixelTransparent = false;
365 : : QRgb pixelColor;
366 : 0 : QList< QPair<int, int> > candidates;
367 : 0 : int minRow = std::max( idealPos - maxSearchDistance, 0 );
368 : 0 : for ( int candidateRow = idealPos; candidateRow >= minRow; --candidateRow )
369 : : {
370 : 0 : changes = 0;
371 : 0 : currentColor = qRgba( 0, 0, 0, 0 );
372 : : //check all pixels in this line
373 : 0 : 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 : 0 : pixelColor = mRenderedPage.pixel( col, candidateRow );
380 : 0 : currentPixelTransparent = qAlpha( pixelColor ) == 0;
381 : 0 : if ( pixelColor != currentColor && !( currentPixelTransparent && previousPixelTransparent ) )
382 : : {
383 : : //color has changed
384 : 0 : currentColor = pixelColor;
385 : 0 : changes++;
386 : 0 : }
387 : 0 : previousPixelTransparent = currentPixelTransparent;
388 : 0 : }
389 : 0 : candidates.append( qMakePair( candidateRow, changes ) );
390 : 0 : }
391 : :
392 : : //sort candidate rows by number of changes ascending, row number descending
393 : 0 : 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 : 0 : int maxCandidateRow = candidates[0].first;
401 : 0 : int minCandidateRow = maxCandidateRow + 1;
402 : 0 : int minCandidateChanges = candidates[0].second;
403 : :
404 : 0 : QList< QPair<int, int> >::iterator it;
405 : 0 : for ( it = candidates.begin(); it != candidates.end(); ++it )
406 : : {
407 : 0 : 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 : 0 : return ( minCandidateRow + ( maxCandidateRow - minCandidateRow ) / 2 ) / htmlUnitsToLayoutUnits();
413 : : }
414 : 0 : minCandidateRow = ( *it ).first;
415 : 0 : }
416 : :
417 : : //above loop didn't work for some reason
418 : : //return first candidate converted to mm
419 : 0 : return candidates[0].first / htmlUnitsToLayoutUnits();
420 : 0 : }
421 : :
422 : 0 : void QgsLayoutItemHtml::setUseSmartBreaks( bool useSmartBreaks )
423 : : {
424 : 0 : mUseSmartBreaks = useSmartBreaks;
425 : 0 : recalculateFrameSizes();
426 : 0 : emit changed();
427 : 0 : }
428 : :
429 : 0 : void QgsLayoutItemHtml::setMaxBreakDistance( double maxBreakDistance )
430 : : {
431 : 0 : mMaxBreakDistance = maxBreakDistance;
432 : 0 : recalculateFrameSizes();
433 : 0 : emit changed();
434 : 0 : }
435 : :
436 : 0 : void QgsLayoutItemHtml::setUserStylesheet( const QString &stylesheet )
437 : : {
438 : 0 : 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 : 0 : }
444 : :
445 : 0 : void QgsLayoutItemHtml::setUserStylesheetEnabled( const bool stylesheetEnabled )
446 : : {
447 : 0 : if ( mEnableUserStylesheet != stylesheetEnabled )
448 : : {
449 : 0 : mEnableUserStylesheet = stylesheetEnabled;
450 : 0 : loadHtml( true );
451 : 0 : emit changed();
452 : 0 : }
453 : 0 : }
454 : :
455 : 0 : QString QgsLayoutItemHtml::displayName() const
456 : : {
457 : 0 : return tr( "<HTML frame>" );
458 : : }
459 : :
460 : 0 : bool QgsLayoutItemHtml::writePropertiesToElement( QDomElement &htmlElem, QDomDocument &, const QgsReadWriteContext & ) const
461 : : {
462 : 0 : htmlElem.setAttribute( QStringLiteral( "contentMode" ), QString::number( static_cast< int >( mContentMode ) ) );
463 : 0 : htmlElem.setAttribute( QStringLiteral( "url" ), mUrl.toString() );
464 : 0 : htmlElem.setAttribute( QStringLiteral( "html" ), mHtml );
465 : 0 : htmlElem.setAttribute( QStringLiteral( "evaluateExpressions" ), mEvaluateExpressions ? "true" : "false" );
466 : 0 : htmlElem.setAttribute( QStringLiteral( "useSmartBreaks" ), mUseSmartBreaks ? "true" : "false" );
467 : 0 : htmlElem.setAttribute( QStringLiteral( "maxBreakDistance" ), QString::number( mMaxBreakDistance ) );
468 : 0 : htmlElem.setAttribute( QStringLiteral( "stylesheet" ), mUserStylesheet );
469 : 0 : htmlElem.setAttribute( QStringLiteral( "stylesheetEnabled" ), mEnableUserStylesheet ? "true" : "false" );
470 : 0 : return true;
471 : 0 : }
472 : :
473 : 0 : bool QgsLayoutItemHtml::readPropertiesFromElement( const QDomElement &itemElem, const QDomDocument &, const QgsReadWriteContext & )
474 : : {
475 : : bool contentModeOK;
476 : 0 : mContentMode = static_cast< QgsLayoutItemHtml::ContentMode >( itemElem.attribute( QStringLiteral( "contentMode" ) ).toInt( &contentModeOK ) );
477 : 0 : if ( !contentModeOK )
478 : : {
479 : 0 : mContentMode = QgsLayoutItemHtml::Url;
480 : 0 : }
481 : 0 : mEvaluateExpressions = itemElem.attribute( QStringLiteral( "evaluateExpressions" ), QStringLiteral( "true" ) ) == QLatin1String( "true" );
482 : 0 : mUseSmartBreaks = itemElem.attribute( QStringLiteral( "useSmartBreaks" ), QStringLiteral( "true" ) ) == QLatin1String( "true" );
483 : 0 : mMaxBreakDistance = itemElem.attribute( QStringLiteral( "maxBreakDistance" ), QStringLiteral( "10" ) ).toDouble();
484 : 0 : mHtml = itemElem.attribute( QStringLiteral( "html" ) );
485 : 0 : mUserStylesheet = itemElem.attribute( QStringLiteral( "stylesheet" ) );
486 : 0 : mEnableUserStylesheet = itemElem.attribute( QStringLiteral( "stylesheetEnabled" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
487 : :
488 : : //finally load the set url
489 : 0 : QString urlString = itemElem.attribute( QStringLiteral( "url" ) );
490 : 0 : if ( !urlString.isEmpty() )
491 : : {
492 : 0 : mUrl = urlString;
493 : 0 : }
494 : 0 : loadHtml( true );
495 : :
496 : : //since frames had to be created before, we need to emit a changed signal to refresh the widget
497 : 0 : emit changed();
498 : : return true;
499 : 0 : }
500 : :
501 : 0 : void QgsLayoutItemHtml::setExpressionContext( const QgsFeature &feature, QgsVectorLayer *layer )
502 : : {
503 : 0 : mExpressionFeature = feature;
504 : 0 : mExpressionLayer = layer;
505 : :
506 : : //setup distance area conversion
507 : 0 : if ( layer )
508 : : {
509 : 0 : mDistanceArea.setSourceCrs( layer->crs(), mLayout->project()->transformContext() );
510 : 0 : }
511 : 0 : else if ( mLayout )
512 : : {
513 : : //set to composition's mapsettings' crs
514 : 0 : QgsLayoutItemMap *referenceMap = mLayout->referenceMap();
515 : 0 : if ( referenceMap )
516 : 0 : mDistanceArea.setSourceCrs( referenceMap->crs(), mLayout->project()->transformContext() );
517 : 0 : }
518 : 0 : if ( mLayout )
519 : : {
520 : 0 : mDistanceArea.setEllipsoid( mLayout->project()->ellipsoid() );
521 : 0 : }
522 : :
523 : 0 : if ( feature.isValid() )
524 : : {
525 : : // create JSON representation of feature
526 : 0 : QgsJsonExporter exporter( layer );
527 : 0 : exporter.setIncludeRelated( true );
528 : 0 : mAtlasFeatureJSON = exporter.exportFeature( feature );
529 : 0 : }
530 : : else
531 : : {
532 : 0 : mAtlasFeatureJSON.clear();
533 : : }
534 : 0 : }
535 : :
536 : 0 : void QgsLayoutItemHtml::refreshExpressionContext()
537 : : {
538 : 0 : QgsVectorLayer *vl = nullptr;
539 : 0 : QgsFeature feature;
540 : :
541 : 0 : if ( mLayout )
542 : : {
543 : 0 : vl = mLayout->reportContext().layer();
544 : 0 : feature = mLayout->reportContext().feature();
545 : 0 : }
546 : :
547 : 0 : setExpressionContext( feature, vl );
548 : 0 : loadHtml( true );
549 : 0 : }
550 : :
551 : 0 : void QgsLayoutItemHtml::refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property )
552 : : {
553 : 0 : QgsExpressionContext context = createExpressionContext();
554 : :
555 : : //updates data defined properties and redraws item to match
556 : 0 : if ( property == QgsLayoutObject::SourceUrl || property == QgsLayoutObject::AllProperties )
557 : : {
558 : 0 : loadHtml( true, &context );
559 : 0 : }
560 : 0 : }
561 : :
562 : : //JavascriptExecutorLoop
563 : : ///@cond PRIVATE
564 : :
565 : 0 : void JavascriptExecutorLoop::done()
566 : : {
567 : 0 : mDone = true;
568 : 0 : quit();
569 : 0 : }
570 : :
571 : 0 : void JavascriptExecutorLoop::execIfNotDone()
572 : : {
573 : 0 : if ( !mDone )
574 : 0 : 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 : 0 : for ( int i = 0; i < 100; i++ )
579 : 0 : qApp->processEvents();
580 : 0 : }
581 : :
582 : 0 : void JavascriptExecutorLoop::reportError( const QString &error )
583 : : {
584 : 0 : mDone = true;
585 : 0 : QgsMessageLog::logMessage( tr( "HTML setFeature function error: %1" ).arg( error ), tr( "Layout" ) );
586 : 0 : quit();
587 : 0 : }
588 : :
589 : : ///@endcond
|