File indexing completed on 2025-01-05 03:58:35

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-12-01
0007  * Description : Widget for displaying HTML in the backends - QtWebEngine version
0008  *
0009  * SPDX-FileCopyrightText: 2010-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2009-2011 by Michael G. Hansen <mike at mghansen dot de>
0011  * SPDX-FileCopyrightText: 2015      by Mohamed_Anwer <m_dot_anwer at gmx dot com>
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "htmlwidget_qwebengine.h"
0018 
0019 // Qt includes
0020 
0021 #include <QEventLoop>
0022 #include <QResizeEvent>
0023 #include <QCoreApplication>
0024 #include <QWebEngineSettings>
0025 #include <QtWebEngineWidgetsVersion>
0026 
0027 // Local includes
0028 
0029 #include "digikam_debug.h"
0030 
0031 namespace Digikam
0032 {
0033 
0034 HTMLWidgetPage::HTMLWidgetPage(HTMLWidget* const parent)
0035     : QWebEnginePage(parent)
0036 {
0037     m_timer = new QTimer(this);
0038     m_timer->setInterval(100);
0039     m_timer->setSingleShot(true);
0040 
0041     connect(m_timer, SIGNAL(timeout()),
0042             this, SLOT(slotSendHTMLEvents()),
0043             Qt::QueuedConnection);
0044 }
0045 
0046 HTMLWidgetPage::~HTMLWidgetPage()
0047 {
0048 }
0049 
0050 void HTMLWidgetPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel /*level*/,
0051                                               const QString& message,
0052                                               int /*lineNumber*/,
0053                                               const QString& /*sourceID*/)
0054 {
0055     if (!message.startsWith(QLatin1String("(event)")))
0056     {
0057         m_message = message;
0058         m_timer->start();
0059 
0060         return;
0061     }
0062 
0063     qCDebug(DIGIKAM_GEOIFACE_LOG) << message;
0064 
0065     const QString eventString = message.mid(7);
0066 
0067     if (eventString.isEmpty())
0068     {
0069         return;
0070     }
0071 
0072     m_events << eventString;
0073     m_timer->start();
0074 }
0075 
0076 void HTMLWidgetPage::slotSendHTMLEvents()
0077 {
0078     if (!m_message.isEmpty())
0079     {
0080         Q_EMIT signalMessageEvent(m_message);
0081 
0082         m_message.clear();
0083     }
0084 
0085     if (!m_events.isEmpty())
0086     {
0087         Q_EMIT signalHTMLEvents(m_events);
0088 
0089         m_events.clear();
0090     }
0091 }
0092 
0093 // ---------------------------------------------------------------------------------------------
0094 
0095 class Q_DECL_HIDDEN HTMLWidget::Private
0096 {
0097 public:
0098 
0099     explicit Private()
0100       : parent                          (nullptr),
0101         child                           (nullptr),
0102         hpage                           (nullptr),
0103         isReady                         (false),
0104         selectionStatus                 (false),
0105         firstSelectionPoint             (),
0106         intermediateSelectionPoint      (),
0107         firstSelectionScreenPoint       (),
0108         intermediateSelectionScreenPoint()
0109     {
0110     }
0111 
0112     QWidget*        parent;
0113     QWidget*        child;
0114     HTMLWidgetPage* hpage;
0115 
0116     bool            isReady;
0117     bool            selectionStatus;
0118 
0119     GeoCoordinates  firstSelectionPoint;
0120     GeoCoordinates  intermediateSelectionPoint;
0121 
0122     QPoint          firstSelectionScreenPoint;
0123     QPoint          intermediateSelectionScreenPoint;
0124 };
0125 
0126 HTMLWidget::HTMLWidget(QWidget* const parent)
0127     : QWebEngineView(parent),
0128       d             (new Private()),
0129       s             (nullptr)
0130 {
0131     d->parent = parent;
0132     setAcceptDrops(false);
0133     setFocusPolicy(Qt::WheelFocus);
0134 
0135     d->hpage  = new HTMLWidgetPage(this);
0136     setPage(d->hpage);
0137 
0138     settings()->setAttribute(QWebEngineSettings::WebGLEnabled, false);
0139     settings()->setAttribute(QWebEngineSettings::Accelerated2dCanvasEnabled, false);
0140     settings()->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
0141 
0142     d->parent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0143 
0144     connect(this, SIGNAL(loadProgress(int)),
0145             this, SLOT(progress(int)));
0146 
0147     connect(this, SIGNAL(loadFinished(bool)),
0148             this, SLOT(slotHTMLCompleted(bool)));
0149 
0150     connect(d->hpage, SIGNAL(signalHTMLEvents(QStringList)),
0151             this, SIGNAL(signalHTMLEvents(QStringList)));
0152 
0153     connect(d->hpage, SIGNAL(signalMessageEvent(QString)),
0154             this, SIGNAL(signalMessageEvent(QString)));
0155 
0156     if (d->parent)
0157     {
0158         d->parent->installEventFilter(this);
0159     }
0160 
0161     installEventFilter(this);
0162 }
0163 
0164 HTMLWidget::~HTMLWidget()
0165 {
0166     delete d;
0167 }
0168 
0169 void HTMLWidget::progress(int progress)
0170 {
0171     qCDebug(DIGIKAM_GEOIFACE_LOG) << "Maps Loading Progress: " << progress << "%";
0172 }
0173 
0174 void HTMLWidget::slotHTMLCompleted(bool ok)
0175 {
0176     qCDebug(DIGIKAM_GEOIFACE_LOG) << "Map Loading Completed: " << ok;
0177     d->isReady = ok;
0178 
0179     Q_EMIT signalJavaScriptReady();
0180 }
0181 
0182 /**
0183  * @brief Wrapper around executeScript to catch more errors
0184  */
0185 QVariant HTMLWidget::runScript(const QString& scriptCode, bool async)
0186 {
0187     GEOIFACE_ASSERT(d->isReady);
0188 
0189     if (!d->isReady)
0190     {
0191         return QVariant();
0192     }
0193 
0194     //qCDebug(DIGIKAM_GEOIFACE_LOG) << scriptCode;
0195 
0196     if (async)
0197     {
0198         page()->runJavaScript(scriptCode);
0199     }
0200     else
0201     {
0202         QVariant   ret;
0203         QEventLoop loop;
0204 
0205         // Lambda c++11 function capturing value returned by java script code which is not synchro with QWebEngineView.
0206         // See https://wiki.qt.io/Porting_from_QtWebKit_to_QtWebEngine.
0207 
0208         page()->runJavaScript(scriptCode,
0209                               [&ret, &loop](const QVariant& result)
0210                                 {
0211                                     ret.setValue(result);
0212                                     loop.quit();
0213                                 }
0214                              );
0215 
0216         loop.exec();
0217 
0218         return ret;
0219     }
0220 
0221     return QVariant();
0222 }
0223 
0224 /**
0225  * @brief Execute a script which returns coordinates and parse these
0226  */
0227 bool HTMLWidget::runScript2Coordinates(const QString& scriptCode, GeoCoordinates* const coordinates)
0228 {
0229     const QVariant scriptResult = runScript(scriptCode, false);
0230 
0231     return GeoIfaceHelperParseLatLonString(scriptResult.toString(), coordinates);
0232 }
0233 
0234 bool HTMLWidget::eventFilter(QObject* object, QEvent* event)
0235 {
0236     if      (object == this)
0237     {
0238         if (event->type() == QEvent::ChildAdded)
0239         {
0240             d->child = findChild<QWidget*>();
0241 
0242             if (d->child)
0243             {
0244                 d->child->installEventFilter(this);
0245             }
0246         }
0247 
0248         return QWebEngineView::eventFilter(object, event);
0249     }
0250     else if (d->parent && (object == d->parent))
0251     {
0252         if (event->type() == QEvent::Resize)
0253         {
0254             QResizeEvent* const resizeEvent = dynamic_cast<QResizeEvent*>(event);
0255 
0256             if (resizeEvent)
0257             {
0258                 resize(resizeEvent->size());
0259             }
0260         }
0261     }
0262     else if (d->child && (object == d->child))
0263     {
0264         if (event->type() == QEvent::MouseButtonRelease)
0265         {
0266             QMouseEvent* const e = dynamic_cast<QMouseEvent*>(event);
0267 
0268             if (e && (s->currentMouseMode == MouseModeRegionSelection))
0269             {
0270                 if (!d->firstSelectionPoint.hasCoordinates())
0271                 {
0272                     runScript2Coordinates(QString::fromLatin1("kgeomapPixelToLatLng(%1, %2);")
0273 
0274 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0275 
0276                                           .arg(e->position().x())
0277                                           .arg(e->position().y()),
0278 
0279 #else
0280 
0281                                           .arg(e->x())
0282                                           .arg(e->y()),
0283 
0284 #endif
0285 
0286                                           &d->firstSelectionPoint);
0287 
0288                     d->firstSelectionScreenPoint = QPoint(
0289 
0290 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0291 
0292                         e->position().toPoint()
0293 
0294 #else
0295 
0296                         e->x(), e->y()
0297 
0298 #endif
0299 
0300                     );
0301                 }
0302                 else
0303                 {
0304                     runScript2Coordinates(QString::fromLatin1("kgeomapPixelToLatLng(%1, %2);")
0305 
0306 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0307 
0308                                           .arg(e->position().toPoint().x())
0309                                           .arg(e->position().toPoint().y()),
0310 
0311 #else
0312 
0313                                           .arg(e->x())
0314                                           .arg(e->y()),
0315 
0316 #endif
0317 
0318                                           &d->intermediateSelectionPoint);
0319 
0320                     d->intermediateSelectionScreenPoint = QPoint(
0321 
0322 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0323 
0324                         e->position().toPoint()
0325 
0326 #else
0327 
0328                         e->x(), e->y()
0329 
0330 #endif
0331 
0332                     );
0333 
0334                     qreal lonWest = 0.0, latNorth = 0.0, lonEast = 0.0, latSouth = 0.0;
0335 
0336                     if (d->firstSelectionScreenPoint.x() < d->intermediateSelectionScreenPoint.x())
0337                     {
0338                         lonWest  = d->firstSelectionPoint.lon();
0339                         lonEast  = d->intermediateSelectionPoint.lon();
0340                     }
0341                     else
0342                     {
0343                         lonEast  = d->firstSelectionPoint.lon();
0344                         lonWest  = d->intermediateSelectionPoint.lon();
0345                     }
0346 
0347                     if (d->firstSelectionScreenPoint.y() < d->intermediateSelectionScreenPoint.y())
0348                     {
0349                         latNorth = d->firstSelectionPoint.lat();
0350                         latSouth = d->intermediateSelectionPoint.lat();
0351                     }
0352                     else
0353                     {
0354                         latNorth = d->intermediateSelectionPoint.lat();
0355                         latSouth = d->firstSelectionPoint.lat();
0356                     }
0357 
0358                     runScript(QLatin1String("kgeomapRemoveTemporarySelectionRectangle();"));
0359                     runScript(QString::fromLatin1("kgeomapSetSelectionRectangle(%1, %2, %3, %4);")
0360                               .arg(lonWest)
0361                               .arg(latNorth)
0362                               .arg(lonEast)
0363                               .arg(latSouth));
0364 
0365                     const GeoCoordinates::Pair selectionCoordinates(
0366                             GeoCoordinates(latNorth, lonWest),
0367                             GeoCoordinates(latSouth, lonEast));
0368 
0369                     d->firstSelectionPoint.clear();
0370                     d->intermediateSelectionPoint.clear();
0371 
0372                     Q_EMIT selectionHasBeenMade(selectionCoordinates);
0373                 }
0374             }
0375         }
0376         else if (event->type() == QEvent::MouseMove)
0377         {
0378             QMouseEvent* const e = dynamic_cast<QMouseEvent*>(event);
0379 
0380             if (e                                                 &&
0381                 (s->currentMouseMode == MouseModeRegionSelection) &&
0382                 d->firstSelectionPoint.hasCoordinates())
0383             {
0384                 runScript2Coordinates(QString::fromLatin1("kgeomapPixelToLatLng(%1, %2);")
0385 
0386 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0387 
0388                                       .arg(e->position().toPoint().x())
0389                                       .arg(e->position().toPoint().y()),
0390 
0391 #else
0392 
0393                                       .arg(e->x())
0394                                       .arg(e->y()),
0395 
0396 #endif
0397 
0398                                       &d->intermediateSelectionPoint);
0399 
0400                 d->intermediateSelectionScreenPoint = QPoint(
0401 
0402 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0403 
0404                         e->position().toPoint()
0405 
0406 #else
0407 
0408                         e->x(), e->y()
0409 
0410 #endif
0411 
0412                 );
0413 
0414                 qCDebug(DIGIKAM_GEOIFACE_LOG) << d->firstSelectionScreenPoint << QLatin1Char(' ')
0415                                               << d->intermediateSelectionScreenPoint;
0416 
0417                 qreal lonWest = 0.0, latNorth = 0.0, lonEast = 0.0, latSouth = 0.0;
0418 
0419                 if (d->firstSelectionScreenPoint.x() < d->intermediateSelectionScreenPoint.x())
0420                 {
0421                     lonWest  = d->firstSelectionPoint.lon();
0422                     lonEast  = d->intermediateSelectionPoint.lon();
0423                 }
0424                 else
0425                 {
0426                     lonEast  = d->firstSelectionPoint.lon();
0427                     lonWest  = d->intermediateSelectionPoint.lon();
0428                 }
0429 
0430                 if (d->firstSelectionScreenPoint.y() < d->intermediateSelectionScreenPoint.y())
0431                 {
0432                     latNorth = d->firstSelectionPoint.lat();
0433                     latSouth = d->intermediateSelectionPoint.lat();
0434                 }
0435                 else
0436                 {
0437                     latNorth = d->intermediateSelectionPoint.lat();
0438                     latSouth = d->firstSelectionPoint.lat();
0439                 }
0440 
0441                 runScript(QString::fromLatin1("kgeomapSetTemporarySelectionRectangle(%1, %2, %3, %4);")
0442                           .arg(lonWest)
0443                           .arg(latNorth)
0444                           .arg(lonEast)
0445                           .arg(latSouth));
0446             }
0447         }
0448     }
0449 
0450     return false;
0451 }
0452 
0453 void HTMLWidget::setSelectionRectangle(const GeoCoordinates::Pair& searchCoordinates)
0454 {
0455     if (!searchCoordinates.first.hasCoordinates())
0456     {
0457         runScript(QLatin1String("kgeomapRemoveSelectionRectangle();"));
0458 
0459         return;
0460     }
0461 
0462     qreal West  = searchCoordinates.first.lon();
0463     qreal North = searchCoordinates.first.lat();
0464     qreal East  = searchCoordinates.second.lon();
0465     qreal South = searchCoordinates.second.lat();
0466 
0467     runScript(QString::fromLatin1("kgeomapSetSelectionRectangle(%1, %2, %3, %4);")
0468               .arg(West).arg(North).arg(East).arg(South));
0469 }
0470 
0471 void HTMLWidget::removeSelectionRectangle()
0472 {
0473     runScript(QLatin1String("kgeomapRemoveSelectionRectangle();"));
0474 }
0475 
0476 void HTMLWidget::mouseModeChanged(const GeoMouseModes mouseMode)
0477 {
0478     const bool inSelectionMode = (mouseMode == MouseModeRegionSelection);
0479 
0480     if (inSelectionMode)
0481     {
0482         d->firstSelectionPoint.clear();
0483         d->intermediateSelectionPoint.clear();
0484         runScript(QString::fromLatin1("kgeomapSelectionModeStatus(%1);").arg(inSelectionMode));
0485     }
0486     else
0487     {
0488         runScript(QString::fromLatin1("kgeomapSelectionModeStatus(%1);").arg(inSelectionMode));
0489     }
0490 }
0491 
0492 void HTMLWidget::centerOn(const qreal west, const qreal north,
0493                           const qreal east, const qreal south,
0494                           const bool useSaneZoomLevel)
0495 {
0496 /*
0497     qCDebug(DIGIKAM_GEOIFACE_LOG) << "West:" << west
0498                                   << " North:" << north
0499                                   << " East:" << east
0500                                   << " South:" << south;
0501 */
0502     runScript(QString::fromLatin1("kgeomapSetMapBoundaries(%1, %2, %3, %4, %5);")
0503               .arg(west)
0504               .arg(north)
0505               .arg(east)
0506               .arg(south)
0507               .arg(useSaneZoomLevel ? 1 : 0));
0508 }
0509 
0510 void HTMLWidget::setSharedGeoIfaceObject(GeoIfaceSharedData* const sharedData)
0511 {
0512     s = sharedData;
0513 }
0514 
0515 } // namespace Digikam
0516 
0517 #include "moc_htmlwidget_qwebengine.cpp"