/*
* Copyright 2001-2015, Haiku, Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marc Flerackers (mflerackers@androme.be)
* Stefano Ceccherini (stefano.ceccherini@gmail.com)
*/
//! BMenuWindow is a custom BWindow for BMenus.
#include <MenuWindow.h>
#include <ControlLook.h>
#include <Debug.h>
#include <Menu.h>
#include <MenuItem.h>
#include <MenuPrivate.h>
#include <WindowPrivate.h>
namespace BPrivate {
class BMenuScroller : public BView {
public:
BMenuScroller(BRect frame);
bool IsEnabled() const;
void SetEnabled(bool enabled);
private:
bool fEnabled;
};
class BMenuFrame : public BView {
public:
BMenuFrame(BMenu* menu);
virtual void AttachedToWindow();
virtual void DetachedFromWindow();
virtual void Draw(BRect updateRect);
private:
friend class BMenuWindow;
BMenu* fMenu;
};
class UpperScroller : public BMenuScroller {
public:
UpperScroller(BRect frame);
virtual void Draw(BRect updateRect);
};
class LowerScroller : public BMenuScroller {
public:
LowerScroller(BRect frame);
virtual void Draw(BRect updateRect);
};
} // namespace BPrivate
using namespace BPrivate;
const int kScrollerHeight = 12;
BMenuScroller::BMenuScroller(BRect frame)
:
BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS),
fEnabled(false)
{
SetViewUIColor(B_MENU_BACKGROUND_COLOR);
}
bool
BMenuScroller::IsEnabled() const
{
return fEnabled;
}
void
BMenuScroller::SetEnabled(bool enabled)
{
fEnabled = enabled;
}
// #pragma mark -
UpperScroller::UpperScroller(BRect frame)
:
BMenuScroller(frame)
{
}
void
UpperScroller::Draw(BRect updateRect)
{
SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
float middle = Bounds().right / 2;
// Draw the upper arrow.
if (IsEnabled())
SetHighColor(0, 0, 0);
else {
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
}
FillRect(Bounds(), B_SOLID_LOW);
FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
BPoint(middle + 5, (kScrollerHeight / 2) + 2),
BPoint(middle - 5, (kScrollerHeight / 2) + 2));
}
// #pragma mark -
LowerScroller::LowerScroller(BRect frame)
:
BMenuScroller(frame)
{
}
void
LowerScroller::Draw(BRect updateRect)
{
SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
BRect frame = Bounds();
// Draw the lower arrow.
if (IsEnabled())
SetHighColor(0, 0, 0);
else {
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
}
FillRect(frame, B_SOLID_LOW);
float middle = Bounds().right / 2;
FillTriangle(BPoint(middle, frame.bottom - (kScrollerHeight / 2) + 3),
BPoint(middle + 5, frame.bottom - (kScrollerHeight / 2) - 2),
BPoint(middle - 5, frame.bottom - (kScrollerHeight / 2) - 2));
}
// #pragma mark -
BMenuFrame::BMenuFrame(BMenu *menu)
:
BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
fMenu(menu)
{
}
void
BMenuFrame::AttachedToWindow()
{
BView::AttachedToWindow();
if (fMenu != NULL)
AddChild(fMenu);
ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
if (fMenu != NULL) {
BFont font;
fMenu->GetFont(&font);
SetFont(&font);
}
}
void
BMenuFrame::DetachedFromWindow()
{
if (fMenu != NULL)
RemoveChild(fMenu);
}
void
BMenuFrame::Draw(BRect updateRect)
{
if (fMenu != NULL && fMenu->CountItems() == 0) {
BRect rect(Bounds());
be_control_look->DrawMenuBackground(this, rect, updateRect,
ui_color(B_MENU_BACKGROUND_COLOR));
SetDrawingMode(B_OP_OVER);
// TODO: Review this as it's a bit hacky.
// Since there are no items in this menu, its size is 0x0.
// To show an empty BMenu, we use BMenuFrame to draw an empty item.
// It would be nice to simply add a real "empty" item, but in that case
// we couldn't tell if the item was added by us or not, and applications
// could break (because CountItems() would return 1 for an empty BMenu).
// See also BMenu::UpdateWindowViewSize()
font_height height;
GetFontHeight(&height);
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
B_DISABLED_LABEL_TINT));
BPoint where(
(Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2,
ceilf(height.ascent + 1));
DrawString(kEmptyMenuLabel, where);
}
}
// #pragma mark -
BMenuWindow::BMenuWindow(const char *name)
// The window will be resized by BMenu, so just pass a dummy rect
:
BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS
| kAcceptKeyboardFocusFlag),
fMenu(NULL),
fMenuFrame(NULL),
fUpperScroller(NULL),
fLowerScroller(NULL),
fScrollStep(19)
{
SetSizeLimits(2, 10000, 2, 10000);
}
BMenuWindow::~BMenuWindow()
{
DetachMenu();
}
void
BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
{
BWindow::DispatchMessage(message, handler);
}
void
BMenuWindow::AttachMenu(BMenu *menu)
{
if (fMenuFrame)
debugger("BMenuWindow: a menu is already attached!");
if (menu != NULL) {
fMenuFrame = new BMenuFrame(menu);
AddChild(fMenuFrame);
menu->MakeFocus(true);
fMenu = menu;
}
}
void
BMenuWindow::DetachMenu()
{
DetachScrollers();
if (fMenuFrame) {
RemoveChild(fMenuFrame);
delete fMenuFrame;
fMenuFrame = NULL;
fMenu = NULL;
}
}
void
BMenuWindow::AttachScrollers()
{
// We want to attach a scroller only if there's a
// menu frame already existing.
if (!fMenu || !fMenuFrame)
return;
fMenu->MakeFocus(true);
BRect frame = Bounds();
if (fUpperScroller == NULL) {
fUpperScroller = new UpperScroller(
BRect(0, 0, frame.right, kScrollerHeight - 1));
AddChild(fUpperScroller);
}
if (fLowerScroller == NULL) {
fLowerScroller = new LowerScroller(
BRect(0, frame.bottom - kScrollerHeight + 1, frame.right,
frame.bottom));
AddChild(fLowerScroller);
}
fUpperScroller->SetEnabled(false);
fLowerScroller->SetEnabled(true);
fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
fMenuFrame->MoveBy(0, kScrollerHeight);
fValue = 0;
fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
}
void
BMenuWindow::DetachScrollers()
{
// BeOS doesn't remember the position where the last scrolling ended,
// so we just scroll back to the beginning.
if (fMenu)
fMenu->ScrollTo(0, 0);
if (fLowerScroller) {
RemoveChild(fLowerScroller);
delete fLowerScroller;
fLowerScroller = NULL;
}
if (fUpperScroller) {
RemoveChild(fUpperScroller);
delete fUpperScroller;
fUpperScroller = NULL;
}
}
void
BMenuWindow::SetSmallStep(float step)
{
fScrollStep = step;
}
void
BMenuWindow::GetSteps(float* _smallStep, float* _largeStep) const
{
if (_smallStep != NULL)
*_smallStep = fScrollStep;
if (_largeStep != NULL) {
if (fMenuFrame != NULL)
*_largeStep = fMenuFrame->Bounds().Height() - fScrollStep;
else
*_largeStep = fScrollStep * 2;
}
}
bool
BMenuWindow::HasScrollers() const
{
return fMenuFrame != NULL && fUpperScroller != NULL
&& fLowerScroller != NULL;
}
bool
BMenuWindow::CheckForScrolling(const BPoint &cursor)
{
if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
return false;
return _Scroll(cursor);
}
bool
BMenuWindow::TryScrollBy(const float& step)
{
if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
return false;
_ScrollBy(step);
return true;
}
bool
BMenuWindow::TryScrollTo(const float& where)
{
if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
return false;
_ScrollBy(where - fValue);
return true;
}
bool
BMenuWindow::_Scroll(const BPoint& where)
{
ASSERT((fLowerScroller != NULL));
ASSERT((fUpperScroller != NULL));
const BPoint cursor = ConvertFromScreen(where);
const BRect &lowerFrame = fLowerScroller->Frame();
const BRect &upperFrame = fUpperScroller->Frame();
int32 delta = 0;
if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor))
delta = 1;
else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor))
delta = -1;
if (delta == 0)
return false;
float smallStep;
GetSteps(&smallStep, NULL);
_ScrollBy(smallStep * delta);
snooze(5000);
return true;
}
void
BMenuWindow::_ScrollBy(const float& step)
{
if (step > 0) {
if (fValue == 0) {
fUpperScroller->SetEnabled(true);
fUpperScroller->Invalidate();
}
if (fValue + step >= fLimit) {
// If we reached the limit, only scroll to the end
fMenu->ScrollBy(0, fLimit - fValue);
fValue = fLimit;
fLowerScroller->SetEnabled(false);
fLowerScroller->Invalidate();
} else {
fMenu->ScrollBy(0, step);
fValue += step;
}
} else if (step < 0) {
if (fValue == fLimit) {
fLowerScroller->SetEnabled(true);
fLowerScroller->Invalidate();
}
if (fValue + step <= 0) {
fMenu->ScrollBy(0, -fValue);
fValue = 0;
fUpperScroller->SetEnabled(false);
fUpperScroller->Invalidate();
} else {
fMenu->ScrollBy(0, step);
fValue += step;
}
}
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fValue, fLimit.