QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsmaptip.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmaptips.cpp - Query a layer and show a maptip on the canvas
3  ---------------------
4  begin : October 2007
5  copyright : (C) 2007 by Gary Sherman
6  email : sherman @ mrcc 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 // QGIS includes
16 #include "qgsfeatureiterator.h"
17 #include "qgsmapcanvas.h"
18 #include "qgsmaptool.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsexpression.h"
21 #include "qgslogger.h"
22 #include "qgssettings.h"
23 #include "qgswebview.h"
24 #include "qgswebframe.h"
25 #include "qgsapplication.h"
26 #include "qgsrenderer.h"
30 #include "qgsrendercontext.h"
31 #include "qgsmapcanvasutils.h"
32 
33 // Qt includes
34 #include <QPoint>
35 #include <QToolTip>
36 #include <QSettings>
37 #include <QLabel>
38 #include <QDesktopServices>
39 #if WITH_QTWEBKIT
40 #include <QWebElement>
41 #endif
42 #include <QHBoxLayout>
43 
44 
45 #include "qgsmaptip.h"
46 
48 {
49  // Init the visible flag
50  mMapTipVisible = false;
51 
52  // Init font-related values
54 
55  mDelayedClearTimer.setSingleShot( true );
56  connect( &mDelayedClearTimer, &QTimer::timeout, this, [ = ]() {this->clear();} );
57 }
58 
60  QgsPointXY &mapPosition,
61  QPoint &pixelPosition,
62  QgsMapCanvas *pMapCanvas )
63 {
64  // Do the search using the active layer and the preferred label field for the
65  // layer. The label field must be defined in the layer configuration
66  // file/database. The code required to do this is similar to identify, except
67  // we only want the first qualifying feature and we will only display the
68  // field defined as the label field in the layer configuration file/database
69 
70  // Do not render map tips if the layer is not visible
71  if ( !pMapCanvas->layers( true ).contains( pLayer ) )
72  {
73  return;
74  }
75 
76  // Do not render a new map tip when the mouse hovers an existing one
77  if ( mWidget && mWidget->underMouse() )
78  return;
79 
80  // Show the maptip on the canvas
81  QString tipText, lastTipText, tipHtml, bodyStyle, containerStyle,
82  backgroundColor, strokeColor, textColor;
83 
84  delete mWidget;
85  mWidget = new QWidget( pMapCanvas );
86  mWidget->setContentsMargins( MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE );
87  mWebView = new QgsWebView( mWidget );
88 
89 
90 #if WITH_QTWEBKIT
91  mWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );//Handle link clicks by yourself
92  mWebView->setContextMenuPolicy( Qt::NoContextMenu ); //No context menu is allowed if you don't need it
93  connect( mWebView, &QWebView::linkClicked, this, &QgsMapTip::onLinkClicked );
94  connect( mWebView, &QWebView::loadFinished, this, [ = ]( bool ) { resizeContent(); } );
95 #endif
96 
97  mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
98  mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
99  mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
100 
101  // Disable scrollbars, avoid random resizing issues
102  mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
103  mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
104 
105  QHBoxLayout *layout = new QHBoxLayout;
106  layout->setContentsMargins( 0, 0, 0, 0 );
107  layout->addWidget( mWebView );
108 
109  mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
110  mWidget->setLayout( layout );
111 
112  // Assure the map tip is never larger than half the map canvas
113  const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
114  const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
115  mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
116 
117  // Start with 0 size,
118  // The content will automatically make it grow up to MaximumSize
119  mWidget->resize( 0, 0 );
120 
121  backgroundColor = mWidget->palette().base().color().name();
122  strokeColor = mWidget->palette().shadow().color().name();
123  textColor = mWidget->palette().text().color().name();
124  mWidget->setStyleSheet( QString(
125  ".QWidget{"
126  "border: 1px solid %1;"
127  "background-color: %2;}" ).arg(
128  strokeColor, backgroundColor ) );
129 
130  tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
131 
132  mMapTipVisible = !tipText.isEmpty();
133  if ( !mMapTipVisible )
134  {
135  clear();
136  return;
137  }
138 
139  if ( tipText == lastTipText )
140  {
141  return;
142  }
143 
144  bodyStyle = QString(
145  "background-color: %1;"
146  "margin: 0;"
147  "font: %2pt \"%3\";"
148  "color: %4;" ).arg( backgroundColor ).arg( mFontSize ).arg( mFontFamily, textColor );
149 
150  containerStyle = QString(
151  "display: inline-block;"
152  "margin: 0px" );
153 
154  tipHtml = QString(
155  "<html>"
156  "<body style='%1'>"
157  "<div id='QgsWebViewContainer' style='%2'>%3</div>"
158  "</body>"
159  "</html>" ).arg( bodyStyle, containerStyle, tipText );
160 
161  QgsDebugMsg( tipHtml );
162 
163  int cursorOffset = 0;
164  // attempt to shift the tip away from the cursor.
165  if ( QgsApplication::instance() )
166  {
167  // The following calculations are taken
168  // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
169  // for both hi-dpi and non-hi-dpi screens.
170  double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
171  cursorOffset = static_cast< int >( std::ceil( scale * 32 ) );
172  }
173 
174  mWidget->move( pixelPosition.x() + cursorOffset, pixelPosition.y() );
175 
176  mWebView->setHtml( tipHtml );
177  lastTipText = tipText;
178 
179  mWidget->show();
180 }
181 
182 void QgsMapTip::resizeContent()
183 {
184 #if WITH_QTWEBKIT
185  // Get the content size
186  const QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
187  QStringLiteral( "#QgsWebViewContainer" ) );
188  const int width = container.geometry().width() + MARGIN_VALUE * 2;
189  const int height = container.geometry().height() + MARGIN_VALUE * 2;
190  mWidget->resize( width, height );
191 #else
192  mWebView->adjustSize();
193 #endif
194 }
195 
196 void QgsMapTip::clear( QgsMapCanvas *, int msDelay )
197 {
198  if ( !mMapTipVisible )
199  return;
200 
201  // Skip clearing the map tip if the user interacts with it or the timer still runs
202  if ( mDelayedClearTimer.isActive() || mWidget->underMouse() )
203  return;
204 
205  if ( msDelay > 0 )
206  {
207  mDelayedClearTimer.start( msDelay );
208  return;
209  }
210  mWebView->setHtml( QString() );
211  mWidget->hide();
212 
213  // Reset the visible flag
214  mMapTipVisible = false;
215 }
216 
217 QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
218 {
219  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
220  if ( !vlayer || !vlayer->isSpatial() )
221  return QString();
222 
223  if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) )
224  {
225  return QString();
226  }
227 
228  const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
229 
230  QgsRectangle r;
231  r.setXMinimum( mapPosition.x() - searchRadius );
232  r.setYMinimum( mapPosition.y() - searchRadius );
233  r.setXMaximum( mapPosition.x() + searchRadius );
234  r.setYMaximum( mapPosition.y() + searchRadius );
235 
236  r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
237 
239  context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
240 
241  const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
242  if ( canvasFilter == QLatin1String( "FALSE" ) )
243  return QString();
244 
245  const QString mapTip = vlayer->mapTipTemplate();
246  QString tipString;
247  QgsExpression exp( vlayer->displayExpression() );
248  QgsFeature feature;
249 
250  QgsFeatureRequest request;
251  request.setFilterRect( r );
253  if ( !canvasFilter.isEmpty() )
254  request.setFilterExpression( canvasFilter );
255 
256  if ( mapTip.isEmpty() )
257  {
258  exp.prepare( &context );
259  request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
260  }
261 
263  renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
265 
266  bool filter = false;
267  std::unique_ptr< QgsFeatureRenderer > renderer;
268  if ( vlayer->renderer() )
269  {
270  renderer.reset( vlayer->renderer()->clone() );
271  renderer->startRender( renderCtx, vlayer->fields() );
272  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
273 
274  const QString filterExpression = renderer->filter( vlayer->fields() );
275  if ( ! filterExpression.isEmpty() )
276  {
277  request.combineFilterExpression( filterExpression );
278  }
279  }
280  request.setExpressionContext( renderCtx.expressionContext() );
281 
282  QgsFeatureIterator it = vlayer->getFeatures( request );
283  QElapsedTimer timer;
284  timer.start();
285  while ( it.nextFeature( feature ) )
286  {
287  context.setFeature( feature );
288 
289  renderCtx.expressionContext().setFeature( feature );
290  if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
291  {
292  continue;
293  }
294 
295  if ( !mapTip.isEmpty() )
296  {
297  tipString = QgsExpression::replaceExpressionText( mapTip, &context );
298  }
299  else
300  {
301  tipString = exp.evaluate( &context ).toString();
302  }
303 
304  if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
305  {
306  break;
307  }
308  }
309 
310  if ( renderer )
311  renderer->stopRender( renderCtx );
312 
313  return tipString;
314 }
315 
317 {
318  const QgsSettings settings;
319  const QFont defaultFont = qApp->font();
320  mFontSize = settings.value( QStringLiteral( "/qgis/stylesheet/fontPointSize" ), defaultFont.pointSize() ).toInt();
321  mFontFamily = settings.value( QStringLiteral( "/qgis/stylesheet/fontFamily" ), defaultFont.family() ).toString();
322 }
323 
324 // This slot handles all clicks
325 void QgsMapTip::onLinkClicked( const QUrl &url )
326 {
327  QDesktopServices::openUrl( url );
328 }
QgsVectorLayer::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Definition: qgsvectorlayer.cpp:1052
QgsExpressionContext
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Definition: qgsexpressioncontext.h:406
qgsexpressioncontextutils.h
QgsFeatureRenderer::Filter
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
Definition: qgsrenderer.h:265
QgsPointXY::y
double y
Definition: qgspointxy.h:63
QgsMapSettings::mapToLayerCoordinates
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
Definition: qgsmapsettings.cpp:633
QgsRenderContext::expressionContext
QgsExpressionContext & expressionContext()
Gets the expression context.
Definition: qgsrendercontext.h:625
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:161
QgsFeatureRequest::ExactIntersect
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition: qgsfeaturerequest.h:117
qgsmapcanvas.h
QgsRenderContext::fromMapSettings
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Definition: qgsrendercontext.cpp:234
qgsexpression.h
qgswebframe.h
QgsMapCanvas::mapSettings
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Definition: qgsmapcanvas.cpp:437
qgsmapcanvasutils.h
qgsfeatureiterator.h
QgsExpressionContextUtils::layerScope
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Definition: qgsexpressioncontextutils.cpp:334
QgsMapCanvas
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:89
QgsExpressionContextUtils::mapSettingsScope
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
Definition: qgsexpressioncontextutils.cpp:427
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:59
QgsVectorLayer::isSpatial
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Definition: qgsvectorlayer.cpp:3733
QgsExpressionContextUtils::globalProjectLayerScopes
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Definition: qgsexpressioncontextutils.cpp:377
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QgsApplication::instance
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
Definition: qgsapplication.cpp:478
QgsFeatureRequest::setSubsetOfAttributes
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
Definition: qgsfeaturerequest.cpp:228
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsFeatureRequest::setExpressionContext
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
Definition: qgsfeaturerequest.cpp:187
QgsFeatureRequest::setFilterExpression
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
Definition: qgsfeaturerequest.cpp:167
qgsapplication.h
QgsFeatureRequest::setFilterRect
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
Definition: qgsfeaturerequest.cpp:101
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsMapLayer::isInScaleRange
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
Definition: qgsmaplayer.cpp:832
QgsFeatureRequest
This class wraps a request for features to a vector layer (or directly its vector data provider).
Definition: qgsfeaturerequest.h:83
qgsmaplayertemporalproperties.h
qgsvectorlayertemporalproperties.h
QgsFeatureRequest::combineFilterExpression
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
Definition: qgsfeaturerequest.cpp:174
QgsFeatureRenderer::clone
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
qgsmaptool.h
qgsrendercontext.h
QgsRectangle::setXMinimum
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
Qgis::UI_SCALE_FACTOR
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2043
QgsMapSettings::scale
double scale() const
Returns the calculated map scale.
Definition: qgsmapsettings.cpp:458
qgsrenderer.h
QgsRectangle::setXMaximum
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
QgsMapTip::clear
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
Definition: qgsmaptip.cpp:196
qgsvectorlayer.h
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:58
QgsRectangle::setYMaximum
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
qgsmaptip.h
QgsMapCanvas::layers
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers shown within the map canvas.
Definition: qgsmapcanvas.cpp:2607
QgsVectorLayer::mapTipTemplate
QString mapTipTemplate
Definition: qgsvectorlayer.h:397
QgsMapTip::applyFontSettings
void applyFontSettings()
Apply font family and size to match user settings.
Definition: qgsmaptip.cpp:316
QgsFeatureIterator::nextFeature
bool nextFeature(QgsFeature &f)
Definition: qgsfeatureiterator.h:399
QgsRectangle::setYMinimum
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsPointXY::x
double x
Definition: qgspointxy.h:62
qgssettings.h
QgsMapTool::searchRadiusMU
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:232
QgsRenderContext::setExpressionContext
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Definition: qgsrendercontext.h:617
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
qgswebview.h
QgsMapCanvas::createExpressionContext
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Definition: qgsmapcanvas.cpp:626
qgslogger.h
QgsMapTip::QgsMapTip
QgsMapTip()
Default constructor.
Definition: qgsmaptip.cpp:47
QgsExpression
Class for parsing and evaluation of expressions (formerly called "search strings")....
Definition: qgsexpression.h:102
QgsVectorLayer::displayExpression
QString displayExpression
Definition: qgsvectorlayer.h:396
QgsFeatureIterator
Wrapper for iterator of features from vector data provider or vector layer.
Definition: qgsfeatureiterator.h:289
QgsExpression::replaceExpressionText
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...
Definition: qgsexpression.cpp:434
QgsFeatureRequest::setFlags
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
Definition: qgsfeaturerequest.cpp:222
QgsMapTip::showMapTip
void showMapTip(QgsMapLayer *thepLayer, QgsPointXY &mapPosition, QPoint &pixelPosition, QgsMapCanvas *mpMapCanvas)
Show a maptip at a given point on the map canvas.
Definition: qgsmaptip.cpp:59
QgsMapCanvasUtils::filterForLayer
static QString filterForLayer(QgsMapCanvas *canvas, QgsVectorLayer *layer)
Constructs a filter to use for selecting features from the given layer, in order to apply filters whi...
Definition: qgsmapcanvasutils.cpp:79
QgsWebView
The QgsWebView class is a collection of stubs to mimic the API of QWebView on systems where the real ...
Definition: qgswebview.h:65
QgsExpressionContext::setFeature
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Definition: qgsexpressioncontext.cpp:525
QgsVectorLayer::renderer
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
Definition: qgsvectorlayer.h:903