QtPass Developer documentation
QtPass test coverage
Current view: top level - src/menubarpeek.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 92.8 % 69 64
Test Date: 2026-11-14 13:19:18 Functions: 81.8 % 11 9

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements MenuBarPeek; an application-wide event filter installed
       6              :  *        in the constructor feeds onKeyPress() and onKeyRelease().
       7              :  */
       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              : 
      17          131 : MenuBarPeek::MenuBarPeek(QMainWindow *window, QAction *toggle)
      18          131 :     : 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          131 :   qApp->installEventFilter(this);
      22          136 :   connect(m_toggle, &QAction::toggled, this, [this] { m_peeking = false; });
      23          917 :   for (QAction *entry : m_window->menuBar()->actions()) {
      24          524 :     if (QMenu *menu = entry->menu()) {
      25          524 :       connect(menu, &QMenu::aboutToHide, this,
      26         1048 :               &MenuBarPeek::hideWhenNoMenuIsOpen);
      27              :     }
      28              :   }
      29          131 : }
      30              : 
      31            0 : auto MenuBarPeek::isPeeking() const -> bool { return m_peeking; }
      32              : 
      33       110112 : auto MenuBarPeek::eventFilter(QObject *watched, QEvent *event) -> bool {
      34       110112 :   switch (event->type()) {
      35           20 :   case QEvent::KeyPress:
      36           20 :     onKeyPress(watched, static_cast<QKeyEvent *>(event));
      37           20 :     break;
      38           16 :   case QEvent::KeyRelease:
      39           16 :     onKeyRelease(static_cast<QKeyEvent *>(event));
      40           16 :     break;
      41            1 :   case QEvent::MouseButtonPress:
      42            1 :     if (m_peeking && inWindow(watched) && !onMenuBar(watched)) {
      43            0 :       hideWhenNoMenuIsOpen();
      44              :     }
      45              :     break;
      46         1112 :   case QEvent::WindowDeactivate:
      47         1112 :     if (watched == m_window) {
      48              :       // Alt+Tab: the release goes to another application.
      49           25 :       m_altDown = false;
      50           25 :       hideWhenNoMenuIsOpen();
      51              :     }
      52              :     break;
      53              :   default:
      54              :     break;
      55              :   }
      56       110112 :   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.
      61           20 : void MenuBarPeek::onKeyPress(QObject *watched, const QKeyEvent *event) {
      62           20 :   if (event->key() != Qt::Key_Alt) {
      63           10 :     m_altAlone = false;
      64           10 :     if (event->key() == Qt::Key_Escape && m_peeking) {
      65            1 :       hideWhenNoMenuIsOpen();
      66              :     }
      67           10 :     return;
      68              :   }
      69           10 :   if (m_altDown || event->isAutoRepeat() || !inWindow(watched)) {
      70            2 :     return;
      71              :   }
      72            8 :   m_altDown = true;
      73            8 :   m_altAlone = true;
      74            8 :   m_shownByThisPress =
      75            8 :       !m_peeking && !m_toggle->isChecked() && !m_window->menuBar()->isVisible();
      76            8 :   if (m_shownByThisPress) {
      77            5 :     show();
      78              :   }
      79              : }
      80              : 
      81           16 : void MenuBarPeek::onKeyRelease(const QKeyEvent *event) {
      82           16 :   if (event->key() != Qt::Key_Alt || !m_altDown || event->isAutoRepeat()) {
      83              :     return;
      84              :   }
      85            8 :   m_altDown = false;
      86            8 :   if (!m_peeking) {
      87              :     return;
      88              :   }
      89            6 :   if (m_altAlone && !m_shownByThisPress) {
      90            1 :     hide(); // the second tap
      91            5 :   } else if (!m_altAlone) {
      92            1 :     hideWhenNoMenuIsOpen(); // Alt+something that was not a menu
      93              :   }
      94              : }
      95              : 
      96            8 : auto MenuBarPeek::inWindow(QObject *watched) const -> bool {
      97              :   const auto *widget = qobject_cast<QWidget *>(watched);
      98            8 :   return widget != nullptr && widget->window() == m_window;
      99              : }
     100              : 
     101            0 : auto MenuBarPeek::onMenuBar(QObject *watched) const -> bool {
     102              :   const auto *widget = qobject_cast<QWidget *>(watched);
     103            0 :   const QMenuBar *bar = m_window->menuBar();
     104            0 :   return widget == bar || bar->isAncestorOf(widget);
     105              : }
     106              : 
     107            5 : void MenuBarPeek::show() {
     108            5 :   m_peeking = true;
     109            5 :   m_window->menuBar()->show();
     110            5 : }
     111              : 
     112            4 : void MenuBarPeek::hide() {
     113            4 :   m_peeking = false;
     114            4 :   if (!m_toggle->isChecked()) {
     115            4 :     m_window->menuBar()->hide();
     116              :   }
     117            4 : }
     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.
     121           28 : void MenuBarPeek::hideWhenNoMenuIsOpen() {
     122           28 :   QTimer::singleShot(0, this, [this] {
     123           11 :     if (m_peeking && QApplication::activePopupWidget() == nullptr &&
     124            3 :         m_window->menuBar()->activeAction() == nullptr) {
     125            3 :       hide();
     126              :     }
     127            8 :   });
     128           28 : }
        

Generated by: LCOV version 2.0-1