QGIS API Documentation 3.29.0-Master (8c80f25a4f)
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"
31#include "qgsrendercontext.h"
32#include "qgsmapcanvasutils.h"
33
34// Qt includes
35#include <QPoint>
36#include <QToolTip>
37#include <QSettings>
38#include <QLabel>
39#include <QDesktopServices>
40#if WITH_QTWEBKIT
41#include <QWebElement>
42#endif
43#include <QHBoxLayout>
44
45
46#include "qgsmaptip.h"
47
49{
50 // Init the visible flag
51 mMapTipVisible = false;
52
53 // Init font-related values
55
56 mDelayedClearTimer.setSingleShot( true );
57 connect( &mDelayedClearTimer, &QTimer::timeout, this, [ = ]() {this->clear();} );
58}
59
61 QgsPointXY &mapPosition,
62 QPoint &pixelPosition,
63 QgsMapCanvas *pMapCanvas )
64{
65 // Do the search using the active layer and the preferred label field for the
66 // layer. The label field must be defined in the layer configuration
67 // file/database. The code required to do this is similar to identify, except
68 // we only want the first qualifying feature and we will only display the
69 // field defined as the label field in the layer configuration file/database
70
71 // Do not render map tips if the layer is not visible
72 if ( !pMapCanvas->layers( true ).contains( pLayer ) )
73 {
74 return;
75 }
76
77 // Do not render a new map tip when the mouse hovers an existing one
78 if ( mWidget && mWidget->underMouse() )
79 return;
80
81 // Show the maptip on the canvas
82 QString tipText, lastTipText, tipHtml, bodyStyle, containerStyle,
83 backgroundColor, strokeColor, textColor;
84
85 if ( ! mWidget )
86 {
87 mWidget = new QWidget( pMapCanvas );
88 mWidget->setContentsMargins( MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE );
89 mWebView = new QgsWebView( mWidget );
90
91
92#if WITH_QTWEBKIT
93 mWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );//Handle link clicks by yourself
94 mWebView->setContextMenuPolicy( Qt::NoContextMenu ); //No context menu is allowed if you don't need it
95 connect( mWebView, &QWebView::linkClicked, this, &QgsMapTip::onLinkClicked );
96 connect( mWebView, &QWebView::loadFinished, this, [ = ]( bool ) { resizeContent(); } );
97#endif
98
99 mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
100 mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
101 mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
102
103 // Disable scrollbars, avoid random resizing issues
104 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
105 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
106
107 QHBoxLayout *layout = new QHBoxLayout;
108 layout->setContentsMargins( 0, 0, 0, 0 );
109 layout->addWidget( mWebView );
110
111 mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
112 mWidget->setLayout( layout );
113
114 // Assure the map tip is never larger than half the map canvas
115 const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
116 const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
117 mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
118
119 // Start with 0 size,
120 // The content will automatically make it grow up to MaximumSize
121 mWidget->resize( 0, 0 );
122
123 backgroundColor = mWidget->palette().base().color().name();
124 strokeColor = mWidget->palette().shadow().color().name();
125 textColor = mWidget->palette().text().color().name();
126 mWidget->setStyleSheet( QString(
127 ".QWidget{"
128 "border: 1px solid %1;"
129 "background-color: %2;}" ).arg(
130 strokeColor, backgroundColor ) );
131 }
132
133 // Only supported layer types here:
134 switch ( pLayer->type() )
135 {
137 tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
138 break;
140 tipText = fetchRaster( pLayer, mapPosition, pMapCanvas );
141 break;
142 default:
143 break;
144 }
145
146 mMapTipVisible = !tipText.isEmpty();
147 if ( !mMapTipVisible )
148 {
149 clear();
150 return;
151 }
152
153 if ( tipText == lastTipText )
154 {
155 return;
156 }
157
158 bodyStyle = QString(
159 "background-color: %1;"
160 "margin: 0;"
161 "font: %2pt \"%3\";"
162 "color: %4;" ).arg( backgroundColor ).arg( mFontSize ).arg( mFontFamily, textColor );
163
164 containerStyle = QString(
165 "display: inline-block;"
166 "margin: 0px" );
167
168 tipHtml = QString(
169 "<html>"
170 "<body style='%1'>"
171 "<div id='QgsWebViewContainer' style='%2'>%3</div>"
172 "</body>"
173 "</html>" ).arg( bodyStyle, containerStyle, tipText );
174
175 QgsDebugMsg( tipHtml );
176
177 int cursorOffset = 0;
178 // attempt to shift the tip away from the cursor.
180 {
181 // The following calculations are taken
182 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
183 // for both hi-dpi and non-hi-dpi screens.
184 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
185 cursorOffset = static_cast< int >( std::ceil( scale * 32 ) );
186 }
187
188 mWidget->move( pixelPosition.x() + cursorOffset, pixelPosition.y() );
189 mWebView->setHtml( tipHtml );
190 lastTipText = tipText;
191
192 mWidget->show();
193
194}
195
196void QgsMapTip::resizeContent()
197{
198#if WITH_QTWEBKIT
199 // Get the content size
200 const QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
201 QStringLiteral( "#QgsWebViewContainer" ) );
202 const int width = container.geometry().width() + MARGIN_VALUE * 2;
203 const int height = container.geometry().height() + MARGIN_VALUE * 2;
204 mWidget->resize( width, height );
205#else
206 mWebView->adjustSize();
207#endif
208}
209
210void QgsMapTip::clear( QgsMapCanvas *, int msDelay )
211{
212 if ( !mMapTipVisible )
213 return;
214
215 // Skip clearing the map tip if the user interacts with it or the timer still runs
216 if ( mDelayedClearTimer.isActive() || mWidget->underMouse() )
217 return;
218
219 if ( msDelay > 0 )
220 {
221 mDelayedClearTimer.start( msDelay );
222 return;
223 }
224 mWebView->setHtml( QString() );
225 mWidget->hide();
226
227 // Reset the visible flag
228 mMapTipVisible = false;
229}
230
231QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
232{
233 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
234 if ( !vlayer || !vlayer->isSpatial() )
235 return QString();
236
237 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) ||
238 ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
239 {
240 return QString();
241 }
242
243 const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
244
245 QgsRectangle r;
246 r.setXMinimum( mapPosition.x() - searchRadius );
247 r.setYMinimum( mapPosition.y() - searchRadius );
248 r.setXMaximum( mapPosition.x() + searchRadius );
249 r.setYMaximum( mapPosition.y() + searchRadius );
250
251 r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
252
254 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
255 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( r.center() ) );
256
257 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
258 if ( canvasFilter == QLatin1String( "FALSE" ) )
259 return QString();
260
261 const QString mapTip = vlayer->mapTipTemplate();
262 QString tipString;
263 QgsExpression exp( vlayer->displayExpression() );
264 QgsFeature feature;
265
266 QgsFeatureRequest request;
267 request.setFilterRect( r );
269 if ( !canvasFilter.isEmpty() )
270 request.setFilterExpression( canvasFilter );
271
272 if ( mapTip.isEmpty() )
273 {
274 exp.prepare( &context );
275 request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
276 }
277
279 renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
281
282 bool filter = false;
283 std::unique_ptr< QgsFeatureRenderer > renderer;
284 if ( vlayer->renderer() )
285 {
286 renderer.reset( vlayer->renderer()->clone() );
287 renderer->startRender( renderCtx, vlayer->fields() );
288 filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
289
290 const QString filterExpression = renderer->filter( vlayer->fields() );
291 if ( ! filterExpression.isEmpty() )
292 {
293 request.combineFilterExpression( filterExpression );
294 }
295 }
296 request.setExpressionContext( renderCtx.expressionContext() );
297
298 QgsFeatureIterator it = vlayer->getFeatures( request );
299 QElapsedTimer timer;
300 timer.start();
301 while ( it.nextFeature( feature ) )
302 {
303 context.setFeature( feature );
304
305 renderCtx.expressionContext().setFeature( feature );
306 if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
307 {
308 continue;
309 }
310
311 if ( !mapTip.isEmpty() )
312 {
313 tipString = QgsExpression::replaceExpressionText( mapTip, &context );
314 }
315 else
316 {
317 tipString = exp.evaluate( &context ).toString();
318 }
319
320 if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
321 {
322 break;
323 }
324 }
325
326 if ( renderer )
327 renderer->stopRender( renderCtx );
328
329 return tipString;
330}
331
332QString QgsMapTip::fetchRaster( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
333{
334 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
335 if ( !rlayer )
336 return QString();
337
338 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) ||
339 ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
340 {
341 return QString();
342 }
343
344 const QgsPointXY mappedPosition { mapCanvas->mapSettings().mapToLayerCoordinates( layer, mapPosition ) };
345
346 if ( ! layer->extent().contains( mappedPosition ) )
347 {
348 return QString( );
349 }
350
351 QString tipText { rlayer->mapTipTemplate() };
352
353 if ( ! tipText.isEmpty() )
354 {
356 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
357 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( mappedPosition ) );
358 tipText = QgsExpression::replaceExpressionText( tipText, &context );
359 }
360
361 return tipText;
362}
363
365{
366 const QgsSettings settings;
367 const QFont defaultFont = qApp->font();
368 mFontSize = settings.value( QStringLiteral( "/qgis/stylesheet/fontPointSize" ), defaultFont.pointSize() ).toInt();
369 mFontFamily = settings.value( QStringLiteral( "/qgis/stylesheet/fontFamily" ), defaultFont.family() ).toString();
370}
371
372// This slot handles all clicks
373void QgsMapTip::onLinkClicked( const QUrl &url )
374{
375 QDesktopServices::openUrl( url );
376}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2686
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:265
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.
QgsMapLayerType type
Definition: qgsmaplayer.h:80
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
Definition: qgsmaplayer.h:1504
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, QPoint &pixelPosition, QgsMapCanvas *mpMapCanvas)
Show a maptip at a given point on the map canvas.
Definition: qgsmaptip.cpp:60
QgsMapTip()
Default constructor.
Definition: qgsmaptip.cpp:48
void applyFontSettings()
Apply font family and size to match user settings.
Definition: qgsmaptip.cpp:364
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
Definition: qgsmaptip.cpp:210
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:62
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
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38