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 Pulse(); 27 virtual void Draw(BRect updateRect); 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 = 16; 66 67 68 BMenuScroller::BMenuScroller(BRect frame, BMenu *menu) 69 : BView(frame, "menu scroller", 0, 70 B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED), 71 fMenu(menu), 72 fUpperButton(0, 0, frame.right, kScrollerHeight), 73 fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom), 74 fValue(0), 75 fUpperEnabled(false), 76 fLowerEnabled(true) 77 { 78 if (!menu) 79 debugger("BMenuScroller(): Scroller not attached to a menu!"); 80 SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); 81 82 fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight); 83 } 84 85 86 BMenuScroller::~BMenuScroller() 87 { 88 } 89 90 91 void 92 BMenuScroller::Pulse() 93 { 94 // To reduce the overhead even a little, fButton and fPosition were 95 // made private variables. 96 97 // We track the mouse and move the scrollers depending on its position. 98 GetMouse(&fPosition, &fButton); 99 if (fLowerButton.Contains(fPosition) && fLowerEnabled) { 100 if (fValue == 0) { 101 fUpperEnabled = true; 102 103 Invalidate(fUpperButton); 104 } 105 106 if (fValue + kScrollStep >= fLimit) { 107 // If we reached the limit, we don't want to scroll a whole 108 // 'step' if not needed. 109 fMenu->ScrollBy(0, fLimit - fValue); 110 fValue = fLimit; 111 fLowerEnabled = false; 112 Invalidate(fLowerButton); 113 } else { 114 fMenu->ScrollBy(0, kScrollStep); 115 fValue += kScrollStep; 116 } 117 } else if (fUpperButton.Contains(fPosition) && fUpperEnabled) { 118 if (fValue == fLimit) { 119 fLowerEnabled = true; 120 Invalidate(fLowerButton); 121 } 122 123 if (fValue - kScrollStep <= 0) { 124 fMenu->ScrollBy(0, -fValue); 125 fValue = 0; 126 fUpperEnabled = false; 127 Invalidate(fUpperButton); 128 } else { 129 fMenu->ScrollBy(0, -kScrollStep); 130 fValue -= kScrollStep; 131 } 132 } 133 } 134 135 136 void 137 BMenuScroller::Draw(BRect updateRect) 138 { 139 SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT)); 140 float middle = Bounds().right / 2; 141 142 // Draw the upper arrow. 143 if (updateRect.Intersects(fUpperButton)) { 144 if (fUpperEnabled) 145 SetHighColor(0, 0, 0); 146 else 147 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 148 B_DARKEN_2_TINT)); 149 150 FillRect(fUpperButton, B_SOLID_LOW); 151 152 FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3), 153 BPoint(middle + 5, (kScrollerHeight / 2) + 2), 154 BPoint(middle - 5, (kScrollerHeight / 2) + 2)); 155 } 156 157 // Draw the lower arrow. 158 if (updateRect.Intersects(fLowerButton)) { 159 if (fLowerEnabled) 160 SetHighColor(0, 0, 0); 161 else 162 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 163 B_DARKEN_2_TINT)); 164 165 FillRect(fLowerButton, B_SOLID_LOW); 166 167 FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3), 168 BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2), 169 BPoint(middle - 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2)); 170 } 171 } 172 173 174 // #pragma mark - 175 176 177 BMenuFrame::BMenuFrame(BMenu *menu) 178 : BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW), 179 fMenu(menu) 180 { 181 } 182 183 184 BMenuFrame::~BMenuFrame() 185 { 186 } 187 188 189 void 190 BMenuFrame::AttachedToWindow() 191 { 192 BView::AttachedToWindow(); 193 194 if (fMenu != NULL) 195 AddChild(fMenu); 196 197 ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height()); 198 if (fMenu != NULL) { 199 BFont font; 200 fMenu->GetFont(&font); 201 SetFont(&font); 202 } 203 } 204 205 206 void 207 BMenuFrame::DetachedFromWindow() 208 { 209 if (fMenu != NULL) 210 RemoveChild(fMenu); 211 } 212 213 214 void 215 BMenuFrame::Draw(BRect updateRect) 216 { 217 if (fMenu != NULL && fMenu->CountItems() == 0) { 218 // TODO: Review this as it's a bit hacky. 219 // Menu has a size of 0, 0, since there are no items in it. 220 // So the BMenuFrame class has to fake it and draw an empty item. 221 // Note that we can't add a real "empty" item because then we couldn't 222 // tell if the item was added by us or not. 223 // See also BMenu::UpdateWindowViewSize() 224 SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR)); 225 SetLowColor(HighColor()); 226 FillRect(updateRect); 227 228 font_height height; 229 GetFontHeight(&height); 230 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DISABLED_LABEL_TINT)); 231 BPoint where((Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, ceilf(height.ascent + 1)); 232 DrawString(kEmptyMenuLabel, where); 233 } 234 235 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT)); 236 BRect bounds(Bounds()); 237 238 StrokeLine(BPoint(bounds.right, bounds.top), 239 BPoint(bounds.right, bounds.bottom - 1)); 240 StrokeLine(BPoint(bounds.left + 1, bounds.bottom), 241 BPoint(bounds.right, bounds.bottom)); 242 } 243 244 245 // #pragma mark - 246 247 248 BMenuWindow::BMenuWindow(const char *name) 249 // The window will be resized by BMenu, so just pass a dummy rect 250 : BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel, 251 B_NOT_ZOOMABLE | B_AVOID_FOCUS), 252 fScroller(NULL), 253 fMenuFrame(NULL) 254 { 255 SetPulseRate(200000); 256 } 257 258 259 BMenuWindow::~BMenuWindow() 260 { 261 } 262 263 264 void 265 BMenuWindow::AttachMenu(BMenu *menu) 266 { 267 if (fMenuFrame) 268 debugger("BMenuWindow: a menu is already attached!"); 269 if (menu != NULL) { 270 fMenuFrame = new BMenuFrame(menu); 271 AddChild(fMenuFrame); 272 menu->MakeFocus(true); 273 } 274 } 275 276 277 void 278 BMenuWindow::DetachMenu() 279 { 280 if (fMenuFrame) { 281 if (fScroller) { 282 DetachScrollers(); 283 } else { 284 RemoveChild(fMenuFrame); 285 } 286 delete fMenuFrame; 287 fMenuFrame = NULL; 288 } 289 } 290 291 292 void 293 BMenuWindow::AttachScrollers() 294 { 295 // We want to attach a scroller only if there's a menu frame already 296 // existing. 297 if (fScroller || !fMenuFrame) 298 return; 299 300 RemoveChild(fMenuFrame); 301 fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu); 302 fScroller->AddChild(fMenuFrame); 303 AddChild(fScroller); 304 305 fMenuFrame->fMenu->MakeFocus(true); 306 307 fMenuFrame->ResizeBy(0, -2 * kScrollerHeight); 308 fMenuFrame->MoveBy(0, kScrollerHeight); 309 } 310 311 312 void 313 BMenuWindow::DetachScrollers() 314 { 315 if(!fScroller || !fMenuFrame) 316 return; 317 318 // BeOS doesn't remember the position where the last scrolling ended, 319 // so we just scroll back to the beginning. 320 fMenuFrame->fMenu->ScrollTo(0, 0); 321 322 fScroller->RemoveChild(fMenuFrame); 323 RemoveChild(fScroller); 324 325 delete fScroller; 326 fScroller = NULL; 327 } 328