1c7023acdSAxel Dörfler /* 2c7023acdSAxel Dörfler * Copyright 2001-2006, Haiku, Inc. 3c7023acdSAxel Dörfler * Distributed under the terms of the MIT License. 4c7023acdSAxel Dörfler * 5c7023acdSAxel Dörfler * Authors: 6c7023acdSAxel Dörfler * Marc Flerackers (mflerackers@androme.be) 7c7023acdSAxel Dörfler * Stefano Ceccherini (burton666@libero.it) 8*27cc2508SStefano Ceccherini * 9c7023acdSAxel Dörfler */ 10c7023acdSAxel Dörfler 11c7023acdSAxel Dörfler //! BMenuWindow is a custom BWindow for BMenus. 12c7023acdSAxel Dörfler 13446b8c19SStefano Ceccherini #include <Menu.h> 14446b8c19SStefano Ceccherini 152c11ec31SStefano Ceccherini #include <MenuPrivate.h> 162c11ec31SStefano Ceccherini #include <MenuWindow.h> 17c7023acdSAxel Dörfler #include <WindowPrivate.h> 18c7023acdSAxel Dörfler 19e411c658SStefano Ceccherini 20*27cc2508SStefano Ceccherini const int kScrollerHeight = 10; 21*27cc2508SStefano Ceccherini const int kScrollStep = 16; 225b752875SStefano Ceccherini 23*27cc2508SStefano Ceccherini BMenuScroller::BMenuScroller(BRect frame, BMenu *menu) 24*27cc2508SStefano Ceccherini : BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS 25*27cc2508SStefano Ceccherini | B_PULSE_NEEDED), 26*27cc2508SStefano Ceccherini fMenu(menu), 27*27cc2508SStefano Ceccherini fUpperButton(0, 0, frame.right, kScrollerHeight), 28*27cc2508SStefano Ceccherini fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom), 29*27cc2508SStefano Ceccherini fValue(0), 30*27cc2508SStefano Ceccherini fUpperEnabled(false), 31*27cc2508SStefano Ceccherini fLowerEnabled(true) 32*27cc2508SStefano Ceccherini { 33*27cc2508SStefano Ceccherini if (!menu) 34*27cc2508SStefano Ceccherini debugger("BMenuScroller(): Scroller not attached to a menu!"); 35*27cc2508SStefano Ceccherini SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); 3661ba5a32SStefano Ceccherini 37*27cc2508SStefano Ceccherini fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight); 38*27cc2508SStefano Ceccherini } 39*27cc2508SStefano Ceccherini 40*27cc2508SStefano Ceccherini 41*27cc2508SStefano Ceccherini BMenuScroller::~BMenuScroller() 42*27cc2508SStefano Ceccherini { 43*27cc2508SStefano Ceccherini } 44*27cc2508SStefano Ceccherini 45*27cc2508SStefano Ceccherini 46*27cc2508SStefano Ceccherini void 47*27cc2508SStefano Ceccherini BMenuScroller::Pulse() 48*27cc2508SStefano Ceccherini { 49*27cc2508SStefano Ceccherini // To reduce the overhead even a little, fButton and fPosition were 50*27cc2508SStefano Ceccherini // made private variables. 51*27cc2508SStefano Ceccherini 52*27cc2508SStefano Ceccherini // We track the mouse and move the scrollers depending on its position. 53*27cc2508SStefano Ceccherini GetMouse(&fPosition, &fButton); 54*27cc2508SStefano Ceccherini if (fLowerButton.Contains(fPosition) && fLowerEnabled) { 55*27cc2508SStefano Ceccherini if (fValue == 0) { 56*27cc2508SStefano Ceccherini fUpperEnabled = true; 57*27cc2508SStefano Ceccherini 58*27cc2508SStefano Ceccherini Invalidate(fUpperButton); 59*27cc2508SStefano Ceccherini } 60*27cc2508SStefano Ceccherini 61*27cc2508SStefano Ceccherini if (fValue + kScrollStep >= fLimit) { 62*27cc2508SStefano Ceccherini // If we reached the limit, we don't want to scroll a whole 63*27cc2508SStefano Ceccherini // 'step' if not needed. 64*27cc2508SStefano Ceccherini fMenu->ScrollBy(0, fLimit - fValue); 65*27cc2508SStefano Ceccherini fValue = fLimit; 66*27cc2508SStefano Ceccherini fLowerEnabled = false; 67*27cc2508SStefano Ceccherini Invalidate(fLowerButton); 68*27cc2508SStefano Ceccherini } else { 69*27cc2508SStefano Ceccherini fMenu->ScrollBy(0, kScrollStep); 70*27cc2508SStefano Ceccherini fValue += kScrollStep; 71*27cc2508SStefano Ceccherini } 72*27cc2508SStefano Ceccherini } else if (fUpperButton.Contains(fPosition) && fUpperEnabled) { 73*27cc2508SStefano Ceccherini if (fValue == fLimit) { 74*27cc2508SStefano Ceccherini fLowerEnabled = true; 75*27cc2508SStefano Ceccherini Invalidate(fLowerButton); 76*27cc2508SStefano Ceccherini } 77*27cc2508SStefano Ceccherini 78*27cc2508SStefano Ceccherini if (fValue - kScrollStep <= 0) { 79*27cc2508SStefano Ceccherini fMenu->ScrollBy(0, -fValue); 80*27cc2508SStefano Ceccherini fValue = 0; 81*27cc2508SStefano Ceccherini fUpperEnabled = false; 82*27cc2508SStefano Ceccherini Invalidate(fUpperButton); 83*27cc2508SStefano Ceccherini } else { 84*27cc2508SStefano Ceccherini fMenu->ScrollBy(0, -kScrollStep); 85*27cc2508SStefano Ceccherini fValue -= kScrollStep; 86*27cc2508SStefano Ceccherini } 87*27cc2508SStefano Ceccherini 88*27cc2508SStefano Ceccherini // In this case, we need to redraw the lower button because of a 89*27cc2508SStefano Ceccherini // probable bug in ScrollBy handling. The scrolled view is drawing below 90*27cc2508SStefano Ceccherini // its bounds, dirtying our button in this process. 91*27cc2508SStefano Ceccherini // Redrawing it everytime makes scrolling a little slower. 92*27cc2508SStefano Ceccherini // TODO: Try to find why and fix this. 93*27cc2508SStefano Ceccherini Draw(fLowerButton); 94*27cc2508SStefano Ceccherini } 95*27cc2508SStefano Ceccherini } 96*27cc2508SStefano Ceccherini 97*27cc2508SStefano Ceccherini 98*27cc2508SStefano Ceccherini void 99*27cc2508SStefano Ceccherini BMenuScroller::Draw(BRect updateRect) 100*27cc2508SStefano Ceccherini { 101*27cc2508SStefano Ceccherini SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 102*27cc2508SStefano Ceccherini float middle = Bounds().right / 2; 103*27cc2508SStefano Ceccherini 104*27cc2508SStefano Ceccherini // Draw the upper arrow. 105*27cc2508SStefano Ceccherini if (updateRect.Intersects(fUpperButton)) { 106*27cc2508SStefano Ceccherini if (fUpperEnabled) 107*27cc2508SStefano Ceccherini SetHighColor(0, 0, 0); 108*27cc2508SStefano Ceccherini else 109*27cc2508SStefano Ceccherini SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 110*27cc2508SStefano Ceccherini B_DARKEN_2_TINT)); 111*27cc2508SStefano Ceccherini 112*27cc2508SStefano Ceccherini FillRect(fUpperButton, B_SOLID_LOW); 113*27cc2508SStefano Ceccherini 114*27cc2508SStefano Ceccherini FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3), 115*27cc2508SStefano Ceccherini BPoint(middle + 5, (kScrollerHeight / 2) + 2), 116*27cc2508SStefano Ceccherini BPoint(middle - 5, (kScrollerHeight / 2) + 2)); 117*27cc2508SStefano Ceccherini } 118*27cc2508SStefano Ceccherini 119*27cc2508SStefano Ceccherini // Draw the lower arrow. 120*27cc2508SStefano Ceccherini if (updateRect.Intersects(fLowerButton)) { 121*27cc2508SStefano Ceccherini if (fLowerEnabled) 122*27cc2508SStefano Ceccherini SetHighColor(0, 0, 0); 123*27cc2508SStefano Ceccherini else 124*27cc2508SStefano Ceccherini SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 125*27cc2508SStefano Ceccherini B_DARKEN_2_TINT)); 126*27cc2508SStefano Ceccherini 127*27cc2508SStefano Ceccherini FillRect(fLowerButton, B_SOLID_LOW); 128*27cc2508SStefano Ceccherini 129*27cc2508SStefano Ceccherini FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3), 130*27cc2508SStefano Ceccherini BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2), 131*27cc2508SStefano Ceccherini BPoint(middle - 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2)); 132*27cc2508SStefano Ceccherini } 133*27cc2508SStefano Ceccherini } 134*27cc2508SStefano Ceccherini 135*27cc2508SStefano Ceccherini 136*27cc2508SStefano Ceccherini // #pragma mark - 1375b752875SStefano Ceccherini 1385b752875SStefano Ceccherini 1394185bd8bSStefano Ceccherini BMenuWindow::BMenuWindow(const char *name) 1401664b981SStefano Ceccherini // The window will be resized by BMenu, so just pass a dummy rect 141c7023acdSAxel Dörfler : BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel, 142c7023acdSAxel Dörfler B_NOT_ZOOMABLE | B_AVOID_FOCUS), 143*27cc2508SStefano Ceccherini fScroller(NULL), 144*27cc2508SStefano Ceccherini fMenuFrame(NULL) 145446b8c19SStefano Ceccherini { 146*27cc2508SStefano Ceccherini SetPulseRate(200000); 147446b8c19SStefano Ceccherini } 148446b8c19SStefano Ceccherini 149446b8c19SStefano Ceccherini 150446b8c19SStefano Ceccherini BMenuWindow::~BMenuWindow() 151446b8c19SStefano Ceccherini { 152446b8c19SStefano Ceccherini } 1535aa032f1SStefano Ceccherini 1545aa032f1SStefano Ceccherini 15561ba5a32SStefano Ceccherini void 1564185bd8bSStefano Ceccherini BMenuWindow::AttachMenu(BMenu *menu) 1574185bd8bSStefano Ceccherini { 158*27cc2508SStefano Ceccherini if (fMenuFrame) 159*27cc2508SStefano Ceccherini debugger("BMenuWindow: a menu is already attached!"); 1604185bd8bSStefano Ceccherini if (menu != NULL) { 161*27cc2508SStefano Ceccherini fMenuFrame = new BMenuFrame(menu); 162*27cc2508SStefano Ceccherini AddChild(fMenuFrame); 16307dc2c69SAxel Dörfler menu->MakeFocus(true); 1644185bd8bSStefano Ceccherini } 1654185bd8bSStefano Ceccherini } 1664185bd8bSStefano Ceccherini 1674185bd8bSStefano Ceccherini 1684185bd8bSStefano Ceccherini void 1694185bd8bSStefano Ceccherini BMenuWindow::DetachMenu() 17061ba5a32SStefano Ceccherini { 171*27cc2508SStefano Ceccherini if (fMenuFrame) { 172*27cc2508SStefano Ceccherini if (fScroller) { 173*27cc2508SStefano Ceccherini DetachScrollers(); 174*27cc2508SStefano Ceccherini } else { 175*27cc2508SStefano Ceccherini RemoveChild(fMenuFrame); 176*27cc2508SStefano Ceccherini } 177*27cc2508SStefano Ceccherini delete fMenuFrame; 178*27cc2508SStefano Ceccherini fMenuFrame = NULL; 179*27cc2508SStefano Ceccherini } 180*27cc2508SStefano Ceccherini } 181*27cc2508SStefano Ceccherini 182*27cc2508SStefano Ceccherini 183*27cc2508SStefano Ceccherini void 184*27cc2508SStefano Ceccherini BMenuWindow::AttachScrollers() 185*27cc2508SStefano Ceccherini { 186*27cc2508SStefano Ceccherini // We want to attach a scroller only if there's a menu frame already 187*27cc2508SStefano Ceccherini // existing. 188*27cc2508SStefano Ceccherini if (fScroller || !fMenuFrame) 189*27cc2508SStefano Ceccherini return; 190*27cc2508SStefano Ceccherini 191*27cc2508SStefano Ceccherini RemoveChild(fMenuFrame); 192*27cc2508SStefano Ceccherini fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu); 193*27cc2508SStefano Ceccherini fScroller->AddChild(fMenuFrame); 194*27cc2508SStefano Ceccherini AddChild(fScroller); 195*27cc2508SStefano Ceccherini 196*27cc2508SStefano Ceccherini fMenuFrame->fMenu->MakeFocus(true); 197*27cc2508SStefano Ceccherini 198*27cc2508SStefano Ceccherini fMenuFrame->ResizeBy(0, -2 * kScrollerHeight); 199*27cc2508SStefano Ceccherini fMenuFrame->MoveBy(0, kScrollerHeight); 200*27cc2508SStefano Ceccherini } 201*27cc2508SStefano Ceccherini 202*27cc2508SStefano Ceccherini 203*27cc2508SStefano Ceccherini void 204*27cc2508SStefano Ceccherini BMenuWindow::DetachScrollers() 205*27cc2508SStefano Ceccherini { 206*27cc2508SStefano Ceccherini if(!fScroller || !fMenuFrame) 207*27cc2508SStefano Ceccherini return; 208*27cc2508SStefano Ceccherini 209*27cc2508SStefano Ceccherini // BeOS doesn't remember the position where the last scrolling ended, 210*27cc2508SStefano Ceccherini // so we just scroll back to the beginning. 211*27cc2508SStefano Ceccherini fMenuFrame->fMenu->ScrollTo(0, 0); 212*27cc2508SStefano Ceccherini 213*27cc2508SStefano Ceccherini fScroller->RemoveChild(fMenuFrame); 214*27cc2508SStefano Ceccherini RemoveChild(fScroller); 215*27cc2508SStefano Ceccherini 216*27cc2508SStefano Ceccherini delete fScroller; 217*27cc2508SStefano Ceccherini fScroller = NULL; 21861ba5a32SStefano Ceccherini } 21961ba5a32SStefano Ceccherini 22061ba5a32SStefano Ceccherini 221c7023acdSAxel Dörfler // #pragma mark - 222c7023acdSAxel Dörfler 223c7023acdSAxel Dörfler 22461ba5a32SStefano Ceccherini BMenuFrame::BMenuFrame(BMenu *menu) 225c7023acdSAxel Dörfler : BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW), 22661ba5a32SStefano Ceccherini fMenu(menu) 2275aa032f1SStefano Ceccherini { 2285aa032f1SStefano Ceccherini } 2295aa032f1SStefano Ceccherini 2305aa032f1SStefano Ceccherini 2315aa032f1SStefano Ceccherini void 2325aa032f1SStefano Ceccherini BMenuFrame::AttachedToWindow() 2335aa032f1SStefano Ceccherini { 2345aa032f1SStefano Ceccherini BView::AttachedToWindow(); 2354185bd8bSStefano Ceccherini 2364185bd8bSStefano Ceccherini if (fMenu != NULL) 2374185bd8bSStefano Ceccherini AddChild(fMenu); 2384185bd8bSStefano Ceccherini 2395aa032f1SStefano Ceccherini ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height()); 24055b35fb4SStefano Ceccherini if (fMenu != NULL) { 24155b35fb4SStefano Ceccherini BFont font; 24255b35fb4SStefano Ceccherini fMenu->GetFont(&font); 24355b35fb4SStefano Ceccherini SetFont(&font); 24455b35fb4SStefano Ceccherini } 2455aa032f1SStefano Ceccherini } 2465aa032f1SStefano Ceccherini 2475aa032f1SStefano Ceccherini 2485aa032f1SStefano Ceccherini void 2494185bd8bSStefano Ceccherini BMenuFrame::DetachedFromWindow() 2504185bd8bSStefano Ceccherini { 2514185bd8bSStefano Ceccherini if (fMenu != NULL) 2524185bd8bSStefano Ceccherini RemoveChild(fMenu); 2534185bd8bSStefano Ceccherini } 2544185bd8bSStefano Ceccherini 2554185bd8bSStefano Ceccherini 2564185bd8bSStefano Ceccherini void 2575aa032f1SStefano Ceccherini BMenuFrame::Draw(BRect updateRect) 2585aa032f1SStefano Ceccherini { 2594185bd8bSStefano Ceccherini if (fMenu != NULL && fMenu->CountItems() == 0) { 26055b35fb4SStefano Ceccherini // TODO: Review this as it's a bit hacky. 26155b35fb4SStefano Ceccherini // Menu has a size of 0, 0, since there are no items in it. 26255b35fb4SStefano Ceccherini // So the BMenuFrame class has to fake it and draw an empty item. 26355b35fb4SStefano Ceccherini // Note that we can't add a real "empty" item because then we couldn't 26455b35fb4SStefano Ceccherini // tell if the item was added by us or not. 26555b35fb4SStefano Ceccherini // See also BMenu::UpdateWindowViewSize() 26661ba5a32SStefano Ceccherini SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR)); 26755b35fb4SStefano Ceccherini SetLowColor(HighColor()); 26861ba5a32SStefano Ceccherini FillRect(updateRect); 26955b35fb4SStefano Ceccherini 27061ba5a32SStefano Ceccherini font_height height; 27155b35fb4SStefano Ceccherini GetFontHeight(&height); 27255b35fb4SStefano Ceccherini SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DISABLED_LABEL_TINT)); 2734436b726SStefano Ceccherini BPoint where((Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, ceilf(height.ascent + 1)); 2744436b726SStefano Ceccherini DrawString(kEmptyMenuLabel, where); 27561ba5a32SStefano Ceccherini } 2765aa032f1SStefano Ceccherini 2775aa032f1SStefano Ceccherini SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 27861ba5a32SStefano Ceccherini BRect bounds(Bounds()); 279c7023acdSAxel Dörfler 2805aa032f1SStefano Ceccherini StrokeLine(BPoint(bounds.right, bounds.top), 2815aa032f1SStefano Ceccherini BPoint(bounds.right, bounds.bottom - 1)); 2825aa032f1SStefano Ceccherini StrokeLine(BPoint(bounds.left + 1, bounds.bottom), 2835aa032f1SStefano Ceccherini BPoint(bounds.right, bounds.bottom)); 2845aa032f1SStefano Ceccherini } 285