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