/* 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 */ #ifdef _DEBUG #include "test/test_widgets.h" #include "base/flat_map.h" #include "base/weak_qptr.h" #include "core/sandbox.h" #include "test/test_agent.h" #include #include #include #include #include #include #include namespace Test { namespace { struct LiveWidgetEntry { QPointer widget; int generation = 0; }; struct LiveActionEntry { QPointer context; std::shared_ptr> action; int generation = 0; int invocationCount = 0; bool repeatable = false; }; [[nodiscard]] base::flat_map &LiveWidgets() { static auto result = base::flat_map(); return result; } [[nodiscard]] base::flat_map &LiveActions() { static auto result = base::flat_map(); return result; } bool DeliverAndSettle( const base::weak_qptr &widget, QEvent &event) { const auto strong = widget.get(); if (!strong) { return false; } Settle([&] { QApplication::sendEvent(strong, &event); }); return (widget.get() != nullptr); } } // namespace QWidget *FindByObjectName( not_null root, const QString &name) { return root->findChild(name); } void PublishLiveWidget( const QString &key, not_null widget) { if (!Active()) { return; } auto &entry = LiveWidgets()[key]; entry = { .widget = widget.get(), .generation = entry.generation + 1, }; } LiveWidgetSnapshot ReadLiveWidget(const QString &key) { const auto i = LiveWidgets().find(key); if (i == end(LiveWidgets())) { return {}; } return { .widget = i->second.widget.data(), .generation = i->second.generation, }; } void PublishLiveAction( const QString &key, not_null context, Fn action, bool repeatable) { if (!Active()) { return; } const auto callback = action ? std::make_shared>(std::move(action)) : std::shared_ptr>(); auto &entry = LiveActions()[key]; entry = { .context = context.get(), .action = callback, .generation = entry.generation + 1, .invocationCount = 0, .repeatable = repeatable, }; } LiveActionSnapshot ReadLiveAction(const QString &key) { const auto i = LiveActions().find(key); if (i == end(LiveActions())) { return {}; } const auto &entry = i->second; return { .available = entry.context && entry.action && (entry.repeatable || !entry.invocationCount), .generation = entry.generation, .invocationCount = entry.invocationCount, .repeatable = entry.repeatable, }; } bool InvokeLiveAction(const QString &key) { auto i = LiveActions().find(key); if (i == end(LiveActions())) { return false; } auto &entry = i->second; if (!entry.context || !entry.action || (!entry.repeatable && entry.invocationCount)) { return false; } ++entry.invocationCount; const auto action = entry.action; (*action)(); return true; } void Click(not_null widget, std::optional point) { const auto alive = base::make_weak(widget); const auto local = QPointF(point.value_or(widget->rect().center())); const auto global = QPointF(widget->mapToGlobal(local.toPoint())); auto press = QMouseEvent( QEvent::MouseButtonPress, local, global, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); if (!DeliverAndSettle(alive, press)) { return; } auto release = QMouseEvent( QEvent::MouseButtonRelease, local, global, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); DeliverAndSettle(alive, release); } void TypeText(not_null widget, const QString &text) { const auto alive = base::make_weak(widget); auto finder = QTextBoundaryFinder(QTextBoundaryFinder::Grapheme, text); finder.toStart(); auto from = 0; while (from < text.size()) { const auto till = finder.toNextBoundary(); if (till < 0) { break; } const auto grapheme = text.mid(from, till - from); auto press = QKeyEvent( QEvent::KeyPress, 0, Qt::NoModifier, grapheme); if (!DeliverAndSettle(alive, press)) { return; } auto release = QKeyEvent( QEvent::KeyRelease, 0, Qt::NoModifier, grapheme); if (!DeliverAndSettle(alive, release)) { return; } from = till; } } void CommitText(not_null widget, const QString &text) { const auto alive = base::make_weak(widget); auto event = QInputMethodEvent(); event.setCommitString(text); DeliverAndSettle(alive, event); } void Drag( not_null widget, QPoint from, QPoint to, int steps) { const auto alive = base::make_weak(widget); const auto makeEvent = [&](QEvent::Type type, QPoint local, auto button) { return QMouseEvent( type, QPointF(local), QPointF(widget->mapToGlobal(local)), button, (type == QEvent::MouseButtonRelease) ? Qt::NoButton : Qt::LeftButton, Qt::NoModifier); }; auto press = makeEvent(QEvent::MouseButtonPress, from, Qt::LeftButton); if (!DeliverAndSettle(alive, press)) { return; } steps = std::max(steps, 1); for (auto step = 1; step <= steps; ++step) { const auto local = from + ((to - from) * step) / steps; auto move = makeEvent(QEvent::MouseMove, local, Qt::NoButton); if (!DeliverAndSettle(alive, move)) { return; } } auto release = makeEvent(QEvent::MouseButtonRelease, to, Qt::LeftButton); DeliverAndSettle(alive, release); } void Wheel( not_null widget, QPoint angleDelta, std::optional point) { const auto alive = base::make_weak(widget); const auto local = QPointF(point.value_or(widget->rect().center())); const auto global = QPointF(widget->mapToGlobal(local.toPoint())); auto event = QWheelEvent( local, global, QPoint(), angleDelta, Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication); DeliverAndSettle(alive, event); } void PressKey( not_null widget, int key, Qt::KeyboardModifiers modifiers) { const auto alive = base::make_weak(widget); auto press = QKeyEvent(QEvent::KeyPress, key, modifiers); if (!DeliverAndSettle(alive, press)) { return; } auto release = QKeyEvent(QEvent::KeyRelease, key, modifiers); DeliverAndSettle(alive, release); } void Settle(Fn action) { auto &sandbox = Core::Sandbox::Instance(); sandbox.setPostponedCallsDeferred(true); { const auto guard = gsl::finally([&] { sandbox.setPostponedCallsDeferred(false); }); action(); } SettlePostponedCalls(); } void SettlePostponedCalls() { Core::Sandbox::Instance().drainPostponedCalls(); } } // namespace Test #endif // _DEBUG