LCOV - code coverage report
Current view: top level - core/layout - qgslayoutitemhtml.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 0 317 0.0 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           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

Generated by: LCOV version 1.14