mirror of
https://github.com/vale981/tdesktop
synced 2025-03-08 11:11:39 -05:00

Also don't change mask corner images when color theme is changed. This prevents race condition in mask corner images access, because the GIF frame readers access mask corner images from other threads.
491 lines
13 KiB
C++
491 lines
13 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
|
*/
|
|
#include "ui/widgets/popup_menu.h"
|
|
|
|
#include "ui/widgets/shadow.h"
|
|
#include "platform/platform_specific.h"
|
|
#include "application.h"
|
|
#include "mainwindow.h"
|
|
#include "lang/lang_keys.h"
|
|
|
|
namespace Ui {
|
|
|
|
PopupMenu::PopupMenu(QWidget*, const style::PopupMenu &st) : TWidget(nullptr)
|
|
, _st(st)
|
|
, _menu(this, _st.menu) {
|
|
init();
|
|
}
|
|
|
|
PopupMenu::PopupMenu(QWidget*, QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr)
|
|
, _st(st)
|
|
, _menu(this, menu, _st.menu) {
|
|
init();
|
|
|
|
for (auto action : actions()) {
|
|
if (auto submenu = action->menu()) {
|
|
auto it = _submenus.insert(action, new PopupMenu(nullptr, submenu, st));
|
|
it.value()->deleteOnHide(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PopupMenu::init() {
|
|
_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, base::lambda<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();
|
|
}
|
|
}
|
|
|
|
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 = myGrab(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() {
|
|
myEnsureResized(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) {
|
|
_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);
|
|
windowHandle()->requestActivate();
|
|
activateWindow();
|
|
}
|
|
|
|
PopupMenu::~PopupMenu() {
|
|
for (auto submenu : base::take(_submenus)) {
|
|
delete submenu;
|
|
}
|
|
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
|
if (auto w = App::wnd()) {
|
|
w->reActivateWindow();
|
|
}
|
|
#endif
|
|
if (_destroyedCallback) {
|
|
_destroyedCallback();
|
|
}
|
|
}
|
|
|
|
} // namespace Ui
|