2 * Copyright 2001-2015, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
6 * Marc Flerackers (mflerackers@androme.be)
7 * Stefano Ceccherini (stefano.ceccherini@gmail.com)
10 //! BMenuWindow is a custom BWindow for BMenus.
12 #include <MenuWindow.h>
14 #include <ControlLook.h>
19 #include <MenuPrivate.h>
20 #include <WindowPrivate.h>
25 class BMenuScroller
: public BView
{
27 BMenuScroller(BRect frame
);
29 bool IsEnabled() const;
30 void SetEnabled(bool enabled
);
37 class BMenuFrame
: public BView
{
39 BMenuFrame(BMenu
* menu
);
41 virtual void AttachedToWindow();
42 virtual void DetachedFromWindow();
43 virtual void Draw(BRect updateRect
);
46 friend class BMenuWindow
;
52 class UpperScroller
: public BMenuScroller
{
54 UpperScroller(BRect frame
);
56 virtual void Draw(BRect updateRect
);
60 class LowerScroller
: public BMenuScroller
{
62 LowerScroller(BRect frame
);
64 virtual void Draw(BRect updateRect
);
68 } // namespace BPrivate
71 using namespace BPrivate
;
74 const int kScrollerHeight
= 12;
77 BMenuScroller::BMenuScroller(BRect frame
)
79 BView(frame
, "menu scroller", 0, B_WILL_DRAW
| B_FRAME_EVENTS
),
82 SetViewUIColor(B_MENU_BACKGROUND_COLOR
);
87 BMenuScroller::IsEnabled() const
94 BMenuScroller::SetEnabled(bool enabled
)
103 UpperScroller::UpperScroller(BRect frame
)
111 UpperScroller::Draw(BRect updateRect
)
113 SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
), B_DARKEN_1_TINT
));
114 float middle
= Bounds().right
/ 2;
116 // Draw the upper arrow.
118 SetHighColor(0, 0, 0);
120 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
),
124 FillRect(Bounds(), B_SOLID_LOW
);
126 FillTriangle(BPoint(middle
, (kScrollerHeight
/ 2) - 3),
127 BPoint(middle
+ 5, (kScrollerHeight
/ 2) + 2),
128 BPoint(middle
- 5, (kScrollerHeight
/ 2) + 2));
135 LowerScroller::LowerScroller(BRect frame
)
143 LowerScroller::Draw(BRect updateRect
)
145 SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
), B_DARKEN_1_TINT
));
147 BRect frame
= Bounds();
148 // Draw the lower arrow.
150 SetHighColor(0, 0, 0);
152 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
),
156 FillRect(frame
, B_SOLID_LOW
);
158 float middle
= Bounds().right
/ 2;
160 FillTriangle(BPoint(middle
, frame
.bottom
- (kScrollerHeight
/ 2) + 3),
161 BPoint(middle
+ 5, frame
.bottom
- (kScrollerHeight
/ 2) - 2),
162 BPoint(middle
- 5, frame
.bottom
- (kScrollerHeight
/ 2) - 2));
169 BMenuFrame::BMenuFrame(BMenu
*menu
)
171 BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES
, B_WILL_DRAW
),
178 BMenuFrame::AttachedToWindow()
180 BView::AttachedToWindow();
185 ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
188 fMenu
->GetFont(&font
);
195 BMenuFrame::DetachedFromWindow()
203 BMenuFrame::Draw(BRect updateRect
)
205 if (fMenu
!= NULL
&& fMenu
->CountItems() == 0) {
206 BRect
rect(Bounds());
207 be_control_look
->DrawMenuBackground(this, rect
, updateRect
,
208 ui_color(B_MENU_BACKGROUND_COLOR
));
209 SetDrawingMode(B_OP_OVER
);
211 // TODO: Review this as it's a bit hacky.
212 // Since there are no items in this menu, its size is 0x0.
213 // To show an empty BMenu, we use BMenuFrame to draw an empty item.
214 // It would be nice to simply add a real "empty" item, but in that case
215 // we couldn't tell if the item was added by us or not, and applications
216 // could break (because CountItems() would return 1 for an empty BMenu).
217 // See also BMenu::UpdateWindowViewSize()
219 GetFontHeight(&height
);
220 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
),
221 B_DISABLED_LABEL_TINT
));
223 (Bounds().Width() - fMenu
->StringWidth(kEmptyMenuLabel
)) / 2,
224 ceilf(height
.ascent
+ 1));
225 DrawString(kEmptyMenuLabel
, where
);
234 BMenuWindow::BMenuWindow(const char *name
)
235 // The window will be resized by BMenu, so just pass a dummy rect
237 BWindow(BRect(0, 0, 0, 0), name
, B_BORDERED_WINDOW_LOOK
, kMenuWindowFeel
,
238 B_NOT_MOVABLE
| B_NOT_ZOOMABLE
| B_NOT_RESIZABLE
| B_AVOID_FOCUS
239 | kAcceptKeyboardFocusFlag
),
242 fUpperScroller(NULL
),
243 fLowerScroller(NULL
),
246 SetSizeLimits(2, 10000, 2, 10000);
250 BMenuWindow::~BMenuWindow()
257 BMenuWindow::DispatchMessage(BMessage
*message
, BHandler
*handler
)
259 BWindow::DispatchMessage(message
, handler
);
264 BMenuWindow::AttachMenu(BMenu
*menu
)
267 debugger("BMenuWindow: a menu is already attached!");
269 fMenuFrame
= new BMenuFrame(menu
);
270 AddChild(fMenuFrame
);
271 menu
->MakeFocus(true);
278 BMenuWindow::DetachMenu()
282 RemoveChild(fMenuFrame
);
291 BMenuWindow::AttachScrollers()
293 // We want to attach a scroller only if there's a
294 // menu frame already existing.
295 if (!fMenu
|| !fMenuFrame
)
298 fMenu
->MakeFocus(true);
300 BRect frame
= Bounds();
302 if (fUpperScroller
== NULL
) {
303 fUpperScroller
= new UpperScroller(
304 BRect(0, 0, frame
.right
, kScrollerHeight
- 1));
305 AddChild(fUpperScroller
);
308 if (fLowerScroller
== NULL
) {
309 fLowerScroller
= new LowerScroller(
310 BRect(0, frame
.bottom
- kScrollerHeight
+ 1, frame
.right
,
312 AddChild(fLowerScroller
);
315 fUpperScroller
->SetEnabled(false);
316 fLowerScroller
->SetEnabled(true);
318 fMenuFrame
->ResizeBy(0, -2 * kScrollerHeight
);
319 fMenuFrame
->MoveBy(0, kScrollerHeight
);
322 fLimit
= fMenu
->Bounds().Height() - (frame
.Height() - 2 * kScrollerHeight
);
327 BMenuWindow::DetachScrollers()
329 // BeOS doesn't remember the position where the last scrolling ended,
330 // so we just scroll back to the beginning.
332 fMenu
->ScrollTo(0, 0);
334 if (fLowerScroller
) {
335 RemoveChild(fLowerScroller
);
336 delete fLowerScroller
;
337 fLowerScroller
= NULL
;
340 if (fUpperScroller
) {
341 RemoveChild(fUpperScroller
);
342 delete fUpperScroller
;
343 fUpperScroller
= NULL
;
349 BMenuWindow::SetSmallStep(float step
)
356 BMenuWindow::GetSteps(float* _smallStep
, float* _largeStep
) const
358 if (_smallStep
!= NULL
)
359 *_smallStep
= fScrollStep
;
360 if (_largeStep
!= NULL
) {
361 if (fMenuFrame
!= NULL
)
362 *_largeStep
= fMenuFrame
->Bounds().Height() - fScrollStep
;
364 *_largeStep
= fScrollStep
* 2;
370 BMenuWindow::HasScrollers() const
372 return fMenuFrame
!= NULL
&& fUpperScroller
!= NULL
373 && fLowerScroller
!= NULL
;
378 BMenuWindow::CheckForScrolling(const BPoint
&cursor
)
380 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
383 return _Scroll(cursor
);
388 BMenuWindow::TryScrollBy(const float& step
)
390 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
399 BMenuWindow::TryScrollTo(const float& where
)
401 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
404 _ScrollBy(where
- fValue
);
410 BMenuWindow::_Scroll(const BPoint
& where
)
412 ASSERT((fLowerScroller
!= NULL
));
413 ASSERT((fUpperScroller
!= NULL
));
415 const BPoint cursor
= ConvertFromScreen(where
);
416 const BRect
&lowerFrame
= fLowerScroller
->Frame();
417 const BRect
&upperFrame
= fUpperScroller
->Frame();
420 if (fLowerScroller
->IsEnabled() && lowerFrame
.Contains(cursor
))
422 else if (fUpperScroller
->IsEnabled() && upperFrame
.Contains(cursor
))
429 GetSteps(&smallStep
, NULL
);
430 _ScrollBy(smallStep
* delta
);
439 BMenuWindow::_ScrollBy(const float& step
)
443 fUpperScroller
->SetEnabled(true);
444 fUpperScroller
->Invalidate();
447 if (fValue
+ step
>= fLimit
) {
448 // If we reached the limit, only scroll to the end
449 fMenu
->ScrollBy(0, fLimit
- fValue
);
451 fLowerScroller
->SetEnabled(false);
452 fLowerScroller
->Invalidate();
454 fMenu
->ScrollBy(0, step
);
457 } else if (step
< 0) {
458 if (fValue
== fLimit
) {
459 fLowerScroller
->SetEnabled(true);
460 fLowerScroller
->Invalidate();
463 if (fValue
+ step
<= 0) {
464 fMenu
->ScrollBy(0, -fValue
);
466 fUpperScroller
->SetEnabled(false);
467 fUpperScroller
->Invalidate();
469 fMenu
->ScrollBy(0, step
);