Skip to content
Permalink
Browse files

[FEATURE] QgsTooltip support full HTML

This is useful for example to add URL links that
allow being clicked on.
  • Loading branch information
mbernasocchi authored and m-kuhn committed May 31, 2016
1 parent 2269543 commit 45227e51d88a822409ef04e3c932fb10d4fc2a85
Showing with 129 additions and 26 deletions.
  1. +105 −19 src/gui/qgsmaptip.cpp
  2. +24 −7 src/gui/qgsmaptip.h
@@ -18,15 +18,22 @@
#include "qgsvectorlayer.h"
#include "qgsexpression.h"
#include "qgslogger.h"
#include "qgswebview.h"
#include "qgswebframe.h"

// Qt includes
#include <QPoint>
#include <QToolTip>
#include <QSettings>
#include <QLabel>
#include <QWebElement>
#include <QHBoxLayout>


#include "qgsmaptip.h"

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

}

void QgsMapTip::showMapTip( QgsMapLayer *thepLayer,
QgsPoint & theMapPosition,
void QgsMapTip::showMapTip( QgsMapLayer *pLayer,
QgsPoint & mapPosition,
QPoint & thePixelPosition,
QgsMapCanvas *thepMapCanvas )
QgsMapCanvas *pMapCanvas )
{
// Do the search using the active layer and the preferred label
// field for the layer. The label field must be defined in the layer configuration
// Do the search using the active layer and the preferred label field for the
// layer. The label field must be defined in the layer configuration
// file/database. The code required to do this is similar to identify, except
// we only want the first qualifying feature and we will only display the
// field defined as the label field in the layer configuration file/database.
//
// TODO: Define the label (display) field for each map layer in the map configuration file/database
// field defined as the label field in the layer configuration file/database

// Show the maptip on the canvas
QString myTipText = fetchFeature( thepLayer, theMapPosition, thepMapCanvas );
mMapTipVisible = !myTipText.isEmpty();
QString tipText, lastTipText, tipHtml, bodyStyle, containerStyle,
backgroundColor, borderColor;

delete mWidget;
mWidget = new QWidget( pMapCanvas );
mWebView = new QgsWebView( mWidget );

mWebView->page()->settings()->setAttribute(
QWebSettings::DeveloperExtrasEnabled, true );
mWebView->page()->settings()->setAttribute(
QWebSettings::JavascriptEnabled, true );

QHBoxLayout* layout = new QHBoxLayout;
layout->addWidget( mWebView );

mWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
mWidget->setLayout( layout );

//assure the map tip is never larger than half the map canvas
const int MAX_WIDTH = pMapCanvas->geometry().width() / 2;
const int MAX_HEIGHT = pMapCanvas->geometry().height() / 2;
mWidget->setMaximumSize( MAX_WIDTH, MAX_HEIGHT );

// start with 0 size,
// the content will automatically make it grow up to MaximumSize
mWidget->resize( 0, 0 );

backgroundColor = mWidget->palette().base().color().name();
borderColor = mWidget->palette().shadow().color().name();
mWidget->setStyleSheet( QString(
".QWidget{"
"border: 1px solid %1;"
"background-color: %2;}" ).arg(
borderColor, backgroundColor ) );

if ( mMapTipVisible )
tipText = fetchFeature( pLayer, mapPosition, pMapCanvas );

mMapTipVisible = !tipText.isEmpty();
if ( !mMapTipVisible )
{
QToolTip::showText( thepMapCanvas->mapToGlobal( thePixelPosition ), myTipText, thepMapCanvas );
// store the point so we can use it to clear the maptip later
mLastPosition = thePixelPosition;
clear();
return;
}

if ( tipText == lastTipText )
{
return;
}

bodyStyle = QString(
"background-color: %1;"
"margin: 0;" ).arg( backgroundColor );

containerStyle = QString(
"display: inline-block;"
"margin: 0px" );

tipHtml = QString(
"<html>"
"<body style='%1'>"
"<div id='QgsWebViewContainer' style='%2'>%3</div>"
"</body>"
"</html>" ).arg( bodyStyle, containerStyle, tipText );

mWidget->move( thePixelPosition.x(),
thePixelPosition.y() );

mWebView->setHtml( tipHtml );
lastTipText = tipText;

mWidget->show();

int scrollbarWidth = mWebView->page()->mainFrame()->scrollBarGeometry(
Qt::Vertical ).width();
int scrollbarHeight = mWebView->page()->mainFrame()->scrollBarGeometry(
Qt::Horizontal ).height();

if ( scrollbarWidth > 0 || scrollbarHeight > 0 )
{
// Get the content size
QWebElement container = mWebView->page()->mainFrame()->findFirstElement(
"#QgsWebViewContainer" );
int width = container.geometry().width() + 5 + scrollbarWidth;
int height = container.geometry().height() + 5 + scrollbarHeight;

mWidget->resize( width, height );
}
}

void QgsMapTip::clear( QgsMapCanvas *mpMapCanvas )
void QgsMapTip::clear( QgsMapCanvas * )
{
if ( !mMapTipVisible )
return;

// set the maptip to blank
QToolTip::showText( mpMapCanvas->mapToGlobal( mLastPosition ), "", mpMapCanvas );
mWebView->setHtml( QString() );
mWidget->hide();

// reset the visible flag
mMapTipVisible = false;
}
@@ -77,7 +161,7 @@ QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPoint &mapPosition, QgsM
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
if ( !vlayer )
return "";
return QString();

double searchRadius = QgsMapTool::searchRadiusMU( mpMapCanvas );

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

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

int idx = vlayer->fieldNameIndex( vlayer->displayField() );
if ( idx < 0 )
@@ -108,5 +192,7 @@ QString QgsMapTip::fetchFeature( QgsMapLayer *layer, QgsPoint &mapPosition, QgsM
return QgsExpression::replaceExpressionText( vlayer->displayField(), &context );
}
else
{
return feature.attribute( idx ).toString();
}
}
@@ -19,12 +19,28 @@ class QgsMapLayer;
class QgsMapCanvas;
class QPoint;
class QString;
class QgsWebView;

#include "qgsfeature.h"

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

QString replaceText( QString displayText, QgsVectorLayer *layer, QgsFeature &feat );
QString replaceText(
QString displayText, QgsVectorLayer *layer, QgsFeature &feat );

// Flag to indicate if a maptip is currently being displayed
bool mMapTipVisible;
// Last point on the map canvas when the maptip timer fired. This point is in widget pixel
// coordinates
QPoint mLastPosition;

QWidget* mWidget;
QgsWebView* mWebView;

};
#endif // QGSMAPTIP_H

0 comments on commit 45227e5

Please sign in to comment.
You can’t perform that action at this time.