| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // This file is part of Desktop App Toolkit,
- // a set of libraries for developing nice desktop applications.
- //
- // For license and copyright information please follow this link:
- // https://github.com/desktop-app/legal/blob/master/LEGAL
- //
- #include "ui/widgets/menu/menu.h"
- #include "ui/widgets/menu/menu_action.h"
- #include "ui/widgets/menu/menu_item_base.h"
- #include "ui/widgets/menu/menu_separator.h"
- #include "ui/widgets/scroll_area.h"
- #include "styles/style_widgets.h"
- #include <QtGui/QtEvents>
- namespace Ui::Menu {
- Menu::Menu(QWidget *parent, const style::Menu &st)
- : RpWidget(parent)
- , _st(st) {
- init();
- }
- Menu::Menu(QWidget *parent, QMenu *menu, const style::Menu &st)
- : RpWidget(parent)
- , _st(st)
- , _wappedMenu(menu) {
- init();
- _wappedMenu->setParent(this);
- for (auto action : _wappedMenu->actions()) {
- addAction(action);
- }
- _wappedMenu->hide();
- }
- Menu::~Menu() = default;
- void Menu::init() {
- resize(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
- setMouseTracking(true);
- if (_st.itemBg->c.alpha() == 255) {
- setAttribute(Qt::WA_OpaquePaintEvent);
- }
- paintRequest(
- ) | rpl::start_with_next([=](const QRect &clip) {
- QPainter(this).fillRect(clip, _st.itemBg);
- }, lifetime());
- positionValue(
- ) | rpl::start_with_next([=] {
- handleMouseMove(QCursor::pos());
- }, lifetime());
- }
- not_null<QAction*> Menu::addAction(
- const QString &text,
- Fn<void()> callback,
- const style::icon *icon,
- const style::icon *iconOver) {
- auto action = CreateAction(this, text, std::move(callback));
- return addAction(std::move(action), icon, iconOver);
- }
- not_null<QAction*> Menu::addAction(
- const QString &text,
- std::unique_ptr<QMenu> submenu,
- const style::icon *icon,
- const style::icon *iconOver) {
- const auto action = new QAction(text, this);
- action->setMenu(submenu.release());
- return addAction(action, icon, iconOver);
- }
- not_null<QAction*> Menu::addAction(
- not_null<QAction*> action,
- const style::icon *icon,
- const style::icon *iconOver) {
- if (action->isSeparator()) {
- return addSeparator();
- }
- auto item = base::make_unique_q<Action>(
- this,
- _st,
- std::move(action),
- icon,
- iconOver ? iconOver : icon);
- return addAction(std::move(item));
- }
- not_null<QAction*> Menu::addAction(base::unique_qptr<ItemBase> widget) {
- return insertAction(_actions.size(), std::move(widget));
- }
- not_null<QAction*> Menu::insertAction(
- int position,
- base::unique_qptr<ItemBase> widget) {
- Expects(position >= 0 && position <= _actions.size());
- Expects(position >= 0 && position <= _actionWidgets.size());
- const auto raw = widget.get();
- const auto action = raw->action();
- _actions.insert(begin(_actions) + position, action);
- raw->setParent(this);
- raw->show();
- raw->setIndex(position);
- for (auto i = position, to = int(_actionWidgets.size()); i != to; ++i) {
- _actionWidgets[i]->setIndex(i + 1);
- }
- _actionWidgets.insert(
- begin(_actionWidgets) + position,
- std::move(widget));
- raw->selects(
- ) | rpl::start_with_next([=](const CallbackData &data) {
- if (!data.selected) {
- if (!findSelectedAction()
- && data.index < _actionWidgets.size()
- && _childShownAction == data.action) {
- const auto widget = _actionWidgets[data.index].get();
- widget->setSelected(true, widget->lastTriggeredSource());
- }
- return;
- }
- _lastSelectedByMouse = (data.source == TriggeredSource::Mouse);
- for (auto i = 0; i < _actionWidgets.size(); i++) {
- if (i != data.index) {
- _actionWidgets[i]->setSelected(false);
- }
- }
- if (_activatedCallback) {
- _activatedCallback(data);
- }
- }, raw->lifetime());
- raw->clicks(
- ) | rpl::start_with_next([=](const CallbackData &data) {
- if (_triggeredCallback) {
- _triggeredCallback(data);
- }
- }, raw->lifetime());
- QObject::connect(action.get(), &QAction::changed, raw, [=] {
- // Select an item under mouse that was disabled and became enabled.
- if (_lastSelectedByMouse
- && !findSelectedAction()
- && action->isEnabled()) {
- updateSelected(QCursor::pos());
- }
- });
- raw->minWidthValue(
- ) | rpl::skip(1) | rpl::filter([=] {
- return !_forceWidth;
- }) | rpl::start_with_next([=] {
- resizeFromInner(recountWidth(), height());
- }, raw->lifetime());
- raw->heightValue(
- ) | rpl::skip(1) | rpl::start_with_next([=] {
- resizeFromInner(width(), recountHeight());
- }, raw->lifetime());
- resizeFromInner(recountWidth(), recountHeight());
- updateSelected(QCursor::pos());
- return action;
- }
- int Menu::recountWidth() const {
- return _forceWidth
- ? _forceWidth
- : std::clamp(
- (_actionWidgets.empty()
- ? 0
- : (*ranges::max_element(
- _actionWidgets,
- std::less<>(),
- &ItemBase::minWidth))->minWidth()),
- _st.widthMin,
- _st.widthMax);
- }
- int Menu::recountHeight() const {
- auto result = 0;
- for (const auto &widget : _actionWidgets) {
- if (widget->y() != result) {
- widget->move(0, result);
- }
- result += widget->height();
- }
- return result;
- }
- void Menu::removeAction(int position) {
- Expects(position >= 0 && position < actions().size());
- _actionWidgets.erase(begin(_actionWidgets) + position);
- if (_actions[position]->parent() == this) {
- delete _actions[position];
- }
- _actions.erase(begin(_actions) + position);
- resizeFromInner(width(), recountHeight());
- }
- not_null<QAction*> Menu::addSeparator(const style::MenuSeparator *st) {
- const auto separator = new QAction(this);
- separator->setSeparator(true);
- auto item = base::make_unique_q<Separator>(
- this,
- _st,
- st ? *st : _st.separator,
- separator);
- return addAction(std::move(item));
- }
- void Menu::clearActions() {
- _actionWidgets.clear();
- for (auto action : base::take(_actions)) {
- if (action->parent() == this) {
- delete action;
- }
- }
- resizeFromInner(_forceWidth ? _forceWidth : _st.widthMin, _st.skip * 2);
- }
- void Menu::clearLastSeparator() {
- if (_actionWidgets.empty() || _actions.empty()) {
- return;
- }
- if (_actionWidgets.back()->action() == _actions.back()) {
- if (_actions.back()->isSeparator()) {
- resizeFromInner(
- width(),
- height() - _actionWidgets.back()->height());
- _actionWidgets.pop_back();
- if (_actions.back()->parent() == this) {
- delete _actions.back();
- _actions.pop_back();
- }
- }
- }
- }
- void Menu::finishAnimating() {
- for (const auto &widget : _actionWidgets) {
- widget->finishAnimating();
- }
- }
- bool Menu::empty() const {
- return _actionWidgets.empty();
- }
- void Menu::resizeFromInner(int w, int h) {
- if (const auto s = QSize(w, h); s != size()) {
- resize(s);
- _resizesFromInner.fire({});
- }
- }
- rpl::producer<> Menu::resizesFromInner() const {
- return _resizesFromInner.events();
- }
- rpl::producer<ScrollToRequest> Menu::scrollToRequests() const {
- return _scrollToRequests.events();
- }
- void Menu::setShowSource(TriggeredSource source) {
- const auto mouseSelection = (source == TriggeredSource::Mouse);
- setSelected(
- (mouseSelection || _actions.empty()) ? -1 : 0,
- mouseSelection);
- }
- const std::vector<not_null<QAction*>> &Menu::actions() const {
- return _actions;
- }
- void Menu::setForceWidth(int forceWidth) {
- _forceWidth = forceWidth;
- resizeFromInner(_forceWidth, height());
- }
- void Menu::updateSelected(QPoint globalPosition) {
- const auto p = mapFromGlobal(globalPosition) - QPoint(0, _st.skip);
- for (const auto &widget : _actionWidgets) {
- const auto widgetRect = QRect(widget->pos(), widget->size());
- if (widgetRect.contains(p)) {
- _lastSelectedByMouse = true;
- // It may actually fail to become selected (if it is disabled).
- widget->setSelected(true);
- break;
- }
- }
- }
- void Menu::itemPressed(TriggeredSource source) {
- if (const auto action = findSelectedAction()) {
- if (action->lastTriggeredSource() == source) {
- action->setClicked(source);
- }
- }
- }
- void Menu::keyPressEvent(QKeyEvent *e) {
- const auto key = e->key();
- if (!_keyPressDelegate || !_keyPressDelegate(key)) {
- handleKeyPress(e);
- }
- }
- ItemBase *Menu::findSelectedAction() const {
- const auto it = ranges::find_if(_actionWidgets, &ItemBase::isSelected);
- return (it == end(_actionWidgets)) ? nullptr : it->get();
- }
- void Menu::handleKeyPress(not_null<QKeyEvent*> e) {
- const auto key = e->key();
- const auto selected = findSelectedAction();
- if ((key != Qt::Key_Up && key != Qt::Key_Down) || _actions.empty()) {
- if (selected) {
- selected->handleKeyPress(e);
- }
- return;
- }
- const auto delta = (key == Qt::Key_Down ? 1 : -1);
- auto start = selected ? selected->index() : -1;
- if (start < 0 || start >= _actions.size()) {
- start = (delta > 0) ? (_actions.size() - 1) : 0;
- }
- auto newSelected = start;
- do {
- newSelected += delta;
- if (newSelected < 0) {
- newSelected += _actions.size();
- } else if (newSelected >= _actions.size()) {
- newSelected -= _actions.size();
- }
- } while (newSelected != start
- && (!_actionWidgets[newSelected]->isEnabled()));
- if (_actionWidgets[newSelected]->isEnabled()) {
- setSelected(newSelected, false);
- }
- }
- void Menu::clearSelection() {
- setSelected(-1, false);
- }
- void Menu::clearMouseSelection() {
- const auto selected = findSelectedAction();
- const auto mouseSelection = selected
- ? (selected->lastTriggeredSource() == TriggeredSource::Mouse)
- : false;
- if (mouseSelection && !_childShownAction) {
- clearSelection();
- }
- }
- void Menu::setSelected(int selected, bool isMouseSelection) {
- if (selected >= _actionWidgets.size()) {
- selected = -1;
- }
- const auto source = isMouseSelection
- ? TriggeredSource::Mouse
- : TriggeredSource::Keyboard;
- if (selected >= 0 && source == TriggeredSource::Keyboard) {
- const auto widget = _actionWidgets[selected].get();
- _scrollToRequests.fire({
- widget->y(),
- widget->y() + widget->height(),
- });
- }
- if (const auto selectedItem = findSelectedAction()) {
- if (selectedItem->index() == selected) {
- return;
- }
- selectedItem->setSelected(false, source);
- }
- if (selected >= 0) {
- _actionWidgets[selected].get()->setSelected(true, source);
- }
- }
- void Menu::mouseMoveEvent(QMouseEvent *e) {
- handleMouseMove(e->globalPos());
- }
- void Menu::handleMouseMove(QPoint globalPosition) {
- const auto margins = style::margins(0, _st.skip, 0, _st.skip);
- const auto inner = rect().marginsRemoved(margins);
- const auto localPosition = mapFromGlobal(globalPosition);
- if (inner.contains(localPosition)) {
- updateSelected(globalPosition);
- } else {
- clearMouseSelection();
- if (_mouseMoveDelegate) {
- _mouseMoveDelegate(globalPosition);
- }
- }
- }
- void Menu::mousePressEvent(QMouseEvent *e) {
- handleMousePress(e->globalPos());
- }
- void Menu::mouseReleaseEvent(QMouseEvent *e) {
- handleMouseRelease(e->globalPos());
- }
- void Menu::handleMousePress(QPoint globalPosition) {
- handleMouseMove(globalPosition);
- const auto margins = style::margins(0, _st.skip, 0, _st.skip);
- const auto inner = rect().marginsRemoved(margins);
- const auto localPosition = mapFromGlobal(globalPosition);
- const auto pressed = (inner.contains(localPosition)
- && _lastSelectedByMouse)
- ? findSelectedAction()
- : nullptr;
- if (pressed) {
- pressed->setClicked();
- } else {
- if (_mousePressDelegate) {
- _mousePressDelegate(globalPosition);
- }
- }
- }
- void Menu::handleMouseRelease(QPoint globalPosition) {
- if (!rect().contains(mapFromGlobal(globalPosition))
- && _mouseReleaseDelegate) {
- _mouseReleaseDelegate(globalPosition);
- }
- }
- } // namespace Ui::Menu
|