diff --git a/CMakeLists.txt b/CMakeLists.txt index 03a9e26807..27b5f3e54b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ if (NOT PLATFORM_IPHONE AND NOT PLATFORM_ANDROID) find_package(Qt5Widgets REQUIRED) find_package(Qt5Xml REQUIRED) find_package(Qt5Svg REQUIRED) + find_package(Qt5WebEngineWidgets REQUIRED) endif() endif() @@ -273,6 +274,13 @@ function(link_qt5_network target) ) endfunction() +function(link_qt5_webengine target) + omim_link_libraries( + ${target} + ${Qt5WebEngineWidgets_LIBRARIES} + ) +endfunction() + function(add_clang_compile_options) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(${ARGV}) diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt b/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt index 3aeb1ec6b6..e4693a9bc9 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt +++ b/openlr/openlr_match_quality/openlr_assessment_tool/CMakeLists.txt @@ -26,6 +26,8 @@ set( traffic_panel.hpp trafficmodeinitdlg.cpp trafficmodeinitdlg.h + web_view.cpp + web_view.hpp ) omim_add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${SRC}) @@ -78,6 +80,7 @@ omim_link_libraries( link_opengl(${PROJECT_NAME}) link_qt5_core(${PROJECT_NAME}) link_qt5_network(${PROJECT_NAME}) +link_qt5_webengine(${PROJECT_NAME}) if (PLATFORM_MAC) set_target_properties( diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/main.cpp b/openlr/openlr_match_quality/openlr_assessment_tool/main.cpp index 3d24cf1fa9..fff3675f61 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/main.cpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/main.cpp @@ -6,11 +6,30 @@ #include +namespace +{ DEFINE_string(resources_path, "", "Path to resources directory"); DEFINE_string(data_path, "", "Path to data directory"); +DEFINE_string(login, "", "Login string"); +DEFINE_string(paswd, "", "Password string"); +DEFINE_string(url, "", "Url to a partner map"); + +bool ValidateStringFlag(char const * flagName, std::string const & val) +{ + if (!val.empty()) + return true; + + LOG(LERROR, (flagName, "cannot be empty. Please specify a proper", flagName)); + return false; +} +} // namespace int main(int argc, char * argv[]) { + ::google::RegisterFlagValidator(&FLAGS_login, &ValidateStringFlag); + ::google::RegisterFlagValidator(&FLAGS_paswd, &ValidateStringFlag); + ::google::RegisterFlagValidator(&FLAGS_url, &ValidateStringFlag); + google::SetUsageMessage("Visualize and check matched routes."); google::ParseCommandLineFlags(&argc, &argv, true); @@ -27,7 +46,7 @@ int main(int argc, char * argv[]) params.m_enableLocalAds = false; Framework framework(params); - MainWindow mainWindow(framework); + MainWindow mainWindow(framework, FLAGS_url, FLAGS_login, FLAGS_paswd); mainWindow.showMaximized(); diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.cpp b/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.cpp index 979d4053d3..212498380a 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.cpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.cpp @@ -5,6 +5,7 @@ #include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_drawer_delegate_base.hpp" #include "openlr/openlr_match_quality/openlr_assessment_tool/traffic_panel.hpp" #include "openlr/openlr_match_quality/openlr_assessment_tool/trafficmodeinitdlg.h" +#include "openlr/openlr_match_quality/openlr_assessment_tool/web_view.hpp" #include "map/framework.hpp" @@ -22,6 +23,7 @@ #include #include +#include #include #include #include @@ -231,14 +233,26 @@ private: }; } // namespace - -MainWindow::MainWindow(Framework & framework) +MainWindow::MainWindow(Framework & framework, std::string const & url, std::string const & login, + std::string const & paswd) : m_framework(framework) { + m_mapWidget = new MapWidget( m_framework, false /* apiOpenGLES3 */, this /* parent */ ); - setCentralWidget(m_mapWidget); + + m_webView = new WebView(url, login, paswd); + + m_layout = new QHBoxLayout(); + m_layout->addWidget(m_webView); + m_layout->addWidget(m_mapWidget); + + auto * window = new QWidget(); + window->setLayout(m_layout); + window->setGraphicsEffect(nullptr); + + setCentralWidget(window); // setWindowTitle(tr("MAPS.ME")); // setWindowIcon(QIcon(":/ui/logo.png")); @@ -302,6 +316,8 @@ void MainWindow::CreateTrafficPanel(string const & dataFilePath) m_trafficMode, &TrafficMode::OnClick); connect(m_trafficMode, &TrafficMode::EditingStopped, this, &MainWindow::OnPathEditingStop); + connect(m_trafficMode, &TrafficMode::SegmentSelected, + m_webView, &WebView::SetCurrentSegment); m_docWidget = new QDockWidget(tr("Routes"), this); addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_docWidget); diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp b/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp index efcf6f4c4d..226b166b7b 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/mainwindow.hpp @@ -10,7 +10,9 @@ class Framework; class MapWidget; +class QHBoxLayout; class TrafficMode; +class WebView; namespace df { @@ -24,7 +26,8 @@ class MainWindow : public QMainWindow Q_OBJECT public: - MainWindow(Framework & framework); + MainWindow(Framework & framework, std::string const & url, std::string const & login, + std::string const & paswd); private: void CreateTrafficPanel(std::string const & dataFilePath); @@ -48,4 +51,6 @@ private: QAction * m_ignorePathAction = nullptr; MapWidget * m_mapWidget = nullptr; + QHBoxLayout * m_layout = nullptr; + WebView * m_webView = nullptr; }; diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp b/openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp index e705463640..4bd70b94a9 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/map_widget.hpp @@ -27,6 +27,11 @@ public: void SetMode(Mode const mode) { m_mode = mode; } + QSize sizeHint() const override + { + return QSize(800, 600); + } + signals: void TrafficMarkupClick(m2::PointD const & p, Qt::MouseButton const b); diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.cpp b/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.cpp index f8d39fc7ce..76ae28cba1 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.cpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.cpp @@ -227,17 +227,20 @@ void TrafficMode::OnItemSelected(QItemSelection const & selected, QItemSelection CHECK_LESS(row, m_segments.size(), ()); m_currentSegment = &m_segments[row]; - auto const & partnerSegment = m_currentSegment->GetPartnerSegment().GetMercatorPoints(); - auto const & viewportCenter = partnerSegment.front(); + auto const & partnerSegment = m_currentSegment->GetPartnerSegment(); + auto const & partnerSegmentPoints = partnerSegment.GetMercatorPoints(); + auto const & viewportCenter = partnerSegmentPoints.front(); m_drawerDelegate->ClearAllPaths(); // TODO(mgsergio): Use a better way to set viewport and scale. m_drawerDelegate->SetViewportCenter(viewportCenter); - m_drawerDelegate->DrawEncodedSegment(partnerSegment); + m_drawerDelegate->DrawEncodedSegment(partnerSegmentPoints); if (!m_currentSegment->GetMatchedPath().empty()) m_drawerDelegate->DrawDecodedSegments(GetPoints(m_currentSegment->GetMatchedPath())); if (!m_currentSegment->GetGoldenPath().empty()) m_drawerDelegate->DrawGoldenPath(GetPoints(m_currentSegment->GetGoldenPath())); + + emit SegmentSelected(static_cast(partnerSegment.m_segmentId)); } Qt::ItemFlags TrafficMode::flags(QModelIndex const & index) const diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp b/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp index f077b4750e..4c55d0a709 100644 --- a/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp +++ b/openlr/openlr_match_quality/openlr_assessment_tool/traffic_mode.hpp @@ -104,6 +104,7 @@ public slots: signals: void EditingStopped(); + void SegmentSelected(int segmentId); private: void HandlePoint(m2::PointD clickPoint, Qt::MouseButton const button); diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/web_view.cpp b/openlr/openlr_match_quality/openlr_assessment_tool/web_view.cpp new file mode 100644 index 0000000000..53338182d9 --- /dev/null +++ b/openlr/openlr_match_quality/openlr_assessment_tool/web_view.cpp @@ -0,0 +1,138 @@ +#include "openlr/openlr_match_quality/openlr_assessment_tool/web_view.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include +#include +#include +#include +#include + +namespace +{ +bool IsLoginUrl(QString const & url) +{ + return url.contains("Login"); +} +} // namespace + +WebView::WebView(std::string const & url, std::string const & login, std::string const & paswd) + : QWebEngineView(nullptr), m_loadProgress(0), m_url(url), m_login(login), m_paswd(paswd) +{ + connect(this, &QWebEngineView::loadProgress, [this](int progress) { m_loadProgress = progress; }); + connect(this, &QWebEngineView::loadFinished, [this](bool success) { + if (!success) + { + m_loadProgress = 0; + } + }); + + connect(this, &QWebEngineView::renderProcessTerminated, + [this](QWebEnginePage::RenderProcessTerminationStatus termStatus, int statusCode) { + QString status; + switch (termStatus) + { + case QWebEnginePage::NormalTerminationStatus: + status = tr("Render process normal exit"); + break; + case QWebEnginePage::AbnormalTerminationStatus: + status = tr("Render process abnormal exit"); + break; + case QWebEnginePage::CrashedTerminationStatus: + status = tr("Render process crashed"); + break; + case QWebEnginePage::KilledTerminationStatus: + status = tr("Render process killed"); + break; + } + QMessageBox::StandardButton btn = + QMessageBox::question(window(), status, + tr("Render process exited with code: %1\n" + "Do you want to reload the page ?") + .arg(statusCode)); + if (btn == QMessageBox::Yes) + QTimer::singleShot(0, [this] { reload(); }); + }); + + connect(this, &QWebEngineView::loadFinished, this, &WebView::OnLoadFinished); + load(QString(m_url.data())); +} + +int WebView::loadProgress() const { return m_loadProgress; } + +void WebView::SetCurrentSegment(int segmentId) +{ + m_requestedSegmentId = segmentId; + LOG(LDEBUG, ("SegmentID seg to", segmentId)); + if (!IsLoginUrl(url().toString())) + GoToSegment(); +} + +bool WebView::isWebActionEnabled(QWebEnginePage::WebAction webAction) const +{ + return page()->action(webAction)->isEnabled(); +} + +QWebEngineView * WebView::createWindow(QWebEnginePage::WebWindowType type) +{ + CHECK(false, ("We are not going to use but one window.")); + return nullptr; +} + +void WebView::contextMenuEvent(QContextMenuEvent * event) +{ + CHECK(false, ("No context menu is necessary.")); +} + +void WebView::OnLoadFinished(bool ok) +{ + if (!ok) + { + LOG(LDEBUG, ("Page is not loaded properly, giving up")); + return; + } + + if (IsLoginUrl(url().toString())) + Login(); + else + GoToSegment(); +} + +void WebView::GoToSegment() +{ + CHECK(!IsLoginUrl(url().toString()), ("Can't go to a segment on the login page.")); + if (m_requestedSegmentId == kInvalidId) + return; + + auto const script = QString(R"EOT( + function s(ctx, arg) { return ctx.querySelector(arg); } + function turnOff(cb) { if (cb.checked) cb.click(); } + turnOff(s(document, "#inrix\\:filters\\:traffic\\:trafficflowvisible")); + turnOff(s(document, "#inrix\\:filters\\:traffic\\:incidentsvisible")); + var navSpan = s(document, "#inrix\\:navigation\\:contextual"); + var input = s(navSpan, "input.FreeFormInput"); + input.value = %1; + input.classList.remove("WaterMark"); + s(navSpan, "button.LocationSearchButton").click(); + )EOT").arg(m_requestedSegmentId); + + page()->runJavaScript(script, [](QVariant const & v) { + LOG(LDEBUG, ("DEMO JS is done:", v.toString().toStdString())); + }); + m_requestedSegmentId = kInvalidId; +} + +void WebView::Login() +{ + auto const script = QString(R"EOT( + function s(arg) { return document.querySelector(arg); } + s("#ctl00_BodyPlaceHolder_LoginControl_UserName").value = "%1" + s("#ctl00_BodyPlaceHolder_LoginControl_Password").value = "%2" + s("#ctl00_BodyPlaceHolder_LoginControl_LoginButton").click(); + )EOT").arg(m_login.data()).arg(m_paswd.data()); + + page()->runJavaScript(script, [](QVariant const & v) { + LOG(LDEBUG, ("Login JS is done:", v.toString().toStdString())); + }); +} diff --git a/openlr/openlr_match_quality/openlr_assessment_tool/web_view.hpp b/openlr/openlr_match_quality/openlr_assessment_tool/web_view.hpp new file mode 100644 index 0000000000..3218986c19 --- /dev/null +++ b/openlr/openlr_match_quality/openlr_assessment_tool/web_view.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +class WebView : public QWebEngineView +{ + Q_OBJECT + + int static constexpr kInvalidId = -1; + +public: + WebView(std::string const & url, std::string const & login, std::string const & paswd); + + int loadProgress() const; + bool isWebActionEnabled(QWebEnginePage::WebAction webAction) const; + +public slots: + void SetCurrentSegment(int segmentId); + +signals: + void webActionEnabledChanged(QWebEnginePage::WebAction webAction, bool enabled); + +protected: + void contextMenuEvent(QContextMenuEvent * event) override; + QWebEngineView * createWindow(QWebEnginePage::WebWindowType type) override; + +private slots: + void OnLoadFinished(bool ok); + +private: + void GoToSegment(); + void Login(); + + int m_loadProgress; + int m_requestedSegmentId = kInvalidId; + + std::string m_url; + std::string m_login; + std::string m_paswd; +};