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 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 fEnabled(false) 81{ 82 SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); 83} 84 85 86bool 87BMenuScroller::IsEnabled() const 88{ 89 return fEnabled; 90} 91 92 93void 94BMenuScroller::SetEnabled(bool enabled) 95{ 96 fEnabled = enabled; 97} 98 99 100// #pragma mark - 101 102 103UpperScroller::UpperScroller(BRect frame) 104 : 105 BMenuScroller(frame) 106{ 107} 108 109 110void 111UpperScroller::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 135LowerScroller::LowerScroller(BRect frame) 136 : 137 BMenuScroller(frame) 138{ 139} 140 141 142void 143LowerScroller::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 169BMenuFrame::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 177void 178BMenuFrame::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 194void 195BMenuFrame::DetachedFromWindow() 196{ 197 if (fMenu != NULL) 198 RemoveChild(fMenu); 199} 200 201 202void 203BMenuFrame::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), 237 B_DARKEN_2_TINT)); 238 BRect bounds(Bounds()); 239 240 StrokeLine(BPoint(bounds.right, bounds.top), 241 BPoint(bounds.right, bounds.bottom - 1)); 242 StrokeLine(BPoint(bounds.left + 1, bounds.bottom), 243 BPoint(bounds.right, bounds.bottom)); 244} 245 246 247 248// #pragma mark - 249 250 251BMenuWindow::BMenuWindow(const char *name) 252 // The window will be resized by BMenu, so just pass a dummy rect 253 : 254 BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel, 255 B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS 256 | kAcceptKeyboardFocusFlag), 257 fMenu(NULL), 258 fMenuFrame(NULL), 259 fUpperScroller(NULL), 260 fLowerScroller(NULL), 261 fScrollStep(19) 262{ 263 SetSizeLimits(2, 10000, 2, 10000); 264} 265 266 267BMenuWindow::~BMenuWindow() 268{ 269 DetachMenu(); 270} 271 272 273void 274BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler) 275{ 276 BWindow::DispatchMessage(message, handler); 277} 278 279 280void 281BMenuWindow::AttachMenu(BMenu *menu) 282{ 283 if (fMenuFrame) 284 debugger("BMenuWindow: a menu is already attached!"); 285 if (menu != NULL) { 286 fMenuFrame = new BMenuFrame(menu); 287 AddChild(fMenuFrame); 288 menu->MakeFocus(true); 289 fMenu = menu; 290 } 291} 292 293 294void 295BMenuWindow::DetachMenu() 296{ 297 DetachScrollers(); 298 if (fMenuFrame) { 299 RemoveChild(fMenuFrame); 300 delete fMenuFrame; 301 fMenuFrame = NULL; 302 fMenu = NULL; 303 } 304} 305 306 307void 308BMenuWindow::AttachScrollers() 309{ 310 // We want to attach a scroller only if there's a 311 // menu frame already existing. 312 if (!fMenu || !fMenuFrame) 313 return; 314 315 fMenu->MakeFocus(true); 316 317 BRect frame = Bounds(); 318 319 if (fUpperScroller == NULL) { 320 fUpperScroller = new UpperScroller( 321 BRect(0, 0, frame.right, kScrollerHeight - 1)); 322 AddChild(fUpperScroller); 323 } 324 325 if (fLowerScroller == NULL) { 326 fLowerScroller = new LowerScroller( 327 BRect(0, frame.bottom - kScrollerHeight + 1, frame.right, 328 frame.bottom)); 329 AddChild(fLowerScroller); 330 } 331 332 fUpperScroller->SetEnabled(false); 333 fLowerScroller->SetEnabled(true); 334 335 fMenuFrame->ResizeBy(0, -2 * kScrollerHeight); 336 fMenuFrame->MoveBy(0, kScrollerHeight); 337 338 fValue = 0; 339 fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight); 340} 341 342 343void 344BMenuWindow::DetachScrollers() 345{ 346 // BeOS doesn't remember the position where the last scrolling ended, 347 // so we just scroll back to the beginning. 348 if (fMenu) 349 fMenu->ScrollTo(0, 0); 350 351 if (fLowerScroller) { 352 RemoveChild(fLowerScroller); 353 delete fLowerScroller; 354 fLowerScroller = NULL; 355 } 356 357 if (fUpperScroller) { 358 RemoveChild(fUpperScroller); 359 delete fUpperScroller; 360 fUpperScroller = NULL; 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::_Scroll(const BPoint& where) 417{ 418 ASSERT((fLowerScroller != NULL)); 419 ASSERT((fUpperScroller != NULL)); 420 421 const BPoint cursor = ConvertFromScreen(where); 422 const BRect &lowerFrame = fLowerScroller->Frame(); 423 const BRect &upperFrame = fUpperScroller->Frame(); 424 425 int32 delta = 0; 426 if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor)) 427 delta = 1; 428 else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor)) 429 delta = -1; 430 431 if (delta == 0) 432 return false; 433 434 float smallStep; 435 GetSteps(&smallStep, NULL); 436 _ScrollBy(smallStep * delta); 437 438 snooze(5000); 439 440 return true; 441} 442 443 444void 445BMenuWindow::_ScrollBy(const float& step) 446{ 447 if (step > 0) { 448 if (fValue == 0) { 449 fUpperScroller->SetEnabled(true); 450 fUpperScroller->Invalidate(); 451 } 452 453 if (fValue + step >= fLimit) { 454 // If we reached the limit, only scroll to the end 455 fMenu->ScrollBy(0, fLimit - fValue); 456 fValue = fLimit; 457 fLowerScroller->SetEnabled(false); 458 fLowerScroller->Invalidate(); 459 } else { 460 fMenu->ScrollBy(0, step); 461 fValue += step; 462 } 463 } else if (step < 0) { 464 if (fValue == fLimit) { 465 fLowerScroller->SetEnabled(true); 466 fLowerScroller->Invalidate(); 467 } 468 469 if (fValue + step <= 0) { 470 fMenu->ScrollBy(0, -fValue); 471 fValue = 0; 472 fUpperScroller->SetEnabled(false); 473 fUpperScroller->Invalidate(); 474 } else { 475 fMenu->ScrollBy(0, step); 476 fValue += step; 477 } 478 } 479} 480 481