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