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