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