xref: /haiku/src/kits/interface/MenuWindow.cpp (revision 27cc25083edc760e3ce0f2163f5222fa118e7852)
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)
8*27cc2508SStefano Ceccherini  *
9c7023acdSAxel Dörfler  */
10c7023acdSAxel Dörfler 
11c7023acdSAxel Dörfler //!	BMenuWindow is a custom BWindow for BMenus.
12c7023acdSAxel Dörfler 
13446b8c19SStefano Ceccherini #include <Menu.h>
14446b8c19SStefano Ceccherini 
152c11ec31SStefano Ceccherini #include <MenuPrivate.h>
162c11ec31SStefano Ceccherini #include <MenuWindow.h>
17c7023acdSAxel Dörfler #include <WindowPrivate.h>
18c7023acdSAxel Dörfler 
19e411c658SStefano Ceccherini 
20*27cc2508SStefano Ceccherini const int kScrollerHeight = 10;
21*27cc2508SStefano Ceccherini const int kScrollStep = 16;
225b752875SStefano Ceccherini 
23*27cc2508SStefano Ceccherini BMenuScroller::BMenuScroller(BRect frame, BMenu *menu)
24*27cc2508SStefano Ceccherini 	:	BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS
25*27cc2508SStefano Ceccherini 			| B_PULSE_NEEDED),
26*27cc2508SStefano Ceccherini 	fMenu(menu),
27*27cc2508SStefano Ceccherini 	fUpperButton(0, 0, frame.right, kScrollerHeight),
28*27cc2508SStefano Ceccherini 	fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom),
29*27cc2508SStefano Ceccherini 	fValue(0),
30*27cc2508SStefano Ceccherini 	fUpperEnabled(false),
31*27cc2508SStefano Ceccherini 	fLowerEnabled(true)
32*27cc2508SStefano Ceccherini {
33*27cc2508SStefano Ceccherini 	if (!menu)
34*27cc2508SStefano Ceccherini 		debugger("BMenuScroller(): Scroller not attached to a menu!");
35*27cc2508SStefano Ceccherini 	SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
3661ba5a32SStefano Ceccherini 
37*27cc2508SStefano Ceccherini 	fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
38*27cc2508SStefano Ceccherini }
39*27cc2508SStefano Ceccherini 
40*27cc2508SStefano Ceccherini 
41*27cc2508SStefano Ceccherini BMenuScroller::~BMenuScroller()
42*27cc2508SStefano Ceccherini {
43*27cc2508SStefano Ceccherini }
44*27cc2508SStefano Ceccherini 
45*27cc2508SStefano Ceccherini 
46*27cc2508SStefano Ceccherini void
47*27cc2508SStefano Ceccherini BMenuScroller::Pulse()
48*27cc2508SStefano Ceccherini {
49*27cc2508SStefano Ceccherini 	// To reduce the overhead even a little, fButton and fPosition were
50*27cc2508SStefano Ceccherini 	// made private variables.
51*27cc2508SStefano Ceccherini 
52*27cc2508SStefano Ceccherini 	// We track the mouse and move the scrollers depending on its position.
53*27cc2508SStefano Ceccherini 	GetMouse(&fPosition, &fButton);
54*27cc2508SStefano Ceccherini 	if (fLowerButton.Contains(fPosition) && fLowerEnabled) {
55*27cc2508SStefano Ceccherini 		if (fValue == 0) {
56*27cc2508SStefano Ceccherini 			fUpperEnabled = true;
57*27cc2508SStefano Ceccherini 
58*27cc2508SStefano Ceccherini 			Invalidate(fUpperButton);
59*27cc2508SStefano Ceccherini 		}
60*27cc2508SStefano Ceccherini 
61*27cc2508SStefano Ceccherini 		if (fValue + kScrollStep >= fLimit) {
62*27cc2508SStefano Ceccherini 			// If we reached the limit, we don't want to scroll a whole
63*27cc2508SStefano Ceccherini 			// 'step' if not needed.
64*27cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, fLimit - fValue);
65*27cc2508SStefano Ceccherini 			fValue = fLimit;
66*27cc2508SStefano Ceccherini 			fLowerEnabled = false;
67*27cc2508SStefano Ceccherini 			Invalidate(fLowerButton);
68*27cc2508SStefano Ceccherini 		} else {
69*27cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, kScrollStep);
70*27cc2508SStefano Ceccherini 			fValue += kScrollStep;
71*27cc2508SStefano Ceccherini 		}
72*27cc2508SStefano Ceccherini 	} else if (fUpperButton.Contains(fPosition) && fUpperEnabled) {
73*27cc2508SStefano Ceccherini 		if (fValue == fLimit) {
74*27cc2508SStefano Ceccherini 			fLowerEnabled = true;
75*27cc2508SStefano Ceccherini 			Invalidate(fLowerButton);
76*27cc2508SStefano Ceccherini 		}
77*27cc2508SStefano Ceccherini 
78*27cc2508SStefano Ceccherini 		if (fValue - kScrollStep <= 0) {
79*27cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, -fValue);
80*27cc2508SStefano Ceccherini 			fValue = 0;
81*27cc2508SStefano Ceccherini 			fUpperEnabled = false;
82*27cc2508SStefano Ceccherini 			Invalidate(fUpperButton);
83*27cc2508SStefano Ceccherini 		} else {
84*27cc2508SStefano Ceccherini 			fMenu->ScrollBy(0, -kScrollStep);
85*27cc2508SStefano Ceccherini 			fValue -= kScrollStep;
86*27cc2508SStefano Ceccherini 		}
87*27cc2508SStefano Ceccherini 
88*27cc2508SStefano Ceccherini 		// In this case, we need to redraw the lower button because of a
89*27cc2508SStefano Ceccherini 		// probable bug in ScrollBy handling. The scrolled view is drawing below
90*27cc2508SStefano Ceccherini 		// its bounds, dirtying our button in this process.
91*27cc2508SStefano Ceccherini 		// Redrawing it everytime makes scrolling a little slower.
92*27cc2508SStefano Ceccherini 		// TODO: Try to find why and fix this.
93*27cc2508SStefano Ceccherini 		Draw(fLowerButton);
94*27cc2508SStefano Ceccherini 	}
95*27cc2508SStefano Ceccherini }
96*27cc2508SStefano Ceccherini 
97*27cc2508SStefano Ceccherini 
98*27cc2508SStefano Ceccherini void
99*27cc2508SStefano Ceccherini BMenuScroller::Draw(BRect updateRect)
100*27cc2508SStefano Ceccherini {
101*27cc2508SStefano Ceccherini 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
102*27cc2508SStefano Ceccherini 	float middle = Bounds().right / 2;
103*27cc2508SStefano Ceccherini 
104*27cc2508SStefano Ceccherini 	// Draw the upper arrow.
105*27cc2508SStefano Ceccherini 	if (updateRect.Intersects(fUpperButton)) {
106*27cc2508SStefano Ceccherini 		if (fUpperEnabled)
107*27cc2508SStefano Ceccherini 			SetHighColor(0, 0, 0);
108*27cc2508SStefano Ceccherini 		else
109*27cc2508SStefano Ceccherini 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
110*27cc2508SStefano Ceccherini 						B_DARKEN_2_TINT));
111*27cc2508SStefano Ceccherini 
112*27cc2508SStefano Ceccherini 		FillRect(fUpperButton, B_SOLID_LOW);
113*27cc2508SStefano Ceccherini 
114*27cc2508SStefano Ceccherini 		FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
115*27cc2508SStefano Ceccherini 				BPoint(middle + 5, (kScrollerHeight / 2) + 2),
116*27cc2508SStefano Ceccherini 				BPoint(middle - 5, (kScrollerHeight / 2) + 2));
117*27cc2508SStefano Ceccherini 	}
118*27cc2508SStefano Ceccherini 
119*27cc2508SStefano Ceccherini 	// Draw the lower arrow.
120*27cc2508SStefano Ceccherini 	if (updateRect.Intersects(fLowerButton)) {
121*27cc2508SStefano Ceccherini 		if (fLowerEnabled)
122*27cc2508SStefano Ceccherini 			SetHighColor(0, 0, 0);
123*27cc2508SStefano Ceccherini 		else
124*27cc2508SStefano Ceccherini 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
125*27cc2508SStefano Ceccherini 						B_DARKEN_2_TINT));
126*27cc2508SStefano Ceccherini 
127*27cc2508SStefano Ceccherini 		FillRect(fLowerButton, B_SOLID_LOW);
128*27cc2508SStefano Ceccherini 
129*27cc2508SStefano Ceccherini 		FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3),
130*27cc2508SStefano Ceccherini 				BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2),
131*27cc2508SStefano Ceccherini 				BPoint(middle - 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2));
132*27cc2508SStefano Ceccherini 	}
133*27cc2508SStefano Ceccherini }
134*27cc2508SStefano Ceccherini 
135*27cc2508SStefano Ceccherini 
136*27cc2508SStefano Ceccherini //	#pragma mark -
1375b752875SStefano Ceccherini 
1385b752875SStefano Ceccherini 
1394185bd8bSStefano Ceccherini BMenuWindow::BMenuWindow(const char *name)
1401664b981SStefano Ceccherini 	// The window will be resized by BMenu, so just pass a dummy rect
141c7023acdSAxel Dörfler 	: BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
142c7023acdSAxel Dörfler 			B_NOT_ZOOMABLE | B_AVOID_FOCUS),
143*27cc2508SStefano Ceccherini 	fScroller(NULL),
144*27cc2508SStefano Ceccherini 	fMenuFrame(NULL)
145446b8c19SStefano Ceccherini {
146*27cc2508SStefano Ceccherini 	SetPulseRate(200000);
147446b8c19SStefano Ceccherini }
148446b8c19SStefano Ceccherini 
149446b8c19SStefano Ceccherini 
150446b8c19SStefano Ceccherini BMenuWindow::~BMenuWindow()
151446b8c19SStefano Ceccherini {
152446b8c19SStefano Ceccherini }
1535aa032f1SStefano Ceccherini 
1545aa032f1SStefano Ceccherini 
15561ba5a32SStefano Ceccherini void
1564185bd8bSStefano Ceccherini BMenuWindow::AttachMenu(BMenu *menu)
1574185bd8bSStefano Ceccherini {
158*27cc2508SStefano Ceccherini 	if (fMenuFrame)
159*27cc2508SStefano Ceccherini 		debugger("BMenuWindow: a menu is already attached!");
1604185bd8bSStefano Ceccherini 	if (menu != NULL) {
161*27cc2508SStefano Ceccherini 		fMenuFrame = new BMenuFrame(menu);
162*27cc2508SStefano Ceccherini 		AddChild(fMenuFrame);
16307dc2c69SAxel Dörfler 		menu->MakeFocus(true);
1644185bd8bSStefano Ceccherini 	}
1654185bd8bSStefano Ceccherini }
1664185bd8bSStefano Ceccherini 
1674185bd8bSStefano Ceccherini 
1684185bd8bSStefano Ceccherini void
1694185bd8bSStefano Ceccherini BMenuWindow::DetachMenu()
17061ba5a32SStefano Ceccherini {
171*27cc2508SStefano Ceccherini 	if (fMenuFrame) {
172*27cc2508SStefano Ceccherini 		if (fScroller) {
173*27cc2508SStefano Ceccherini 			DetachScrollers();
174*27cc2508SStefano Ceccherini 		} else {
175*27cc2508SStefano Ceccherini 			RemoveChild(fMenuFrame);
176*27cc2508SStefano Ceccherini 		}
177*27cc2508SStefano Ceccherini 		delete fMenuFrame;
178*27cc2508SStefano Ceccherini 		fMenuFrame = NULL;
179*27cc2508SStefano Ceccherini 	}
180*27cc2508SStefano Ceccherini }
181*27cc2508SStefano Ceccherini 
182*27cc2508SStefano Ceccherini 
183*27cc2508SStefano Ceccherini void
184*27cc2508SStefano Ceccherini BMenuWindow::AttachScrollers()
185*27cc2508SStefano Ceccherini {
186*27cc2508SStefano Ceccherini 	// We want to attach a scroller only if there's a menu frame already
187*27cc2508SStefano Ceccherini 	// existing.
188*27cc2508SStefano Ceccherini 	if (fScroller || !fMenuFrame)
189*27cc2508SStefano Ceccherini 		return;
190*27cc2508SStefano Ceccherini 
191*27cc2508SStefano Ceccherini 	RemoveChild(fMenuFrame);
192*27cc2508SStefano Ceccherini 	fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu);
193*27cc2508SStefano Ceccherini 	fScroller->AddChild(fMenuFrame);
194*27cc2508SStefano Ceccherini 	AddChild(fScroller);
195*27cc2508SStefano Ceccherini 
196*27cc2508SStefano Ceccherini 	fMenuFrame->fMenu->MakeFocus(true);
197*27cc2508SStefano Ceccherini 
198*27cc2508SStefano Ceccherini 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
199*27cc2508SStefano Ceccherini 	fMenuFrame->MoveBy(0, kScrollerHeight);
200*27cc2508SStefano Ceccherini }
201*27cc2508SStefano Ceccherini 
202*27cc2508SStefano Ceccherini 
203*27cc2508SStefano Ceccherini void
204*27cc2508SStefano Ceccherini BMenuWindow::DetachScrollers()
205*27cc2508SStefano Ceccherini {
206*27cc2508SStefano Ceccherini 	if(!fScroller || !fMenuFrame)
207*27cc2508SStefano Ceccherini 		return;
208*27cc2508SStefano Ceccherini 
209*27cc2508SStefano Ceccherini 	// BeOS doesn't remember the position where the last scrolling ended,
210*27cc2508SStefano Ceccherini 	// so we just scroll back to the beginning.
211*27cc2508SStefano Ceccherini 	fMenuFrame->fMenu->ScrollTo(0, 0);
212*27cc2508SStefano Ceccherini 
213*27cc2508SStefano Ceccherini 	fScroller->RemoveChild(fMenuFrame);
214*27cc2508SStefano Ceccherini 	RemoveChild(fScroller);
215*27cc2508SStefano Ceccherini 
216*27cc2508SStefano Ceccherini 	delete fScroller;
217*27cc2508SStefano Ceccherini 	fScroller = NULL;
21861ba5a32SStefano Ceccherini }
21961ba5a32SStefano Ceccherini 
22061ba5a32SStefano Ceccherini 
221c7023acdSAxel Dörfler //	#pragma mark -
222c7023acdSAxel Dörfler 
223c7023acdSAxel Dörfler 
22461ba5a32SStefano Ceccherini BMenuFrame::BMenuFrame(BMenu *menu)
225c7023acdSAxel Dörfler 	: BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
22661ba5a32SStefano Ceccherini 	fMenu(menu)
2275aa032f1SStefano Ceccherini {
2285aa032f1SStefano Ceccherini }
2295aa032f1SStefano Ceccherini 
2305aa032f1SStefano Ceccherini 
2315aa032f1SStefano Ceccherini void
2325aa032f1SStefano Ceccherini BMenuFrame::AttachedToWindow()
2335aa032f1SStefano Ceccherini {
2345aa032f1SStefano Ceccherini 	BView::AttachedToWindow();
2354185bd8bSStefano Ceccherini 
2364185bd8bSStefano Ceccherini 	if (fMenu != NULL)
2374185bd8bSStefano Ceccherini 		AddChild(fMenu);
2384185bd8bSStefano Ceccherini 
2395aa032f1SStefano Ceccherini 	ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
24055b35fb4SStefano Ceccherini 	if (fMenu != NULL) {
24155b35fb4SStefano Ceccherini 		BFont font;
24255b35fb4SStefano Ceccherini 		fMenu->GetFont(&font);
24355b35fb4SStefano Ceccherini 		SetFont(&font);
24455b35fb4SStefano Ceccherini 	}
2455aa032f1SStefano Ceccherini }
2465aa032f1SStefano Ceccherini 
2475aa032f1SStefano Ceccherini 
2485aa032f1SStefano Ceccherini void
2494185bd8bSStefano Ceccherini BMenuFrame::DetachedFromWindow()
2504185bd8bSStefano Ceccherini {
2514185bd8bSStefano Ceccherini 	if (fMenu != NULL)
2524185bd8bSStefano Ceccherini 		RemoveChild(fMenu);
2534185bd8bSStefano Ceccherini }
2544185bd8bSStefano Ceccherini 
2554185bd8bSStefano Ceccherini 
2564185bd8bSStefano Ceccherini void
2575aa032f1SStefano Ceccherini BMenuFrame::Draw(BRect updateRect)
2585aa032f1SStefano Ceccherini {
2594185bd8bSStefano Ceccherini 	if (fMenu != NULL && fMenu->CountItems() == 0) {
26055b35fb4SStefano Ceccherini 		// TODO: Review this as it's a bit hacky.
26155b35fb4SStefano Ceccherini 		// Menu has a size of 0, 0, since there are no items in it.
26255b35fb4SStefano Ceccherini 		// So the BMenuFrame class has to fake it and draw an empty item.
26355b35fb4SStefano Ceccherini 		// Note that we can't add a real "empty" item because then we couldn't
26455b35fb4SStefano Ceccherini 		// tell if the item was added by us or not.
26555b35fb4SStefano Ceccherini 		// See also BMenu::UpdateWindowViewSize()
26661ba5a32SStefano Ceccherini 		SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
26755b35fb4SStefano Ceccherini 		SetLowColor(HighColor());
26861ba5a32SStefano Ceccherini 		FillRect(updateRect);
26955b35fb4SStefano Ceccherini 
27061ba5a32SStefano Ceccherini 		font_height height;
27155b35fb4SStefano Ceccherini 		GetFontHeight(&height);
27255b35fb4SStefano Ceccherini 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DISABLED_LABEL_TINT));
2734436b726SStefano Ceccherini 		BPoint where((Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, ceilf(height.ascent + 1));
2744436b726SStefano Ceccherini 		DrawString(kEmptyMenuLabel, where);
27561ba5a32SStefano Ceccherini 	}
2765aa032f1SStefano Ceccherini 
2775aa032f1SStefano Ceccherini 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
27861ba5a32SStefano Ceccherini 	BRect bounds(Bounds());
279c7023acdSAxel Dörfler 
2805aa032f1SStefano Ceccherini 	StrokeLine(BPoint(bounds.right, bounds.top),
2815aa032f1SStefano Ceccherini 		BPoint(bounds.right, bounds.bottom - 1));
2825aa032f1SStefano Ceccherini 	StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
2835aa032f1SStefano Ceccherini 		BPoint(bounds.right, bounds.bottom));
2845aa032f1SStefano Ceccherini }
285