QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
Loading...
Searching...
No Matches
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 "qgsapplication.h"
17#include "qgsexpression.h"
19#include "qgsfeatureiterator.h"
20#include "qgslogger.h"
21#include "qgsmapcanvas.h"
22#include "qgsmapcanvasutils.h"
24#include "qgsmaptool.h"
25#include "qgsrasterlayer.h"
26#include "qgsrendercontext.h"
27#include "qgsrenderer.h"
28#include "qgssettings.h"
29#include "qgsvectorlayer.h"
30#include "qgswebframe.h"
31#include "qgswebview.h"
32
33#include <QString>
34
35using namespace Qt::StringLiterals;
36
37// Qt includes
38#include <QPoint>
39#include <QToolTip>
40#include <QSettings>
41#include <QLabel>
42#include <QDesktopServices>
43#include <QHBoxLayout>
44
45#include "qgsmaptip.h"
46#include "moc_qgsmaptip.cpp"
47
48
49const QString QgsMapTip::sMapTipTemplate = "<html>\n"
50 " <head>\n"
51 " <style>\n"
52 " body {\n"
53 " margin: 0;\n"
54 " font: %1pt \"%2\";\n"
55 " color: %3;\n"
56 " width: %4px;\n"
57 " }\n"
58 " #QgsWebViewContainer {\n"
59 " background-color: %5;\n"
60 " border: 1px solid %6;\n"
61 " display: inline-block;\n"
62 " margin: 0\n"
63 " }\n"
64 " #QgsWebViewContainerInner {\n"
65 " margin: 5px\n"
66 " }\n"
67 " </style>\n"
68 " </head>\n"
69 " <body>\n"
70 " <div id='QgsWebViewContainer'>\n"
71 " <div id='QgsWebViewContainerInner'>\n"
72 " %7\n"
73 " </div>\n"
74 " </div>\n"
75 " </body>\n"
76 "</html>\n";
77
78
80{
81 // Init the visible flag
82 mMapTipVisible = false;
83
84 mDelayedClearTimer.setSingleShot( true );
85 connect( &mDelayedClearTimer, &QTimer::timeout, this, [this]() { this->clear(); } );
86}
87
88void QgsMapTip::showMapTip( QgsMapLayer *pLayer, QgsPointXY &mapPosition, const QPoint &pixelPosition, QgsMapCanvas *pMapCanvas )
89{
90 // Do the search using the active layer and the preferred label field for the
91 // layer. The label field must be defined in the layer configuration
92 // file/database. The code required to do this is similar to identify, except
93 // we only want the first qualifying feature and we will only display the
94 // field defined as the label field in the layer configuration file/database
95
96 // Do not render map tips if the layer is not visible
97 if ( !pMapCanvas->layers( true ).contains( pLayer ) )
98 {
99 return;
100 }
101
102 // Do not render a new map tip when the mouse hovers an existing one
103 if ( mWebView && mWebView->underMouse() )
104 {
105 return;
106 }
107
108 // Show the maptip on the canvas
109 QString tipText, lastTipText, tipHtml;
110
111 if ( !mWebView )
112 {
113 mWebView = new QgsWebView( pMapCanvas );
114 // Make the webwiew transparent
115
116 // Setting the background color to 'transparent' does not play nice
117 // with webkit scrollbars, that are rendered as black rectangles (#54683)
118 QColor transparentColor = mWebView->palette().color( QPalette::Window );
119 transparentColor.setAlpha( 0 );
120 mWebView->setStyleSheet( QString( "background:%1;" ).arg( transparentColor.name( QColor::HexArgb ) ) );
121
122 mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
123 mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
124 mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
125
126 // Disable scrollbars, avoid random resizing issues
127 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
128 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
129 }
130
131 // Only supported layer types here:
132 switch ( pLayer->type() )
133 {
135 tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
136 break;
138 tipText = fetchRaster( pLayer, mapPosition, pMapCanvas );
139 break;
140 default:
141 break;
142 }
143
144 mMapTipVisible = !tipText.isEmpty();
145 if ( !mMapTipVisible )
146 {
147 clear();
148 return;
149 }
150
151 if ( tipText == lastTipText )
152 {
153 return;
154 }
155
156 // Compute offset from the cursor position
157 int cursorOffset = 0;
159 {
160 // The following calculations are taken
161 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
162 // for both hi-dpi and non-hi-dpi screens.
163 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
164 cursorOffset = static_cast<int>( std::ceil( scale * 32 ) );
165 }
166
167 // Ensures the map tip is never larger than the available space
168 const int MAX_WIDTH = std::max( pixelPosition.x(), pMapCanvas->width() - pixelPosition.x() ) - cursorOffset - 5;
169 const int MAX_HEIGHT = std::max( pixelPosition.y(), pMapCanvas->height() - pixelPosition.y() ) - 5;
170
171 mWebView->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
172
173 tipHtml = QgsMapTip::htmlText( tipText, MAX_WIDTH );
174
175 QgsDebugMsgLevel( tipHtml, 2 );
176
177 mPosition = pixelPosition;
178 mMapCanvas = pMapCanvas;
179 mWebView->setHtml( tipHtml );
180 lastTipText = tipText;
181
182 resizeAndMoveToolTip();
183}
184
185void QgsMapTip::resizeAndMoveToolTip()
186{
187 mWebView->adjustSize();
188
189 int cursorOffset = 0;
190 // attempt to shift the tip away from the cursor.
192 {
193 // The following calculations are taken
194 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
195 // for both hi-dpi and non-hi-dpi screens.
196 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
197 cursorOffset = static_cast<int>( std::ceil( scale * 32 ) );
198 }
199
200 if ( !mMapCanvas )
201 {
202 mWebView->move( mPosition );
203 mWebView->show();
204 return;
205 }
206
207 // Check if there is enough space to the right of the cursor
208 int availableWidthRight = mMapCanvas->width() - mPosition.x() - cursorOffset;
209 int availableWidthLeft = mPosition.x() - cursorOffset;
210 int availableHeightBottom = mMapCanvas->height() - mPosition.y();
211 int availableHeightTop = mPosition.y();
212 int x, y;
213 // If there is enough space on the right, or more space on the right than on the left, move the map tip to the right of the cursor
214 if ( mWebView->width() < availableWidthRight || availableWidthRight > availableWidthLeft )
215 {
216 x = mPosition.x() + cursorOffset;
217 }
218 // Otherwise, move the map tip to the left of the cursor
219 else
220 {
221 x = mPosition.x() - mWebView->width() - cursorOffset;
222 }
223
224 // If there is enough space on the bottom, or more space on the bottom than on the top, move the map tip to the bottom of the cursor
225 if ( mWebView->height() < availableHeightBottom || availableHeightBottom > availableHeightTop )
226 {
227 y = mPosition.y();
228 }
229 // Otherwise, move the map tip to the top of the cursor
230 else
231 {
232 y = mPosition.y() - mWebView->height();
233 }
234 mWebView->move( x, y );
235 mWebView->show();
236}
237
238void QgsMapTip::clear( QgsMapCanvas *, int msDelay )
239{
240 if ( !mMapTipVisible )
241 {
242 return;
243 }
244
245 // Skip clearing the map tip if the user interacts with it or the timer still runs
246 if ( mDelayedClearTimer.isActive() || mWebView->underMouse() )
247 {
248 return;
249 }
250
251 if ( msDelay > 0 )
252 {
253 mDelayedClearTimer.start( msDelay );
254 return;
255 }
256 mWebView->setHtml( QString() );
257 mWebView->hide();
258
259 // Reset the visible flag
260 mMapTipVisible = false;
261}
262
263QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
264{
265 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
266 if ( !vlayer || !vlayer->isSpatial() || !vlayer->mapTipsEnabled() )
267 {
268 return QString();
269 }
270
271 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() )
272 || ( mapCanvas->mapSettings().isTemporal() && layer->temporalProperties() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
273 {
274 return QString();
275 }
276
277 const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
278
279 QgsRectangle r;
280 r.setXMinimum( mapPosition.x() - searchRadius );
281 r.setYMinimum( mapPosition.y() - searchRadius );
282 r.setXMaximum( mapPosition.x() + searchRadius );
283 r.setYMaximum( mapPosition.y() + searchRadius );
284
285 r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
286
287 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vlayer ) );
288 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
289 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( r.center() ) );
290
291 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
292 if ( canvasFilter == "FALSE"_L1 )
293 {
294 return QString();
295 }
296
297 const QString mapTip = vlayer->mapTipTemplate();
298 QString tipString;
299 QgsExpression exp( vlayer->displayExpression() );
300 QgsFeature feature;
301
302 QgsFeatureRequest request;
303 request.setFilterRect( r );
305 if ( !canvasFilter.isEmpty() )
306 {
307 request.setFilterExpression( canvasFilter );
308 }
309
310 if ( mapTip.isEmpty() )
311 {
312 exp.prepare( &context );
313 request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
314 }
315
316 QgsRenderContext renderCtx = QgsRenderContext::fromMapSettings( mapCanvas->mapSettings() );
317 renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
319
320 bool filter = false;
321 std::unique_ptr<QgsFeatureRenderer> renderer;
322 if ( vlayer->renderer() )
323 {
324 renderer.reset( vlayer->renderer()->clone() );
325 renderer->startRender( renderCtx, vlayer->fields() );
326 filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
327
328 const QString filterExpression = renderer->filter( vlayer->fields() );
329 if ( !filterExpression.isEmpty() )
330 {
331 request.combineFilterExpression( filterExpression );
332 }
333 }
334 request.setExpressionContext( renderCtx.expressionContext() );
335
336 QgsFeatureIterator it = vlayer->getFeatures( request );
337 QElapsedTimer timer;
338 timer.start();
339 while ( it.nextFeature( feature ) )
340 {
341 context.setFeature( feature );
342
343 renderCtx.expressionContext().setFeature( feature );
344 if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
345 {
346 continue;
347 }
348
349 if ( !mapTip.isEmpty() )
350 {
351 tipString = QgsExpression::replaceExpressionText( mapTip, &context );
352 }
353 else
354 {
355 tipString = exp.evaluate( &context ).toString();
356 }
357
358 if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
359 {
360 break;
361 }
362 }
363
364 if ( renderer )
365 {
366 renderer->stopRender( renderCtx );
367 }
368
369 return tipString;
370}
371
372QString QgsMapTip::fetchRaster( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
373{
374 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
375 if ( !rlayer || !rlayer->mapTipsEnabled() )
376 {
377 return QString();
378 }
379
380 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) || ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
381 {
382 return QString();
383 }
384
385 if ( rlayer->mapTipTemplate().isEmpty() )
386 {
387 return QString();
388 }
389
390 const QgsPointXY mappedPosition { mapCanvas->mapSettings().mapToLayerCoordinates( layer, mapPosition ) };
391
392 if ( !layer->extent().contains( mappedPosition ) )
393 {
394 return QString();
395 }
396
397 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
398 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
399 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( mappedPosition ) );
400 return QgsExpression::replaceExpressionText( rlayer->mapTipTemplate(), &context );
401}
402
403QString QgsMapTip::htmlText( const QString &text, int maxWidth )
404{
405 const QgsSettings settings;
406 const QFont defaultFont = qApp->font();
407 const int fontSize = defaultFont.pointSize();
408 const QString fontFamily = defaultFont.family();
409 const QString backgroundColor = QgsApplication::palette().base().color().name();
410 const QString strokeColor = QgsApplication::palette().shadow().color().name();
411 const QString textColor = QgsApplication::palette().toolTipText().color().name();
412 return sMapTipTemplate.arg( fontSize ).arg( fontFamily ).arg( textColor ).arg( maxWidth == -1 ? "" : QString::number( maxWidth ) ).arg( backgroundColor ).arg( strokeColor ).arg( text );
413}
414
415// This slot handles all clicks
416void QgsMapTip::onLinkClicked( const QUrl &url )
417{
418 QDesktopServices::openUrl( url );
419}
420
421
422QString QgsMapTip::vectorMapTipPreviewText( QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate, const QString &displayExpression )
423{
424 // Only spatial layers can have map tips
425 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
426 if ( !mapCanvas || !vlayer || !vlayer->isSpatial() )
427 return QString();
428
429 // If no map tip template or display expression is set, return an empty string
430 if ( mapTemplate.isEmpty() && displayExpression.isEmpty() )
431 return QString();
432
433 // Create an expression context
436
437 // Get the first feature if any, and add it to the expression context
438 QgsFeature previewFeature;
439 if ( vlayer->featureCount() > 0 )
440 {
441 QgsFeatureIterator it = vlayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) );
442 it.nextFeature( previewFeature );
443 }
444 else
445 {
446 previewFeature = QgsFeature( vlayer->fields() );
447 }
448 context.setFeature( previewFeature );
449
450 // Generate the map tip text from the context and the mapTipTemplate/displayExpression
451 QString tipText;
452 if ( mapTemplate.isEmpty() )
453 {
454 QgsExpression exp( displayExpression );
455 exp.prepare( &context );
456 tipText = exp.evaluate( &context ).toString();
457 }
458 else
459 {
460 tipText = QgsExpression::replaceExpressionText( mapTemplate, &context );
461 }
462
463 // Insert the map tip text into the html template
464 return QgsMapTip::htmlText( tipText, mapCanvas->width() / 2 );
465}
466
467QString QgsMapTip::rasterMapTipPreviewText( QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate )
468{
469 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
470 if ( !mapCanvas || !rlayer || mapTemplate.isEmpty() )
471 {
472 return QString();
473 }
474
475 // Create an expression context
478
479 // Get the position of the center of the layer, and add it to the expression context
480 const QgsPointXY mappedPosition { layer->extent().center() };
482
483 // Generate the map tip text from the context and the mapTipTemplate
484 const QString tipText = QgsExpression::replaceExpressionText( mapTemplate, &context );
485
486 // Insert the map tip text into the html template
487 return QgsMapTip::htmlText( tipText, mapCanvas->width() / 2 );
488}
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2278
@ Vector
Vector layer.
Definition qgis.h:207
@ Raster
Raster layer.
Definition qgis.h:208
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6591
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 appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
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...
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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:60
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.
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:83
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:93
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
bool mapTipsEnabled
Definition qgsmaplayer.h:97
QString mapTipTemplate
Definition qgsmaplayer.h:96
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:88
static QString rasterMapTipPreviewText(QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate)
Returns the html that would be displayed in a maptip for a given layer.
static QString vectorMapTipPreviewText(QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate, const QString &displayExpression)
Returns the html that would be displayed in a maptip for a given layer.
QgsMapTip()
Default constructor.
Definition qgsmaptip.cpp:79
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Represents a raster layer.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
QgsPointXY center
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.
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 dataset.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QString displayExpression
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
A collection of stubs to mimic the API of QWebView on systems where the real library is not available...
Definition qgswebview.h:39
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63