QGIS API Documentation 4.3.0-Master (16eddb2670e)
Loading...
Searching...
No Matches
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 "qgsapplication.h"
17#include "qgsexpression.h"
19#include "qgsfeatureiterator.h"
20#include "qgslogger.h"
21#include "qgsmapcanvas.h"
22#include "qgsmapcanvasutils.h"
24#include "qgsmaptool.h"
25#include "qgsrasterlayer.h"
26#include "qgsrendercontext.h"
27#include "qgsrenderer.h"
28#include "qgssettings.h"
29#include "qgsvectorlayer.h"
30#include "qgswebframe.h"
31#include "qgswebview.h"
32
33#include <QString>
34
35using namespace Qt::StringLiterals;
36
37// Qt includes
38#include <QPoint>
39#include <QToolTip>
40#include <QSettings>
41#include <QLabel>
42#include <QDesktopServices>
43#include <QHBoxLayout>
44
45#include "qgsmaptip.h"
46#include "moc_qgsmaptip.cpp"
47
48
49const QString QgsMapTip::sMapTipTemplate = "<html>\n"
50 " <head>\n"
51 " <style>\n"
52 " body {\n"
53 " margin: 0;\n"
54 " font: %1pt \"%2\";\n"
55 " color: %3;\n"
56 " width: %4px;\n"
57 " }\n"
58 " #QgsWebViewContainer {\n"
59 " background-color: %5;\n"
60 " border: 1px solid %6;\n"
61 " display: inline-block;\n"
62 " margin: 0\n"
63 " }\n"
64 " #QgsWebViewContainerInner {\n"
65 " margin: 5px\n"
66 " }\n"
67 " </style>\n"
68 " </head>\n"
69 " <body>\n"
70 " <div id='QgsWebViewContainer'>\n"
71 " <div id='QgsWebViewContainerInner'>\n"
72 " %7\n"
73 " </div>\n"
74 " </div>\n"
75 " </body>\n"
76 "</html>\n";
77
78
80{
81 // Init the visible flag
82 mMapTipVisible = false;
83
84 mDelayedClearTimer.setSingleShot( true );
85 connect( &mDelayedClearTimer, &QTimer::timeout, this, [this]() { this->clear(); } );
86}
87
88void QgsMapTip::showMapTip( QgsMapLayer *pLayer, QgsPointXY &mapPosition, const QPoint &pixelPosition, QgsMapCanvas *pMapCanvas )
89{
90 // Do the search using the active layer and the preferred label field for the
91 // layer. The label field must be defined in the layer configuration
92 // file/database. The code required to do this is similar to identify, except
93 // we only want the first qualifying feature and we will only display the
94 // field defined as the label field in the layer configuration file/database
95
96 // Do not render map tips if the layer is not visible
97 if ( !pMapCanvas->layers( true ).contains( pLayer ) )
98 {
99 return;
100 }
101
102 // Do not render a new map tip when the mouse hovers an existing one
103 if ( mWebView && mWebView->underMouse() )
104 {
105 return;
106 }
107
108 // Show the maptip on the canvas
109 QString tipText, lastTipText, tipHtml;
110
111 if ( !mWebView )
112 {
113 mWebView = new QgsWebView( pMapCanvas );
114 mWebView->setOpenExternalLinks( true );
115
116 // Make the webwiew transparent
117 // Setting the background color to 'transparent' does not play nice
118 // with webkit scrollbars, that are rendered as black rectangles (#54683)
119 QColor transparentColor = mWebView->palette().color( QPalette::Window );
120 transparentColor.setAlpha( 0 );
121 mWebView->setStyleSheet( QString( "background:%1;" ).arg( transparentColor.name( QColor::HexArgb ) ) );
122
123 mWebView->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
124 mWebView->page()->settings()->setAttribute( QWebSettings::JavascriptEnabled, true );
125 mWebView->page()->settings()->setAttribute( QWebSettings::LocalStorageEnabled, true );
126
127 // Disable scrollbars, avoid random resizing issues
128 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Horizontal, Qt::ScrollBarAlwaysOff );
129 mWebView->page()->mainFrame()->setScrollBarPolicy( Qt::Vertical, Qt::ScrollBarAlwaysOff );
130 }
131
132 // Only supported layer types here:
133 switch ( pLayer->type() )
134 {
136 tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
137 break;
139 tipText = fetchRaster( pLayer, mapPosition, pMapCanvas );
140 break;
141 default:
142 break;
143 }
144
145 mMapTipVisible = !tipText.isEmpty();
146 if ( !mMapTipVisible )
147 {
148 clear();
149 return;
150 }
151
152 if ( tipText == lastTipText )
153 {
154 return;
155 }
156
157 // Compute offset from the cursor position
158 int cursorOffset = 0;
160 {
161 // The following calculations are taken
162 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
163 // for both hi-dpi and non-hi-dpi screens.
164 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
165 cursorOffset = static_cast<int>( std::ceil( scale * 32 ) );
166 }
167
168 // Ensures the map tip is never larger than the available space
169 const int MAX_WIDTH = std::max( pixelPosition.x(), pMapCanvas->width() - pixelPosition.x() ) - cursorOffset - 5;
170 const int MAX_HEIGHT = std::max( pixelPosition.y(), pMapCanvas->height() - pixelPosition.y() ) - 5;
171
172 mWebView->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
173
174 tipHtml = QgsMapTip::htmlText( tipText, MAX_WIDTH );
175
176 QgsDebugMsgLevel( tipHtml, 2 );
177
178 mPosition = pixelPosition;
179 mMapCanvas = pMapCanvas;
180 mWebView->setHtml( tipHtml );
181 lastTipText = tipText;
182
183 resizeAndMoveToolTip();
184}
185
186void QgsMapTip::resizeAndMoveToolTip()
187{
188 mWebView->adjustSize();
189
190 int cursorOffset = 0;
191 // attempt to shift the tip away from the cursor.
193 {
194 // The following calculations are taken
195 // from QgsApplication::getThemeCursor, and are used to calculate the correct cursor size
196 // for both hi-dpi and non-hi-dpi screens.
197 double scale = Qgis::UI_SCALE_FACTOR * QgsApplication::instance()->fontMetrics().height() / 32.0;
198 cursorOffset = static_cast<int>( std::ceil( scale * 32 ) );
199 }
200
201 if ( !mMapCanvas )
202 {
203 mWebView->move( mPosition );
204 mWebView->show();
205 return;
206 }
207
208 // Check if there is enough space to the right of the cursor
209 int availableWidthRight = mMapCanvas->width() - mPosition.x() - cursorOffset;
210 int availableWidthLeft = mPosition.x() - cursorOffset;
211 int availableHeightBottom = mMapCanvas->height() - mPosition.y();
212 int availableHeightTop = mPosition.y();
213 int x, y;
214 // If there is enough space on the right, or more space on the right than on the left, move the map tip to the right of the cursor
215 if ( mWebView->width() < availableWidthRight || availableWidthRight > availableWidthLeft )
216 {
217 x = mPosition.x() + cursorOffset;
218 }
219 // Otherwise, move the map tip to the left of the cursor
220 else
221 {
222 x = mPosition.x() - mWebView->width() - cursorOffset;
223 }
224
225 // If there is enough space on the bottom, or more space on the bottom than on the top, move the map tip to the bottom of the cursor
226 if ( mWebView->height() < availableHeightBottom || availableHeightBottom > availableHeightTop )
227 {
228 y = mPosition.y();
229 }
230 // Otherwise, move the map tip to the top of the cursor
231 else
232 {
233 y = mPosition.y() - mWebView->height();
234 }
235 mWebView->move( x, y );
236 mWebView->show();
237}
238
239void QgsMapTip::clear( QgsMapCanvas *, int msDelay )
240{
241 if ( !mMapTipVisible )
242 {
243 return;
244 }
245
246 // Skip clearing the map tip if the user interacts with it or the timer still runs
247 if ( mDelayedClearTimer.isActive() || mWebView->underMouse() )
248 {
249 return;
250 }
251
252 if ( msDelay > 0 )
253 {
254 mDelayedClearTimer.start( msDelay );
255 return;
256 }
257 mWebView->setHtml( QString() );
258 mWebView->hide();
259
260 // Reset the visible flag
261 mMapTipVisible = false;
262}
263
264QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
265{
266 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
267 if ( !vlayer || !vlayer->isSpatial() || !vlayer->mapTipsEnabled() )
268 {
269 return QString();
270 }
271
272 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() )
273 || ( mapCanvas->mapSettings().isTemporal() && layer->temporalProperties() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
274 {
275 return QString();
276 }
277
278 const double searchRadius = QgsMapTool::searchRadiusMU( mapCanvas );
279
280 QgsRectangle r;
281 r.setXMinimum( mapPosition.x() - searchRadius );
282 r.setYMinimum( mapPosition.y() - searchRadius );
283 r.setXMaximum( mapPosition.x() + searchRadius );
284 r.setYMaximum( mapPosition.y() + searchRadius );
285
286 r = mapCanvas->mapSettings().mapToLayerCoordinates( layer, r );
287
288 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( vlayer ) );
289 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
290 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( r.center() ) );
291
292 const QString canvasFilter = QgsMapCanvasUtils::filterForLayer( mapCanvas, vlayer );
293 if ( canvasFilter == "FALSE"_L1 )
294 {
295 return QString();
296 }
297
298 const QString mapTip = vlayer->mapTipTemplate();
299 QString tipString;
300 QgsExpression exp( vlayer->displayExpression() );
301 QgsFeature feature;
302
303 QgsFeatureRequest request;
304 request.setFilterRect( r );
306 if ( !canvasFilter.isEmpty() )
307 {
308 request.setFilterExpression( canvasFilter );
309 }
310
311 if ( mapTip.isEmpty() )
312 {
313 exp.prepare( &context );
314 request.setSubsetOfAttributes( exp.referencedColumns(), vlayer->fields() );
315 }
316
317 QgsRenderContext renderCtx = QgsRenderContext::fromMapSettings( mapCanvas->mapSettings() );
318 renderCtx.setExpressionContext( mapCanvas->createExpressionContext() );
320
321 bool filter = false;
322 std::unique_ptr<QgsFeatureRenderer> renderer;
323 if ( vlayer->renderer() )
324 {
325 renderer.reset( vlayer->renderer()->clone() );
326 renderer->startRender( renderCtx, vlayer->fields() );
327 filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
328
329 const QString filterExpression = renderer->filter( vlayer->fields() );
330 if ( !filterExpression.isEmpty() )
331 {
332 request.combineFilterExpression( filterExpression );
333 }
334 }
335 request.setExpressionContext( renderCtx.expressionContext() );
336
337 QgsFeatureIterator it = vlayer->getFeatures( request );
338 QElapsedTimer timer;
339 timer.start();
340 while ( it.nextFeature( feature ) )
341 {
342 context.setFeature( feature );
343
344 renderCtx.expressionContext().setFeature( feature );
345 if ( filter && renderer && !renderer->willRenderFeature( feature, renderCtx ) )
346 {
347 continue;
348 }
349
350 if ( !mapTip.isEmpty() )
351 {
352 tipString = QgsExpression::replaceExpressionText( mapTip, &context );
353 }
354 else
355 {
356 tipString = exp.evaluate( &context ).toString();
357 }
358
359 if ( !tipString.isEmpty() || timer.elapsed() >= 1000 )
360 {
361 break;
362 }
363 }
364
365 if ( renderer )
366 {
367 renderer->stopRender( renderCtx );
368 }
369
370 return tipString;
371}
372
373QString QgsMapTip::fetchRaster( QgsMapLayer *layer, QgsPointXY &mapPosition, QgsMapCanvas *mapCanvas )
374{
375 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
376 if ( !rlayer || !rlayer->mapTipsEnabled() )
377 {
378 return QString();
379 }
380
381 if ( !layer->isInScaleRange( mapCanvas->mapSettings().scale() ) || ( mapCanvas->mapSettings().isTemporal() && !layer->temporalProperties()->isVisibleInTemporalRange( mapCanvas->temporalRange() ) ) )
382 {
383 return QString();
384 }
385
386 if ( rlayer->mapTipTemplate().isEmpty() )
387 {
388 return QString();
389 }
390
391 const QgsPointXY mappedPosition { mapCanvas->mapSettings().mapToLayerCoordinates( layer, mapPosition ) };
392
393 if ( !layer->extent().contains( mappedPosition ) )
394 {
395 return QString();
396 }
397
398 QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) );
399 context.appendScope( QgsExpressionContextUtils::mapSettingsScope( mapCanvas->mapSettings() ) );
400 context.appendScope( QgsExpressionContextUtils::mapLayerPositionScope( mappedPosition ) );
401 return QgsExpression::replaceExpressionText( rlayer->mapTipTemplate(), &context );
402}
403
404QString QgsMapTip::htmlText( const QString &text, int maxWidth )
405{
406 const QgsSettings settings;
407 const QFont defaultFont = qApp->font();
408 const int fontSize = defaultFont.pointSize();
409 const QString fontFamily = defaultFont.family();
410 const QString backgroundColor = QgsApplication::palette().base().color().name();
411 const QString strokeColor = QgsApplication::palette().shadow().color().name();
412 const QString textColor = QgsApplication::palette().toolTipText().color().name();
413 return sMapTipTemplate.arg( fontSize ).arg( fontFamily ).arg( textColor ).arg( maxWidth == -1 ? "" : QString::number( maxWidth ) ).arg( backgroundColor ).arg( strokeColor ).arg( text );
414}
415
416// This slot handles all clicks
417void QgsMapTip::onLinkClicked( const QUrl &url )
418{
419 QDesktopServices::openUrl( url );
420}
421
422
423QString QgsMapTip::vectorMapTipPreviewText( QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate, const QString &displayExpression )
424{
425 // Only spatial layers can have map tips
426 QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
427 if ( !mapCanvas || !vlayer || !vlayer->isSpatial() )
428 return QString();
429
430 // If no map tip template or display expression is set, return an empty string
431 if ( mapTemplate.isEmpty() && displayExpression.isEmpty() )
432 return QString();
433
434 // Create an expression context
437
438 // Get the first feature if any, and add it to the expression context
439 QgsFeature previewFeature;
440 if ( vlayer->featureCount() > 0 )
441 {
442 QgsFeatureIterator it = vlayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) );
443 it.nextFeature( previewFeature );
444 }
445 else
446 {
447 previewFeature = QgsFeature( vlayer->fields() );
448 }
449 context.setFeature( previewFeature );
450
451 // Generate the map tip text from the context and the mapTipTemplate/displayExpression
452 QString tipText;
453 if ( mapTemplate.isEmpty() )
454 {
455 QgsExpression exp( displayExpression );
456 exp.prepare( &context );
457 tipText = exp.evaluate( &context ).toString();
458 }
459 else
460 {
461 tipText = QgsExpression::replaceExpressionText( mapTemplate, &context );
462 }
463
464 // Insert the map tip text into the html template
465 return QgsMapTip::htmlText( tipText, mapCanvas->width() / 2 );
466}
467
468QString QgsMapTip::rasterMapTipPreviewText( QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate )
469{
470 QgsRasterLayer *rlayer = qobject_cast<QgsRasterLayer *>( layer );
471 if ( !mapCanvas || !rlayer || mapTemplate.isEmpty() )
472 {
473 return QString();
474 }
475
476 // Create an expression context
479
480 // Get the position of the center of the layer, and add it to the expression context
481 const QgsPointXY mappedPosition { layer->extent().center() };
483
484 // Generate the map tip text from the context and the mapTipTemplate
485 const QString tipText = QgsExpression::replaceExpressionText( mapTemplate, &context );
486
487 // Insert the map tip text into the html template
488 return QgsMapTip::htmlText( tipText, mapCanvas->width() / 2 );
489}
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
Definition qgis.h:2382
@ Vector
Vector layer.
Definition qgis.h:207
@ Raster
Raster layer.
Definition qgis.h:208
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:7338
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QgsExpressionContextScope * mapLayerPositionScope(const QgsPointXY &position)
Sets the expression context variables which are available for expressions triggered by moving the mou...
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 appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
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...
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & combineFilterExpression(const QString &expression)
Modifies the existing filter expression to add an additional expression filter.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
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:60
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.
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(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.
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:83
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
virtual Q_INVOKABLE QgsRectangle extent() const
Returns the extent of the layer.
Qgis::LayerType type
Definition qgsmaplayer.h:93
virtual QgsMapLayerTemporalProperties * temporalProperties()
Returns the layer's temporal properties.
bool mapTipsEnabled
Definition qgsmaplayer.h:97
QString mapTipTemplate
Definition qgsmaplayer.h:96
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, const QPoint &pixelPosition, QgsMapCanvas *mpMapCanvas)
Show a maptip at a given point on the map canvas.
Definition qgsmaptip.cpp:88
static QString rasterMapTipPreviewText(QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate)
Returns the html that would be displayed in a maptip for a given layer.
static QString vectorMapTipPreviewText(QgsMapLayer *layer, QgsMapCanvas *mapCanvas, const QString &mapTemplate, const QString &displayExpression)
Returns the html that would be displayed in a maptip for a given layer.
QgsMapTip()
Default constructor.
Definition qgsmaptip.cpp:79
void clear(QgsMapCanvas *mpMapCanvas=nullptr, int msDelay=0)
Clear the current maptip if it exists.
static double searchRadiusMU(const QgsRenderContext &context)
Gets search radius in map units for given context.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Represents a raster layer.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
QgsPointXY center
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...
Represents a vector layer which manages a vector based dataset.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
bool isSpatial() const final
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QString displayExpression
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
A collection of stubs to mimic the API of QWebView on systems where the real library is not available...
Definition qgswebview.h:39
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80