xref: /haiku/src/kits/interface/MenuWindow.cpp (revision c90684742e7361651849be4116d0e5de3a817194)
1 /*
2  * Copyright 2001-2009, Haiku, Inc.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
8  */
9 
10 //!	BMenuWindow is a custom BWindow for BMenus.
11 
12 #include <MenuWindow.h>
13 
14 #include <ControlLook.h>
15 #include <Debug.h>
16 #include <Menu.h>
17 #include <MenuItem.h>
18 
19 #include <MenuPrivate.h>
20 #include <WindowPrivate.h>
21 
22 
23 namespace BPrivate {
24 
25 class BMenuScroller : public BView {
26 public:
27 							BMenuScroller(BRect frame);
28 
29 			bool			IsEnabled() const;
30 			void			SetEnabled(bool enabled);
31 
32 private:
33 			bool			fEnabled;
34 };
35 
36 
37 class BMenuFrame : public BView {
38 public:
39 							BMenuFrame(BMenu* menu);
40 
41 	virtual	void			AttachedToWindow();
42 	virtual	void			DetachedFromWindow();
43 	virtual	void			Draw(BRect updateRect);
44 
45 private:
46 	friend class BMenuWindow;
47 
48 			BMenu*			fMenu;
49 };
50 
51 
52 class UpperScroller : public BMenuScroller {
53 public:
54 							UpperScroller(BRect frame);
55 
56 	virtual	void			Draw(BRect updateRect);
57 };
58 
59 
60 class LowerScroller : public BMenuScroller {
61 public:
62 							LowerScroller(BRect frame);
63 
64 	virtual	void			Draw(BRect updateRect);
65 };
66 
67 
68 }	// namespace BPrivate
69 
70 
71 using namespace BPrivate;
72 
73 
74 const int kScrollerHeight = 12;
75 
76 
77 BMenuScroller::BMenuScroller(BRect frame)
78 	:
79 	BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS),
80 	fEnabled(false)
81 {
82 	SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR));
83 }
84 
85 
86 bool
87 BMenuScroller::IsEnabled() const
88 {
89 	return fEnabled;
90 }
91 
92 
93 void
94 BMenuScroller::SetEnabled(bool enabled)
95 {
96 	fEnabled = enabled;
97 }
98 
99 
100 //	#pragma mark -
101 
102 
103 UpperScroller::UpperScroller(BRect frame)
104 	:
105 	BMenuScroller(frame)
106 {
107 }
108 
109 
110 void
111 UpperScroller::Draw(BRect updateRect)
112 {
113 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
114 	float middle = Bounds().right / 2;
115 
116 	// Draw the upper arrow.
117 	if (IsEnabled())
118 		SetHighColor(0, 0, 0);
119 	else {
120 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
121 			B_DARKEN_2_TINT));
122 	}
123 
124 	FillRect(Bounds(), B_SOLID_LOW);
125 
126 	FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
127 		BPoint(middle + 5, (kScrollerHeight / 2) + 2),
128 		BPoint(middle - 5, (kScrollerHeight / 2) + 2));
129 }
130 
131 
132 //	#pragma mark -
133 
134 
135 LowerScroller::LowerScroller(BRect frame)
136 	:
137 	BMenuScroller(frame)
138 {
139 }
140 
141 
142 void
143 LowerScroller::Draw(BRect updateRect)
144 {
145 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
146 
147 	BRect frame = Bounds();
148 	// Draw the lower arrow.
149 	if (IsEnabled())
150 		SetHighColor(0, 0, 0);
151 	else {
152 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
153 			B_DARKEN_2_TINT));
154 	}
155 
156 	FillRect(frame, B_SOLID_LOW);
157 
158 	float middle = Bounds().right / 2;
159 
160 	FillTriangle(BPoint(middle, frame.bottom - (kScrollerHeight / 2) + 3),
161 		BPoint(middle + 5, frame.bottom - (kScrollerHeight / 2) - 2),
162 		BPoint(middle - 5, frame.bottom - (kScrollerHeight / 2) - 2));
163 }
164 
165 
166 //	#pragma mark -
167 
168 
169 BMenuFrame::BMenuFrame(BMenu *menu)
170 	:
171 	BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
172 	fMenu(menu)
173 {
174 }
175 
176 
177 void
178 BMenuFrame::AttachedToWindow()
179 {
180 	BView::AttachedToWindow();
181 
182 	if (fMenu != NULL)
183 		AddChild(fMenu);
184 
185 	ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
186 	if (fMenu != NULL) {
187 		BFont font;
188 		fMenu->GetFont(&font);
189 		SetFont(&font);
190 	}
191 }
192 
193 
194 void
195 BMenuFrame::DetachedFromWindow()
196 {
197 	if (fMenu != NULL)
198 		RemoveChild(fMenu);
199 }
200 
201 
202 void
203 BMenuFrame::Draw(BRect updateRect)
204 {
205 	if (fMenu != NULL && fMenu->CountItems() == 0) {
206 		if (be_control_look != NULL) {
207 			BRect rect(Bounds());
208 			be_control_look->DrawMenuBackground(this, rect, updateRect,
209 				ui_color(B_MENU_BACKGROUND_COLOR));
210 			SetDrawingMode(B_OP_OVER);
211 		} else {
212 			// TODO: Review this as it's a bit hacky.
213 			// Menu has a size of 0, 0, since there are no items in it.
214 			// So the BMenuFrame class has to fake it and draw an empty item.
215 			// Note that we can't add a real "empty" item because then we
216 			// couldn't tell if the item was added by us or not.
217 			// See also BMenu::UpdateWindowViewSize()
218 			SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
219 			SetLowColor(HighColor());
220 			FillRect(updateRect);
221 		}
222 
223 		font_height height;
224 		GetFontHeight(&height);
225 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
226 			B_DISABLED_LABEL_TINT));
227 		BPoint where(
228 			(Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2,
229 			ceilf(height.ascent + 1));
230 		DrawString(kEmptyMenuLabel, where);
231 	}
232 
233 	if (be_control_look != NULL)
234 		return;
235 
236 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
237 	BRect bounds(Bounds());
238 
239 	StrokeLine(BPoint(bounds.right, bounds.top),
240 		BPoint(bounds.right, bounds.bottom - 1));
241 	StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
242 		BPoint(bounds.right, bounds.bottom));
243 }
244 
245 
246 
247 //	#pragma mark -
248 
249 
250 BMenuWindow::BMenuWindow(const char *name)
251 	// The window will be resized by BMenu, so just pass a dummy rect
252 	: BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
253 		B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_AVOID_FOCUS
254 			| kAcceptKeyboardFocusFlag),
255 	fMenu(NULL),
256 	fMenuFrame(NULL),
257 	fUpperScroller(NULL),
258 	fLowerScroller(NULL),
259 	fScrollStep(19)
260 {
261 	SetSizeLimits(2, 10000, 2, 10000);
262 }
263 
264 
265 BMenuWindow::~BMenuWindow()
266 {
267 	DetachMenu();
268 }
269 
270 
271 void
272 BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
273 {
274 	BWindow::DispatchMessage(message, handler);
275 }
276 
277 
278 void
279 BMenuWindow::AttachMenu(BMenu *menu)
280 {
281 	if (fMenuFrame)
282 		debugger("BMenuWindow: a menu is already attached!");
283 	if (menu != NULL) {
284 		fMenuFrame = new BMenuFrame(menu);
285 		AddChild(fMenuFrame);
286 		menu->MakeFocus(true);
287 		fMenu = menu;
288 	}
289 }
290 
291 
292 void
293 BMenuWindow::DetachMenu()
294 {
295 	DetachScrollers();
296 	if (fMenuFrame) {
297 		RemoveChild(fMenuFrame);
298 		delete fMenuFrame;
299 		fMenuFrame = NULL;
300 		fMenu = NULL;
301 	}
302 }
303 
304 
305 void
306 BMenuWindow::AttachScrollers()
307 {
308 	// We want to attach a scroller only if there's a
309 	// menu frame already existing.
310 	if (!fMenu || !fMenuFrame)
311 		return;
312 
313 	fMenu->MakeFocus(true);
314 
315 	BRect frame = Bounds();
316 
317 	if (fUpperScroller == NULL) {
318 		fUpperScroller = new UpperScroller(
319 			BRect(0, 0, frame.right, kScrollerHeight - 1));
320 		AddChild(fUpperScroller);
321 	}
322 
323 	if (fLowerScroller == NULL) {
324 		fLowerScroller = new LowerScroller(
325 			BRect(0, frame.bottom - kScrollerHeight + 1, frame.right,
326 				frame.bottom));
327 		AddChild(fLowerScroller);
328 	}
329 
330 	fUpperScroller->SetEnabled(false);
331 	fLowerScroller->SetEnabled(true);
332 
333 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
334 	fMenuFrame->MoveBy(0, kScrollerHeight);
335 
336 	fValue = 0;
337 	fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
338 }
339 
340 
341 void
342 BMenuWindow::DetachScrollers()
343 {
344 	// BeOS doesn't remember the position where the last scrolling ended,
345 	// so we just scroll back to the beginning.
346 	if (fMenu)
347 		fMenu->ScrollTo(0, 0);
348 
349 	if (fLowerScroller) {
350 		RemoveChild(fLowerScroller);
351 		delete fLowerScroller;
352 		fLowerScroller = NULL;
353 	}
354 
355 	if (fUpperScroller) {
356 		RemoveChild(fUpperScroller);
357 		delete fUpperScroller;
358 		fUpperScroller = NULL;
359 	}
360 }
361 
362 
363 void
364 BMenuWindow::SetSmallStep(float step)
365 {
366 	fScrollStep = step;
367 }
368 
369 
370 void
371 BMenuWindow::GetSteps(float* _smallStep, float* _largeStep) const
372 {
373 	if (_smallStep != NULL)
374 		*_smallStep = fScrollStep;
375 	if (_largeStep != NULL) {
376 		if (fMenuFrame != NULL)
377 			*_largeStep = fMenuFrame->Bounds().Height() - fScrollStep;
378 		else
379 			*_largeStep = fScrollStep * 2;
380 	}
381 }
382 
383 
384 bool
385 BMenuWindow::HasScrollers() const
386 {
387 	return fMenuFrame != NULL && fUpperScroller != NULL
388 		&& fLowerScroller != NULL;
389 }
390 
391 
392 bool
393 BMenuWindow::CheckForScrolling(const BPoint &cursor)
394 {
395 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
396 		return false;
397 
398 	return _Scroll(cursor);
399 }
400 
401 
402 bool
403 BMenuWindow::TryScrollBy(const float& step)
404 {
405 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
406 		return false;
407 
408 	_ScrollBy(step);
409 	return true;
410 }
411 
412 
413 bool
414 BMenuWindow::_Scroll(const BPoint& where)
415 {
416 	ASSERT((fLowerScroller != NULL));
417 	ASSERT((fUpperScroller != NULL));
418 
419 	const BPoint cursor = ConvertFromScreen(where);
420 	const BRect &lowerFrame = fLowerScroller->Frame();
421 	const BRect &upperFrame = fUpperScroller->Frame();
422 
423 	int32 delta = 0;
424 	if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor))
425 		delta = 1;
426 	else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor))
427 		delta = -1;
428 
429 	if (delta == 0)
430 		return false;
431 
432 	float smallStep;
433 	GetSteps(&smallStep, NULL);
434 	_ScrollBy(smallStep * delta);
435 
436 	snooze(5000);
437 
438 	return true;
439 }
440 
441 
442 void
443 BMenuWindow::_ScrollBy(const float& step)
444 {
445 	if (step > 0) {
446 		if (fValue == 0) {
447 			fUpperScroller->SetEnabled(true);
448 			fUpperScroller->Invalidate();
449 		}
450 
451 		if (fValue + step >= fLimit) {
452 			// If we reached the limit, only scroll to the end
453 			fMenu->ScrollBy(0, fLimit - fValue);
454 			fValue = fLimit;
455 			fLowerScroller->SetEnabled(false);
456 			fLowerScroller->Invalidate();
457 		} else {
458 			fMenu->ScrollBy(0, step);
459 			fValue += step;
460 		}
461 	} else if (step < 0) {
462 		if (fValue == fLimit) {
463 			fLowerScroller->SetEnabled(true);
464 			fLowerScroller->Invalidate();
465 		}
466 
467 		if (fValue + step <= 0) {
468 			fMenu->ScrollBy(0, -fValue);
469 			fValue = 0;
470 			fUpperScroller->SetEnabled(false);
471 			fUpperScroller->Invalidate();
472 		} else {
473 			fMenu->ScrollBy(0, step);
474 			fValue += step;
475 		}
476 	}
477 }
478 
479