xref: /haiku/src/kits/interface/MenuWindow.cpp (revision 9d6d3fcf5fe8308cd020cecf89dede440346f8c4)
1 /*
2  * Copyright 2001-2006, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (burton666@libero.it)
8  */
9 
10 //!	BMenuWindow is a custom BWindow for BMenus.
11 
12 #include <Menu.h>
13 
14 #include <MenuPrivate.h>
15 #include <MenuWindow.h>
16 #include <WindowPrivate.h>
17 
18 
19 namespace BPrivate {
20 
21 class BMenuScroller : public BView {
22 	public:
23 		BMenuScroller(BRect frame, BMenu *menu);
24 		virtual ~BMenuScroller();
25 
26 		virtual void Pulse();
27 		virtual void Draw(BRect updateRect);
28 
29 	private:
30 		BMenu *fMenu;
31 		BRect fUpperButton;
32 		BRect fLowerButton;
33 
34 		float fValue;
35 		float fLimit;
36 
37 		bool fUpperEnabled;
38 		bool fLowerEnabled;
39 
40 		uint32 fButton;
41 		BPoint fPosition;
42 };
43 
44 class BMenuFrame : public BView {
45 	public:
46 		BMenuFrame(BMenu *menu);
47 		virtual ~BMenuFrame();
48 
49 		virtual void AttachedToWindow();
50 		virtual void DetachedFromWindow();
51 		virtual void Draw(BRect updateRect);
52 
53   private:
54 		friend class BMenuWindow;
55 
56 		BMenu *fMenu;
57 };
58 
59 }	// namespace BPrivate
60 
61 using namespace BPrivate;
62 
63 
64 const int kScrollerHeight = 10;
65 const int kScrollStep = 16;
66 
67 
68 BMenuScroller::BMenuScroller(BRect frame, BMenu *menu)
69 	: BView(frame, "menu scroller", 0,
70 		B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED),
71 	fMenu(menu),
72 	fUpperButton(0, 0, frame.right, kScrollerHeight),
73 	fLowerButton(0, frame.bottom - kScrollerHeight, frame.right, frame.bottom),
74 	fValue(0),
75 	fUpperEnabled(false),
76 	fLowerEnabled(true)
77 {
78 	if (!menu)
79 		debugger("BMenuScroller(): Scroller not attached to a menu!");
80 	SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
81 
82 	fLimit = menu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
83 }
84 
85 
86 BMenuScroller::~BMenuScroller()
87 {
88 }
89 
90 
91 void
92 BMenuScroller::Pulse()
93 {
94 	// To reduce the overhead even a little, fButton and fPosition were
95 	// made private variables.
96 
97 	// We track the mouse and move the scrollers depending on its position.
98 	GetMouse(&fPosition, &fButton);
99 	if (fLowerButton.Contains(fPosition) && fLowerEnabled) {
100 		if (fValue == 0) {
101 			fUpperEnabled = true;
102 
103 			Invalidate(fUpperButton);
104 		}
105 
106 		if (fValue + kScrollStep >= fLimit) {
107 			// If we reached the limit, we don't want to scroll a whole
108 			// 'step' if not needed.
109 			fMenu->ScrollBy(0, fLimit - fValue);
110 			fValue = fLimit;
111 			fLowerEnabled = false;
112 			Invalidate(fLowerButton);
113 		} else {
114 			fMenu->ScrollBy(0, kScrollStep);
115 			fValue += kScrollStep;
116 		}
117 	} else if (fUpperButton.Contains(fPosition) && fUpperEnabled) {
118 		if (fValue == fLimit) {
119 			fLowerEnabled = true;
120 			Invalidate(fLowerButton);
121 		}
122 
123 		if (fValue - kScrollStep <= 0) {
124 			fMenu->ScrollBy(0, -fValue);
125 			fValue = 0;
126 			fUpperEnabled = false;
127 			Invalidate(fUpperButton);
128 		} else {
129 			fMenu->ScrollBy(0, -kScrollStep);
130 			fValue -= kScrollStep;
131 		}
132 	}
133 }
134 
135 
136 void
137 BMenuScroller::Draw(BRect updateRect)
138 {
139 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
140 	float middle = Bounds().right / 2;
141 
142 	// Draw the upper arrow.
143 	if (updateRect.Intersects(fUpperButton)) {
144 		if (fUpperEnabled)
145 			SetHighColor(0, 0, 0);
146 		else
147 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
148 						B_DARKEN_2_TINT));
149 
150 		FillRect(fUpperButton, B_SOLID_LOW);
151 
152 		FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
153 				BPoint(middle + 5, (kScrollerHeight / 2) + 2),
154 				BPoint(middle - 5, (kScrollerHeight / 2) + 2));
155 	}
156 
157 	// Draw the lower arrow.
158 	if (updateRect.Intersects(fLowerButton)) {
159 		if (fLowerEnabled)
160 			SetHighColor(0, 0, 0);
161 		else
162 			SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
163 						B_DARKEN_2_TINT));
164 
165 		FillRect(fLowerButton, B_SOLID_LOW);
166 
167 		FillTriangle(BPoint(middle, fLowerButton.bottom - (kScrollerHeight / 2) + 3),
168 				BPoint(middle + 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2),
169 				BPoint(middle - 5, fLowerButton.bottom - (kScrollerHeight / 2) - 2));
170 	}
171 }
172 
173 
174 //	#pragma mark -
175 
176 
177 BMenuFrame::BMenuFrame(BMenu *menu)
178 	: BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
179 	fMenu(menu)
180 {
181 }
182 
183 
184 BMenuFrame::~BMenuFrame()
185 {
186 }
187 
188 
189 void
190 BMenuFrame::AttachedToWindow()
191 {
192 	BView::AttachedToWindow();
193 
194 	if (fMenu != NULL)
195 		AddChild(fMenu);
196 
197 	ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
198 	if (fMenu != NULL) {
199 		BFont font;
200 		fMenu->GetFont(&font);
201 		SetFont(&font);
202 	}
203 }
204 
205 
206 void
207 BMenuFrame::DetachedFromWindow()
208 {
209 	if (fMenu != NULL)
210 		RemoveChild(fMenu);
211 }
212 
213 
214 void
215 BMenuFrame::Draw(BRect updateRect)
216 {
217 	if (fMenu != NULL && fMenu->CountItems() == 0) {
218 		// TODO: Review this as it's a bit hacky.
219 		// Menu has a size of 0, 0, since there are no items in it.
220 		// So the BMenuFrame class has to fake it and draw an empty item.
221 		// Note that we can't add a real "empty" item because then we couldn't
222 		// tell if the item was added by us or not.
223 		// See also BMenu::UpdateWindowViewSize()
224 		SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
225 		SetLowColor(HighColor());
226 		FillRect(updateRect);
227 
228 		font_height height;
229 		GetFontHeight(&height);
230 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DISABLED_LABEL_TINT));
231 		BPoint where((Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2, ceilf(height.ascent + 1));
232 		DrawString(kEmptyMenuLabel, where);
233 	}
234 
235 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
236 	BRect bounds(Bounds());
237 
238 	StrokeLine(BPoint(bounds.right, bounds.top),
239 		BPoint(bounds.right, bounds.bottom - 1));
240 	StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
241 		BPoint(bounds.right, bounds.bottom));
242 }
243 
244 
245 //	#pragma mark -
246 
247 
248 BMenuWindow::BMenuWindow(const char *name)
249 	// The window will be resized by BMenu, so just pass a dummy rect
250 	: BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
251 			B_NOT_ZOOMABLE | B_AVOID_FOCUS),
252 	fScroller(NULL),
253 	fMenuFrame(NULL)
254 {
255 	SetPulseRate(200000);
256 }
257 
258 
259 BMenuWindow::~BMenuWindow()
260 {
261 }
262 
263 
264 void
265 BMenuWindow::AttachMenu(BMenu *menu)
266 {
267 	if (fMenuFrame)
268 		debugger("BMenuWindow: a menu is already attached!");
269 	if (menu != NULL) {
270 		fMenuFrame = new BMenuFrame(menu);
271 		AddChild(fMenuFrame);
272 		menu->MakeFocus(true);
273 	}
274 }
275 
276 
277 void
278 BMenuWindow::DetachMenu()
279 {
280 	if (fMenuFrame) {
281 		if (fScroller) {
282 			DetachScrollers();
283 		} else {
284 			RemoveChild(fMenuFrame);
285 		}
286 		delete fMenuFrame;
287 		fMenuFrame = NULL;
288 	}
289 }
290 
291 
292 void
293 BMenuWindow::AttachScrollers()
294 {
295 	// We want to attach a scroller only if there's a menu frame already
296 	// existing.
297 	if (fScroller || !fMenuFrame)
298 		return;
299 
300 	RemoveChild(fMenuFrame);
301 	fScroller = new BMenuScroller(Bounds(), fMenuFrame->fMenu);
302 	fScroller->AddChild(fMenuFrame);
303 	AddChild(fScroller);
304 
305 	fMenuFrame->fMenu->MakeFocus(true);
306 
307 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
308 	fMenuFrame->MoveBy(0, kScrollerHeight);
309 }
310 
311 
312 void
313 BMenuWindow::DetachScrollers()
314 {
315 	if(!fScroller || !fMenuFrame)
316 		return;
317 
318 	// BeOS doesn't remember the position where the last scrolling ended,
319 	// so we just scroll back to the beginning.
320 	fMenuFrame->fMenu->ScrollTo(0, 0);
321 
322 	fScroller->RemoveChild(fMenuFrame);
323 	RemoveChild(fScroller);
324 
325 	delete fScroller;
326 	fScroller = NULL;
327 }
328