tdesktop/Telegram/SourceFiles/ui/widgets/popup_menu.cpp
John Preston 550b67236e Reactive main window only if app is active.
Otherwise when we choose "Show in Folder" app looses focus,
then destroys PopupMenu and instantly regains focus back.
2018-11-04 15:51:38 +04:00

519 lines
13 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/widgets/popup_menu.h"
#include "ui/widgets/shadow.h"
#include "ui/image/image_prepare.h"
#include "platform/platform_specific.h"
#include "application.h"
#include "mainwindow.h"
#include "messenger.h"
#include "lang/lang_keys.h"
namespace Ui {
namespace {
bool InactiveMacApplication() {
return (cPlatform() == dbipMac || cPlatform() == dbipMacOld)
&& !Platform::IsApplicationActive();
}
} // namespace
PopupMenu::PopupMenu(QWidget *parent, const style::PopupMenu &st)
: RpWidget(parent)
, _st(st)
, _menu(this, _st.menu) {
init();
}
PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st)
: RpWidget(parent)
, _st(st)
, _menu(this, menu, _st.menu) {
init();
for (auto action : actions()) {
if (auto submenu = action->menu()) {
auto it = _submenus.insert(action, new PopupMenu(parentWidget(), submenu, st));
it.value()->deleteOnHide(false);
}
}
}
void PopupMenu::init() {
using namespace rpl::mappers;
rpl::merge(
Messenger::Instance().passcodeLockChanges(),
Messenger::Instance().termsLockChanges()
) | rpl::start_with_next([=] {
hideMenu(true);
}, lifetime());
_menu->setResizedCallback([this] { handleMenuResize(); });
_menu->setActivatedCallback([this](QAction *action, int actionTop, TriggeredSource source) {
handleActivated(action, actionTop, source);
});
_menu->setTriggeredCallback([this](QAction *action, int actionTop, TriggeredSource source) {
handleTriggered(action, actionTop, source);
});
_menu->setKeyPressDelegate([this](int key) { return handleKeyPress(key); });
_menu->setMouseMoveDelegate([this](QPoint globalPosition) { handleMouseMove(globalPosition); });
_menu->setMousePressDelegate([this](QPoint globalPosition) { handleMousePress(globalPosition); });
_menu->setMouseReleaseDelegate([this](QPoint globalPosition) { handleMouseRelease(globalPosition); });
handleCompositingUpdate();
setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) | Qt::BypassWindowManagerHint | Qt::Popup | Qt::NoDropShadowWindowHint);
setMouseTracking(true);
hide();
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
}
void PopupMenu::handleCompositingUpdate() {
_padding = _useTransparency ? _st.shadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth);
_menu->moveToLeft(_padding.left() + _st.scrollPadding.left(), _padding.top() + _st.scrollPadding.top());
handleMenuResize();
}
void PopupMenu::handleMenuResize() {
auto newWidth = _padding.left() + _st.scrollPadding.left() + _menu->width() + _st.scrollPadding.right() + _padding.right();
auto newHeight = _padding.top() + _st.scrollPadding.top() + _menu->height() + _st.scrollPadding.bottom() + _padding.bottom();
resize(newWidth, newHeight);
_inner = rect().marginsRemoved(_padding);
}
QAction *PopupMenu::addAction(const QString &text, const QObject *receiver, const char* member, const style::icon *icon, const style::icon *iconOver) {
return _menu->addAction(text, receiver, member, icon, iconOver);
}
QAction *PopupMenu::addAction(const QString &text, Fn<void()> callback, const style::icon *icon, const style::icon *iconOver) {
return _menu->addAction(text, std::move(callback), icon, iconOver);
}
QAction *PopupMenu::addSeparator() {
return _menu->addSeparator();
}
void PopupMenu::clearActions() {
for (auto submenu : base::take(_submenus)) {
delete submenu;
}
return _menu->clearActions();
}
PopupMenu::Actions &PopupMenu::actions() {
return _menu->actions();
}
void PopupMenu::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_useTransparency) {
Platform::StartTranslucentPaint(p, e);
}
auto ms = getms();
if (_a_show.animating(ms)) {
if (auto opacity = _a_opacity.current(ms, _hiding ? 0. : 1.)) {
_showAnimation->paintFrame(p, 0, 0, width(), _a_show.current(1.), opacity);
}
} else if (_a_opacity.animating(ms)) {
p.setOpacity(_a_opacity.current(0.));
p.drawPixmap(0, 0, _cache);
} else if (_hiding || isHidden()) {
hideFinished();
} else if (_showAnimation) {
_showAnimation->paintFrame(p, 0, 0, width(), 1., 1.);
_showAnimation.reset();
showChildren();
} else {
paintBg(p);
}
}
void PopupMenu::paintBg(Painter &p) {
if (_useTransparency) {
Shadow::paint(p, _inner, width(), _st.shadow);
App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small);
} else {
p.fillRect(0, 0, width() - _padding.right(), _padding.top(), _st.shadow.fallback);
p.fillRect(width() - _padding.right(), 0, _padding.right(), height() - _padding.bottom(), _st.shadow.fallback);
p.fillRect(_padding.left(), height() - _padding.bottom(), width() - _padding.left(), _padding.bottom(), _st.shadow.fallback);
p.fillRect(0, _padding.top(), _padding.left(), height() - _padding.top(), _st.shadow.fallback);
p.fillRect(_inner, _st.menu.itemBg);
}
}
void PopupMenu::handleActivated(QAction *action, int actionTop, TriggeredSource source) {
if (source == TriggeredSource::Mouse) {
if (!popupSubmenuFromAction(action, actionTop, source)) {
if (auto currentSubmenu = base::take(_activeSubmenu)) {
currentSubmenu->hideMenu(true);
}
}
}
}
void PopupMenu::handleTriggered(QAction *action, int actionTop, TriggeredSource source) {
if (!popupSubmenuFromAction(action, actionTop, source)) {
_triggering = true;
hideMenu();
emit action->trigger();
_triggering = false;
if (_deleteLater) {
_deleteLater = false;
deleteLater();
}
}
}
bool PopupMenu::popupSubmenuFromAction(QAction *action, int actionTop, TriggeredSource source) {
if (auto submenu = _submenus.value(action)) {
if (_activeSubmenu == submenu) {
submenu->hideMenu(true);
} else {
popupSubmenu(submenu, actionTop, source);
}
return true;
}
return false;
}
void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) {
if (auto currentSubmenu = base::take(_activeSubmenu)) {
currentSubmenu->hideMenu(true);
}
if (submenu) {
QPoint p(_inner.x() + (rtl() ? _padding.right() : _inner.width() - _padding.left()), _inner.y() + actionTop);
_activeSubmenu = submenu;
_activeSubmenu->showMenu(geometry().topLeft() + p, this, source);
_menu->setChildShown(true);
} else {
_menu->setChildShown(false);
}
}
void PopupMenu::forwardKeyPress(int key) {
if (!handleKeyPress(key)) {
_menu->handleKeyPress(key);
}
}
bool PopupMenu::handleKeyPress(int key) {
if (_activeSubmenu) {
_activeSubmenu->handleKeyPress(key);
return true;
} else if (key == Qt::Key_Escape) {
hideMenu(_parent ? true : false);
return true;
} else if (key == (rtl() ? Qt::Key_Right : Qt::Key_Left)) {
if (_parent) {
hideMenu(true);
return true;
}
}
return false;
}
void PopupMenu::handleMouseMove(QPoint globalPosition) {
if (_parent) {
_parent->forwardMouseMove(globalPosition);
}
}
void PopupMenu::handleMousePress(QPoint globalPosition) {
if (_parent) {
_parent->forwardMousePress(globalPosition);
} else {
hideMenu();
}
}
void PopupMenu::handleMouseRelease(QPoint globalPosition) {
if (_parent) {
_parent->forwardMouseRelease(globalPosition);
} else {
hideMenu();
}
}
void PopupMenu::focusOutEvent(QFocusEvent *e) {
hideMenu();
}
void PopupMenu::hideEvent(QHideEvent *e) {
if (_deleteOnHide) {
if (_triggering) {
_deleteLater = true;
} else {
deleteLater();
}
}
}
void PopupMenu::hideMenu(bool fast) {
if (isHidden()) return;
if (_parent && !_a_opacity.animating()) {
_parent->childHiding(this);
}
if (fast) {
hideFast();
} else {
hideAnimated();
if (_parent) {
_parent->hideMenu();
}
}
if (_activeSubmenu) {
_activeSubmenu->hideMenu(fast);
}
}
void PopupMenu::childHiding(PopupMenu *child) {
if (_activeSubmenu && _activeSubmenu == child) {
_activeSubmenu = SubmenuPointer();
}
if (!_hiding && !isHidden()) {
activateWindow();
}
}
void PopupMenu::setOrigin(PanelAnimation::Origin origin) {
_origin = origin;
}
void PopupMenu::showAnimated(PanelAnimation::Origin origin) {
setOrigin(origin);
showStarted();
}
void PopupMenu::hideAnimated() {
if (isHidden()) return;
if (_hiding) return;
startOpacityAnimation(true);
}
void PopupMenu::hideFast() {
if (isHidden()) return;
_hiding = false;
_a_opacity.finish();
hideFinished();
}
void PopupMenu::hideFinished() {
_a_show.finish();
_cache = QPixmap();
if (!isHidden()) {
hide();
}
}
void PopupMenu::prepareCache() {
if (_a_opacity.animating()) return;
auto showAnimation = base::take(_a_show);
auto showAnimationData = base::take(_showAnimation);
showChildren();
_cache = GrabWidget(this);
_showAnimation = base::take(showAnimationData);
_a_show = base::take(showAnimation);
}
void PopupMenu::startOpacityAnimation(bool hiding) {
_hiding = false;
if (!_useTransparency) {
_a_opacity.finish();
if (hiding) {
hideFinished();
} else {
update();
}
return;
}
prepareCache();
_hiding = hiding;
hideChildren();
_a_opacity.start([this] { opacityAnimationCallback(); }, _hiding ? 1. : 0., _hiding ? 0. : 1., _st.duration);
}
void PopupMenu::showStarted() {
if (isHidden()) {
show();
startShowAnimation();
return;
} else if (!_hiding) {
return;
}
startOpacityAnimation(false);
}
void PopupMenu::startShowAnimation() {
if (!_useTransparency) {
_a_show.finish();
update();
return;
}
if (!_a_show.animating()) {
auto opacityAnimation = base::take(_a_opacity);
showChildren();
auto cache = grabForPanelAnimation();
_a_opacity = base::take(opacityAnimation);
_showAnimation = std::make_unique<PanelAnimation>(_st.animation, _origin);
_showAnimation->setFinalImage(std::move(cache), QRect(_inner.topLeft() * cIntRetinaFactor(), _inner.size() * cIntRetinaFactor()));
if (_useTransparency) {
auto corners = App::cornersMask(ImageRoundRadius::Small);
_showAnimation->setCornerMasks(corners[0], corners[1], corners[2], corners[3]);
} else {
_showAnimation->setSkipShadow(true);
}
_showAnimation->start();
}
hideChildren();
_a_show.start([this] { showAnimationCallback(); }, 0., 1., _st.showDuration);
}
void PopupMenu::opacityAnimationCallback() {
update();
if (!_a_opacity.animating()) {
if (_hiding) {
_hiding = false;
hideFinished();
} else {
showChildren();
}
}
}
void PopupMenu::showAnimationCallback() {
update();
}
QImage PopupMenu::grabForPanelAnimation() {
SendPendingMoveResizeEvents(this);
auto result = QImage(size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(cRetinaFactor());
result.fill(Qt::transparent);
{
Painter p(&result);
if (_useTransparency) {
App::roundRect(p, _inner, _st.menu.itemBg, ImageRoundRadius::Small);
} else {
p.fillRect(_inner, _st.menu.itemBg);
}
for (auto child : children()) {
if (auto widget = qobject_cast<QWidget*>(child)) {
widget->render(&p, widget->pos(), widget->rect(), QWidget::DrawChildren | QWidget::IgnoreMask);
}
}
}
return result;
}
void PopupMenu::deleteOnHide(bool del) {
_deleteOnHide = del;
}
void PopupMenu::popup(const QPoint &p) {
showMenu(p, nullptr, TriggeredSource::Mouse);
}
void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source) {
if (!parent && InactiveMacApplication()) {
_hiding = false;
_a_opacity.finish();
_a_show.finish();
_cache = QPixmap();
hide();
if (_deleteOnHide) {
deleteLater();
}
return;
}
_parent = parent;
auto origin = PanelAnimation::Origin::TopLeft;
auto w = p - QPoint(0, _padding.top());
auto r = Sandbox::screenGeometry(p);
_useTransparency = Platform::TranslucentWindowsSupported(p);
setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency);
handleCompositingUpdate();
if (rtl()) {
if (w.x() - width() < r.x() - _padding.left()) {
if (_parent && w.x() + _parent->width() - _padding.left() - _padding.right() + width() - _padding.right() <= r.x() + r.width()) {
w.setX(w.x() + _parent->width() - _padding.left() - _padding.right());
} else {
w.setX(r.x() - _padding.left());
}
} else {
w.setX(w.x() - width());
}
} else {
if (w.x() + width() - _padding.right() > r.x() + r.width()) {
if (_parent && w.x() - _parent->width() + _padding.left() + _padding.right() - width() + _padding.right() >= r.x() - _padding.left()) {
w.setX(w.x() + _padding.left() + _padding.right() - _parent->width() - width() + _padding.left() + _padding.right());
} else {
w.setX(p.x() - width() + _padding.right());
}
origin = PanelAnimation::Origin::TopRight;
}
}
if (w.y() + height() - _padding.bottom() > r.y() + r.height()) {
if (_parent) {
w.setY(r.y() + r.height() - height() + _padding.bottom());
} else {
w.setY(p.y() - height() + _padding.bottom());
origin = (origin == PanelAnimation::Origin::TopRight) ? PanelAnimation::Origin::BottomRight : PanelAnimation::Origin::BottomLeft;
}
}
if (w.x() < r.x()) {
w.setX(r.x());
}
if (w.y() < r.y()) {
w.setY(r.y());
}
move(w);
setOrigin(origin);
_menu->setShowSource(source);
startShowAnimation();
psUpdateOverlayed(this);
show();
psShowOverAll(this);
activateWindow();
}
PopupMenu::~PopupMenu() {
for (const auto submenu : base::take(_submenus)) {
delete submenu;
}
if (const auto parent = parentWidget()) {
if (qApp->focusWidget() != nullptr) {
crl::on_main(parent, [=] {
if (!parent->isHidden()) {
parent->activateWindow();
}
});
}
}
if (_destroyedCallback) {
_destroyedCallback();
}
}
} // namespace Ui