Fix window activations handling without event loop nesting.

This was causing an assertion violation in Ui::PostponeCall.

- Add a generic Core::QtSignalProducer to convert Qt signals to rpl::producer.
- Track event loop nesting inside QtSignalProducer.
- Use QtSignalProducer for QWindow::activeChanged tracking.
This commit is contained in:
John Preston 2019-04-05 14:13:54 +04:00
parent 41b2e7c9c7
commit 2bdce7dce6
20 changed files with 136 additions and 140 deletions

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_controller.h"
#include "mainwindow.h"
#include "core/application.h"
#include "core/qt_signal_producer.h"
#include "styles/style_chat_helpers.h"
namespace ChatHelpers {
@ -90,11 +91,13 @@ TabbedPanel::TabbedPanel(
});
}, lifetime());
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
connect(App::wnd()->windowHandle(), &QWindow::activeChanged, this, [=] {
windowActiveChanged();
});
}
macWindowDeactivateEvents(
) | rpl::filter([=] {
return !isHidden() && !preventAutoHide();
}) | rpl::start_with_next([=] {
hideAnimated();
}, lifetime());
setAttribute(Qt::WA_OpaquePaintEvent, false);
hideChildren();
@ -146,12 +149,6 @@ void TabbedPanel::updateContentHeight() {
update();
}
void TabbedPanel::windowActiveChanged() {
if (!App::wnd()->windowHandle()->isActive() && !isHidden() && !preventAutoHide()) {
hideAnimated();
}
}
void TabbedPanel::paintEvent(QPaintEvent *e) {
Painter p(this);

View File

@ -68,7 +68,6 @@ private:
return !_selector;
}
void showFromSelector();
void windowActiveChanged();
style::margins innerPadding() const;

View File

@ -58,9 +58,9 @@ MainQueueProcessor::MainQueueProcessor() {
}
});
crl::wrap_main_queue([](void (*callable)(void*), void *argument) {
Sandbox::Instance().registerEnterFromEventLoop();
const auto wrap = Sandbox::Instance().createEventNestingLevel();
callable(argument);
Sandbox::Instance().customEnterFromEventLoop([&] {
callable(argument);
});
});
base::InitObservables(ProcessObservables);

View File

@ -80,4 +80,4 @@ void MediaActiveCache<Type>::check(Unload &&unload) {
}
}
} // namespace Images
} // namespace Core

View File

@ -16,6 +16,12 @@ class Application;
class Sandbox final
: public QApplication
, private QAbstractNativeEventFilter {
private:
auto createEventNestingLevel() {
incrementEventNestingLevel();
return gsl::finally([=] { decrementEventNestingLevel(); });
}
public:
Sandbox(not_null<Launcher*> launcher, int &argc, char **argv);
@ -29,10 +35,12 @@ public:
void postponeCall(FnMut<void()> &&callable);
bool notify(QObject *receiver, QEvent *e) override;
void registerEnterFromEventLoop();
auto createEventNestingLevel() {
incrementEventNestingLevel();
return gsl::finally([=] { decrementEventNestingLevel(); });
template <typename Callable>
auto customEnterFromEventLoop(Callable &&callable) {
registerEnterFromEventLoop();
const auto wrap = createEventNestingLevel();
return callable();
}
void activateWindowDelayed(not_null<QWidget*> widget);
@ -65,6 +73,7 @@ private:
void closeApplication(); // will be done in aboutToQuit()
void checkForQuit(); // will be done in exec()
void registerEnterFromEventLoop();
void incrementEventNestingLevel();
void decrementEventNestingLevel();
bool nativeEventFilter(

View File

@ -718,7 +718,10 @@ void Inner::onSwitchPm() {
} // namespace internal
Widget::Widget(QWidget *parent, not_null<Window::Controller*> controller) : TWidget(parent)
Widget::Widget(
QWidget *parent,
not_null<Window::Controller*> controller)
: RpWidget(parent)
, _controller(controller)
, _contentMaxHeight(st::emojiPanMaxHeight)
, _contentHeight(_contentMaxHeight)
@ -742,9 +745,12 @@ Widget::Widget(QWidget *parent, not_null<Window::Controller*> controller) : TWid
_inlineRequestTimer.setSingleShot(true);
connect(&_inlineRequestTimer, SIGNAL(timeout()), this, SLOT(onInlineRequest()));
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
}
macWindowDeactivateEvents(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=] {
leaveEvent(nullptr);
}, lifetime());
// Inner widget has OpaquePaintEvent attribute so it doesn't repaint on scroll.
// But we should force it to repaint so that GIFs will continue to animate without update() calls.
@ -795,12 +801,6 @@ void Widget::updateContentHeight() {
update();
}
void Widget::onWndActiveChanged() {
if (!App::wnd()->windowHandle()->isActive() && !isHidden()) {
leaveEvent(0);
}
}
void Widget::paintEvent(QPaintEvent *e) {
Painter p(this);

View File

@ -7,7 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/twidget.h"
#include "ui/rp_widget.h"
#include "ui/abstract_button.h"
#include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
@ -161,7 +161,7 @@ private:
} // namespace internal
class Widget : public TWidget, private MTP::Sender {
class Widget : public Ui::RpWidget, private MTP::Sender {
Q_OBJECT
public:
@ -192,8 +192,6 @@ protected:
void paintEvent(QPaintEvent *e) override;
private slots:
void onWndActiveChanged();
void onScroll();
void onInlineRequest();

View File

@ -106,7 +106,14 @@ void MainWindow::initHook() {
Platform::MainWindow::initHook();
QCoreApplication::instance()->installEventFilter(this);
connect(windowHandle(), &QWindow::activeChanged, this, [this] { checkHistoryActivation(); }, Qt::QueuedConnection);
// Non-queued activeChanged handlers must use QtSignalProducer.
connect(
windowHandle(),
&QWindow::activeChanged,
this,
[=] { checkHistoryActivation(); },
Qt::QueuedConnection);
}
void MainWindow::firstShow() {

View File

@ -54,12 +54,6 @@ bool Panel::overlaps(const QRect &globalRect) {
return rect().marginsRemoved(QMargins(marginLeft, contentTop(), marginRight, contentBottom())).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
}
void Panel::windowActiveChanged() {
if (!App::wnd()->windowHandle()->isActive() && !isHidden()) {
leaveEvent(nullptr);
}
}
void Panel::resizeEvent(QResizeEvent *e) {
updateControlsGeometry();
}
@ -173,7 +167,7 @@ void Panel::enterEventHook(QEvent *e) {
} else {
_showTimer.callOnce(0);
}
return TWidget::enterEventHook(e);
return RpWidget::enterEventHook(e);
}
void Panel::leaveEventHook(QEvent *e) {
@ -186,7 +180,7 @@ void Panel::leaveEventHook(QEvent *e) {
} else {
_hideTimer.callOnce(300);
}
return TWidget::leaveEventHook(e);
return RpWidget::leaveEventHook(e);
}
void Panel::showFromOther() {
@ -217,15 +211,12 @@ void Panel::ensureCreated() {
});
refreshList();
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
if (const auto window = App::wnd()) {
connect(
window->windowHandle(),
&QWindow::activeChanged,
this,
&Panel::windowActiveChanged);
}
}
macWindowDeactivateEvents(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=] {
leaveEvent(nullptr);
}, _refreshListLifetime);
_ignoringEnterEvents = false;
}
@ -306,16 +297,6 @@ void Panel::performDestroy() {
_scroll->takeWidget<QWidget>().destroy();
_listPeer = _listMigratedPeer = nullptr;
_refreshListLifetime.destroy();
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
if (const auto window = App::wnd()) {
disconnect(
window->windowHandle(),
&QWindow::activeChanged,
this,
&Panel::windowActiveChanged);
}
}
}
Info::Key Panel::key() const {

View File

@ -60,7 +60,6 @@ private:
void listHeightUpdated(int newHeight);
int emptyInnerHeight() const;
bool contentTooSmall() const;
void windowActiveChanged();
void ensureCreated();
void performDestroy();

View File

@ -19,7 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Media {
namespace Player {
VolumeController::VolumeController(QWidget *parent) : TWidget(parent)
VolumeController::VolumeController(QWidget *parent)
: TWidget(parent)
, _slider(this, st::mediaPlayerPanelPlayback) {
_slider->setMoveByWheel(true);
_slider->setChangeProgressCallback([this](float64 volume) {
@ -68,7 +69,8 @@ void VolumeController::applyVolumeChange(float64 volume) {
}
}
VolumeWidget::VolumeWidget(QWidget *parent) : TWidget(parent)
VolumeWidget::VolumeWidget(QWidget *parent)
: RpWidget(parent)
, _controller(this) {
hide();
_controller->setIsVertical(true);
@ -79,9 +81,12 @@ VolumeWidget::VolumeWidget(QWidget *parent) : TWidget(parent)
_showTimer.setSingleShot(true);
connect(&_showTimer, SIGNAL(timeout()), this, SLOT(onShowStart()));
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged()));
}
macWindowDeactivateEvents(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=] {
leaveEvent(nullptr);
}, lifetime());
hide();
auto margin = getMargin();
@ -102,12 +107,6 @@ bool VolumeWidget::overlaps(const QRect &globalRect) {
return rect().marginsRemoved(getMargin()).contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
}
void VolumeWidget::onWindowActiveChanged() {
if (!App::wnd()->windowHandle()->isActive() && !isHidden()) {
leaveEvent(nullptr);
}
}
void VolumeWidget::resizeEvent(QResizeEvent *e) {
auto inner = rect().marginsRemoved(getMargin());
_controller->setGeometry(inner.x(), inner.y() - st::lineWidth, inner.width(), inner.height() + st::lineWidth - ((st::mediaPlayerVolumeSize.width() - st::mediaPlayerPanelPlayback.width) / 2));
@ -147,7 +146,7 @@ void VolumeWidget::enterEventHook(QEvent *e) {
} else {
_showTimer.start(0);
}
return TWidget::enterEventHook(e);
return RpWidget::enterEventHook(e);
}
void VolumeWidget::leaveEventHook(QEvent *e) {
@ -157,7 +156,7 @@ void VolumeWidget::leaveEventHook(QEvent *e) {
} else {
_hideTimer.start(300);
}
return TWidget::leaveEventHook(e);
return RpWidget::leaveEventHook(e);
}
void VolumeWidget::otherEnter() {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace Ui {
class IconButton;
@ -34,7 +35,7 @@ private:
};
class VolumeWidget : public TWidget {
class VolumeWidget : public Ui::RpWidget {
Q_OBJECT
public:
@ -55,7 +56,6 @@ protected:
private slots:
void onShowStart();
void onHideStart();
void onWindowActiveChanged();
private:
void otherEnter();

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <rpl/map.h>
#include <rpl/distinct_until_changed.h>
#include "base/unique_qptr.h"
#include "core/qt_signal_producer.h"
namespace Ui {
namespace details {
@ -173,6 +174,25 @@ public:
visibilityChangedHook(wasVisible, !this->isHidden());
}
auto windowDeactivateEvents() const {
Expects(Widget::window()->windowHandle() != nullptr);
const auto window = Widget::window()->windowHandle();
return Core::QtSignalProducer(
window,
&QWindow::activeChanged
) | rpl::filter([=] {
return !window->isActive();
});
}
auto macWindowDeactivateEvents() const {
#ifdef Q_OS_MAC
return windowDeactivateEvents();
#else // Q_OS_MAC
return rpl::never<rpl::empty_value>();
#endif // Q_OS_MAC
}
~RpWidgetWrap() {
base::take(_lifetime);
base::take(_eventStreams);

View File

@ -22,7 +22,10 @@ constexpr int kFadeAlphaMax = 160;
namespace Ui {
InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st) : TWidget(parent)
InnerDropdown::InnerDropdown(
QWidget *parent,
const style::InnerDropdown &st)
: RpWidget(parent)
, _st(st)
, _scroll(this, _st.scroll) {
_hideTimer.setSingleShot(true);
@ -30,11 +33,21 @@ InnerDropdown::InnerDropdown(QWidget *parent, const style::InnerDropdown &st) :
connect(_scroll, SIGNAL(scrolled()), this, SLOT(onScroll()));
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWindowActiveChanged()));
}
hide();
shownValue(
) | rpl::filter([](bool shown) {
return shown;
}) | rpl::take(1) | rpl::map([=] {
// We can't invoke this before the window is created.
// So instead we start handling them on the first show().
return macWindowDeactivateEvents();
}) | rpl::flatten_latest(
) | rpl::filter([=] {
return !isHidden();
}) | rpl::start_with_next([=] {
leaveEvent(nullptr);
}, lifetime());
}
QPointer<TWidget> InnerDropdown::doSetOwnedWidget(object_ptr<TWidget> widget) {
@ -71,12 +84,6 @@ void InnerDropdown::resizeToContent() {
}
}
void InnerDropdown::onWindowActiveChanged() {
if (!App::wnd()->windowHandle()->isActive() && !isHidden()) {
leaveEvent(nullptr);
}
}
void InnerDropdown::resizeEvent(QResizeEvent *e) {
_scroll->setGeometry(rect().marginsRemoved(_st.padding).marginsRemoved(_st.scrollMargin));
if (auto widget = static_cast<TWidget*>(_scroll->widget())) {
@ -124,7 +131,7 @@ void InnerDropdown::enterEventHook(QEvent *e) {
if (_autoHiding) {
showAnimated(_origin);
}
return TWidget::enterEventHook(e);
return RpWidget::enterEventHook(e);
}
void InnerDropdown::leaveEventHook(QEvent *e) {
@ -135,7 +142,7 @@ void InnerDropdown::leaveEventHook(QEvent *e) {
_hideTimer.start(300);
}
}
return TWidget::leaveEventHook(e);
return RpWidget::leaveEventHook(e);
}
void InnerDropdown::otherEnter() {

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "styles/style_widgets.h"
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "ui/effects/panel_animation.h"
@ -15,7 +16,7 @@ namespace Ui {
class ScrollArea;
class InnerDropdown : public TWidget {
class InnerDropdown : public Ui::RpWidget {
Q_OBJECT
public:
@ -81,7 +82,6 @@ private slots:
void onHideAnimated() {
hideAnimated();
}
void onWindowActiveChanged();
void onScroll();
void onWidgetHeightUpdated() {
resizeToContent();

View File

@ -8,8 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/tooltip.h"
#include "mainwindow.h"
#include "styles/style_widgets.h"
#include "platform/platform_specific.h"
#include "core/qt_signal_producer.h"
#include "styles/style_widgets.h"
namespace Ui {
@ -33,7 +34,7 @@ AbstractTooltipShower::~AbstractTooltipShower() {
}
}
Tooltip::Tooltip() : TWidget(nullptr) {
Tooltip::Tooltip() : RpWidget(nullptr) {
TooltipInstance = this;
setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::NoDropShadowWindowHint | Qt::ToolTip);
@ -43,7 +44,10 @@ Tooltip::Tooltip() : TWidget(nullptr) {
_showTimer.setCallback([=] { performShow(); });
_hideByLeaveTimer.setCallback([=] { Hide(); });
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
App::wnd()->windowDeactivateEvents(
) | rpl::start_with_next([=] {
Hide();
}, lifetime());
}
void Tooltip::performShow() {
@ -57,12 +61,6 @@ void Tooltip::performShow() {
}
}
void Tooltip::onWndActiveChanged() {
if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) {
Hide();
}
}
bool Tooltip::eventFilter(QObject *o, QEvent *e) {
if (e->type() == QEvent::Leave) {
_hideByLeaveTimer.callOnce(10);
@ -73,7 +71,7 @@ bool Tooltip::eventFilter(QObject *o, QEvent *e) {
Hide();
}
}
return TWidget::eventFilter(o, e);
return RpWidget::eventFilter(o, e);
}
Tooltip::~Tooltip() {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h"
#include "ui/effects/animations.h"
#include "ui/rp_widget.h"
namespace style {
struct Tooltip;
@ -27,16 +28,11 @@ public:
};
class Tooltip : public TWidget {
Q_OBJECT
class Tooltip : public Ui::RpWidget {
public:
static void Show(int32 delay, const AbstractTooltipShower *shower);
static void Hide();
private slots:
void onWndActiveChanged();
protected:
void paintEvent(QPaintEvent *e) override;
void hideEvent(QHideEvent *e) override;

View File

@ -273,8 +273,18 @@ void MainWindow::init() {
initHook();
updateWindowIcon();
connect(windowHandle(), &QWindow::activeChanged, this, [this] { handleActiveChanged(); }, Qt::QueuedConnection);
connect(windowHandle(), &QWindow::windowStateChanged, this, [this](Qt::WindowState state) { handleStateChanged(state); });
// Non-queued activeChanged handlers must use QtSignalProducer.
connect(
windowHandle(),
&QWindow::activeChanged,
this,
[=] { handleActiveChanged(); },
Qt::QueuedConnection);
connect(
windowHandle(),
&QWindow::windowStateChanged,
this,
[=](Qt::WindowState state) { handleStateChanged(state); });
updatePalette();

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "storage/localstorage.h"
#include "support/support_templates.h"
#include "settings/settings_common.h"
#include "core/qt_signal_producer.h"
#include "boxes/about_box.h"
#include "boxes/peer_list_controllers.h"
#include "calls/calls_box_controller.h"
@ -33,32 +34,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_boxes.h"
namespace Window {
namespace {
template <typename Object, typename Other, typename Value>
auto qtSignalProducer(
Object *object,
void(Other::*signal)(Value)) {
using Produced = std::remove_const_t<std::decay_t<Value>>;
const auto guarded = make_weak(object);
return rpl::make_producer<Produced>([=](auto consumer) {
if (!guarded) {
return rpl::lifetime();
}
auto listener = Ui::CreateChild<QObject>(guarded.data());
QObject::connect(guarded, signal, listener, [=](Value value) {
consumer.put_next_copy(value);
});
const auto weak = make_weak(listener);
return rpl::lifetime([=] {
if (weak) {
delete weak;
}
});
});
}
} // namespace
class MainMenu::ResetScaleButton : public Ui::AbstractButton {
public:
@ -311,15 +286,15 @@ void MainMenu::initResetScaleButton() {
rpl::single(
handle->screen()
) | rpl::then(
qtSignalProducer(handle, &QWindow::screenChanged)
Core::QtSignalProducer(handle, &QWindow::screenChanged)
) | rpl::map([](QScreen *screen) {
return rpl::single(
screen->availableGeometry()
) | rpl::then(
#ifdef OS_MAC_OLD
qtSignalProducer(screen, &QScreen::virtualGeometryChanged)
Core::QtSignalProducer(screen, &QScreen::virtualGeometryChanged)
#else // OS_MAC_OLD
qtSignalProducer(screen, &QScreen::availableGeometryChanged)
Core::QtSignalProducer(screen, &QScreen::availableGeometryChanged)
#endif // OS_MAC_OLD
);
}) | rpl::flatten_latest(

View File

@ -146,6 +146,7 @@
<(src_loc)/core/media_active_cache.h
<(src_loc)/core/mime_type.cpp
<(src_loc)/core/mime_type.h
<(src_loc)/core/qt_signal_producer.h
<(src_loc)/core/sandbox.cpp
<(src_loc)/core/sandbox.h
<(src_loc)/core/shortcuts.cpp