QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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.
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
182void 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
196void 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
217QString 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
325void QgsMapTip::onLinkClicked( const QUrl &url )
326{
327 QDesktopServices::openUrl( url );
328}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2304
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
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...
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.
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.
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:59
QgsMapTip()
Default constructor.
Definition: qgsmaptip.cpp:47
void applyFontSettings()
Apply font family and size to match user settings.
Definition: qgsmaptip.cpp:316
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
Definition: qgsmaptip.cpp:196
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
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
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.
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.
QString mapTipTemplate
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