Marble

MarbleLegendBrowser.cpp
1// SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org>
2// SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org>
3// SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
4// SPDX-FileCopyrightText: 2013 Yazeed Zoabi <yazeedz.zoabi@gmail.com>
5//
6// SPDX-License-Identifier: LGPL-2.1-or-later
7
8#include "MarbleLegendBrowser.h"
9
10#include <QCoreApplication>
11#include <QUrl>
12#include <QDesktopServices>
13#include <QEvent>
14#include <QFile>
15#include <QMouseEvent>
16#include <QPainter>
17#include <QRegExp>
18
19#ifndef MARBLE_NO_WEBKITWIDGETS
20#include <QWebEnginePage>
21#include <QWebChannel>
22#endif
23
24#include <QTextDocument>
25
26#include "GeoSceneDocument.h"
27#include "GeoSceneHead.h"
28#include "GeoSceneLegend.h"
29#include "GeoSceneSection.h"
30#include "GeoSceneIcon.h"
31#include "GeoSceneItem.h"
32#include "GeoSceneProperty.h"
33#include "GeoSceneSettings.h"
34#include "MarbleModel.h"
35#include "MarbleDebug.h"
36#include "TemplateDocument.h"
37#include "MarbleDirs.h"
38
39namespace Marble
40{
41
42class MarbleLegendBrowserPrivate
43{
44 public:
45 MarbleModel *m_marbleModel;
46 QMap<QString, bool> m_checkBoxMap;
47 QMap<QString, QPixmap> m_symbolMap;
48 QString m_currentThemeId;
49 MarbleJsWrapper *m_jsWrapper;
50};
51
52
53// ================================================================
54
55
56MarbleLegendBrowser::MarbleLegendBrowser( QWidget *parent )
57 : MarbleWebView( parent ),
58 d( new MarbleLegendBrowserPrivate )
59{
60 d->m_marbleModel = nullptr;
61 d->m_jsWrapper = new MarbleJsWrapper(this);
62}
63
64MarbleLegendBrowser::~MarbleLegendBrowser()
65{
66 delete d;
67}
68
69void MarbleLegendBrowser::setMarbleModel( MarbleModel *marbleModel )
70{
71 // We need this to be able to get to the MapTheme.
72 d->m_marbleModel = marbleModel;
73
74 if ( d->m_marbleModel ) {
75 connect ( d->m_marbleModel, SIGNAL(themeChanged(QString)),
76 this, SLOT(initTheme()) );
77 }
78}
79
80QSize MarbleLegendBrowser::sizeHint() const
81{
82 return QSize( 320, 320 );
83}
84
85void MarbleLegendBrowser::initTheme()
86{
87 // Check for a theme specific legend.html first
88 if ( d->m_marbleModel != nullptr && d->m_marbleModel->mapTheme() != nullptr )
89 {
90 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme();
91
92 d->m_checkBoxMap.clear();
93
94 for ( const GeoSceneProperty *property: currentMapTheme->settings()->allProperties() ) {
95 if ( property->available() ) {
96 d->m_checkBoxMap[ property->name() ] = property->value();
97 }
98 }
99
100 disconnect ( currentMapTheme, SIGNAL(valueChanged(QString,bool)), nullptr, nullptr );
101 connect ( currentMapTheme, SIGNAL(valueChanged(QString,bool)),
102 this, SLOT(setCheckedProperty(QString,bool)) );
103 }
104
105 if ( isVisible() ) {
106 loadLegend();
107 }
108}
109
110void MarbleLegendBrowser::loadLegend()
111{
112 if (!d->m_marbleModel) {
113 return;
114 }
115
116#ifndef MARBLE_NO_WEBKITWIDGETS
117 if (d->m_currentThemeId != d->m_marbleModel->mapThemeId()) {
118 d->m_currentThemeId = d->m_marbleModel->mapThemeId();
119 } else {
120 return;
121 }
122
123 // Read the html string.
124 QString legendPath;
125
126 // Check for a theme specific legend.html first
127 if (d->m_marbleModel->mapTheme() != nullptr ) {
128 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme();
129
130 legendPath = MarbleDirs::path(QLatin1String("maps/") +
131 currentMapTheme->head()->target() + QLatin1Char('/') +
132 currentMapTheme->head()->theme() + QLatin1String("/legend.html"));
133 }
134 if ( legendPath.isEmpty() ) {
135 legendPath = MarbleDirs::path(QStringLiteral("legend.html"));
136 }
137
138 QString finalHtml = readHtml( QUrl::fromLocalFile( legendPath ) );
139
140 TemplateDocument doc(finalHtml);
141 finalHtml = doc.finalText();
142
143 injectWebChannel(finalHtml);
144 reverseSupportCheckboxes(finalHtml);
145
146 // Generate some parts of the html from the MapTheme <Legend> tag.
147 const QString sectionsHtml = generateSectionsHtml();
148
149 // And then create the final html from these two parts.
150 finalHtml.replace( QString( "<!-- ##customLegendEntries:all## -->" ), sectionsHtml );
151
152 translateHtml( finalHtml );
153
154 QUrl baseUrl = QUrl::fromLocalFile( legendPath );
155
156 // Set the html string in the QTextBrowser.
157 MarbleWebPage * page = new MarbleWebPage(this);
158 connect( page, SIGNAL(linkClicked(QUrl)), this, SLOT(openLinkExternally(QUrl)) );
159 page->setHtml(finalHtml, baseUrl);
160 setPage(page);
161
162 QWebChannel *channel = new QWebChannel(page);
163 channel->registerObject(QStringLiteral("Marble"), d->m_jsWrapper);
164 page->setWebChannel(channel);
165
166 if ( d->m_marbleModel ) {
167 page->toHtml([=]( QString document ) {
168 d->m_marbleModel->setLegend( new QTextDocument(document) );
169 });
170 }
171#endif
172}
173
174void MarbleLegendBrowser::openLinkExternally( const QUrl &url )
175{
176 if (url.scheme() == QLatin1String("tour")) {
177 emit tourLinkClicked(QLatin1String("maps/") + url.host() + url.path());
178 } else {
180 }
181}
182
183bool MarbleLegendBrowser::event( QEvent * event )
184{
185 // "Delayed initialization": legend gets created only
186 if ( event->type() == QEvent::Show ) {
187 loadLegend();
188 }
189
190 return MarbleWebView::event( event );
191}
192
193QString MarbleLegendBrowser::readHtml( const QUrl & name )
194{
195 QString html;
196
197 QFile data( name.toLocalFile() );
198 if ( data.open( QFile::ReadOnly ) ) {
199 QTextStream in( &data );
200 html = in.readAll();
201 data.close();
202 }
203
204 return html;
205}
206
207void MarbleLegendBrowser::translateHtml( QString & html )
208{
209 // must match string extraction in Messages.sh
210 QString s = html;
211 QRegExp rx( "</?\\w+((\\s+\\w+(\\s*=\\s*(?:\".*\"|'.*'|[^'\">\\s]+))?)+\\s*|\\s*)/?>" );
212 rx.setMinimal( true );
213 s.replace( rx, "\n" );
214 s.replace( QRegExp( "\\s*\n\\s*" ), "\n" );
215 const QStringList words = s.split(QLatin1Char('\n'), QString::SkipEmptyParts);
216
219 for (; i != end; ++i )
220 html.replace(*i, QCoreApplication::translate("Legends", (*i).toUtf8().constData()));
221}
222
223void MarbleLegendBrowser::injectWebChannel(QString &html)
224{
225 QString webChannelCode = "<script type=\"text/javascript\" src=\"qrc:///qtwebchannel/qwebchannel.js\"></script>";
226 webChannelCode += "<script> document.addEventListener(\"DOMContentLoaded\", function() {"
227 "new QWebChannel(qt.webChannelTransport, function (channel) {"
228 "Marble = channel.objects.Marble;"
229 "});"
230 "}); </script>"
231 "</head>";
232 html.replace("</head>", webChannelCode);
233}
234
235void MarbleLegendBrowser::reverseSupportCheckboxes(QString &html)
236{
237 const QString old = "<a href=\"checkbox:cities\"/>";
238
239 QString checked;
240 if (d->m_checkBoxMap["cities"])
241 checked = "checked";
242
243 const QString repair = QLatin1String(
244 "<input style=\"position: relative; top: -4px;\" type=\"checkbox\" "
245 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ") + checked + QLatin1String(" name=\"cities\"/>");
246
247 html.replace(old, repair);
248}
249
250QString MarbleLegendBrowser::generateSectionsHtml()
251{
252 // Generate HTML to include into legend.html here.
253
254 QString customLegendString;
255
256 if ( d->m_marbleModel == nullptr || d->m_marbleModel->mapTheme() == nullptr )
257 return QString();
258
259 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme();
260
261 d->m_symbolMap.clear();
262
263 /* Okay, if you are reading it now, be ready for hell!
264 * We can't optimize this part of Legend Browser, but we will
265 * do it, anyway. It's complicated a lot, the most important
266 * thing is to understand everything.
267 */
268 for ( const GeoSceneSection *section: currentMapTheme->legend()->sections() ) {
269 // Each section is divided into the "well"
270 // Well is like a block of data with rounded corners
271 customLegendString += QLatin1String("<div class=\"well well-small well-legend\">");
272
273 const QString heading = QCoreApplication::translate("DGML", section->heading().toUtf8().constData());
274 QString checkBoxString;
275 if (section->checkable()) {
276 // If it's needed to make a checkbox here, we will
277 QString const checked = d->m_checkBoxMap[section->connectTo()] ? "checked" : "";
278 /* Important comment:
279 * We inject Marble object into JavaScript of each legend html file
280 * This is only one way to handle checkbox changes we see, so
281 * Marble.setCheckedProperty is a function that does it
282 */
283 if(!section->radio().isEmpty()) {
284 checkBoxString = QLatin1String(
285 "<label class=\"section-head\">"
286 "<input style=\"position: relative; top: -4px;\" type=\"radio\" "
287 "onchange=\"Marble.setRadioCheckedProperty(this.value, this.name ,this.checked);\" ") +
288 checked + QLatin1String(" value=\"") + section->connectTo() + QLatin1String("\" name=\"") + section->radio() + QLatin1String("\" /><span>")
289 + heading +
290 QLatin1String("</span></label>");
291
292 } else {
293 checkBoxString = QLatin1String(
294 "<label class=\"section-head\">"
295 "<input style=\"position: relative; top: -4px;\" type=\"checkbox\" "
296 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ") + checked + QLatin1String(" name=\"") + section->connectTo() + QLatin1String("\" /><span>")
297 + heading +
298 QLatin1String("</span></label>");
299
300 }
301 customLegendString += checkBoxString;
302
303 } else {
304 customLegendString += QLatin1String("<h4 class=\"section-head\">") + heading + QLatin1String("</h4>");
305 }
306
307 for (const GeoSceneItem *item: section->items()) {
308
309 // checkbox for item
310 QString checkBoxString;
311 if (item->checkable()) {
312 QString const checked = d->m_checkBoxMap[item->connectTo()] ? "checked" : "";
313 checkBoxString = QLatin1String(
314 "<input type=\"checkbox\" "
315 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ")
316 + checked + QLatin1String(" name=\"") + item->connectTo() + QLatin1String("\" />");
317
318 }
319
320 // pixmap and text
321 QString src;
322 QString styleDiv;
323 int pixmapWidth = 24;
324 int pixmapHeight = 12;
325 if (!item->icon()->pixmap().isEmpty()) {
326 QString path = MarbleDirs::path( item->icon()->pixmap() );
327 const QPixmap oncePixmap(path);
328 pixmapWidth = oncePixmap.width();
329 pixmapHeight = oncePixmap.height();
330 src = QUrl::fromLocalFile( path ).toString();
331 styleDiv = QLatin1String("width: ") + QString::number(pixmapWidth) + QLatin1String("px; height: ") +
332 QString::number(pixmapHeight) + QLatin1String("px;");
333 } else {
334 // Workaround for rendered border around empty images in webkit
335 src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
336 }
337 // NOTICE. There are some pixmaps without image, so we should
338 // create just a plain rectangle with set color
339 if (QColor(item->icon()->color()).isValid()) {
340 const QColor color = item->icon()->color();
341 styleDiv = QLatin1String("width: ") + QString::number(pixmapWidth) + QLatin1String("px; height: ") +
342 QString::number(pixmapHeight) + QLatin1String("px; background-color: ") + color.name() + QLatin1Char(';');
343 }
344 styleDiv += " position: relative; top: -3px;";
345 const QString text = QCoreApplication::translate("DGML", item->text().toUtf8().constData());
346 QString html = QLatin1String(
347 "<div class=\"legend-entry\">"
348 " <label>") + checkBoxString + QLatin1String(
349 " <img class=\"image-pic\" src=\"") + src + QLatin1String("\" style=\"") + styleDiv + QLatin1String("\"/>"
350 " <span class=\"kotation\" >") + text + QLatin1String("</span>"
351 " </label>"
352 "</div>");
353 customLegendString += html;
354 }
355 customLegendString += QLatin1String("</div>"); // <div class="well">
356 }
357
358 return customLegendString;
359}
360
361void MarbleLegendBrowser::setCheckedProperty( const QString& name, bool checked )
362{
363 if (checked != d->m_checkBoxMap[name]) {
364 d->m_checkBoxMap[name] = checked;
365 emit toggledShowProperty( name, checked );
366 }
367}
368
369void MarbleLegendBrowser::setRadioCheckedProperty( const QString& value, const QString& name , bool checked )
370{
371 Q_UNUSED(value)
372 if (checked != d->m_checkBoxMap[name]) {
373 d->m_checkBoxMap[name] = checked;
374 emit toggledShowProperty( name, checked );
375 }
376}
377
378}
379
380#include "moc_MarbleLegendBrowser.cpp"
This file contains the headers for MarbleModel.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QString path(const QString &relativePath)
const QList< QKeySequence > & end()
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
bool isValid() const const
QString name(NameFormat format) const const
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
bool openUrl(const QUrl &url)
const_iterator constBegin() const const
const_iterator constEnd() const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QUrl fromLocalFile(const QString &localFile)
QString host(ComponentFormattingOptions options) const const
QString path(ComponentFormattingOptions options) const const
QString scheme() const const
QString toString(FormattingOptions options) const const
void registerObject(const QString &id, QObject *object)
virtual bool event(QEvent *event) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.