QToolBar焦點問題 (QTBUG18896)

QTBUG18896問題

Sometimes it occurs that keypresses act like the Alt-key has been pressed also, which means that the key event is only sent to the menubar and not to the application. I encountered this in our own application, but I could reproduce this with Qt Designer. Please follow the next recipe exactly and come back to me if you can't reproduce.

簡單地說:菜單欄原本須要ALT+'M'(或其餘字符)來激活並彈出某個菜單,如今直接按'M'就能夠激活了。 ide

初次看到感受頗有意思,後來發現問題還算簡單。咱們能夠用下面的程序來重現這個問題:工具

#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>

class MainWindow : public QMainWindow
{
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow(){}
};

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    QMenu * menu = menuBar()->addMenu("&Menu");
    menu->addAction("&Item");
    menu->addAction("I&tem");
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}
  • 一旦菜單欄得到焦點,咱們就能夠經過"m"鍵來彈出菜單(而不須要"Alt+M")。這是正常行爲
  • 在這個例子中,一旦工具欄得到焦點,它不會自動失去焦點(沒有其餘控件接受焦點)。這是緣由

根源

  • 工具欄開始工做時,
    • setKeyboardMode(true)
    • 會先設置本身得到焦點(若是這次其餘widget擁有焦點,則先保存下來該指針(使用QPointer,做用你應該懂的))。
  • 工做完畢
    • setKeyboardMode(false)
    • 嘗試恢復原來的焦點
    • 若是一開始沒有其餘widget擁有焦點,或者擁有焦點的widget在這期間被銷燬了怎麼辦??
      • Qt 目前的作法是:讓工具欄繼續持有焦點
      • 咱們期待的應:讓工具欄失去焦點

源碼

  • QToolBar 初始化會給它的parent和它所在的頂級窗口安裝事件過濾器:
void QMenuBarPrivate::handleReparent()
{
    Q_Q(QMenuBar);
    QWidget *newParent = q->parentWidget();
    //Note: if parent is reparented, then window may change even if parent doesn't

    // we need to install an event filter on parent, and remove the old one

    if (oldParent != newParent) {
        if (oldParent)
            oldParent->removeEventFilter(q);
        if (newParent)
            newParent->installEventFilter(q);
    }

    //we also need event filter on top-level (for shortcuts)
    QWidget *newWindow = newParent ? newParent->window() : 0;

    if (oldWindow != newWindow) {
        if (oldParent && oldParent != oldWindow)
            oldWindow->removeEventFilter(q);

        if (newParent && newParent != newWindow)
            newWindow->installEventFilter(q);
    }

    oldParent = newParent;
    oldWindow = newWindow;
  • 在顯示以前,它還會將菜單欄各菜單的加速鍵註冊成快捷鍵:
    • 經過QKeySequence的mnemonic()成員
void QMenuBarPrivate::updateGeometries()
{
...
        for(int i = 0; i < actions.count(); i++)
            shortcutIndexMap.append(q->grabShortcut(QKeySequence::mnemonic(actions.at(i)->text())));
  • 事件過濾器所作工做:
    • 主要爲了處理Alt鍵?(菜單導航)
    • 注意:一旦遇到包含Alt鍵值的QEvent::ShortcutOverride事件,它會將本身安裝成QApplication的事件過濾器this

bool QMenuBar::eventFilter(QObject *object, QEvent *event)
{
    if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this)) {
        if (d->altPressed) {
            switch (event->type()) {
            case QEvent::KeyPress:
            case QEvent::KeyRelease:
            {
                QKeyEvent *kev = static_cast<QKeyEvent*>(event);
                if (kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta) {
                    if (event->type() == QEvent::KeyPress) // Alt-press does not interest us, we have the shortcut-override event
                        break;
                    d->setKeyboardMode(!d->keyboardState);
                }
            }
            // fall through
            case QEvent::MouseButtonPress:
            case QEvent::MouseButtonRelease:
            case QEvent::MouseMove:
            case QEvent::FocusIn:
            case QEvent::FocusOut:
            case QEvent::ActivationChange:
                d->altPressed = false;
                qApp->removeEventFilter(this);
                break;
            default:
                break;
            }
        } else if (isVisible()) {
            if (event->type() == QEvent::ShortcutOverride) {
                QKeyEvent *kev = static_cast<QKeyEvent*>(event);
                if ((kev->key() == Qt::Key_Alt || kev->key() == Qt::Key_Meta)
                    && kev->modifiers() == Qt::AltModifier) {
                    d->altPressed = true;
                    qApp->installEventFilter(this);
                }
            }
        }
    }

    return false;
  • QShortcutEvent 事件的接收
    • 其中 _q_internalShortcutActivated 用來激活(彈出)相應的菜單。
bool QMenuBar::event(QEvent *e)
{
    Q_D(QMenuBar);
    switch (e->type()) {
    case QEvent::Shortcut: {
        QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
        int shortcutId = se->shortcutId();
        for(int j = 0; j < d->shortcutIndexMap.size(); ++j) {
            if (shortcutId == d->shortcutIndexMap.value(j))
                d->_q_internalShortcutActivated(j);
        }
    } break;
  • keypress的處理
    • 菜單的導航控制
    • 菜單的彈出控制
    • 對加速鍵字符的處理
void QMenuBar::keyPressEvent(QKeyEvent *e)
{
    Q_D(QMenuBar);
    d->updateGeometries();
    int key = e->key();
    if(isRightToLeft()) {  // in reverse mode open/close key for submenues are reversed
        if(key == Qt::Key_Left)
            key = Qt::Key_Right;
        else if(key == Qt::Key_Right)
            key = Qt::Key_Left;
    }
    if(key == Qt::Key_Tab) //means right
        key = Qt::Key_Right;
    else if(key == Qt::Key_Backtab) //means left
        key = Qt::Key_Left;

    bool key_consumed = false;
    switch(key) {
    case Qt::Key_Up:
    case Qt::Key_Down:
    case Qt::Key_Enter:
    case Qt::Key_Space:
    case Qt::Key_Return: {
        if(!style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this) || !d->currentAction)
           break;
        if(d->currentAction->menu()) {
            d->popupAction(d->currentAction, true);
        } else if(key == Qt::Key_Enter || key == Qt::Key_Return || key == Qt::Key_Space) {
            d->activateAction(d->currentAction, QAction::Trigger);
            d->setCurrentAction(d->currentAction, false);
            d->setKeyboardMode(false);
        }
        key_consumed = true;
        break; }

    case Qt::Key_Right:
    case Qt::Key_Left: {
        if(d->currentAction) {
            int index = d->actions.indexOf(d->currentAction);
            if (QAction *nextAction = d->getNextAction(index, key == Qt::Key_Left ? -1 : +1)) {
                d->setCurrentAction(nextAction, d->popupState, true);
                key_consumed = true;
            }
        }
        break; }

    case Qt::Key_Escape:
        d->setCurrentAction(0);
        d->setKeyboardMode(false);
        key_consumed = true;
        break;

    default:
        key_consumed = false;
    }

    if(!key_consumed &&
       (!e->modifiers() ||
        (e->modifiers()&(Qt::MetaModifier|Qt::AltModifier))) && e->text().length()==1 && !d->popupState) {
        int clashCount = 0;
        QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0;
        {
            QChar c = e->text()[0].toUpper();
            for(int i = 0; i < d->actions.size(); ++i) {
                if (d->actionRects.at(i).isNull())
                    continue;
                QAction *act = d->actions.at(i);
                QString s = act->text();
                if(!s.isEmpty()) {
                    int ampersand = s.indexOf(QLatin1Char('&'));
                    if(ampersand >= 0) {
                        if(s[ampersand+1].toUpper() == c) {
                            clashCount++;
                            if(!first)
                                first = act;
                            if(act == d->currentAction)
                                currentSelected = act;
                            else if (!firstAfterCurrent && currentSelected)
                                firstAfterCurrent = act;
                        }
                    }
                }
            }
        }
        QAction *next_action = 0;
        if(clashCount >= 1) {
            if(clashCount == 1 || !d->currentAction || (currentSelected && !firstAfterCurrent))
                next_action = first;
            else
                next_action = firstAfterCurrent;
        }
        if(next_action) {
            key_consumed = true;
            d->setCurrentAction(next_action, true, true);
        }
    }
    if(key_consumed)
        e->accept();
    else
        e->ignore();
}
  • 這兒還有一個setKeyboardMode,作什麼工做呢?
    • 控制焦點轉移的
      • true:設置本身爲焦點控件,將先保存原來擁有焦點的控件
      • false:恢復原來擁有焦點的控件
void QMenuBarPrivate::setKeyboardMode(bool b)
{
    Q_Q(QMenuBar);
    if (b && !q->style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, q)) {
        setCurrentAction(0);
        return;
    }
    keyboardState = b;
    if(b) {
        QWidget *fw = QApplication::focusWidget();
        if (fw != q)
            keyboardFocusWidget = fw;
        focusFirstAction();
        q->setFocus(Qt::MenuBarFocusReason);
    } else {
        if(!popupState)
            setCurrentAction(0);
        if(keyboardFocusWidget) {
            if (QApplication::focusWidget() == q)
                keyboardFocusWidget->setFocus(Qt::MenuBarFocusReason);
            keyboardFocusWidget = 0;
        }
    }
    q->update();
}

參考

相關文章
相關標籤/搜索