QGIS API Documentation  3.25.0-Master (6b426f5f8a)
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 
57  QgsPointXY &mapPosition,
58  QPoint &pixelPosition,
59  QgsMapCanvas *pMapCanvas )
60 {
61  // Do the search using the active layer and the preferred label field for the
62  // layer. The label field must be defined in the layer configuration
63  // file/database. The code required to do this is similar to identify, except
64  // we only want the first qualifying feature and we will only display the
65  // field defined as the label field in the layer configuration file/database
66 
67  // Do not render map tips if the layer is not visible
68  if ( !pMapCanvas->layers( true ).contains( pLayer ) )
69  {
70  return;
71  }
72 
73  // Show the maptip on the canvas
74  QString tipText, lastTipText, tipHtml, bodyStyle, containerStyle,
75  backgroundColor, strokeColor, textColor;
76 
77  delete mWidget;
78  mWidget = new QWidget( pMapCanvas );
79  mWidget->setContentsMargins( MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE, MARGIN_VALUE );
80  mWebView = new QgsWebView( mWidget );
81 
82 
83 #if WITH_QTWEBKIT
84  mWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );//Handle link clicks by yourself
85  mWebView->setContextMenuPolicy( Qt::NoContextMenu ); //No context menu is allowed if you don't need it
86  connect( mWebView, &QWebView::linkClicked, this, &QgsMapTip::onLinkClicked );
87  connect( mWebView, &QWebView::loadFinished, this, [ = ]( bool ) { resizeContent(); } );
88 #endif
89 
90  mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
91  mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
92  mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
93 
94  // Disable scrollbars, avoid random resizing issues
95  mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
96  mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
97 
98  QHBoxLayout *layout = new QHBoxLayout;
99  layout->setContentsMargins( 0, 0, 0, 0 );
100  layout->addWidget( mWebView );
101 
102  mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
103  mWidget->setLayout( layout );
104 
105  // Assure the map tip is never larger than half the map canvas
106  const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
107  const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
108  mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
109 
110  // Start with 0 size,
111  // The content will automatically make it grow up to MaximumSize
112  mWidget->resize( 0, 0 );
113 
114  backgroundColor = mWidget->palette().base().color().name();
115  strokeColor = mWidget->palette().shadow().color().name();
116  textColor = mWidget->palette().text().color().name();
117  mWidget->setStyleSheet( QString(
118  ".QWidget{"
119  "border: 1px solid %1;"
120  "background-color: %2;}" ).arg(
121  strokeColor, backgroundColor ) );
122 
123  tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
124 
125  mMapTipVisible = !tipText.isEmpty();
126  if ( !mMapTipVisible )
127  {
128  clear();
129  return;
130  }
131 
132  if ( tipText == lastTipText )
133  {
134  return;
135  }
136 
137  bodyStyle = QString(
138  "background-color: %1;"
139  "margin: 0;"
140  "font: %2pt \"%3\";"
141  "color: %4;" ).arg( backgroundColor ).arg( mFontSize ).arg( mFontFamily, textColor );
142 
143  containerStyle = QString(
144  "display: inline-block;"
145  "margin: 0px" );
146 
147  tipHtml = QString(
148  "<html>"
149  "<body style='%1'>"
150  "<div id='QgsWebViewContainer' style='%2'>%3</div>"
151  "</body>"
152  "</html>" ).arg( bodyStyle, containerStyle, tipText );
153 
154  QgsDebugMsg( tipHtml );
155 
156  mWidget->move( pixelPosition.x(),
157  pixelPosition.y() );
158 
159  mWebView->setHtml( tipHtml );
160  lastTipText = tipText;
161 
162  mWidget->show();
163 }
164 
165 void QgsMapTip::resizeContent()
166 {
167 #if WITH_QTWEBKIT
168  // Get the content size
169  const QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
170  QStringLiteral( "#QgsWebViewContainer" ) );
171  const int width = container.geometry().width() + MARGIN_VALUE * 2;
172  const int height = container.geometry().height() + MARGIN_VALUE * 2;
173  mWidget->resize( width, height );
174 #else
175  mWebView->adjustSize();
176 #endif
177 }
178 
180 {
181  if ( !mMapTipVisible )
182  return;
183 
184  mWebView->setHtml( QString() );
185  mWidget->hide();
186 
187  // Reset the visible flag
188  mMapTipVisible = false;
189 }
190 
191 QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
192 {
193  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
194  if ( !vlayer || !vlayer->isSpatial() )
195  return QString();
196 
197  if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) )
198  {
199  return QString();
200  }
201 
202  const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
203 
204  QgsRectangle r;
205  r.setXMinimum( mapPosition.x() - searchRadius );
206  r.setYMinimum( mapPosition.y() - searchRadius );
207  r.setXMaximum( mapPosition.x() + searchRadius );
208  r.setYMaximum( mapPosition.y() + searchRadius );
209 
210  r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
211 
213  context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
214 
215  const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
216  if ( canvasFilter == QLatin1String( "FALSE" ) )
217  return QString();
218 
219  const QString mapTip = vlayer->mapTipTemplate();
220  QString tipString;
221  QgsExpression exp( vlayer->displayExpression() );
222  QgsFeature feature;
223 
224  QgsFeatureRequest request;
225  request.setFilterRect( r );
227  if ( !canvasFilter.isEmpty() )
228  request.setFilterExpression( canvasFilter );
229 
230  if ( mapTip.isEmpty() )
231  {
232  exp.prepare( &context );
233  request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
234  }
235 
237  renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
239 
240  bool filter = false;
241  std::unique_ptr< QgsFeatureRenderer > renderer;
242  if ( vlayer->renderer() )
243  {
244  renderer.reset( vlayer->renderer()->clone() );
245  renderer->startRender( renderCtx, vlayer->fields() );
246  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
247 
248  const QString filterExpression = renderer->filter( vlayer->fields() );
249  if ( ! filterExpression.isEmpty() )
250  {
251  request.combineFilterExpression( filterExpression );
252  }
253  }
254  request.setExpressionContext( renderCtx.expressionContext() );
255 
256  QgsFeatureIterator it = vlayer->getFeatures( request );
257  QElapsedTimer timer;
258  timer.start();
259  while ( it.nextFeature( feature ) )
260  {
261  context.setFeature( feature );
262 
263  renderCtx.expressionContext().setFeature( feature );
264  if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
265  {
266  continue;
267  }
268 
269  if ( !mapTip.isEmpty() )
270  {
271  tipString = QgsExpression::replaceExpressionText( mapTip, &context );
272  }
273  else
274  {
275  tipString = exp.evaluate( &context ).toString();
276  }
277 
278  if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
279  {
280  break;
281  }
282  }
283 
284  if ( renderer )
285  renderer->stopRender( renderCtx );
286 
287  return tipString;
288 }
289 
291 {
292  const QgsSettings settings;
293  const QFont defaultFont = qApp->font();
294  mFontSize = settings.value( QStringLiteral( "/qgis/stylesheet/fontPointSize" ), defaultFont.pointSize() ).toInt();
295  mFontFamily = settings.value( QStringLiteral( "/qgis/stylesheet/fontFamily" ), defaultFont.family() ).toString();
296 }
297 
298 // This slot handles all clicks
299 void QgsMapTip::onLinkClicked( const QUrl &url )
300 {
301  QDesktopServices::openUrl( url );
302 }
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)
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
Definition: qgsrenderer.h:265
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:56
void clear(QgsMapCanvas *mpMapCanvas=nullptr)
Clear the current maptip if it exists.
Definition: qgsmaptip.cpp:179
QgsMapTip()
Default constructor.
Definition: qgsmaptip.cpp:47
void applyFontSettings()
Apply font family and size to match user settings.
Definition: qgsmaptip.cpp:290
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.
QString displayExpression
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
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