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