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