2016-10-26 16:43:13 +00:00
|
|
|
/*
|
|
|
|
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-2016 John Preston, https://desktop.telegram.org
|
|
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "ui/widgets/popup_menu.h"
|
|
|
|
|
|
|
|
#include "pspecific.h"
|
|
|
|
#include "application.h"
|
|
|
|
#include "lang.h"
|
|
|
|
|
|
|
|
namespace Ui {
|
|
|
|
|
|
|
|
PopupMenu::PopupMenu(const style::PopupMenu &st) : TWidget(nullptr)
|
|
|
|
, _st(st)
|
|
|
|
, _menu(this, _st.menu)
|
|
|
|
, _shadow(_st.shadow)
|
|
|
|
, a_opacity(1)
|
|
|
|
, _a_hide(animation(this, &PopupMenu::step_hide)) {
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
PopupMenu::PopupMenu(QMenu *menu, const style::PopupMenu &st) : TWidget(nullptr)
|
|
|
|
, _st(st)
|
|
|
|
, _menu(this, menu, _st.menu)
|
|
|
|
, _shadow(_st.shadow)
|
|
|
|
, a_opacity(1)
|
|
|
|
, _a_hide(animation(this, &PopupMenu::step_hide)) {
|
|
|
|
init();
|
|
|
|
|
|
|
|
for (auto action : actions()) {
|
|
|
|
if (auto submenu = action->menu()) {
|
|
|
|
auto it = _submenus.insert(action, new PopupMenu(submenu, st));
|
|
|
|
it.value()->deleteOnHide(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PopupMenu::init() {
|
|
|
|
_padding = _shadow.getDimensions(_st.shadowShift);
|
|
|
|
|
|
|
|
_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->moveToLeft(_padding.left(), _padding.top());
|
|
|
|
handleMenuResize();
|
|
|
|
|
|
|
|
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::handleMenuResize() {
|
|
|
|
resize(_padding.left() + _menu->width() + _padding.right(), _padding.top() + _menu->height() + _padding.bottom());
|
|
|
|
_inner = QRect(_padding.left(), _padding.top(), width() - _padding.left() - _padding.right(), height() - _padding.top() - _padding.bottom());
|
|
|
|
}
|
|
|
|
|
2016-11-01 12:46:34 +00:00
|
|
|
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);
|
2016-10-26 16:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
auto clip = e->rect();
|
|
|
|
p.setClipRect(clip);
|
|
|
|
auto compositionMode = p.compositionMode();
|
|
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
|
|
if (_a_hide.animating()) {
|
|
|
|
p.setOpacity(a_opacity.current());
|
|
|
|
p.drawPixmap(0, 0, _cache);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-31 12:29:26 +00:00
|
|
|
// This is the minimal alpha value that allowed mouse tracking in OS X.
|
|
|
|
p.fillRect(clip, QColor(255, 255, 255, 13));
|
2016-10-26 16:43:13 +00:00
|
|
|
p.setCompositionMode(compositionMode);
|
|
|
|
|
|
|
|
_shadow.paint(p, _inner, _st.shadowShift);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)) {
|
|
|
|
hideMenu();
|
|
|
|
_triggering = true;
|
|
|
|
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::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_hide.animating()) {
|
|
|
|
_parent->childHiding(this);
|
|
|
|
}
|
|
|
|
if (fast) {
|
|
|
|
if (_a_hide.animating()) {
|
|
|
|
_a_hide.stop();
|
|
|
|
}
|
|
|
|
a_opacity = anim::fvalue(0, 0);
|
|
|
|
hideFinish();
|
|
|
|
} else {
|
|
|
|
if (!_a_hide.animating()) {
|
|
|
|
_cache = myGrab(this);
|
|
|
|
a_opacity.start(0);
|
|
|
|
_a_hide.start();
|
|
|
|
}
|
|
|
|
if (_parent) {
|
|
|
|
_parent->hideMenu();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (_activeSubmenu) {
|
|
|
|
_activeSubmenu->hideMenu(fast);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PopupMenu::childHiding(PopupMenu *child) {
|
|
|
|
if (_activeSubmenu && _activeSubmenu == child) {
|
|
|
|
_activeSubmenu = SubmenuPointer();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PopupMenu::hideFinish() {
|
|
|
|
hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
void PopupMenu::step_hide(float64 ms, bool timer) {
|
|
|
|
float64 dt = ms / _st.duration;
|
|
|
|
if (dt >= 1) {
|
|
|
|
_a_hide.stop();
|
|
|
|
a_opacity.finish();
|
|
|
|
hideFinish();
|
|
|
|
} else {
|
|
|
|
a_opacity.update(dt, anim::linear);
|
|
|
|
}
|
|
|
|
if (timer) update();
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
QPoint w = p - QPoint(0, _padding.top());
|
|
|
|
QRect r = Sandbox::screenGeometry(p);
|
|
|
|
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(r.x() + r.width() - width() + _padding.right());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (w.y() < r.y()) {
|
|
|
|
w.setY(r.y());
|
|
|
|
}
|
|
|
|
move(w);
|
|
|
|
|
|
|
|
_menu->setShowSource(source);
|
|
|
|
|
|
|
|
psUpdateOverlayed(this);
|
|
|
|
show();
|
|
|
|
psShowOverAll(this);
|
|
|
|
windowHandle()->requestActivate();
|
|
|
|
activateWindow();
|
|
|
|
|
|
|
|
if (_a_hide.animating()) {
|
|
|
|
_a_hide.stop();
|
|
|
|
_cache = QPixmap();
|
|
|
|
}
|
|
|
|
a_opacity = anim::fvalue(1, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
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->onReActivate();
|
|
|
|
QTimer::singleShot(200, w, SLOT(onReActivate()));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Ui
|