mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-29 12:35:13 +02:00
368 lines
10 KiB
C++
368 lines
10 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "ui/controls/location_picker.h"
|
|
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "core/current_geo_location.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "ui/widgets/rp_window.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "webview/webview_data_stream_memory.h"
|
|
#include "webview/webview_embed.h"
|
|
#include "webview/webview_interface.h"
|
|
#include "styles/style_dialogs.h"
|
|
#include "styles/style_window.h"
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtCore/QJsonDocument>
|
|
#include <QtCore/QJsonObject>
|
|
#include <QtCore/QJsonValue>
|
|
#include <QtGui/QGuiApplication>
|
|
#include <QtGui/QScreen>
|
|
|
|
namespace Ui {
|
|
namespace {
|
|
|
|
#ifdef Q_OS_MAC
|
|
const auto kProtocolOverride = "mapboxapihelper";
|
|
#else // Q_OS_MAC
|
|
const auto kProtocolOverride = "";
|
|
#endif // Q_OS_MAC
|
|
|
|
Core::GeoLocation LastExactLocation;
|
|
QString MapsProviderToken;
|
|
|
|
[[nodiscard]] QByteArray DefaultCenter() {
|
|
if (!LastExactLocation) {
|
|
return "null";
|
|
}
|
|
return "["_q
|
|
+ QByteArray::number(LastExactLocation.point.x())
|
|
+ ","_q
|
|
+ QByteArray::number(LastExactLocation.point.y())
|
|
+ "]"_q;
|
|
}
|
|
|
|
[[nodiscard]] QByteArray DefaultBounds() {
|
|
const auto country = Core::ResolveCurrentCountryLocation();
|
|
if (!country) {
|
|
return "null";
|
|
}
|
|
return "[["_q
|
|
+ QByteArray::number(country.bounds.x())
|
|
+ ","_q
|
|
+ QByteArray::number(country.bounds.y())
|
|
+ "],["_q
|
|
+ QByteArray::number(country.bounds.x() + country.bounds.width())
|
|
+ ","_q
|
|
+ QByteArray::number(country.bounds.y() + country.bounds.height())
|
|
+ "]]"_q;
|
|
}
|
|
|
|
[[nodiscard]] QByteArray ComputeStyles() {
|
|
return "";
|
|
}
|
|
|
|
[[nodiscard]] QByteArray EscapeForAttribute(QByteArray value) {
|
|
return value
|
|
.replace('&', "&")
|
|
.replace('"', """)
|
|
.replace('\'', "'")
|
|
.replace('<', "<")
|
|
.replace('>', ">");
|
|
}
|
|
|
|
[[nodiscard]] QByteArray EscapeForScriptString(QByteArray value) {
|
|
return value
|
|
.replace('\\', "\\\\")
|
|
.replace('"', "\\\"")
|
|
.replace('\'', "\\\'");
|
|
}
|
|
|
|
[[nodiscard]] QByteArray ReadResource(const QString &name) {
|
|
auto file = QFile(u":/picker/"_q + name);
|
|
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
|
}
|
|
|
|
[[nodiscard]] QByteArray PickerContent() {
|
|
return R"(<!DOCTYPE html>
|
|
<html style=")"
|
|
+ EscapeForAttribute(ComputeStyles())
|
|
+ R"(">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="robots" content="noindex, nofollow">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<script src="/location/picker.js"></script>
|
|
<link rel="stylesheet" href="/location/picker.css" />
|
|
<script src='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.js'></script>
|
|
<link href='https://api.mapbox.com/mapbox-gl-js/v3.4.0/mapbox-gl.css' rel='stylesheet' />
|
|
</head>
|
|
<body>
|
|
<div id="marker"><div id="marker_drop"></div></div>
|
|
<div id="map"></div>
|
|
<script>LocationPicker.notify({ event: 'ready' });</script>
|
|
</body>
|
|
</html>
|
|
)"_q;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
LocationPicker::LocationPicker(Descriptor &&descriptor)
|
|
: _callback(std::move(descriptor.callback))
|
|
, _quit(std::move(descriptor.quit))
|
|
, _window(std::make_unique<RpWindow>())
|
|
, _updateStyles([=] {
|
|
const auto str = EscapeForScriptString(ComputeStyles());
|
|
if (_webview) {
|
|
_webview->eval("IV.updateStyles('" + str + "');");
|
|
}
|
|
}) {
|
|
std::move(
|
|
descriptor.closeRequests
|
|
) | rpl::start_with_next([=] {
|
|
_window = nullptr;
|
|
delete this;
|
|
}, _lifetime);
|
|
|
|
setup(descriptor);
|
|
}
|
|
|
|
bool LocationPicker::Available(const QString &token) {
|
|
static const auto Supported = Webview::NavigateToDataSupported();
|
|
MapsProviderToken = token;
|
|
return Supported && !MapsProviderToken.isEmpty();
|
|
}
|
|
|
|
void LocationPicker::setup(const Descriptor &descriptor) {
|
|
setupWindow(descriptor);
|
|
setupWebview(descriptor);
|
|
}
|
|
|
|
void LocationPicker::setupWindow(const Descriptor &descriptor) {
|
|
const auto window = _window.get();
|
|
|
|
const auto parent = descriptor.parent
|
|
? descriptor.parent->window()->geometry()
|
|
: QGuiApplication::primaryScreen()->availableGeometry();
|
|
window->setGeometry(QRect(
|
|
parent.x() + (parent.width() - st::windowMinHeight) / 2,
|
|
parent.y() + (parent.height() - st::windowMinWidth) / 2,
|
|
st::windowMinHeight,
|
|
st::windowMinWidth));
|
|
window->setMinimumSize({ st::windowMinHeight, st::windowMinWidth });
|
|
|
|
_container = Ui::CreateChild<Ui::RpWidget>(window->body().get());
|
|
const auto button = Ui::CreateChild<FlatButton>(
|
|
window->body(),
|
|
tr::lng_maps_point_send(tr::now),
|
|
st::dialogsUpdateButton);
|
|
button->show();
|
|
button->setClickedCallback([=] {
|
|
_webview->eval("LocationPicker.send();");
|
|
});
|
|
window->body()->sizeValue(
|
|
) | rpl::start_with_next([=](QSize size) {
|
|
_container->setGeometry(QRect(QPoint(), size).marginsRemoved(
|
|
{ 0, 0, 0, button->height() }));
|
|
button->resizeToWidth(size.width());
|
|
button->setGeometry(
|
|
0,
|
|
size.height() - button->height(),
|
|
button->width(),
|
|
button->height());
|
|
}, _container->lifetime());
|
|
|
|
_container->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
|
QPainter(_container).fillRect(clip, st::windowBg);
|
|
}, _container->lifetime());
|
|
|
|
_container->show();
|
|
window->show();
|
|
}
|
|
|
|
void LocationPicker::setupWebview(const Descriptor &descriptor) {
|
|
Expects(!_webview);
|
|
|
|
const auto window = _window.get();
|
|
_webview = std::make_unique<Webview::Window>(
|
|
_container,
|
|
Webview::WindowConfig{
|
|
.opaqueBg = st::windowBg->c,
|
|
.storageId = descriptor.storageId,
|
|
.dataProtocolOverride = kProtocolOverride,
|
|
});
|
|
const auto raw = _webview.get();
|
|
|
|
window->lifetime().add([=] {
|
|
_webview = nullptr;
|
|
});
|
|
|
|
window->events(
|
|
) | rpl::start_with_next([=](not_null<QEvent*> e) {
|
|
if (e->type() == QEvent::Close) {
|
|
close();
|
|
} else if (e->type() == QEvent::KeyPress) {
|
|
const auto event = static_cast<QKeyEvent*>(e.get());
|
|
if (event->key() == Qt::Key_Escape) {
|
|
close();
|
|
}
|
|
}
|
|
}, window->lifetime());
|
|
raw->widget()->show();
|
|
|
|
_container->sizeValue(
|
|
) | rpl::start_with_next([=](QSize size) {
|
|
raw->widget()->setGeometry(QRect(QPoint(), size));
|
|
}, _container->lifetime());
|
|
|
|
raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) {
|
|
return true;
|
|
});
|
|
raw->setNavigationDoneHandler([=](bool success) {
|
|
});
|
|
raw->setMessageHandler([=](const QJsonDocument &message) {
|
|
crl::on_main(_window.get(), [=] {
|
|
const auto object = message.object();
|
|
const auto event = object.value("event").toString();
|
|
if (event == u"ready"_q) {
|
|
initMap();
|
|
resolveCurrentLocation();
|
|
} else if (event == u"keydown"_q) {
|
|
const auto key = object.value("key").toString();
|
|
const auto modifier = object.value("modifier").toString();
|
|
processKey(key, modifier);
|
|
} else if (event == u"send"_q) {
|
|
const auto lat = object.value("latitude").toDouble();
|
|
const auto lon = object.value("longitude").toDouble();
|
|
_callback({ lat, lon });
|
|
close();
|
|
}
|
|
});
|
|
});
|
|
raw->setDataRequestHandler([=](Webview::DataRequest request) {
|
|
const auto pos = request.id.find('#');
|
|
if (pos != request.id.npos) {
|
|
request.id = request.id.substr(0, pos);
|
|
}
|
|
if (!request.id.starts_with("location/")) {
|
|
return Webview::DataResult::Failed;
|
|
}
|
|
const auto finishWith = [&](QByteArray data, std::string mime) {
|
|
request.done({
|
|
.stream = std::make_unique<Webview::DataStreamFromMemory>(
|
|
std::move(data),
|
|
std::move(mime)),
|
|
});
|
|
return Webview::DataResult::Done;
|
|
};
|
|
if (!_subscribedToColors) {
|
|
_subscribedToColors = true;
|
|
|
|
rpl::merge(
|
|
Lang::Updated(),
|
|
style::PaletteChanged()
|
|
) | rpl::start_with_next([=] {
|
|
_updateStyles.call();
|
|
}, _webview->lifetime());
|
|
}
|
|
const auto id = std::string_view(request.id).substr(9);
|
|
if (id == "picker.html") {
|
|
return finishWith(PickerContent(), "text/html; charset=utf-8");
|
|
}
|
|
const auto css = id.ends_with(".css");
|
|
const auto js = !css && id.ends_with(".js");
|
|
if (!css && !js) {
|
|
return Webview::DataResult::Failed;
|
|
}
|
|
const auto qstring = QString::fromUtf8(id.data(), id.size());
|
|
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
|
|
if (QRegularExpression(pattern).match(qstring).hasMatch()) {
|
|
const auto bytes = ReadResource(qstring);
|
|
if (!bytes.isEmpty()) {
|
|
const auto mime = css ? "text/css" : "text/javascript";
|
|
return finishWith(bytes, mime);
|
|
}
|
|
}
|
|
return Webview::DataResult::Failed;
|
|
});
|
|
|
|
raw->init(R"()");
|
|
raw->navigateToData("location/picker.html");
|
|
}
|
|
|
|
void LocationPicker::initMap() {
|
|
const auto token = MapsProviderToken.toUtf8();
|
|
const auto center = DefaultCenter();
|
|
const auto bounds = DefaultBounds();
|
|
const auto protocol = *kProtocolOverride
|
|
? "'"_q + kProtocolOverride + "'"
|
|
: "null";
|
|
const auto params = "token: '" + token + "'"
|
|
+ ", center: " + center
|
|
+ ", bounds: " + bounds
|
|
+ ", protocol: " + protocol;
|
|
_webview->eval("LocationPicker.init({ " + params + " });");
|
|
}
|
|
|
|
void LocationPicker::resolveCurrentLocation() {
|
|
using namespace Core;
|
|
const auto window = _window.get();
|
|
ResolveCurrentGeoLocation(crl::guard(window, [=](GeoLocation location) {
|
|
if (location.accuracy != GeoLocationAccuracy::Exact) {
|
|
return;
|
|
}
|
|
LastExactLocation = location;
|
|
if (_webview) {
|
|
const auto point = QByteArray::number(location.point.x())
|
|
+ ","_q
|
|
+ QByteArray::number(location.point.y());
|
|
_webview->eval("LocationPicker.narrowTo([" + point + "]);");
|
|
}
|
|
}));
|
|
}
|
|
|
|
void LocationPicker::processKey(
|
|
const QString &key,
|
|
const QString &modifier) {
|
|
const auto ctrl = ::Platform::IsMac() ? u"cmd"_q : u"ctrl"_q;
|
|
if (key == u"escape"_q || (key == u"w"_q && modifier == ctrl)) {
|
|
close();
|
|
} else if (key == u"m"_q && modifier == ctrl) {
|
|
minimize();
|
|
} else if (key == u"q"_q && modifier == ctrl) {
|
|
quit();
|
|
}
|
|
}
|
|
|
|
void LocationPicker::close() {
|
|
crl::on_main(this, [=] {
|
|
_window = nullptr;
|
|
delete this;
|
|
});
|
|
}
|
|
|
|
void LocationPicker::minimize() {
|
|
if (_window) {
|
|
_window->setWindowState(_window->windowState()
|
|
| Qt::WindowMinimized);
|
|
}
|
|
}
|
|
|
|
void LocationPicker::quit() {
|
|
if (const auto onstack = _quit) {
|
|
onstack();
|
|
}
|
|
}
|
|
|
|
not_null<LocationPicker*> LocationPicker::Show(Descriptor &&descriptor) {
|
|
return new LocationPicker(std::move(descriptor));
|
|
}
|
|
|
|
} // namespace Ui
|