File indexing completed on 2024-04-21 03:49:42
0001 // SPDX-FileCopyrightText: 2006-2007 Torsten Rahn <tackat@kde.org> 0002 // SPDX-FileCopyrightText: 2007 Inge Wallin <ingwa@kde.org> 0003 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com> 0004 // SPDX-FileCopyrightText: 2013 Yazeed Zoabi <yazeedz.zoabi@gmail.com> 0005 // 0006 // SPDX-License-Identifier: LGPL-2.1-or-later 0007 0008 #include "MarbleLegendBrowser.h" 0009 0010 #include <QCoreApplication> 0011 #include <QUrl> 0012 #include <QDesktopServices> 0013 #include <QEvent> 0014 #include <QFile> 0015 #include <QMouseEvent> 0016 #include <QPainter> 0017 #include <QRegExp> 0018 0019 #ifndef MARBLE_NO_WEBKITWIDGETS 0020 #include <QWebEnginePage> 0021 #include <QWebChannel> 0022 #endif 0023 0024 #include <QTextDocument> 0025 0026 #include "GeoSceneDocument.h" 0027 #include "GeoSceneHead.h" 0028 #include "GeoSceneLegend.h" 0029 #include "GeoSceneSection.h" 0030 #include "GeoSceneIcon.h" 0031 #include "GeoSceneItem.h" 0032 #include "GeoSceneProperty.h" 0033 #include "GeoSceneSettings.h" 0034 #include "MarbleModel.h" 0035 #include "MarbleDebug.h" 0036 #include "TemplateDocument.h" 0037 #include "MarbleDirs.h" 0038 0039 namespace Marble 0040 { 0041 0042 class MarbleLegendBrowserPrivate 0043 { 0044 public: 0045 MarbleModel *m_marbleModel; 0046 QMap<QString, bool> m_checkBoxMap; 0047 QMap<QString, QPixmap> m_symbolMap; 0048 QString m_currentThemeId; 0049 MarbleJsWrapper *m_jsWrapper; 0050 }; 0051 0052 0053 // ================================================================ 0054 0055 0056 MarbleLegendBrowser::MarbleLegendBrowser( QWidget *parent ) 0057 : MarbleWebView( parent ), 0058 d( new MarbleLegendBrowserPrivate ) 0059 { 0060 d->m_marbleModel = nullptr; 0061 d->m_jsWrapper = new MarbleJsWrapper(this); 0062 } 0063 0064 MarbleLegendBrowser::~MarbleLegendBrowser() 0065 { 0066 delete d; 0067 } 0068 0069 void MarbleLegendBrowser::setMarbleModel( MarbleModel *marbleModel ) 0070 { 0071 // We need this to be able to get to the MapTheme. 0072 d->m_marbleModel = marbleModel; 0073 0074 if ( d->m_marbleModel ) { 0075 connect ( d->m_marbleModel, SIGNAL(themeChanged(QString)), 0076 this, SLOT(initTheme()) ); 0077 } 0078 } 0079 0080 QSize MarbleLegendBrowser::sizeHint() const 0081 { 0082 return QSize( 320, 320 ); 0083 } 0084 0085 void MarbleLegendBrowser::initTheme() 0086 { 0087 // Check for a theme specific legend.html first 0088 if ( d->m_marbleModel != nullptr && d->m_marbleModel->mapTheme() != nullptr ) 0089 { 0090 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme(); 0091 0092 d->m_checkBoxMap.clear(); 0093 0094 for ( const GeoSceneProperty *property: currentMapTheme->settings()->allProperties() ) { 0095 if ( property->available() ) { 0096 d->m_checkBoxMap[ property->name() ] = property->value(); 0097 } 0098 } 0099 0100 disconnect ( currentMapTheme, SIGNAL(valueChanged(QString,bool)), nullptr, nullptr ); 0101 connect ( currentMapTheme, SIGNAL(valueChanged(QString,bool)), 0102 this, SLOT(setCheckedProperty(QString,bool)) ); 0103 } 0104 0105 if ( isVisible() ) { 0106 loadLegend(); 0107 } 0108 } 0109 0110 void MarbleLegendBrowser::loadLegend() 0111 { 0112 if (!d->m_marbleModel) { 0113 return; 0114 } 0115 0116 #ifndef MARBLE_NO_WEBKITWIDGETS 0117 if (d->m_currentThemeId != d->m_marbleModel->mapThemeId()) { 0118 d->m_currentThemeId = d->m_marbleModel->mapThemeId(); 0119 } else { 0120 return; 0121 } 0122 0123 // Read the html string. 0124 QString legendPath; 0125 0126 // Check for a theme specific legend.html first 0127 if (d->m_marbleModel->mapTheme() != nullptr ) { 0128 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme(); 0129 0130 legendPath = MarbleDirs::path(QLatin1String("maps/") + 0131 currentMapTheme->head()->target() + QLatin1Char('/') + 0132 currentMapTheme->head()->theme() + QLatin1String("/legend.html")); 0133 } 0134 if ( legendPath.isEmpty() ) { 0135 legendPath = MarbleDirs::path(QStringLiteral("legend.html")); 0136 } 0137 0138 QString finalHtml = readHtml( QUrl::fromLocalFile( legendPath ) ); 0139 0140 TemplateDocument doc(finalHtml); 0141 finalHtml = doc.finalText(); 0142 0143 injectWebChannel(finalHtml); 0144 reverseSupportCheckboxes(finalHtml); 0145 0146 // Generate some parts of the html from the MapTheme <Legend> tag. 0147 const QString sectionsHtml = generateSectionsHtml(); 0148 0149 // And then create the final html from these two parts. 0150 finalHtml.replace( QString( "<!-- ##customLegendEntries:all## -->" ), sectionsHtml ); 0151 0152 translateHtml( finalHtml ); 0153 0154 QUrl baseUrl = QUrl::fromLocalFile( legendPath ); 0155 0156 // Set the html string in the QTextBrowser. 0157 MarbleWebPage * page = new MarbleWebPage(this); 0158 connect( page, SIGNAL(linkClicked(QUrl)), this, SLOT(openLinkExternally(QUrl)) ); 0159 page->setHtml(finalHtml, baseUrl); 0160 setPage(page); 0161 0162 QWebChannel *channel = new QWebChannel(page); 0163 channel->registerObject(QStringLiteral("Marble"), d->m_jsWrapper); 0164 page->setWebChannel(channel); 0165 0166 if ( d->m_marbleModel ) { 0167 page->toHtml([=]( QString document ) { 0168 d->m_marbleModel->setLegend( new QTextDocument(document) ); 0169 }); 0170 } 0171 #endif 0172 } 0173 0174 void MarbleLegendBrowser::openLinkExternally( const QUrl &url ) 0175 { 0176 if (url.scheme() == QLatin1String("tour")) { 0177 emit tourLinkClicked(QLatin1String("maps/") + url.host() + url.path()); 0178 } else { 0179 QDesktopServices::openUrl( url ); 0180 } 0181 } 0182 0183 bool MarbleLegendBrowser::event( QEvent * event ) 0184 { 0185 // "Delayed initialization": legend gets created only 0186 if ( event->type() == QEvent::Show ) { 0187 loadLegend(); 0188 } 0189 0190 return MarbleWebView::event( event ); 0191 } 0192 0193 QString MarbleLegendBrowser::readHtml( const QUrl & name ) 0194 { 0195 QString html; 0196 0197 QFile data( name.toLocalFile() ); 0198 if ( data.open( QFile::ReadOnly ) ) { 0199 QTextStream in( &data ); 0200 html = in.readAll(); 0201 data.close(); 0202 } 0203 0204 return html; 0205 } 0206 0207 void MarbleLegendBrowser::translateHtml( QString & html ) 0208 { 0209 // must match string extraction in Messages.sh 0210 QString s = html; 0211 QRegExp rx( "</?\\w+((\\s+\\w+(\\s*=\\s*(?:\".*\"|'.*'|[^'\">\\s]+))?)+\\s*|\\s*)/?>" ); 0212 rx.setMinimal( true ); 0213 s.replace( rx, "\n" ); 0214 s.replace( QRegExp( "\\s*\n\\s*" ), "\n" ); 0215 const QStringList words = s.split(QLatin1Char('\n'), QString::SkipEmptyParts); 0216 0217 QStringList::const_iterator i = words.constBegin(); 0218 QStringList::const_iterator const end = words.constEnd(); 0219 for (; i != end; ++i ) 0220 html.replace(*i, QCoreApplication::translate("Legends", (*i).toUtf8().constData())); 0221 } 0222 0223 void MarbleLegendBrowser::injectWebChannel(QString &html) 0224 { 0225 QString webChannelCode = "<script type=\"text/javascript\" src=\"qrc:///qtwebchannel/qwebchannel.js\"></script>"; 0226 webChannelCode += "<script> document.addEventListener(\"DOMContentLoaded\", function() {" 0227 "new QWebChannel(qt.webChannelTransport, function (channel) {" 0228 "Marble = channel.objects.Marble;" 0229 "});" 0230 "}); </script>" 0231 "</head>"; 0232 html.replace("</head>", webChannelCode); 0233 } 0234 0235 void MarbleLegendBrowser::reverseSupportCheckboxes(QString &html) 0236 { 0237 const QString old = "<a href=\"checkbox:cities\"/>"; 0238 0239 QString checked; 0240 if (d->m_checkBoxMap["cities"]) 0241 checked = "checked"; 0242 0243 const QString repair = QLatin1String( 0244 "<input style=\"position: relative; top: -4px;\" type=\"checkbox\" " 0245 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ") + checked + QLatin1String(" name=\"cities\"/>"); 0246 0247 html.replace(old, repair); 0248 } 0249 0250 QString MarbleLegendBrowser::generateSectionsHtml() 0251 { 0252 // Generate HTML to include into legend.html here. 0253 0254 QString customLegendString; 0255 0256 if ( d->m_marbleModel == nullptr || d->m_marbleModel->mapTheme() == nullptr ) 0257 return QString(); 0258 0259 const GeoSceneDocument *currentMapTheme = d->m_marbleModel->mapTheme(); 0260 0261 d->m_symbolMap.clear(); 0262 0263 /* Okay, if you are reading it now, be ready for hell! 0264 * We can't optimize this part of Legend Browser, but we will 0265 * do it, anyway. It's complicated a lot, the most important 0266 * thing is to understand everything. 0267 */ 0268 for ( const GeoSceneSection *section: currentMapTheme->legend()->sections() ) { 0269 // Each section is divided into the "well" 0270 // Well is like a block of data with rounded corners 0271 customLegendString += QLatin1String("<div class=\"well well-small well-legend\">"); 0272 0273 const QString heading = QCoreApplication::translate("DGML", section->heading().toUtf8().constData()); 0274 QString checkBoxString; 0275 if (section->checkable()) { 0276 // If it's needed to make a checkbox here, we will 0277 QString const checked = d->m_checkBoxMap[section->connectTo()] ? "checked" : ""; 0278 /* Important comment: 0279 * We inject Marble object into JavaScript of each legend html file 0280 * This is only one way to handle checkbox changes we see, so 0281 * Marble.setCheckedProperty is a function that does it 0282 */ 0283 if(!section->radio().isEmpty()) { 0284 checkBoxString = QLatin1String( 0285 "<label class=\"section-head\">" 0286 "<input style=\"position: relative; top: -4px;\" type=\"radio\" " 0287 "onchange=\"Marble.setRadioCheckedProperty(this.value, this.name ,this.checked);\" ") + 0288 checked + QLatin1String(" value=\"") + section->connectTo() + QLatin1String("\" name=\"") + section->radio() + QLatin1String("\" /><span>") 0289 + heading + 0290 QLatin1String("</span></label>"); 0291 0292 } else { 0293 checkBoxString = QLatin1String( 0294 "<label class=\"section-head\">" 0295 "<input style=\"position: relative; top: -4px;\" type=\"checkbox\" " 0296 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ") + checked + QLatin1String(" name=\"") + section->connectTo() + QLatin1String("\" /><span>") 0297 + heading + 0298 QLatin1String("</span></label>"); 0299 0300 } 0301 customLegendString += checkBoxString; 0302 0303 } else { 0304 customLegendString += QLatin1String("<h4 class=\"section-head\">") + heading + QLatin1String("</h4>"); 0305 } 0306 0307 for (const GeoSceneItem *item: section->items()) { 0308 0309 // checkbox for item 0310 QString checkBoxString; 0311 if (item->checkable()) { 0312 QString const checked = d->m_checkBoxMap[item->connectTo()] ? "checked" : ""; 0313 checkBoxString = QLatin1String( 0314 "<input type=\"checkbox\" " 0315 "onchange=\"Marble.setCheckedProperty(this.name, this.checked);\" ") 0316 + checked + QLatin1String(" name=\"") + item->connectTo() + QLatin1String("\" />"); 0317 0318 } 0319 0320 // pixmap and text 0321 QString src; 0322 QString styleDiv; 0323 int pixmapWidth = 24; 0324 int pixmapHeight = 12; 0325 if (!item->icon()->pixmap().isEmpty()) { 0326 QString path = MarbleDirs::path( item->icon()->pixmap() ); 0327 const QPixmap oncePixmap(path); 0328 pixmapWidth = oncePixmap.width(); 0329 pixmapHeight = oncePixmap.height(); 0330 src = QUrl::fromLocalFile( path ).toString(); 0331 styleDiv = QLatin1String("width: ") + QString::number(pixmapWidth) + QLatin1String("px; height: ") + 0332 QString::number(pixmapHeight) + QLatin1String("px;"); 0333 } else { 0334 // Workaround for rendered border around empty images in webkit 0335 src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"; 0336 } 0337 // NOTICE. There are some pixmaps without image, so we should 0338 // create just a plain rectangle with set color 0339 if (QColor(item->icon()->color()).isValid()) { 0340 const QColor color = item->icon()->color(); 0341 styleDiv = QLatin1String("width: ") + QString::number(pixmapWidth) + QLatin1String("px; height: ") + 0342 QString::number(pixmapHeight) + QLatin1String("px; background-color: ") + color.name() + QLatin1Char(';'); 0343 } 0344 styleDiv += " position: relative; top: -3px;"; 0345 const QString text = QCoreApplication::translate("DGML", item->text().toUtf8().constData()); 0346 QString html = QLatin1String( 0347 "<div class=\"legend-entry\">" 0348 " <label>") + checkBoxString + QLatin1String( 0349 " <img class=\"image-pic\" src=\"") + src + QLatin1String("\" style=\"") + styleDiv + QLatin1String("\"/>" 0350 " <span class=\"kotation\" >") + text + QLatin1String("</span>" 0351 " </label>" 0352 "</div>"); 0353 customLegendString += html; 0354 } 0355 customLegendString += QLatin1String("</div>"); // <div class="well"> 0356 } 0357 0358 return customLegendString; 0359 } 0360 0361 void MarbleLegendBrowser::setCheckedProperty( const QString& name, bool checked ) 0362 { 0363 if (checked != d->m_checkBoxMap[name]) { 0364 d->m_checkBoxMap[name] = checked; 0365 emit toggledShowProperty( name, checked ); 0366 } 0367 } 0368 0369 void MarbleLegendBrowser::setRadioCheckedProperty( const QString& value, const QString& name , bool checked ) 0370 { 0371 Q_UNUSED(value) 0372 if (checked != d->m_checkBoxMap[name]) { 0373 d->m_checkBoxMap[name] = checked; 0374 emit toggledShowProperty( name, checked ); 0375 } 0376 } 0377 0378 } 0379 0380 #include "moc_MarbleLegendBrowser.cpp"