Skip to content

Commit 45227e5

Browse files
mbernasocchim-kuhn
authored andcommitted
[FEATURE] QgsTooltip support full HTML
This is useful for example to add URL links that allow being clicked on.
1 parent 2269543 commit 45227e5

File tree

2 files changed

+129
-26
lines changed

2 files changed

+129
-26
lines changed

src/gui/qgsmaptip.cpp

+105-19
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,22 @@
1818
#include "qgsvectorlayer.h"
1919
#include "qgsexpression.h"
2020
#include "qgslogger.h"
21+
#include "qgswebview.h"
22+
#include "qgswebframe.h"
2123

2224
// Qt includes
2325
#include <QPoint>
2426
#include <QToolTip>
2527
#include <QSettings>
28+
#include <QLabel>
29+
#include <QWebElement>
30+
#include <QHBoxLayout>
31+
2632

2733
#include "qgsmaptip.h"
2834

2935
QgsMapTip::QgsMapTip()
36+
: mWidget( nullptr ), mWebView( nullptr )
3037
{
3138
// init the visible flag
3239
mMapTipVisible = false;
@@ -37,38 +44,115 @@ QgsMapTip::~QgsMapTip()
3744

3845
}
3946

40-
void QgsMapTip::showMapTip( QgsMapLayer *thepLayer,
41-
QgsPoint & theMapPosition,
47+
void QgsMapTip::showMapTip( QgsMapLayer *pLayer,
48+
QgsPoint & mapPosition,
4249
QPoint & thePixelPosition,
43-
QgsMapCanvas *thepMapCanvas )
50+
QgsMapCanvas *pMapCanvas )
4451
{
45-
// Do the search using the active layer and the preferred label
46-
// field for the layer. The label field must be defined in the layer configuration
52+
// Do the search using the active layer and the preferred label field for the
53+
// layer. The label field must be defined in the layer configuration
4754
// file/database. The code required to do this is similar to identify, except
4855
// we only want the first qualifying feature and we will only display the
49-
// field defined as the label field in the layer configuration file/database.
50-
//
51-
// TODO: Define the label (display) field for each map layer in the map configuration file/database
56+
// field defined as the label field in the layer configuration file/database
5257

5358
// Show the maptip on the canvas
54-
QString myTipText = fetchFeature( thepLayer, theMapPosition, thepMapCanvas );
55-
mMapTipVisible = !myTipText.isEmpty();
59+
QString tipText, lastTipText, tipHtml, bodyStyle, containerStyle,
60+
backgroundColor, borderColor;
61+
62+
delete mWidget;
63+
mWidget = new QWidget( pMapCanvas );
64+
mWebView = new QgsWebView( mWidget );
65+
66+
mWebView->page()->settings()->setAttribute(
67+
QWebSettings::DeveloperExtrasEnabled, true );
68+
mWebView->page()->settings()->setAttribute(
69+
QWebSettings::JavascriptEnabled, true );
70+
71+
QHBoxLayout* layout = new QHBoxLayout;
72+
layout->addWidget( mWebView );
73+
74+
mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
75+
mWidget->setLayout( layout );
76+
77+
//assure the map tip is never larger than half the map canvas
78+
const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
79+
const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
80+
mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );
81+
82+
// start with 0 size,
83+
// the content will automatically make it grow up to MaximumSize
84+
mWidget->resize( 0, 0 );
85+
86+
backgroundColor = mWidget->palette().base().color().name();
87+
borderColor = mWidget->palette().shadow().color().name();
88+
mWidget->setStyleSheet( QString(
89+
".QWidget{"
90+
"border: 1px solid %1;"
91+
"background-color: %2;}" ).arg(
92+
borderColor, backgroundColor ) );
5693

57-
if ( mMapTipVisible )
94+
tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );
95+
96+
mMapTipVisible = !tipText.isEmpty();
97+
if ( !mMapTipVisible )
5898
{
59-
QToolTip::showText( thepMapCanvas->mapToGlobal( thePixelPosition ), myTipText, thepMapCanvas );
60-
// store the point so we can use it to clear the maptip later
61-
mLastPosition = thePixelPosition;
99+
clear();
100+
return;
101+
}
102+
103+
if ( tipText == lastTipText )
104+
{
105+
return;
106+
}
107+
108+
bodyStyle = QString(
109+
"background-color: %1;"
110+
"margin: 0;" ).arg( backgroundColor );
111+
112+
containerStyle = QString(
113+
"display: inline-block;"
114+
"margin: 0px" );
115+
116+
tipHtml = QString(
117+
"<html>"
118+
"<body style='%1'>"
119+
"<div id='QgsWebViewContainer' style='%2'>%3</div>"
120+
"</body>"
121+
"</html>" ).arg( bodyStyle, containerStyle, tipText );
122+
123+
mWidget->move( thePixelPosition.x(),
124+
thePixelPosition.y() );
125+
126+
mWebView->setHtml( tipHtml );
127+
lastTipText = tipText;
128+
129+
mWidget->show();
130+
131+
int scrollbarWidth = mWebView->page()->mainFrame()->scrollBarGeometry(
132+
Qt::Vertical ).width();
133+
int scrollbarHeight = mWebView->page()->mainFrame()->scrollBarGeometry(
134+
Qt::Horizontal ).height();
135+
136+
if ( scrollbarWidth > 0 || scrollbarHeight > 0 )
137+
{
138+
// Get the content size
139+
QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
140+
"#QgsWebViewContainer" );
141+
int width = container.geometry().width() + 5 + scrollbarWidth;
142+
int height = container.geometry().height() + 5 + scrollbarHeight;
143+
144+
mWidget->resize( width, height );
62145
}
63146
}
64147

65-
void QgsMapTip::clear( QgsMapCanvas *mpMapCanvas )
148+
void QgsMapTip::clear( QgsMapCanvas * )
66149
{
67150
if ( !mMapTipVisible )
68151
return;
69152

70-
// set the maptip to blank
71-
QToolTip::showText( mpMapCanvas->mapToGlobal( mLastPosition ), "", mpMapCanvas );
153+
mWebView->setHtml( QString() );
154+
mWidget->hide();
155+
72156
// reset the visible flag
73157
mMapTipVisible = false;
74158
}
@@ -77,7 +161,7 @@ QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPoint &mapPosition, QgsM
77161
{
78162
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
79163
if ( !vlayer )
80-
return "";
164+
return QString();
81165

82166
double searchRadius = QgsMapTool::searchRadiusMU( mpMapCanvas );
83167

@@ -92,7 +176,7 @@ QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPoint &mapPosition, QgsM
92176
QgsFeature feature;
93177

94178
if ( !vlayer->getFeatures( QgsFeatureRequest().setFilterRect( r ).setFlags( QgsFeatureRequest::ExactIntersect ) ).nextFeature( feature ) )
95-
return "";
179+
return QString();
96180

97181
int idx = vlayer->fieldNameIndex( vlayer->displayField() );
98182
if ( idx < 0 )
@@ -108,5 +192,7 @@ QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPoint &mapPosition, QgsM
108192
return QgsExpression::replaceExpressionText( vlayer->displayField(), &context );
109193
}
110194
else
195+
{
111196
return feature.attribute( idx ).toString();
197+
}
112198
}

src/gui/qgsmaptip.h

+24-7
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,28 @@ class QgsMapLayer;
1919
class QgsMapCanvas;
2020
class QPoint;
2121
class QString;
22+
class QgsWebView;
2223

2324
#include "qgsfeature.h"
2425

2526
/** \ingroup gui
2627
* A maptip is a class to display a tip on a map canvas
2728
* when a mouse is hovered over a feature.
29+
*
30+
* Since QGIS 2.16 a maptip can show full html.
31+
* QgsMapTip is a QgsWebview, so you can load full HTML/JS/CSS in it.
32+
*
33+
* The code found in the map tips tab is inserted in a inline-block div
34+
* so the frame can be resized based on the content size.
35+
*
36+
* If no element in the html has a width attribute, the frame will squeeze down
37+
* to the widest word. To avoid this you can wrap your HTML in a
38+
* div style="width:300px" or similar.
39+
*
40+
* JS can be included using the script tag as usual, while CSS files must be
41+
* linked using link rel="stylesheet" href="URL.css" the html specs
42+
* discourages link rel="stylesheet" in the body, but all browsers allow it.
43+
* see https://jakearchibald.com/2016/link-in-body/
2844
*/
2945
class GUI_EXPORT QgsMapTip
3046
{
@@ -51,21 +67,22 @@ class GUI_EXPORT QgsMapTip
5167
/** Clear the current maptip if it exists
5268
* @param mpMapCanvas the canvas from which the tip should be cleared.
5369
*/
54-
void clear( QgsMapCanvas *mpMapCanvas );
70+
void clear( QgsMapCanvas *mpMapCanvas = nullptr );
5571
private:
56-
// Fetch the feature to use for the maptip text. Only the first feature in the
57-
// search radius is used
72+
// Fetch the feature to use for the maptip text.
73+
// Only the first feature in the search radius is used
5874
QString fetchFeature( QgsMapLayer * thepLayer,
5975
QgsPoint & theMapPosition,
6076
QgsMapCanvas *thepMapCanvas );
6177

62-
QString replaceText( QString displayText, QgsVectorLayer *layer, QgsFeature &feat );
78+
QString replaceText(
79+
QString displayText, QgsVectorLayer *layer, QgsFeature &feat );
6380

6481
// Flag to indicate if a maptip is currently being displayed
6582
bool mMapTipVisible;
66-
// Last point on the map canvas when the maptip timer fired. This point is in widget pixel
67-
// coordinates
68-
QPoint mLastPosition;
83+
84+
QWidget* mWidget;
85+
QgsWebView* mWebView;
6986

7087
};
7188
#endif // QGSMAPTIP_H

0 commit comments

Comments
 (0)