Applied patch by Pete Goodeve from ticket #7182. It improves

keyboard navigation/tracking of BMenus and BMenuBars, although
many issues remain.
Should not yet go into alpha, since there is one issue which
I am not sure if it's not a regression. The issue is that
invoking a menu item with Enter for the first time seems to
have no effect, while invoking it subsequently works as
expected. I don't know, yet, if that's a regression of this patch.
In any case, it's better than before, thanks, Pete!


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@42004 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus 2011-06-07 12:46:00 +00:00
parent 7b03963e8a
commit e750d35cbf
3 changed files with 69 additions and 22 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2006-2008, Haiku, Inc.
* Copyright 2006-2011, Haiku, Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
@ -14,6 +14,8 @@
enum menu_states {
MENU_STATE_TRACKING = 0,
MENU_STATE_TRACKING_SUBMENU = 1,
MENU_STATE_KEY_TO_SUBMENU = 2,
MENU_STATE_KEY_LEAVE_SUBMENU = 3,
MENU_STATE_CLOSED = 5
};

View File

@ -1,5 +1,5 @@
/*
* Copyright 2001-2009, Haiku Inc. All rights reserved.
* Copyright 2001-2011, Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT license.
*
* Authors:
@ -477,6 +477,13 @@ BMenu::KeyDown(const char* bytes, int32 numBytes)
break;
case B_DOWN_ARROW:
{
BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
if (bar != NULL && fState == MENU_STATE_CLOSED) {
// tell MenuBar's _Track:
bar->fState = MENU_STATE_KEY_TO_SUBMENU;
}
}
if (fLayout == B_ITEMS_IN_COLUMN)
_SelectNextItem(fSelected, true);
break;
@ -494,8 +501,10 @@ BMenu::KeyDown(const char* bytes, int32 numBytes)
// another top level menu.
BMessenger msgr(Supermenu());
msgr.SendMessage(Window()->CurrentMessage());
} else
_QuitTracking();
} else {
// tell _Track
fState = MENU_STATE_KEY_LEAVE_SUBMENU;
}
}
}
break;
@ -505,10 +514,11 @@ BMenu::KeyDown(const char* bytes, int32 numBytes)
_SelectNextItem(fSelected, true);
else {
if (fSelected && fSelected->Submenu()) {
fSelected->Submenu()->_SetStickyMode(true);
fSelected->Submenu()->_SetStickyMode(true);
// fix me: this shouldn't be needed but dynamic menus
// aren't getting it set correctly when keyboard
// navigating, which aborts the attach
fState = MENU_STATE_KEY_TO_SUBMENU;
_SelectItem(fSelected, true, true, true);
} else if (dynamic_cast<BMenuBar*>(Supermenu())) {
// if we have no submenu and we're an
@ -539,13 +549,20 @@ BMenu::KeyDown(const char* bytes, int32 numBytes)
case B_ENTER:
case B_SPACE:
if (fSelected) {
_InvokeItem(fSelected);
// preserve for exit handling
fChosenItem = fSelected;
_QuitTracking(false);
}
break;
case B_ESCAPE:
_QuitTracking();
_SelectItem(NULL);
if (fState == MENU_STATE_CLOSED && dynamic_cast<BMenuBar*>(Supermenu())) {
// Keyboard may show menu without tracking it
BMessenger msgr(Supermenu());
msgr.SendMessage(Window()->CurrentMessage());
} else
_QuitTracking(false);
break;
default:
@ -1580,8 +1597,8 @@ BMenu::_Track(int* action, long start)
bigtime_t navigationAreaTime = 0;
fState = MENU_STATE_TRACKING;
if (fSuper != NULL)
fSuper->fState = MENU_STATE_TRACKING_SUBMENU;
// we will use this for keyboard selection:
fChosenItem = NULL;
BPoint location;
uint32 buttons = 0;
@ -1608,11 +1625,15 @@ BMenu::_Track(int* action, long start)
// The order of the checks is important
// to be able to handle overlapping menus:
// first we check if mouse is inside a submenu,
// then if the menu is inside this menu,
// then if the mouse is inside this menu,
// then if it's over a super menu.
bool overSub = _OverSubmenu(fSelected, screenLocation);
item = _HitTestItems(location, B_ORIGIN);
if (overSub) {
if (overSub || fState == MENU_STATE_KEY_TO_SUBMENU) {
if (fState == MENU_STATE_TRACKING) {
// not if from R.Arrow
fState = MENU_STATE_TRACKING_SUBMENU;
}
navAreaRectAbove = BRect();
navAreaRectBelow = BRect();
@ -1633,7 +1654,19 @@ BMenu::_Track(int* action, long start)
if (submenuAction == MENU_STATE_CLOSED) {
item = submenuItem;
fState = MENU_STATE_CLOSED;
}
} else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
if (LockLooper()) {
BMenuItem *temp = fSelected;
// close the submenu:
_SelectItem(NULL);
// but reselect the item itself for user:
_SelectItem(temp, false);
UnlockLooper();
}
// cancel key-nav state
fState = MENU_STATE_TRACKING;
} else
fState = MENU_STATE_TRACKING;
if (!LockLooper())
break;
} else if (item != NULL) {
@ -1641,11 +1674,14 @@ BMenu::_Track(int* action, long start)
navAreaRectBelow, selectedTime, navigationAreaTime);
if (!releasedOnce)
releasedOnce = true;
} else if (_OverSuper(screenLocation)) {
} else if (_OverSuper(screenLocation) && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
fState = MENU_STATE_TRACKING;
UnlockLooper();
break;
} else {
} else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
UnlockLooper();
break;
} else if (fSuper == NULL || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
// Mouse pointer outside menu:
// If there's no other submenu opened,
// deselect the current selected item
@ -1676,7 +1712,7 @@ BMenu::_Track(int* action, long start)
uint32 newButtons = buttons;
// If user doesn't move the mouse, loop here,
// so we don't interfer with keyboard menu navigation
// so we don't interfere with keyboard menu navigation
do {
snooze(snoozeAmount);
if (!LockLooper())
@ -1684,7 +1720,8 @@ BMenu::_Track(int* action, long start)
GetMouse(&newLocation, &newButtons, true);
UnlockLooper();
} while (newLocation == location && newButtons == buttons
&& !(item && item->Submenu() != NULL));
&& !(item && item->Submenu() != NULL)
&& fState == MENU_STATE_TRACKING);
if (newLocation != location || newButtons != buttons) {
if (!releasedOnce && newButtons == 0 && buttons != 0)
@ -1700,12 +1737,19 @@ BMenu::_Track(int* action, long start)
if (action != NULL)
*action = fState;
// keyboard Enter will set this
if (fChosenItem != NULL)
item = fChosenItem;
else if (fSelected == NULL)
// needed to cover (rare) mouse/ESC combination
item = NULL;
if (fSelected != NULL && LockLooper()) {
_SelectItem(NULL);
UnlockLooper();
}
// delete the menu window recycled for all the child menus
_DeleteMenuWindow();
@ -2829,7 +2873,6 @@ BMenu::_QuitTracking(bool onlyThis)
if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
menuBar->_RestoreFocus();
fChosenItem = NULL;
fState = MENU_STATE_CLOSED;
// Close the whole menu hierarchy

View File

@ -566,7 +566,7 @@ BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
if (window->Lock()) {
if (startIndex != -1) {
be_app->ObscureCursor();
_SelectItem(ItemAt(startIndex), true, true);
_SelectItem(ItemAt(startIndex), true, false);
}
GetMouse(&where, &buttons);
window->Unlock();
@ -582,7 +582,8 @@ BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
menuItem = ItemAt(0);
else
menuItem = _HitTestItems(where, B_ORIGIN);
if (_OverSubmenu(fSelected, ConvertToScreen(where))) {
if (_OverSubmenu(fSelected, ConvertToScreen(where))
|| fState == MENU_STATE_KEY_TO_SUBMENU) {
// call _Track() from the selected sub-menu when the mouse cursor
// is over its window
BMenu* menu = fSelected->Submenu();
@ -645,7 +646,7 @@ BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
if (fState != MENU_STATE_CLOSED) {
// If user doesn't move the mouse, loop here,
// so we don't interfer with keyboard menu navigation
// so we don't interfere with keyboard menu navigation
BPoint newLocation = where;
uint32 newButtons = buttons;
do {
@ -654,7 +655,8 @@ BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
break;
GetMouse(&newLocation, &newButtons, true);
UnlockLooper();
} while (newLocation == where && newButtons == buttons);
} while (newLocation == where && newButtons == buttons
&& fState == MENU_STATE_TRACKING);
where = newLocation;
buttons = newButtons;