1/* 2 * Copyright 2001-2015, Haiku, Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Stefano Ceccherini (stefano.ceccherini@gmail.com) 8 */ 9 10//! BMenuWindow is a custom BWindow for BMenus. 11 12#include <MenuWindow.h> 13 14#include <ControlLook.h> 15#include <Debug.h> 16#include <Menu.h> 17#include <MenuItem.h> 18 19#include <MenuPrivate.h> 20#include <WindowPrivate.h> 21 22 23namespace BPrivate { 24 25class BMenuScroller : public BView { 26public: 27 BMenuScroller(BRect frame); 28 29 bool IsEnabled() const; 30 void SetEnabled(bool enabled); 31 32private: 33 bool fEnabled; 34}; 35 36 37class BMenuFrame : public BView { 38public: 39 BMenuFrame(BMenu* menu); 40 41 virtual void AttachedToWindow(); 42 virtual void DetachedFromWindow(); 43 virtual void Draw(BRect updateRect); 44 45private: 46 friend class BMenuWindow; 47 48 BMenu* fMenu; 49}; 50 51 52class UpperScroller : public BMenuScroller { 53public: 54 UpperScroller(BRect frame); 55 56 virtual void Draw(BRect updateRect); 57}; 58 59 60class LowerScroller : public BMenuScroller { 61public: 62 LowerScroller(BRect frame); 63 64 virtual void Draw(BRect updateRect); 65}; 66 67 68} // namespace BPrivate 69 70 71using namespace BPrivate; 72 73 74const int kScrollerHeight = 12; 75 76 77BMenuScroller::BMenuScroller(BRect frame) 78 : 79 BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS 80 | B_FULL_UPDATE_ON_RESIZE), 81 fEnabled(false) 82{ 83 SetViewUIColor(B_MENU_BACKGROUND_COLOR); 84} 85 86 87bool 88BMenuScroller::IsEnabled() const 89{ 90 return fEnabled; 91} 92 93 94void 95BMenuScroller::SetEnabled(bool enabled) 96{ 97 fEnabled = enabled; 98} 99 100 101// #pragma mark - 102 103 104UpperScroller::UpperScroller(BRect frame) 105 : 106 BMenuScroller(frame) 107{ 108} 109 110 111void 112UpperScroller::Draw(BRect updateRect) 113{ 114 SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 115 float middle = Bounds().right / 2; 116 117 // Draw the upper arrow. 118 if (IsEnabled()) 119 SetHighColor(0, 0, 0); 120 else { 121 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 122 B_DARKEN_2_TINT)); 123 } 124 125 FillRect(Bounds(), B_SOLID_LOW); 126 127 FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3), 128 BPoint(middle + 5, (kScrollerHeight / 2) + 2), 129 BPoint(middle - 5, (kScrollerHeight / 2) + 2)); 130} 131 132 133// #pragma mark - 134 135 136LowerScroller::LowerScroller(BRect frame) 137 : 138 BMenuScroller(frame) 139{ 140} 141 142 143void 144LowerScroller::Draw(BRect updateRect) 145{ 146 SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 147 148 BRect frame = Bounds(); 149 // Draw the lower arrow. 150 if (IsEnabled()) 151 SetHighColor(0, 0, 0); 152 else { 153 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 154 B_DARKEN_2_TINT)); 155 } 156 157 FillRect(frame, B_SOLID_LOW); 158 159 float middle = Bounds().right / 2; 160 161 FillTriangle(BPoint(middle, frame.bottom - (kScrollerHeight / 2) + 3), 162 BPoint(middle + 5, frame.bottom - (kScrollerHeight / 2) - 2), 163 BPoint(middle - 5, frame.bottom - (kScrollerHeight / 2) - 2)); 164} 165 166 167// #pragma mark - 168 169 170BMenuFrame::BMenuFrame(BMenu *menu) 171 : 172 BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW), 173 fMenu(menu) 174{ 175} 176 177 178void 179BMenuFrame::AttachedToWindow() 180{ 181 BView::AttachedToWindow(); 182 183 if (fMenu != NULL) 184 AddChild(fMenu); 185 186 ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height()); 187 if (fMenu != NULL) { 188 BFont font; 189 fMenu->GetFont(&font); 190 SetFont(&font); 191 } 192} 193 194 195void 196BMenuFrame::DetachedFromWindow() 197{ 198 if (fMenu != NULL) 199 RemoveChild(fMenu); 200} 201 202 203void 204BMenuFrame::Draw(BRect updateRect) 205{ 206 if (fMenu != NULL && fMenu->CountItems() == 0) { 207 BRect rect(Bounds()); 208 be_control_look->DrawMenuBackground(this, rect, updateRect, 209 ui_color(B_MENU_BACKGROUND_COLOR)); 210 SetDrawingMode(B_OP_OVER); 211 212 // TODO: Review this as it's a bit hacky. 213 // Since there are no items in this menu, its size is 0x0. 214 // To show an empty BMenu, we use BMenuFrame to draw an empty item. 215 // It would be nice to simply add a real "empty" item, but in that case 216 // we couldn't tell if the item was added by us or not, and applications 217 // could break (because CountItems() would return 1 for an empty BMenu). 218 // See also BMenu::UpdateWindowViewSize() 219 font_height height; 220 GetFontHeight(&height); 221 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 222 B_DISABLED_LABEL_TINT)); 223 BPoint where( 224 (Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, 225 ceilf(height.ascent + 1)); 226 DrawString(kEmptyMenuLabel, where); 227 } 228} 229 230 231 232// #pragma mark - 233 234 235BMenuWindow::BMenuWindow(const char *name) 236 // The window will be resized by BMenu, so just pass a dummy rect 237 : 238 BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel, 239 B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS 240 | kAcceptKeyboardFocusFlag), 241 fMenu(NULL), 242 fMenuFrame(NULL), 243 fUpperScroller(NULL), 244 fLowerScroller(NULL), 245 fScrollStep(19) 246{ 247 SetSizeLimits(2, 10000, 2, 10000); 248} 249 250 251BMenuWindow::~BMenuWindow() 252{ 253 DetachMenu(); 254} 255 256 257void 258BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler) 259{ 260 BWindow::DispatchMessage(message, handler); 261} 262 263 264void 265BMenuWindow::AttachMenu(BMenu *menu) 266{ 267 if (fMenuFrame) 268 debugger("BMenuWindow: a menu is already attached!"); 269 if (menu != NULL) { 270 fMenuFrame = new BMenuFrame(menu); 271 AddChild(fMenuFrame); 272 menu->MakeFocus(true); 273 fMenu = menu; 274 } 275} 276 277 278void 279BMenuWindow::DetachMenu() 280{ 281 DetachScrollers(); 282 if (fMenuFrame) { 283 RemoveChild(fMenuFrame); 284 delete fMenuFrame; 285 fMenuFrame = NULL; 286 fMenu = NULL; 287 } 288} 289 290 291void 292BMenuWindow::AttachScrollers() 293{ 294 // We want to attach a scroller only if there's a 295 // menu frame already existing. 296 if (!fMenu || !fMenuFrame) 297 return; 298 299 fMenu->MakeFocus(true); 300 301 BRect frame = Bounds(); 302 float newLimit = fMenu->Bounds().Height() 303 - (frame.Height() - 2 * kScrollerHeight); 304 305 if (!HasScrollers()) 306 fValue = 0; 307 else if (fValue > newLimit) 308 _ScrollBy(newLimit - fValue); 309 310 fLimit = newLimit; 311 312 if (fUpperScroller == NULL) { 313 fUpperScroller = new UpperScroller( 314 BRect(0, 0, frame.right, kScrollerHeight - 1)); 315 AddChild(fUpperScroller); 316 } 317 318 if (fLowerScroller == NULL) { 319 fLowerScroller = new LowerScroller( 320 BRect(0, frame.bottom - kScrollerHeight + 1, frame.right, 321 frame.bottom)); 322 AddChild(fLowerScroller); 323 } 324 325 fUpperScroller->ResizeTo(frame.right, kScrollerHeight - 1); 326 fLowerScroller->ResizeTo(frame.right, kScrollerHeight - 1); 327 328 fUpperScroller->SetEnabled(fValue > 0); 329 fLowerScroller->SetEnabled(fValue < fLimit); 330 331 fMenuFrame->ResizeTo(frame.Width(), frame.Height() - 2 * kScrollerHeight); 332 fMenuFrame->MoveTo(0, kScrollerHeight); 333} 334 335 336void 337BMenuWindow::DetachScrollers() 338{ 339 // BeOS doesn't remember the position where the last scrolling ended, 340 // so we just scroll back to the beginning. 341 if (fMenu) 342 fMenu->ScrollTo(0, 0); 343 344 if (fLowerScroller) { 345 RemoveChild(fLowerScroller); 346 delete fLowerScroller; 347 fLowerScroller = NULL; 348 } 349 350 if (fUpperScroller) { 351 RemoveChild(fUpperScroller); 352 delete fUpperScroller; 353 fUpperScroller = NULL; 354 } 355 356 BRect frame = Bounds(); 357 358 if (fMenuFrame != NULL) { 359 fMenuFrame->ResizeTo(frame.Width(), frame.Height()); 360 fMenuFrame->MoveTo(0, 0); 361 } 362} 363 364 365void 366BMenuWindow::SetSmallStep(float step) 367{ 368 fScrollStep = step; 369} 370 371 372void 373BMenuWindow::GetSteps(float* _smallStep, float* _largeStep) const 374{ 375 if (_smallStep != NULL) 376 *_smallStep = fScrollStep; 377 if (_largeStep != NULL) { 378 if (fMenuFrame != NULL) 379 *_largeStep = fMenuFrame->Bounds().Height() - fScrollStep; 380 else 381 *_largeStep = fScrollStep * 2; 382 } 383} 384 385 386bool 387BMenuWindow::HasScrollers() const 388{ 389 return fMenuFrame != NULL && fUpperScroller != NULL 390 && fLowerScroller != NULL; 391} 392 393 394bool 395BMenuWindow::CheckForScrolling(const BPoint &cursor) 396{ 397 if (!fMenuFrame || !fUpperScroller || !fLowerScroller) 398 return false; 399 400 return _Scroll(cursor); 401} 402 403 404bool 405BMenuWindow::TryScrollBy(const float& step) 406{ 407 if (!fMenuFrame || !fUpperScroller || !fLowerScroller) 408 return false; 409 410 _ScrollBy(step); 411 return true; 412} 413 414 415bool 416BMenuWindow::TryScrollTo(const float& where) 417{ 418 if (!fMenuFrame || !fUpperScroller || !fLowerScroller) 419 return false; 420 421 _ScrollBy(where - fValue); 422 return true; 423} 424 425 426bool 427BMenuWindow::_Scroll(const BPoint& where) 428{ 429 ASSERT((fLowerScroller != NULL)); 430 ASSERT((fUpperScroller != NULL)); 431 432 const BPoint cursor = ConvertFromScreen(where); 433 const BRect &lowerFrame = fLowerScroller->Frame(); 434 const BRect &upperFrame = fUpperScroller->Frame(); 435 436 int32 delta = 0; 437 if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor)) 438 delta = 1; 439 else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor)) 440 delta = -1; 441 442 if (delta == 0) 443 return false; 444 445 float smallStep; 446 GetSteps(&smallStep, NULL); 447 _ScrollBy(smallStep * delta); 448 449 snooze(5000); 450 451 return true; 452} 453 454 455void 456BMenuWindow::_ScrollBy(const float& step) 457{ 458 if (step > 0) { 459 if (fValue == 0) { 460 fUpperScroller->SetEnabled(true); 461 fUpperScroller->Invalidate(); 462 } 463 464 if (fValue + step >= fLimit) { 465 // If we reached the limit, only scroll to the end 466 fMenu->ScrollBy(0, fLimit - fValue); 467 fValue = fLimit; 468 fLowerScroller->SetEnabled(false); 469 fLowerScroller->Invalidate(); 470 } else { 471 fMenu->ScrollBy(0, step); 472 fValue += step; 473 } 474 } else if (step < 0) { 475 if (fValue == fLimit) { 476 fLowerScroller->SetEnabled(true); 477 fLowerScroller->Invalidate(); 478 } 479 480 if (fValue + step <= 0) { 481 fMenu->ScrollBy(0, -fValue); 482 fValue = 0; 483 fUpperScroller->SetEnabled(false); 484 fUpperScroller->Invalidate(); 485 } else { 486 fMenu->ScrollBy(0, step); 487 fValue += step; 488 } 489 } 490} 491 492