BMenuItem* BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu) { // TODO: Cleanup, merge some "if" blocks if possible fChosenItem = NULL; BWindow* window = Window(); fState = MENU_STATE_TRACKING; BPoint where; uint32 buttons; if (window->Lock()) { if (startIndex != -1) { be_app->ObscureCursor(); _SelectItem(ItemAt(startIndex), true, false); } GetMouse(&where, &buttons); window->Unlock(); } while (fState != MENU_STATE_CLOSED) { bigtime_t snoozeAmount = 40000; if (Window() == NULL || !window->Lock()) break; BMenuItem* menuItem = NULL; if (dynamic_cast<_BMCMenuBar_*>(this)) menuItem = ItemAt(0); else menuItem = _HitTestItems(where, B_ORIGIN); 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(); window->Unlock(); snoozeAmount = 30000; bool wasSticky = _IsStickyMode(); menu->_SetStickyMode(wasSticky); int localAction; fChosenItem = menu->_Track(&localAction); // The mouse could have meen moved since the last time we // checked its position, or buttons might have been pressed. // Unfortunately our child menus don't tell // us the new position. // TODO: Maybe have a shared struct between all menus // where to store the current mouse position ? // (Or just use the BView mouse hooks) BPoint newWhere; if (window->Lock()) { GetMouse(&newWhere, &buttons); window->Unlock(); } // This code is needed to make menus // that are children of BMenuFields "sticky" (see ticket #953) if (localAction == MENU_STATE_CLOSED) { if (fExtraRect != NULL && fExtraRect->Contains(where) // 9 = 3 pixels ^ 2 (since point_distance() returns the // square of the distance) && point_distance(newWhere, where) < 9) { _SetStickyMode(true); fExtraRect = NULL; } else fState = MENU_STATE_CLOSED; } if (!window->Lock()) break; } else if (menuItem != NULL) { if (menuItem->Submenu() != NULL && menuItem != fSelected) { if (menuItem->Submenu()->Window() == NULL) { // open the menu if it's not opened yet _SelectItem(menuItem); } else { // Menu was already opened, close it and bail _SelectItem(NULL); fState = MENU_STATE_CLOSED; fChosenItem = NULL; } } else { // No submenu, just select the item _SelectItem(menuItem); } } else if (menuItem == NULL && fSelected != NULL && !_IsStickyMode() && Bounds().Contains(where)) { _SelectItem(NULL); fState = MENU_STATE_TRACKING; } window->Unlock(); if (fState != MENU_STATE_CLOSED) { // If user doesn't move the mouse, loop here, // so we don't interfere with keyboard menu navigation BPoint newLocation = where; uint32 newButtons = buttons; do { snooze(snoozeAmount); if (!LockLooper()) break; GetMouse(&newLocation, &newButtons, true); UnlockLooper(); } while (newLocation == where && newButtons == buttons && fState == MENU_STATE_TRACKING); where = newLocation; buttons = newButtons; if (buttons != 0 && _IsStickyMode()) { if (menuItem == NULL || (menuItem->Submenu() && menuItem->Submenu()->Window())) { // clicked outside menu bar or on item with already // open sub menu fState = MENU_STATE_CLOSED; } else _SetStickyMode(false); } else if (buttons == 0 && !_IsStickyMode()) { if ((fSelected != NULL && fSelected->Submenu() == NULL) || menuItem == NULL) { fChosenItem = fSelected; fState = MENU_STATE_CLOSED; } else _SetStickyMode(true); } } } if (window->Lock()) { if (fSelected != NULL) _SelectItem(NULL); if (fChosenItem != NULL) fChosenItem->Invoke(); _RestoreFocus(); window->Unlock(); } if (_IsStickyMode()) _SetStickyMode(false); _DeleteMenuWindow(); if (action != NULL) *action = fState; return fChosenItem; }