QGIS API Documentation  3.19.0-Master (26212d215f)
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  QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
168  QStringLiteral( "#QgsWebViewContainer" ) );
169  int width = container.geometry().width() + MARGIN_VALUE * 2;
170  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  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  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 
243 
244  bool filter = false;
245  std::unique_ptr< QgsFeatureRenderer > renderer;
246  if ( vlayer->renderer() )
247  {
248  renderer.reset( vlayer->renderer()->clone() );
249  renderer->startRender( renderCtx, vlayer->fields() );
250  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
251 
252  const QString filterExpression = renderer->filter( vlayer->fields() );
253  if ( ! filterExpression.isEmpty() )
254  {
255  request.combineFilterExpression( filterExpression );
256  }
257  }
258 
259  QgsFeatureIterator it = vlayer->getFeatures( request );
260  QElapsedTimer timer;
261  timer.start();
262  while ( it.nextFeature( feature ) )
263  {
264  context.setFeature( feature );
265 
266  renderCtx.expressionContext().setFeature( feature );
267  if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
268  {
269  continue;
270  }
271 
272  if ( !mapTip.isEmpty() )
273  {
274  tipString = QgsExpression::replaceExpressionText( mapTip, &context );
275  }
276  else
277  {
278  tipString = exp.evaluate( &context ).toString();
279  }
280 
281  if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
282  {
283  break;
284  }
285  }
286 
287  if ( renderer )
288  renderer->stopRender( renderCtx );
289 
290  return tipString;
291 }
292 
294 {
295  QgsSettings settings;
296  QFont defaultFont = qApp->font();
297  mFontSize = settings.value( QStringLiteral( "/qgis/stylesheet/fontPointSize" ), defaultFont.pointSize() ).toInt();
298  mFontFamily = settings.value( QStringLiteral( "/qgis/stylesheet/fontFamily" ), defaultFont.family() ).toString();
299 }
300 
301 // This slot handles all clicks
302 void QgsMapTip::onLinkClicked( const QUrl &url )
303 {
304  QDesktopServices::openUrl( url );
305 }
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:256
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 & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:86
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:85
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:1231
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:293
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Definition: qgsmaptool.cpp:212
A class to represent a 2D point.
Definition: qgspointxy.h:44
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
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
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