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