2 * Copyright 2001-2009, 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 SetViewColor(ui_color(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 if (be_control_look
!= NULL
) {
207 BRect
rect(Bounds());
208 be_control_look
->DrawMenuBackground(this, rect
, updateRect
,
209 ui_color(B_MENU_BACKGROUND_COLOR
));
210 SetDrawingMode(B_OP_OVER
);
212 // TODO: Review this as it's a bit hacky.
213 // Menu has a size of 0, 0, since there are no items in it.
214 // So the BMenuFrame class has to fake it and draw an empty item.
215 // Note that we can't add a real "empty" item because then we
216 // couldn't tell if the item was added by us or not.
217 // See also BMenu::UpdateWindowViewSize()
218 SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR
));
219 SetLowColor(HighColor());
220 FillRect(updateRect
);
224 GetFontHeight(&height
);
225 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
),
226 B_DISABLED_LABEL_TINT
));
228 (Bounds().Width() - fMenu
->StringWidth(kEmptyMenuLabel
)) / 2,
229 ceilf(height
.ascent
+ 1));
230 DrawString(kEmptyMenuLabel
, where
);
233 if (be_control_look
!= NULL
)
236 SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR
),
238 BRect
bounds(Bounds());
240 StrokeLine(BPoint(bounds
.right
, bounds
.top
),
241 BPoint(bounds
.right
, bounds
.bottom
- 1));
242 StrokeLine(BPoint(bounds
.left
+ 1, bounds
.bottom
),
243 BPoint(bounds
.right
, bounds
.bottom
));
251 BMenuWindow::BMenuWindow(const char *name
)
252 // 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_MOVABLE
| B_NOT_ZOOMABLE
| B_NOT_RESIZABLE
| B_AVOID_FOCUS
256 | kAcceptKeyboardFocusFlag
),
259 fUpperScroller(NULL
),
260 fLowerScroller(NULL
),
263 SetSizeLimits(2, 10000, 2, 10000);
267 BMenuWindow::~BMenuWindow()
274 BMenuWindow::DispatchMessage(BMessage
*message
, BHandler
*handler
)
276 BWindow::DispatchMessage(message
, handler
);
281 BMenuWindow::AttachMenu(BMenu
*menu
)
284 debugger("BMenuWindow: a menu is already attached!");
286 fMenuFrame
= new BMenuFrame(menu
);
287 AddChild(fMenuFrame
);
288 menu
->MakeFocus(true);
295 BMenuWindow::DetachMenu()
299 RemoveChild(fMenuFrame
);
308 BMenuWindow::AttachScrollers()
310 // We want to attach a scroller only if there's a
311 // menu frame already existing.
312 if (!fMenu
|| !fMenuFrame
)
315 fMenu
->MakeFocus(true);
317 BRect frame
= Bounds();
319 if (fUpperScroller
== NULL
) {
320 fUpperScroller
= new UpperScroller(
321 BRect(0, 0, frame
.right
, kScrollerHeight
- 1));
322 AddChild(fUpperScroller
);
325 if (fLowerScroller
== NULL
) {
326 fLowerScroller
= new LowerScroller(
327 BRect(0, frame
.bottom
- kScrollerHeight
+ 1, frame
.right
,
329 AddChild(fLowerScroller
);
332 fUpperScroller
->SetEnabled(false);
333 fLowerScroller
->SetEnabled(true);
335 fMenuFrame
->ResizeBy(0, -2 * kScrollerHeight
);
336 fMenuFrame
->MoveBy(0, kScrollerHeight
);
339 fLimit
= fMenu
->Bounds().Height() - (frame
.Height() - 2 * kScrollerHeight
);
344 BMenuWindow::DetachScrollers()
346 // BeOS doesn't remember the position where the last scrolling ended,
347 // so we just scroll back to the beginning.
349 fMenu
->ScrollTo(0, 0);
351 if (fLowerScroller
) {
352 RemoveChild(fLowerScroller
);
353 delete fLowerScroller
;
354 fLowerScroller
= NULL
;
357 if (fUpperScroller
) {
358 RemoveChild(fUpperScroller
);
359 delete fUpperScroller
;
360 fUpperScroller
= NULL
;
366 BMenuWindow::SetSmallStep(float step
)
373 BMenuWindow::GetSteps(float* _smallStep
, float* _largeStep
) const
375 if (_smallStep
!= NULL
)
376 *_smallStep
= fScrollStep
;
377 if (_largeStep
!= NULL
) {
378 if (fMenuFrame
!= NULL
)
379 *_largeStep
= fMenuFrame
->Bounds().Height() - fScrollStep
;
381 *_largeStep
= fScrollStep
* 2;
387 BMenuWindow::HasScrollers() const
389 return fMenuFrame
!= NULL
&& fUpperScroller
!= NULL
390 && fLowerScroller
!= NULL
;
395 BMenuWindow::CheckForScrolling(const BPoint
&cursor
)
397 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
400 return _Scroll(cursor
);
405 BMenuWindow::TryScrollBy(const float& step
)
407 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
416 BMenuWindow::TryScrollTo(const float& where
)
418 if (!fMenuFrame
|| !fUpperScroller
|| !fLowerScroller
)
421 _ScrollBy(where
- fValue
);
427 BMenuWindow::_Scroll(const BPoint
& where
)
429 ASSERT((fLowerScroller
!= NULL
));
430 ASSERT((fUpperScroller
!= NULL
));
432 const BPoint cursor
= ConvertFromScreen(where
);
433 const BRect
&lowerFrame
= fLowerScroller
->Frame();
434 const BRect
&upperFrame
= fUpperScroller
->Frame();
437 if (fLowerScroller
->IsEnabled() && lowerFrame
.Contains(cursor
))
439 else if (fUpperScroller
->IsEnabled() && upperFrame
.Contains(cursor
))
446 GetSteps(&smallStep
, NULL
);
447 _ScrollBy(smallStep
* delta
);
456 BMenuWindow::_ScrollBy(const float& step
)
460 fUpperScroller
->SetEnabled(true);
461 fUpperScroller
->Invalidate();
464 if (fValue
+ step
>= fLimit
) {
465 // If we reached the limit, only scroll to the end
466 fMenu
->ScrollBy(0, fLimit
- fValue
);
468 fLowerScroller
->SetEnabled(false);
469 fLowerScroller
->Invalidate();
471 fMenu
->ScrollBy(0, step
);
474 } else if (step
< 0) {
475 if (fValue
== fLimit
) {
476 fLowerScroller
->SetEnabled(true);
477 fLowerScroller
->Invalidate();
480 if (fValue
+ step
<= 0) {
481 fMenu
->ScrollBy(0, -fValue
);
483 fUpperScroller
->SetEnabled(false);
484 fUpperScroller
->Invalidate();
486 fMenu
->ScrollBy(0, step
);