/*
* Copyright 2001-2006, Haiku, Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marc Flerackers (mflerackers@androme.be)
* Stefano Ceccherini (burton666@libero.it)
*/
//! BMenuWindow is a custom BWindow for BMenus.
#include
#include
#include
#include
namespace BPrivate {
class BMenuScroller : public BView {
public:
BMenuScroller(BRect frame, BMenu *menu);
virtual ~BMenuScroller();
virtual void Pulse();
virtual void Draw(BRect updateRect);
private:
BMenu *fMenu;
BRect fUpperButton;
BRect fLowerButton;
float fValue;
float fLimit;
bool fUpperEnabled;
bool fLowerEnabled;
uint32 fButton;
BPoint fPosition;
};
class BMenuFrame : public BView {
public:
BMenuFrame(BMenu *menu);
virtual ~BMenuFrame();
virtual void AttachedToWindow();
virtual void DetachedFromWindow();
virtual void Draw(BRect updateRect);
private:
friend class BMenuWindow;
BMenu *fMenu;
};
} // namespace BPrivate
using namespace BPrivate;
const int kScrollerHeight = 10;
const int kScrollStep = 16;
BMenuScroller::BMenuScroller(BRect frame, BMenu *menu)
: BView(frame, "menu scroller", 0,
B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED),
fMenu(menu),
fUpperButton(0, 0, frame.right, kScrollerHeight),
fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom),
fValue(0),
fUpperEnabled(false),
fLowerEnabled(true)
{
if (!menu)
debugger("BMenuScroller(): Scroller not attached to a menu!");
SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
}
BMenuScroller::~BMenuScroller()
{
}
void
BMenuScroller::Pulse()
{
// To reduce the overhead even a little, fButton and fPosition were
// made private variables.
// We track the mouse and move the scrollers depending on its position.
GetMouse(&fPosition, &fButton);
if (fLowerButton.Contains(fPosition) && fLowerEnabled) {
if (fValue == 0) {
fUpperEnabled = true;
Invalidate(fUpperButton);
}
if (fValue + kScrollStep >= fLimit) {
// If we reached the limit, we don't want to scroll a whole
// 'step' if not needed.
fMenu->ScrollBy(0, fLimit - fValue);
fValue = fLimit;
fLowerEnabled = false;
Invalidate(fLowerButton);
} else {
fMenu->ScrollBy(0, kScrollStep);
fValue += kScrollStep;
}
} else if (fUpperButton.Contains(fPosition) && fUpperEnabled) {
if (fValue == fLimit) {
fLowerEnabled = true;
Invalidate(fLowerButton);
}
if (fValue - kScrollStep <= 0) {
fMenu->ScrollBy(0, -fValue);
fValue = 0;
fUpperEnabled = false;
Invalidate(fUpperButton);
} else {
fMenu->ScrollBy(0, -kScrollStep);
fValue -= kScrollStep;
}
// In this case, we need to redraw the lower button because of a
// probable bug in ScrollBy handling. The scrolled view is drawing below
// its bounds, dirtying our button in this process.
// Redrawing it everytime makes scrolling a little slower.
// TODO: Try to find why and fix this.
Draw(fLowerButton);
}
}
void
BMenuScroller::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 (updateRect.Intersects(fUpperButton)) {
if (fUpperEnabled)
SetHighColor(0, 0, 0);
else
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
FillRect(fUpperButton, B_SOLID_LOW);
FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
BPoint(middle + 5, (kScrollerHeight / 2) + 2),
BPoint(middle - 5, (kScrollerHeight / 2) + 2));
}
// Draw the lower arrow.
if (updateRect.Intersects(fLowerButton)) {
if (fLowerEnabled)
SetHighColor(0, 0, 0);
else
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
B_DARKEN_2_TINT));
FillRect(fLowerButton, B_SOLID_LOW);
FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3),
BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2),
BPoint(middle - 5, fLowerButton.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)
{
}
BMenuFrame::~BMenuFrame()
{
}
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) {
// TODO: Review this as it's a bit hacky.
// Menu has a size of 0, 0, since there are no items in it.
// So the BMenuFrame class has to fake it and draw an empty item.
// Note that we can't add a real "empty" item because then we couldn't
// tell if the item was added by us or not.
// See also BMenu::UpdateWindowViewSize()
SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
SetLowColor(HighColor());
FillRect(updateRect);
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);
}
SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
BRect bounds(Bounds());
StrokeLine(BPoint(bounds.right, bounds.top),
BPoint(bounds.right, bounds.bottom - 1));
StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
BPoint(bounds.right, bounds.bottom));
}
// #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_ZOOMABLE | B_AVOID_FOCUS),
fScroller(NULL),
fMenuFrame(NULL)
{
SetPulseRate(200000);
}
BMenuWindow::~BMenuWindow()
{
}
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);
}
}
void
BMenuWindow::DetachMenu()
{
if (fMenuFrame) {
if (fScroller) {
DetachScrollers();
} else {
RemoveChild(fMenuFrame);
}
delete fMenuFrame;
fMenuFrame = NULL;
}
}
void
BMenuWindow::AttachScrollers()
{
// We want to attach a scroller only if there's a menu frame already
// existing.
if (fScroller || !fMenuFrame)
return;
RemoveChild(fMenuFrame);
fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu);
fScroller->AddChild(fMenuFrame);
AddChild(fScroller);
fMenuFrame->fMenu->MakeFocus(true);
fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
fMenuFrame->MoveBy(0, kScrollerHeight);
}
void
BMenuWindow::DetachScrollers()
{
if(!fScroller || !fMenuFrame)
return;
// BeOS doesn't remember the position where the last scrolling ended,
// so we just scroll back to the beginning.
fMenuFrame->fMenu->ScrollTo(0, 0);
fScroller->RemoveChild(fMenuFrame);
RemoveChild(fScroller);
delete fScroller;
fScroller = NULL;
}