xref: /haiku/src/kits/interface/MenuWindow.cpp (revision 2628e60cbca2ed49bf7d5704f99eb72bec7e249f)
1c7023acdSAxel Dörfler /*
2c7023acdSAxel Dörfler  * Copyright 2001-2006, Haiku, Inc.
3c7023acdSAxel Dörfler  * Distributed under the terms of the MIT License.
4c7023acdSAxel Dörfler  *
5c7023acdSAxel Dörfler  * Authors:
6c7023acdSAxel Dörfler  *		Marc Flerackers (mflerackers@androme.be)
7c7023acdSAxel Dörfler  *		Stefano Ceccherini (burton666@libero.it)
8c7023acdSAxel Dörfler  */
9c7023acdSAxel Dörfler 
10c7023acdSAxel Dörfler //!	BMenuWindow is a custom BWindow for BMenus.
11c7023acdSAxel Dörfler 
12446b8c19SStefano Ceccherini #include <Menu.h>
13446b8c19SStefano Ceccherini 
142c11ec31SStefano Ceccherini #include <MenuPrivate.h>
152c11ec31SStefano Ceccherini #include <MenuWindow.h>
16c7023acdSAxel Dörfler #include <WindowPrivate.h>
17c7023acdSAxel Dörfler 
18e411c658SStefano Ceccherini 
19*2628e60cSAxel Dörfler namespace BPrivate {
20*2628e60cSAxel Dörfler 
21*2628e60cSAxel Dörfler class BMenuScroller : public BView {
22*2628e60cSAxel Dörfler 	public:
23*2628e60cSAxel Dörfler 		BMenuScroller(BRect frame, BMenu *menu);
24*2628e60cSAxel Dörfler 		virtual ~BMenuScroller();
25*2628e60cSAxel Dörfler 
26*2628e60cSAxel Dörfler 		virtual void Pulse();
27*2628e60cSAxel Dörfler 		virtual void Draw(BRect updateRect);
28*2628e60cSAxel Dörfler 
29*2628e60cSAxel Dörfler 	private:
30*2628e60cSAxel Dörfler 		BMenu *fMenu;
31*2628e60cSAxel Dörfler 		BRect fUpperButton;
32*2628e60cSAxel Dörfler 		BRect fLowerButton;
33*2628e60cSAxel Dörfler 
34*2628e60cSAxel Dörfler 		float fValue;
35*2628e60cSAxel Dörfler 		float fLimit;
36*2628e60cSAxel Dörfler 
37*2628e60cSAxel Dörfler 		bool fUpperEnabled;
38*2628e60cSAxel Dörfler 		bool fLowerEnabled;
39*2628e60cSAxel Dörfler 
40*2628e60cSAxel Dörfler 		uint32 fButton;
41*2628e60cSAxel Dörfler 		BPoint fPosition;
42*2628e60cSAxel Dörfler };
43*2628e60cSAxel Dörfler 
44*2628e60cSAxel Dörfler class BMenuFrame : public BView {
45*2628e60cSAxel Dörfler 	public:
46*2628e60cSAxel Dörfler 		BMenuFrame(BMenu *menu);
47*2628e60cSAxel Dörfler 		virtual ~BMenuFrame();
48*2628e60cSAxel Dörfler 
49*2628e60cSAxel Dörfler 		virtual void AttachedToWindow();
50*2628e60cSAxel Dörfler 		virtual void DetachedFromWindow();
51*2628e60cSAxel Dörfler 		virtual void Draw(BRect updateRect);
52*2628e60cSAxel Dörfler 
53*2628e60cSAxel Dörfler   private:
54*2628e60cSAxel Dörfler 		friend class BMenuWindow;
55*2628e60cSAxel Dörfler 
56*2628e60cSAxel Dörfler 		BMenu *fMenu;
57*2628e60cSAxel Dörfler };
58*2628e60cSAxel Dörfler 
59*2628e60cSAxel Dörfler }	// namespace BPrivate
60*2628e60cSAxel Dörfler 
61*2628e60cSAxel Dörfler using namespace BPrivate;
62*2628e60cSAxel Dörfler 
63*2628e60cSAxel Dörfler 
6427cc2508SStefano Ceccherini const int kScrollerHeight = 10;
6527cc2508SStefano Ceccherini const int kScrollStep = 16;
665b752875SStefano Ceccherini 
67*2628e60cSAxel Dörfler 
6827cc2508SStefano Ceccherini BMenuScroller::BMenuScroller(BRect frame, BMenu *menu)
69*2628e60cSAxel Dörfler 	: BView(frame, "menu scroller", 0,
70*2628e60cSAxel Dörfler 		B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED),
7127cc2508SStefano Ceccherini 	fMenu(menu),
7227cc2508SStefano Ceccherini 	fUpperButton(0, 0, frame.right, kScrollerHeight),
7327cc2508SStefano Ceccherini 	fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom),
7427cc2508SStefano Ceccherini 	fValue(0),
7527cc2508SStefano Ceccherini 	fUpperEnabled(false),
7627cc2508SStefano Ceccherini 	fLowerEnabled(true)
7727cc2508SStefano Ceccherini {
7827cc2508SStefano Ceccherini 	if (!menu)
7927cc2508SStefano Ceccherini 		debugger("BMenuScroller(): Scroller not attached to a menu!");
8027cc2508SStefano Ceccherini 	SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
8161ba5a32SStefano Ceccherini 
8227cc2508SStefano Ceccherini 	fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
8327cc2508SStefano Ceccherini }
8427cc2508SStefano Ceccherini 
8527cc2508SStefano Ceccherini 
8627cc2508SStefano Ceccherini BMenuScroller::~BMenuScroller()
8727cc2508SStefano Ceccherini {
8827cc2508SStefano Ceccherini }
8927cc2508SStefano Ceccherini 
9027cc2508SStefano Ceccherini 
9127cc2508SStefano Ceccherini void
9227cc2508SStefano Ceccherini BMenuScroller::Pulse()
9327cc2508SStefano Ceccherini {
9427cc2508SStefano Ceccherini 	// To reduce the overhead even a little, fButton and fPosition were
9527cc2508SStefano Ceccherini 	// made private variables.
9627cc2508SStefano Ceccherini 
9727cc2508SStefano Ceccherini 	// We track the mouse and move the scrollers depending on its position.
9827cc2508SStefano Ceccherini 	GetMouse(&fPosition, &fButton);
9927cc2508SStefano Ceccherini 	if (fLowerButton.Contains(fPosition) && fLowerEnabled) {
10027cc2508SStefano Ceccherini 		if (fValue == 0) {
10127cc2508SStefano Ceccherini 			fUpperEnabled = true;
10227cc2508SStefano Ceccherini 
10327cc2508SStefano Ceccherini 			Invalidate(fUpperButton);
10427cc2508SStefano Ceccherini 		}
10527cc2508SStefano Ceccherini 
10627cc2508SStefano Ceccherini 		if (fValue + kScrollStep >= fLimit) {
10727cc2508SStefano Ceccherini 			// If we reached the limit, we don't want to scroll a whole
10827cc2508SStefano Ceccherini 			// 'step' if not needed.
10927cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, fLimit - fValue);
11027cc2508SStefano Ceccherini 			fValue = fLimit;
11127cc2508SStefano Ceccherini 			fLowerEnabled = false;
11227cc2508SStefano Ceccherini 			Invalidate(fLowerButton);
11327cc2508SStefano Ceccherini 		} else {
11427cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, kScrollStep);
11527cc2508SStefano Ceccherini 			fValue += kScrollStep;
11627cc2508SStefano Ceccherini 		}
11727cc2508SStefano Ceccherini 	} else if (fUpperButton.Contains(fPosition) && fUpperEnabled) {
11827cc2508SStefano Ceccherini 		if (fValue == fLimit) {
11927cc2508SStefano Ceccherini 			fLowerEnabled = true;
12027cc2508SStefano Ceccherini 			Invalidate(fLowerButton);
12127cc2508SStefano Ceccherini 		}
12227cc2508SStefano Ceccherini 
12327cc2508SStefano Ceccherini 		if (fValue - kScrollStep <= 0) {
12427cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, -fValue);
12527cc2508SStefano Ceccherini 			fValue = 0;
12627cc2508SStefano Ceccherini 			fUpperEnabled = false;
12727cc2508SStefano Ceccherini 			Invalidate(fUpperButton);
12827cc2508SStefano Ceccherini 		} else {
12927cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, -kScrollStep);
13027cc2508SStefano Ceccherini 			fValue -= kScrollStep;
13127cc2508SStefano Ceccherini 		}
13227cc2508SStefano Ceccherini 
13327cc2508SStefano Ceccherini 		// In this case, we need to redraw the lower button because of a
13427cc2508SStefano Ceccherini 		// probable bug in ScrollBy handling. The scrolled view is drawing below
13527cc2508SStefano Ceccherini 		// its bounds, dirtying our button in this process.
13627cc2508SStefano Ceccherini 		// Redrawing it everytime makes scrolling a little slower.
13727cc2508SStefano Ceccherini 		// TODO: Try to find why and fix this.
13827cc2508SStefano Ceccherini 		Draw(fLowerButton);
13927cc2508SStefano Ceccherini 	}
14027cc2508SStefano Ceccherini }
14127cc2508SStefano Ceccherini 
14227cc2508SStefano Ceccherini 
14327cc2508SStefano Ceccherini void
14427cc2508SStefano Ceccherini BMenuScroller::Draw(BRect updateRect)
14527cc2508SStefano Ceccherini {
14627cc2508SStefano Ceccherini 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
14727cc2508SStefano Ceccherini 	float middle = Bounds().right / 2;
14827cc2508SStefano Ceccherini 
14927cc2508SStefano Ceccherini 	// Draw the upper arrow.
15027cc2508SStefano Ceccherini 	if (updateRect.Intersects(fUpperButton)) {
15127cc2508SStefano Ceccherini 		if (fUpperEnabled)
15227cc2508SStefano Ceccherini 			SetHighColor(0, 0, 0);
15327cc2508SStefano Ceccherini 		else
15427cc2508SStefano Ceccherini 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
15527cc2508SStefano Ceccherini 						B_DARKEN_2_TINT));
15627cc2508SStefano Ceccherini 
15727cc2508SStefano Ceccherini 		FillRect(fUpperButton, B_SOLID_LOW);
15827cc2508SStefano Ceccherini 
15927cc2508SStefano Ceccherini 		FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
16027cc2508SStefano Ceccherini 				BPoint(middle + 5, (kScrollerHeight / 2) + 2),
16127cc2508SStefano Ceccherini 				BPoint(middle - 5, (kScrollerHeight / 2) + 2));
16227cc2508SStefano Ceccherini 	}
16327cc2508SStefano Ceccherini 
16427cc2508SStefano Ceccherini 	// Draw the lower arrow.
16527cc2508SStefano Ceccherini 	if (updateRect.Intersects(fLowerButton)) {
16627cc2508SStefano Ceccherini 		if (fLowerEnabled)
16727cc2508SStefano Ceccherini 			SetHighColor(0, 0, 0);
16827cc2508SStefano Ceccherini 		else
16927cc2508SStefano Ceccherini 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
17027cc2508SStefano Ceccherini 						B_DARKEN_2_TINT));
17127cc2508SStefano Ceccherini 
17227cc2508SStefano Ceccherini 		FillRect(fLowerButton, B_SOLID_LOW);
17327cc2508SStefano Ceccherini 
17427cc2508SStefano Ceccherini 		FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3),
17527cc2508SStefano Ceccherini 				BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2),
17627cc2508SStefano Ceccherini 				BPoint(middle - 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2));
17727cc2508SStefano Ceccherini 	}
17827cc2508SStefano Ceccherini }
17927cc2508SStefano Ceccherini 
18027cc2508SStefano Ceccherini 
18127cc2508SStefano Ceccherini //	#pragma mark -
1825b752875SStefano Ceccherini 
1835b752875SStefano Ceccherini 
184*2628e60cSAxel Dörfler BMenuFrame::BMenuFrame(BMenu *menu)
185*2628e60cSAxel Dörfler 	: BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
186*2628e60cSAxel Dörfler 	fMenu(menu)
187*2628e60cSAxel Dörfler {
188*2628e60cSAxel Dörfler }
189*2628e60cSAxel Dörfler 
190*2628e60cSAxel Dörfler 
191*2628e60cSAxel Dörfler BMenuFrame::~BMenuFrame()
192*2628e60cSAxel Dörfler {
193*2628e60cSAxel Dörfler }
194*2628e60cSAxel Dörfler 
195*2628e60cSAxel Dörfler 
196*2628e60cSAxel Dörfler void
197*2628e60cSAxel Dörfler BMenuFrame::AttachedToWindow()
198*2628e60cSAxel Dörfler {
199*2628e60cSAxel Dörfler 	BView::AttachedToWindow();
200*2628e60cSAxel Dörfler 
201*2628e60cSAxel Dörfler 	if (fMenu != NULL)
202*2628e60cSAxel Dörfler 		AddChild(fMenu);
203*2628e60cSAxel Dörfler 
204*2628e60cSAxel Dörfler 	ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
205*2628e60cSAxel Dörfler 	if (fMenu != NULL) {
206*2628e60cSAxel Dörfler 		BFont font;
207*2628e60cSAxel Dörfler 		fMenu->GetFont(&font);
208*2628e60cSAxel Dörfler 		SetFont(&font);
209*2628e60cSAxel Dörfler 	}
210*2628e60cSAxel Dörfler }
211*2628e60cSAxel Dörfler 
212*2628e60cSAxel Dörfler 
213*2628e60cSAxel Dörfler void
214*2628e60cSAxel Dörfler BMenuFrame::DetachedFromWindow()
215*2628e60cSAxel Dörfler {
216*2628e60cSAxel Dörfler 	if (fMenu != NULL)
217*2628e60cSAxel Dörfler 		RemoveChild(fMenu);
218*2628e60cSAxel Dörfler }
219*2628e60cSAxel Dörfler 
220*2628e60cSAxel Dörfler 
221*2628e60cSAxel Dörfler void
222*2628e60cSAxel Dörfler BMenuFrame::Draw(BRect updateRect)
223*2628e60cSAxel Dörfler {
224*2628e60cSAxel Dörfler 	if (fMenu != NULL && fMenu->CountItems() == 0) {
225*2628e60cSAxel Dörfler 		// TODO: Review this as it's a bit hacky.
226*2628e60cSAxel Dörfler 		// Menu has a size of 0, 0, since there are no items in it.
227*2628e60cSAxel Dörfler 		// So the BMenuFrame class has to fake it and draw an empty item.
228*2628e60cSAxel Dörfler 		// Note that we can't add a real "empty" item because then we couldn't
229*2628e60cSAxel Dörfler 		// tell if the item was added by us or not.
230*2628e60cSAxel Dörfler 		// See also BMenu::UpdateWindowViewSize()
231*2628e60cSAxel Dörfler 		SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
232*2628e60cSAxel Dörfler 		SetLowColor(HighColor());
233*2628e60cSAxel Dörfler 		FillRect(updateRect);
234*2628e60cSAxel Dörfler 
235*2628e60cSAxel Dörfler 		font_height height;
236*2628e60cSAxel Dörfler 		GetFontHeight(&height);
237*2628e60cSAxel Dörfler 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DISABLED_LABEL_TINT));
238*2628e60cSAxel Dörfler 		BPoint where((Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, ceilf(height.ascent + 1));
239*2628e60cSAxel Dörfler 		DrawString(kEmptyMenuLabel, where);
240*2628e60cSAxel Dörfler 	}
241*2628e60cSAxel Dörfler 
242*2628e60cSAxel Dörfler 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
243*2628e60cSAxel Dörfler 	BRect bounds(Bounds());
244*2628e60cSAxel Dörfler 
245*2628e60cSAxel Dörfler 	StrokeLine(BPoint(bounds.right, bounds.top),
246*2628e60cSAxel Dörfler 		BPoint(bounds.right, bounds.bottom - 1));
247*2628e60cSAxel Dörfler 	StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
248*2628e60cSAxel Dörfler 		BPoint(bounds.right, bounds.bottom));
249*2628e60cSAxel Dörfler }
250*2628e60cSAxel Dörfler 
251*2628e60cSAxel Dörfler 
252*2628e60cSAxel Dörfler //	#pragma mark -
253*2628e60cSAxel Dörfler 
254*2628e60cSAxel Dörfler 
2554185bd8bSStefano Ceccherini BMenuWindow::BMenuWindow(const char *name)
2561664b981SStefano Ceccherini 	// The window will be resized by BMenu, so just pass a dummy rect
257c7023acdSAxel Dörfler 	: BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
258c7023acdSAxel Dörfler 			B_NOT_ZOOMABLE | B_AVOID_FOCUS),
25927cc2508SStefano Ceccherini 	fScroller(NULL),
26027cc2508SStefano Ceccherini 	fMenuFrame(NULL)
261446b8c19SStefano Ceccherini {
26227cc2508SStefano Ceccherini 	SetPulseRate(200000);
263446b8c19SStefano Ceccherini }
264446b8c19SStefano Ceccherini 
265446b8c19SStefano Ceccherini 
266446b8c19SStefano Ceccherini BMenuWindow::~BMenuWindow()
267446b8c19SStefano Ceccherini {
268446b8c19SStefano Ceccherini }
2695aa032f1SStefano Ceccherini 
2705aa032f1SStefano Ceccherini 
27161ba5a32SStefano Ceccherini void
2724185bd8bSStefano Ceccherini BMenuWindow::AttachMenu(BMenu *menu)
2734185bd8bSStefano Ceccherini {
27427cc2508SStefano Ceccherini 	if (fMenuFrame)
27527cc2508SStefano Ceccherini 		debugger("BMenuWindow: a menu is already attached!");
2764185bd8bSStefano Ceccherini 	if (menu != NULL) {
27727cc2508SStefano Ceccherini 		fMenuFrame = new BMenuFrame(menu);
27827cc2508SStefano Ceccherini 		AddChild(fMenuFrame);
27907dc2c69SAxel Dörfler 		menu->MakeFocus(true);
2804185bd8bSStefano Ceccherini 	}
2814185bd8bSStefano Ceccherini }
2824185bd8bSStefano Ceccherini 
2834185bd8bSStefano Ceccherini 
2844185bd8bSStefano Ceccherini void
2854185bd8bSStefano Ceccherini BMenuWindow::DetachMenu()
28661ba5a32SStefano Ceccherini {
28727cc2508SStefano Ceccherini 	if (fMenuFrame) {
28827cc2508SStefano Ceccherini 		if (fScroller) {
28927cc2508SStefano Ceccherini 			DetachScrollers();
29027cc2508SStefano Ceccherini 		} else {
29127cc2508SStefano Ceccherini 			RemoveChild(fMenuFrame);
29227cc2508SStefano Ceccherini 		}
29327cc2508SStefano Ceccherini 		delete fMenuFrame;
29427cc2508SStefano Ceccherini 		fMenuFrame = NULL;
29527cc2508SStefano Ceccherini 	}
29627cc2508SStefano Ceccherini }
29727cc2508SStefano Ceccherini 
29827cc2508SStefano Ceccherini 
29927cc2508SStefano Ceccherini void
30027cc2508SStefano Ceccherini BMenuWindow::AttachScrollers()
30127cc2508SStefano Ceccherini {
30227cc2508SStefano Ceccherini 	// We want to attach a scroller only if there's a menu frame already
30327cc2508SStefano Ceccherini 	// existing.
30427cc2508SStefano Ceccherini 	if (fScroller || !fMenuFrame)
30527cc2508SStefano Ceccherini 		return;
30627cc2508SStefano Ceccherini 
30727cc2508SStefano Ceccherini 	RemoveChild(fMenuFrame);
30827cc2508SStefano Ceccherini 	fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu);
30927cc2508SStefano Ceccherini 	fScroller->AddChild(fMenuFrame);
31027cc2508SStefano Ceccherini 	AddChild(fScroller);
31127cc2508SStefano Ceccherini 
31227cc2508SStefano Ceccherini 	fMenuFrame->fMenu->MakeFocus(true);
31327cc2508SStefano Ceccherini 
31427cc2508SStefano Ceccherini 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
31527cc2508SStefano Ceccherini 	fMenuFrame->MoveBy(0, kScrollerHeight);
31627cc2508SStefano Ceccherini }
31727cc2508SStefano Ceccherini 
31827cc2508SStefano Ceccherini 
31927cc2508SStefano Ceccherini void
32027cc2508SStefano Ceccherini BMenuWindow::DetachScrollers()
32127cc2508SStefano Ceccherini {
32227cc2508SStefano Ceccherini 	if(!fScroller || !fMenuFrame)
32327cc2508SStefano Ceccherini 		return;
32427cc2508SStefano Ceccherini 
32527cc2508SStefano Ceccherini 	// BeOS doesn't remember the position where the last scrolling ended,
32627cc2508SStefano Ceccherini 	// so we just scroll back to the beginning.
32727cc2508SStefano Ceccherini 	fMenuFrame->fMenu->ScrollTo(0, 0);
32827cc2508SStefano Ceccherini 
32927cc2508SStefano Ceccherini 	fScroller->RemoveChild(fMenuFrame);
33027cc2508SStefano Ceccherini 	RemoveChild(fScroller);
33127cc2508SStefano Ceccherini 
33227cc2508SStefano Ceccherini 	delete fScroller;
33327cc2508SStefano Ceccherini 	fScroller = NULL;
33461ba5a32SStefano Ceccherini }
335