QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
menubarpeek.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "menubarpeek.h"
9#include <QAction>
10#include <QApplication>
11#include <QKeyEvent>
12#include <QMainWindow>
13#include <QMenu>
14#include <QMenuBar>
15#include <QTimer>
16
17MenuBarPeek::MenuBarPeek(QMainWindow *window, QAction *toggle)
18 : QObject(window), m_window(window), m_toggle(toggle) {
19 // Key events go to the focus widget, and an open menu is a window of its
20 // own, so the filter has to sit on the application.
21 qApp->installEventFilter(this);
22 connect(m_toggle, &QAction::toggled, this, [this] { m_peeking = false; });
23 for (QAction *entry : m_window->menuBar()->actions()) {
24 if (QMenu *menu = entry->menu()) {
25 connect(menu, &QMenu::aboutToHide, this,
27 }
28 }
29}
30
31auto MenuBarPeek::isPeeking() const -> bool { return m_peeking; }
32
33auto MenuBarPeek::eventFilter(QObject *watched, QEvent *event) -> bool {
34 switch (event->type()) {
35 case QEvent::KeyPress:
36 onKeyPress(watched, static_cast<QKeyEvent *>(event));
37 break;
38 case QEvent::KeyRelease:
39 onKeyRelease(static_cast<QKeyEvent *>(event));
40 break;
41 case QEvent::MouseButtonPress:
42 if (m_peeking && inWindow(watched) && !onMenuBar(watched)) {
44 }
45 break;
46 case QEvent::WindowDeactivate:
47 if (watched == m_window) {
48 // Alt+Tab: the release goes to another application.
49 m_altDown = false;
51 }
52 break;
53 default:
54 break;
55 }
56 return false;
57}
58
59// A key event reaches the filter once for every widget it propagates to, and
60// again as auto-repeat; m_altDown makes the second sighting a no-op.
61void MenuBarPeek::onKeyPress(QObject *watched, const QKeyEvent *event) {
62 if (event->key() != Qt::Key_Alt) {
63 m_altAlone = false;
64 if (event->key() == Qt::Key_Escape && m_peeking) {
66 }
67 return;
68 }
69 if (m_altDown || event->isAutoRepeat() || !inWindow(watched)) {
70 return;
71 }
72 m_altDown = true;
73 m_altAlone = true;
75 !m_peeking && !m_toggle->isChecked() && !m_window->menuBar()->isVisible();
77 show();
78 }
79}
80
81void MenuBarPeek::onKeyRelease(const QKeyEvent *event) {
82 if (event->key() != Qt::Key_Alt || !m_altDown || event->isAutoRepeat()) {
83 return;
84 }
85 m_altDown = false;
86 if (!m_peeking) {
87 return;
88 }
90 hide(); // the second tap
91 } else if (!m_altAlone) {
92 hideWhenNoMenuIsOpen(); // Alt+something that was not a menu
93 }
94}
95
96auto MenuBarPeek::inWindow(QObject *watched) const -> bool {
97 const auto *widget = qobject_cast<QWidget *>(watched);
98 return widget != nullptr && widget->window() == m_window;
99}
100
101auto MenuBarPeek::onMenuBar(QObject *watched) const -> bool {
102 const auto *widget = qobject_cast<QWidget *>(watched);
103 const QMenuBar *bar = m_window->menuBar();
104 return widget == bar || bar->isAncestorOf(widget);
105}
106
108 m_peeking = true;
109 m_window->menuBar()->show();
110}
111
113 m_peeking = false;
114 if (!m_toggle->isChecked()) {
115 m_window->menuBar()->hide();
116 }
117}
118
119// Deferred: a menu's aboutToHide also fires when the pointer moves on to the
120// next menu, and Escape is seen here before the bar leaves keyboard mode.
122 QTimer::singleShot(0, this, [this] {
123 if (m_peeking && QApplication::activePopupWidget() == nullptr &&
124 m_window->menuBar()->activeAction() == nullptr) {
125 hide();
126 }
127 });
128}
void onKeyRelease(const QKeyEvent *event)
Handle the release of Alt: a second lone tap hides the bar, an Alt+key chord that did not open a menu...
void show()
Show the menu bar and mark the peek as active.
bool m_peeking
true while the bar is shown because of Alt, not the toggle.
MenuBarPeek(QMainWindow *window, QAction *toggle)
Watch window for Alt while its menu bar is hidden.
bool m_altDown
true between an Alt press in the window and its release (or the window losing activation); filters re...
bool m_shownByThisPress
true when the current Alt press is the one that showed the bar, so its release must not hide it again...
void hideWhenNoMenuIsOpen()
hide() on the next event-loop pass, and only if no popup is open and the bar has no active action....
QMainWindow * m_window
The main window whose menuBar() is shown and hidden; the parent.
auto eventFilter(QObject *watched, QEvent *event) -> bool override
Follows Alt, Escape, clicks and focus for the whole application.
void onKeyPress(QObject *watched, const QKeyEvent *event)
Handle a key press seen by the application filter.
void hide()
End the peek; hides the menu bar unless the "Show menu bar" toggle was checked in the meantime.
auto isPeeking() const -> bool
Whether the bar is up for a peek rather than by choice.
auto onMenuBar(QObject *watched) const -> bool
Whether watched is the menu bar itself or one of its children, so a click there must not end the peek...
QAction * m_toggle
The window's "Show menu bar" action; while checked the bar is up by choice and the peek state is clea...
bool m_altAlone
true while no other key has been pressed since Alt went down, i.e. the release will be a lone tap rat...
auto inWindow(QObject *watched) const -> bool
Whether watched is a widget that belongs to the main window.
MenuBarPeek: shows the main window's hidden menu bar while Alt is held or tapped, and hides it again ...