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::AttachMenu(BMenu *menu) 248 { 249 if (fMenuFrame) 250 debugger("BMenuWindow: a menu is already attached!"); 251 if (menu != NULL) { 252 fMenuFrame = new BMenuFrame(menu); 253 AddChild(fMenuFrame); 254 menu->MakeFocus(true); 255 fMenu = menu; 256 } 257 } 258 259 260 void 261 BMenuWindow::DetachMenu() 262 { 263 DetachScrollers(); 264 if (fMenuFrame) { 265 RemoveChild(fMenuFrame); 266 delete fMenuFrame; 267 fMenuFrame = NULL; 268 fMenu = NULL; 269 } 270 } 271 272 273 void 274 BMenuWindow::AttachScrollers() 275 { 276 // We want to attach a scroller only if there's a menu frame already 277 // existing. 278 if (!fMenu || !fMenuFrame) 279 return; 280 281 if (fUpperScroller || fLowerScroller) 282 debugger("Scrollers are already attached!"); 283 284 fMenu->MakeFocus(true); 285 286 BRect frame = Bounds(); 287 fUpperScroller = new UpperScroller(BRect(0, 0, frame.right, kScrollerHeight)); 288 AddChild(fUpperScroller); 289 fLowerScroller = new LowerScroller(BRect(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom)); 290 AddChild(fLowerScroller); 291 292 fUpperScroller->SetEnabled(false); 293 fLowerScroller->SetEnabled(true); 294 295 fMenuFrame->ResizeBy(0, -2 * kScrollerHeight - 1); 296 fMenuFrame->MoveBy(0, kScrollerHeight); 297 298 fValue = 0; 299 fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight); 300 } 301 302 303 void 304 BMenuWindow::DetachScrollers() 305 { 306 // BeOS doesn't remember the position where the last scrolling ended, 307 // so we just scroll back to the beginning. 308 if (fMenu) 309 fMenu->ScrollTo(0, 0); 310 311 if (fLowerScroller) { 312 RemoveChild(fLowerScroller); 313 delete fLowerScroller; 314 fLowerScroller = NULL; 315 } 316 317 if (fUpperScroller) { 318 RemoveChild(fUpperScroller); 319 delete fUpperScroller; 320 fUpperScroller = NULL; 321 } 322 } 323 324 325 bool 326 BMenuWindow::CheckForScrolling(BPoint cursor) 327 { 328 if (!fMenuFrame || !fUpperScroller || !fLowerScroller) 329 return false; 330 331 return _Scroll(cursor); 332 } 333 334 335 bool 336 BMenuWindow::_Scroll(BPoint cursor) 337 { 338 ASSERT((fLowerScroller != NULL)); 339 ASSERT((fUpperScroller != NULL)); 340 341 ConvertFromScreen(&cursor); 342 343 BRect lowerFrame = fLowerScroller->Frame(); 344 BRect upperFrame = fUpperScroller->Frame(); 345 346 if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor)) { 347 if (fValue == 0) { 348 fUpperScroller->SetEnabled(true); 349 fUpperScroller->Invalidate(); 350 } 351 352 if (fValue + kScrollStep >= fLimit) { 353 // If we reached the limit, we don't want to scroll a whole 354 // 'step' if not needed. 355 fMenu->ScrollBy(0, fLimit - fValue); 356 fValue = fLimit; 357 fLowerScroller->SetEnabled(false); 358 fLowerScroller->Invalidate(); 359 360 } else { 361 fMenu->ScrollBy(0, kScrollStep); 362 fValue += kScrollStep; 363 } 364 } else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor)) { 365 if (fValue == fLimit) { 366 fLowerScroller->SetEnabled(true); 367 fLowerScroller->Invalidate(); 368 } 369 370 if (fValue - kScrollStep <= 0) { 371 fMenu->ScrollBy(0, -fValue); 372 fValue = 0; 373 fUpperScroller->SetEnabled(false); 374 fUpperScroller->Invalidate(); 375 376 } else { 377 fMenu->ScrollBy(0, -kScrollStep); 378 fValue -= kScrollStep; 379 } 380 } else 381 return false; 382 383 snooze(10000); 384 385 return true; 386 } 387 388