QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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 "qgsrasterlayer.h"
21#include "qgsexpression.h"
22#include "qgslogger.h"
23#include "qgssettings.h"
24#include "qgswebview.h"
25#include "qgswebframe.h"
26#include "qgsapplication.h"
27#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 const 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 if ( ! mWidget )
85 {
86 mWidget = new QWidget( pMapCanvas );
87 mWidget->setContentsMargins( MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE );
88 mWebView = new QgsWebView( mWidget );
89
90
91#if WITH_QTWEBKIT
92 mWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );//Handle link clicks by yourself
93 mWebView->setContextMenuPolicy( Qt::NoContextMenu ); //No context menu is allowed if you don't need it
94 connect( mWebView, &QWebView::linkClicked, this, &QgsMapTip::onLinkClicked );
95 connect( mWebView, &QWebView::loadFinished, this, [ = ]( bool ) { resizeContent(); } );
96#endif
97
98 mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
99 mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
100 mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
101
102 // Disable scrollbars, avoid random resizing issues
103 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
104 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
105
106 QHBoxLayout *layout = new QHBoxLayout;
107 layout->setContentsMargins( 0, 0, 0, 0 );
108 layout->addWidget( mWebView );
109
110 mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
111 mWidget->setLayout( layout );
112
113 // Assure the map tip is never larger than half the map canvas
114 const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
115 const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
116 mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
117
118 // Start with 0 size,
119 // The content will automatically make it grow up to MaximumSize
120 mWidget->resize( 0, 0 );
121
122 backgroundColor = mWidget->palette().base().color().name();
123 strokeColor = mWidget->palette().shadow().color().name();
124 textColor = mWidget->palette().text().color().name();
125 mWidget->setStyleSheet( QString(
126 ".QWidget{"
127 "border: 1px solid %1;"
128 "background-color: %2;}" ).arg(
129 strokeColor, backgroundColor ) );
130 }
131
132 // Only supported layer types here:
133 switch ( pLayer->type() )
134 {
135 case Qgis::LayerType::Vector:
136 tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
137 break;
138 case Qgis::LayerType::Raster:
139 tipText = fetchRaster( pLayer, mapPosition, pMapCanvas );
140 break;
141 default:
142 break;
143 }
144
145 mMapTipVisible = !tipText.isEmpty();
146 if ( !mMapTipVisible )
147 {
148 clear();
149 return;
150 }
151
152 if ( tipText == lastTipText )
153 {
154 return;
155 }
156
157 bodyStyle = QString(
158 "background-color: %1;"
159 "margin: 0;"
160 "font: %2pt \"%3\";"
161 "color: %4;" ).arg( backgroundColor ).arg( mFontSize ).arg( mFontFamily, textColor );
162
163 containerStyle = QString(
164 "display: inline-block;"
165 "margin: 0px" );
166
167 tipHtml = QString(
168 "<html>"
169 "<body style='%1'>"
170 "<div id='QgsWebViewContainer' style='%2'>%3</div>"
171 "</body>"
172 "</html>" ).arg( bodyStyle, containerStyle, tipText );
173
174 QgsDebugMsg( tipHtml );
175
176 int cursorOffset = 0;
177 // attempt to shift the tip away from the cursor.
179 {
180 // The following calculations are taken
181 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
182 // for both hi-dpi and non-hi-dpi screens.
183 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
184 cursorOffset = static_cast< int >( std::ceil( scale * 32 ) );
185 }
186
187 mWidget->move( pixelPosition.x() + cursorOffset, pixelPosition.y() );
188 mWebView->setHtml( tipHtml );
189 lastTipText = tipText;
190
191 mWidget->show();
192
193}
194
195void QgsMapTip::resizeContent()
196{
197#if WITH_QTWEBKIT
198 // Get the content size
199 const QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
200 QStringLiteral( "#QgsWebViewContainer" ) );
201 const int width = container.geometry().width() + MARGIN_VALUE * 2;
202 const int height = container.geometry().height() + MARGIN_VALUE * 2;
203 mWidget->resize( width, height );
204#else
205 mWebView->adjustSize();
206#endif
207}
208
209void QgsMapTip::clear( QgsMapCanvas *, int msDelay )
210{
211 if ( !mMapTipVisible )
212 return;
213
214 // Skip clearing the map tip if the user interacts with it or the timer still runs
215 if ( mDelayedClearTimer.isActive() || mWidget->underMouse() )
216 return;
217
218 if ( msDelay > 0 )
219 {
220 mDelayedClearTimer.start( msDelay );
221 return;
222 }
223 mWebView->setHtml( QString() );
224 mWidget->hide();
225
226 // Reset the visible flag
227 mMapTipVisible = false;
228}
229
230QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
231{
232 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
233 if ( !vlayer || !vlayer->isSpatial() )
234 return QString();
235
236 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) ||
237 ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
238 {
239 return QString();
240 }
241
242 const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
243
244 QgsRectangle r;
245 r.setXMinimum( mapPosition.x() - searchRadius );
246 r.setYMinimum( mapPosition.y() - searchRadius );
247 r.setXMaximum( mapPosition.x() + searchRadius );
248 r.setYMaximum( mapPosition.y() + searchRadius );
249
250 r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
251
253 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
254 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( r.center() ) );
255
256 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
257 if ( canvasFilter == QLatin1String( "FALSE" ) )
258 return QString();
259
260 const QString mapTip = vlayer->mapTipTemplate();
261 QString tipString;
262 QgsExpression exp( vlayer->displayExpression() );
263 QgsFeature feature;
264
265 QgsFeatureRequest request;
266 request.setFilterRect( r );
268 if ( !canvasFilter.isEmpty() )
269 request.setFilterExpression( canvasFilter );
270
271 if ( mapTip.isEmpty() )
272 {
273 exp.prepare( &context );
274 request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
275 }
276
278 renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
280
281 bool filter = false;
282 std::unique_ptr< QgsFeatureRenderer > renderer;
283 if ( vlayer->renderer() )
284 {
285 renderer.reset( vlayer->renderer()->clone() );
286 renderer->startRender( renderCtx, vlayer->fields() );
287 filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
288
289 const QString filterExpression = renderer->filter( vlayer->fields() );
290 if ( ! filterExpression.isEmpty() )
291 {
292 request.combineFilterExpression( filterExpression );
293 }
294 }
295 request.setExpressionContext( renderCtx.expressionContext() );
296
297 QgsFeatureIterator it = vlayer->getFeatures( request );
298 QElapsedTimer timer;
299 timer.start();
300 while ( it.nextFeature( feature ) )
301 {
302 context.setFeature( feature );
303
304 renderCtx.expressionContext().setFeature( feature );
305 if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
306 {
307 continue;
308 }
309
310 if ( !mapTip.isEmpty() )
311 {
312 tipString = QgsExpression::replaceExpressionText( mapTip, &context );
313 }
314 else
315 {
316 tipString = exp.evaluate( &context ).toString();
317 }
318
319 if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
320 {
321 break;
322 }
323 }
324
325 if ( renderer )
326 renderer->stopRender( renderCtx );
327
328 return tipString;
329}
330
331QString QgsMapTip::fetchRaster( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
332{
333 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
334 if ( !rlayer )
335 return QString();
336
337 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) ||
338 ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
339 {
340 return QString();
341 }
342
343 const QgsPointXY mappedPosition { mapCanvas->mapSettings().mapToLayerCoordinates( layer, mapPosition ) };
344
345 if ( ! layer->extent().contains( mappedPosition ) )
346 {
347 return QString( );
348 }
349
350 QString tipText { rlayer->mapTipTemplate() };
351
352 if ( ! tipText.isEmpty() )
353 {
355 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
356 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( mappedPosition ) );
357 tipText = QgsExpression::replaceExpressionText( tipText, &context );
358 }
359
360 return tipText;
361}
362
364{
365 const QgsSettings settings;
366 const QFont defaultFont = qApp->font();
367 mFontSize = settings.value( QStringLiteral( "/qgis/stylesheet/fontPointSize" ), defaultFont.pointSize() ).toInt();
368 mFontFamily = settings.value( QStringLiteral( "/qgis/stylesheet/fontFamily" ), defaultFont.family() ).toString();
369}
370
371// This slot handles all clicks
372void QgsMapTip::onLinkClicked( const QUrl &url )
373{
374 QDesktopServices::openUrl( url );
375}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:3278
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsExpressionContextScope * mapLayerPositionScope(const QgsPointXY &position)
Sets the expression context variables which are available for expressions triggered by moving the mou...
static QgsExpressionContextScope * mapSettingsScope(const QgsMapSettings &mapSettings)
Creates a new scope which contains variables and functions relating to a QgsMapSettings object.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Class for parsing and evaluation of expressions (formerly called "search strings").
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...
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
Definition: qgsrenderer.h:275
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
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...
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:90
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
const QgsDateTimeRange & temporalRange() const
Returns map canvas datetime range.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers shown within the map canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
virtual bool isVisibleInTemporalRange(const QgsDateTimeRange &range) const
Returns true if the layer should be visible and rendered for the specified time range.
Base class for all map layer types.
Definition: qgsmaplayer.h:73
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
virtual QgsRectangle extent() const
Returns the extent of the layer.
Qgis::LayerType type
Definition: qgsmaplayer.h:80
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1523
QString mapTipTemplate
Definition: qgsmaplayer.h:83
double scale() const
Returns the calculated map scale.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
void showMapTip(QgsMapLayer *thepLayer, QgsPointXY &mapPosition, const QPoint &pixelPosition, QgsMapCanvas *mpMapCanvas)
Show a maptip at a given point on the map canvas.
Definition: qgsmaptip.cpp:59
QgsMapTip()
Default constructor.
Definition: qgsmaptip.cpp:47
void applyFontSettings()
Apply font family and size to match user settings.
Definition: qgsmaptip.cpp:363
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
Definition: qgsmaptip.cpp:209
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:232
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
Represents a raster layer.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
Contains information about the context of a rendering operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool isTemporal() const
Returns true if the object's temporal range is enabled, and the object will be filtered when renderin...
Represents a vector layer which manages a vector based data sets.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QString displayExpression
The QgsWebView class is a collection of stubs to mimic the API of QWebView on systems where the real ...
Definition: qgswebview.h:66
#define QgsDebugMsg(str)
Definition: qgslogger.h:38