Allow adding submenu to PopupMenu on the fly.

This commit is contained in:
John Preston 2021-09-07 21:18:43 +03:00
parent e0339b7da1
commit 6651d9f9b6
2 changed files with 59 additions and 23 deletions

View file

@ -36,10 +36,12 @@ PopupMenu::PopupMenu(QWidget *parent, QMenu *menu, const style::PopupMenu &st)
, _menu(this, menu, _st.menu) { , _menu(this, menu, _st.menu) {
init(); init();
for (auto action : actions()) { for (const auto action : actions()) {
if (auto submenu = action->menu()) { if (const auto submenu = action->menu()) {
auto it = _submenus.insert(action, new PopupMenu(parentWidget(), submenu, st)); _submenus.emplace(
it.value()->deleteOnHide(false); action,
base::make_unique_q<PopupMenu>(parentWidget(), submenu, st)
).first->second->deleteOnHide(false);
} }
} }
} }
@ -78,6 +80,27 @@ void PopupMenu::init() {
setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_TranslucentBackground, true);
} }
not_null<PopupMenu*> PopupMenu::ensureSubmenu(not_null<QAction*> action) {
const auto &list = actions();
const auto i = ranges::find(list, action);
Assert(i != end(list));
const auto j = _submenus.find(action);
if (j != end(_submenus)) {
return j->second.get();
}
const auto result = _submenus.emplace(
action,
base::make_unique_q<PopupMenu>(parentWidget(), st())
).first->second.get();
result->deleteOnHide(false);
return result;
}
void PopupMenu::removeSubmenu(not_null<QAction*> action) {
_submenus.remove(action);
}
void PopupMenu::handleCompositingUpdate() { void PopupMenu::handleCompositingUpdate() {
_padding = _useTransparency ? _st.shadow.extend : style::margins(st::lineWidth, st::lineWidth, st::lineWidth, st::lineWidth); _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()); _menu->moveToLeft(_padding.left() + _st.scrollPadding.left(), _padding.top() + _st.scrollPadding.top());
@ -100,11 +123,16 @@ not_null<QAction*> PopupMenu::addAction(const QString &text, Fn<void()> callback
return _menu->addAction(text, std::move(callback), icon, iconOver); return _menu->addAction(text, std::move(callback), icon, iconOver);
} }
not_null<QAction*> PopupMenu::addAction(const QString &text, std::unique_ptr<PopupMenu> submenu) { not_null<QAction*> PopupMenu::addAction(
const QString &text,
std::unique_ptr<PopupMenu> submenu) {
const auto action = _menu->addAction(text, std::make_unique<QMenu>()); const auto action = _menu->addAction(text, std::make_unique<QMenu>());
auto it = _submenus.insert(action, submenu.release()); const auto saved = _submenus.emplace(
it.value()->setParent(parentWidget()); action,
it.value()->deleteOnHide(false); base::unique_qptr<PopupMenu>(submenu.release())
).first->second.get();
saved->setParent(parentWidget());
saved->deleteOnHide(false);
return action; return action;
} }
@ -113,9 +141,7 @@ not_null<QAction*> PopupMenu::addSeparator() {
} }
void PopupMenu::clearActions() { void PopupMenu::clearActions() {
for (const auto &submenu : base::take(_submenus)) { _submenus.clear();
delete submenu;
}
return _menu->clearActions(); return _menu->clearActions();
} }
@ -164,7 +190,7 @@ void PopupMenu::paintBg(QPainter &p) {
void PopupMenu::handleActivated(const Menu::CallbackData &data) { void PopupMenu::handleActivated(const Menu::CallbackData &data) {
if (data.source == TriggeredSource::Mouse) { if (data.source == TriggeredSource::Mouse) {
if (!popupSubmenuFromAction(data)) { if (!popupSubmenuFromAction(data)) {
if (auto currentSubmenu = base::take(_activeSubmenu)) { if (const auto currentSubmenu = base::take(_activeSubmenu)) {
currentSubmenu->hideMenu(true); currentSubmenu->hideMenu(true);
} }
} }
@ -185,7 +211,8 @@ void PopupMenu::handleTriggered(const Menu::CallbackData &data) {
} }
bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) { bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) {
if (auto submenu = _submenus.value(data.action)) { if (const auto i = _submenus.find(data.action); i != end(_submenus)) {
const auto submenu = i->second.get();
if (_activeSubmenu == submenu) { if (_activeSubmenu == submenu) {
// There is a strange problem on macOS // There is a strange problem on macOS
// when a submenu closes arbitrarily // when a submenu closes arbitrarily
@ -201,7 +228,7 @@ bool PopupMenu::popupSubmenuFromAction(const Menu::CallbackData &data) {
return false; return false;
} }
void PopupMenu::popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source) { void PopupMenu::popupSubmenu(not_null<PopupMenu*> submenu, int actionTop, TriggeredSource source) {
if (auto currentSubmenu = base::take(_activeSubmenu)) { if (auto currentSubmenu = base::take(_activeSubmenu)) {
currentSubmenu->hideMenu(true); currentSubmenu->hideMenu(true);
} }
@ -287,7 +314,9 @@ void PopupMenu::mousePressEvent(QMouseEvent *e) {
} }
void PopupMenu::hideMenu(bool fast) { void PopupMenu::hideMenu(bool fast) {
if (isHidden()) return; if (isHidden()) {
return;
}
if (_parent && !_a_opacity.animating()) { if (_parent && !_a_opacity.animating()) {
_parent->childHiding(this); _parent->childHiding(this);
} }
@ -306,7 +335,7 @@ void PopupMenu::hideMenu(bool fast) {
void PopupMenu::childHiding(PopupMenu *child) { void PopupMenu::childHiding(PopupMenu *child) {
if (_activeSubmenu && _activeSubmenu == child) { if (_activeSubmenu && _activeSubmenu == child) {
_activeSubmenu = SubmenuPointer(); _activeSubmenu = nullptr;
} }
if (!_hiding && !isHidden()) { if (!_hiding && !isHidden()) {
raise(); raise();
@ -549,7 +578,7 @@ void PopupMenu::showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource sou
} }
PopupMenu::~PopupMenu() { PopupMenu::~PopupMenu() {
for (const auto &submenu : base::take(_submenus)) { for (const auto &[action, submenu] : base::take(_submenus)) {
delete submenu; delete submenu;
} }
if (const auto parent = parentWidget()) { if (const auto parent = parentWidget()) {

View file

@ -13,6 +13,7 @@
#include "ui/round_rect.h" #include "ui/round_rect.h"
#include "ui/rp_widget.h" #include "ui/rp_widget.h"
#include "base/object_ptr.h" #include "base/object_ptr.h"
#include "base/unique_qptr.h"
namespace Ui { namespace Ui {
@ -31,7 +32,10 @@ public:
not_null<QAction*> addSeparator(); not_null<QAction*> addSeparator();
void clearActions(); void clearActions();
const std::vector<not_null<QAction*>> &actions() const; [[nodiscard]] const std::vector<not_null<QAction*>> &actions() const;
[[nodiscard]] not_null<PopupMenu*> ensureSubmenu(
not_null<QAction*> action);
void removeSubmenu(not_null<QAction*> action);
bool empty() const; bool empty() const;
void deleteOnHide(bool del); void deleteOnHide(bool del);
@ -101,9 +105,11 @@ private:
} }
void handleMouseRelease(QPoint globalPosition); void handleMouseRelease(QPoint globalPosition);
using SubmenuPointer = QPointer<PopupMenu>;
bool popupSubmenuFromAction(const Menu::CallbackData &data); bool popupSubmenuFromAction(const Menu::CallbackData &data);
void popupSubmenu(SubmenuPointer submenu, int actionTop, TriggeredSource source); void popupSubmenu(
not_null<PopupMenu*> submenu,
int actionTop,
TriggeredSource source);
void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source); void showMenu(const QPoint &p, PopupMenu *parent, TriggeredSource source);
const style::PopupMenu &_st; const style::PopupMenu &_st;
@ -111,15 +117,16 @@ private:
RoundRect _roundRect; RoundRect _roundRect;
object_ptr<Menu::Menu> _menu; object_ptr<Menu::Menu> _menu;
using Submenus = QMap<QAction*, SubmenuPointer>; base::flat_map<
Submenus _submenus; not_null<QAction*>,
base::unique_qptr<PopupMenu>> _submenus;
PopupMenu *_parent = nullptr; PopupMenu *_parent = nullptr;
QRect _inner; QRect _inner;
style::margins _padding; style::margins _padding;
SubmenuPointer _activeSubmenu; QPointer<PopupMenu> _activeSubmenu;
PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft; PanelAnimation::Origin _origin = PanelAnimation::Origin::TopLeft;
std::optional<PanelAnimation::Origin> _forcedOrigin; std::optional<PanelAnimation::Origin> _forcedOrigin;