xref: /haiku/src/kits/interface/MenuWindow.cpp (revision e6eaad8615c4734498b9b800847d18bbe62782fa)
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),
237 		B_DARKEN_2_TINT));
238 	BRect bounds(Bounds());
239 
240 	StrokeLine(BPoint(bounds.right, bounds.top),
241 		BPoint(bounds.right, bounds.bottom - 1));
242 	StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
243 		BPoint(bounds.right, bounds.bottom));
244 }
245 
246 
247 
248 //	#pragma mark -
249 
250 
251 BMenuWindow::BMenuWindow(const char *name)
252 	// The window will be resized by BMenu, so just pass a dummy rect
253 	:
254 	BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
255 		B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS
256 			| kAcceptKeyboardFocusFlag),
257 	fMenu(NULL),
258 	fMenuFrame(NULL),
259 	fUpperScroller(NULL),
260 	fLowerScroller(NULL),
261 	fScrollStep(19)
262 {
263 	SetSizeLimits(2, 10000, 2, 10000);
264 }
265 
266 
267 BMenuWindow::~BMenuWindow()
268 {
269 	DetachMenu();
270 }
271 
272 
273 void
274 BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
275 {
276 	BWindow::DispatchMessage(message, handler);
277 }
278 
279 
280 void
281 BMenuWindow::AttachMenu(BMenu *menu)
282 {
283 	if (fMenuFrame)
284 		debugger("BMenuWindow: a menu is already attached!");
285 	if (menu != NULL) {
286 		fMenuFrame = new BMenuFrame(menu);
287 		AddChild(fMenuFrame);
288 		menu->MakeFocus(true);
289 		fMenu = menu;
290 	}
291 }
292 
293 
294 void
295 BMenuWindow::DetachMenu()
296 {
297 	DetachScrollers();
298 	if (fMenuFrame) {
299 		RemoveChild(fMenuFrame);
300 		delete fMenuFrame;
301 		fMenuFrame = NULL;
302 		fMenu = NULL;
303 	}
304 }
305 
306 
307 void
308 BMenuWindow::AttachScrollers()
309 {
310 	// We want to attach a scroller only if there's a
311 	// menu frame already existing.
312 	if (!fMenu || !fMenuFrame)
313 		return;
314 
315 	fMenu->MakeFocus(true);
316 
317 	BRect frame = Bounds();
318 
319 	if (fUpperScroller == NULL) {
320 		fUpperScroller = new UpperScroller(
321 			BRect(0, 0, frame.right, kScrollerHeight - 1));
322 		AddChild(fUpperScroller);
323 	}
324 
325 	if (fLowerScroller == NULL) {
326 		fLowerScroller = new LowerScroller(
327 			BRect(0, frame.bottom - kScrollerHeight + 1, frame.right,
328 				frame.bottom));
329 		AddChild(fLowerScroller);
330 	}
331 
332 	fUpperScroller->SetEnabled(false);
333 	fLowerScroller->SetEnabled(true);
334 
335 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
336 	fMenuFrame->MoveBy(0, kScrollerHeight);
337 
338 	fValue = 0;
339 	fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
340 }
341 
342 
343 void
344 BMenuWindow::DetachScrollers()
345 {
346 	// BeOS doesn't remember the position where the last scrolling ended,
347 	// so we just scroll back to the beginning.
348 	if (fMenu)
349 		fMenu->ScrollTo(0, 0);
350 
351 	if (fLowerScroller) {
352 		RemoveChild(fLowerScroller);
353 		delete fLowerScroller;
354 		fLowerScroller = NULL;
355 	}
356 
357 	if (fUpperScroller) {
358 		RemoveChild(fUpperScroller);
359 		delete fUpperScroller;
360 		fUpperScroller = NULL;
361 	}
362 }
363 
364 
365 void
366 BMenuWindow::SetSmallStep(float step)
367 {
368 	fScrollStep = step;
369 }
370 
371 
372 void
373 BMenuWindow::GetSteps(float* _smallStep, float* _largeStep) const
374 {
375 	if (_smallStep != NULL)
376 		*_smallStep = fScrollStep;
377 	if (_largeStep != NULL) {
378 		if (fMenuFrame != NULL)
379 			*_largeStep = fMenuFrame->Bounds().Height() - fScrollStep;
380 		else
381 			*_largeStep = fScrollStep * 2;
382 	}
383 }
384 
385 
386 bool
387 BMenuWindow::HasScrollers() const
388 {
389 	return fMenuFrame != NULL && fUpperScroller != NULL
390 		&& fLowerScroller != NULL;
391 }
392 
393 
394 bool
395 BMenuWindow::CheckForScrolling(const BPoint &cursor)
396 {
397 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
398 		return false;
399 
400 	return _Scroll(cursor);
401 }
402 
403 
404 bool
405 BMenuWindow::TryScrollBy(const float& step)
406 {
407 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
408 		return false;
409 
410 	_ScrollBy(step);
411 	return true;
412 }
413 
414 
415 bool
416 BMenuWindow::TryScrollTo(const float& where)
417 {
418 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
419 		return false;
420 
421 	_ScrollBy(where - fValue);
422 	return true;
423 }
424 
425 
426 bool
427 BMenuWindow::_Scroll(const BPoint& where)
428 {
429 	ASSERT((fLowerScroller != NULL));
430 	ASSERT((fUpperScroller != NULL));
431 
432 	const BPoint cursor = ConvertFromScreen(where);
433 	const BRect &lowerFrame = fLowerScroller->Frame();
434 	const BRect &upperFrame = fUpperScroller->Frame();
435 
436 	int32 delta = 0;
437 	if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor))
438 		delta = 1;
439 	else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor))
440 		delta = -1;
441 
442 	if (delta == 0)
443 		return false;
444 
445 	float smallStep;
446 	GetSteps(&smallStep, NULL);
447 	_ScrollBy(smallStep * delta);
448 
449 	snooze(5000);
450 
451 	return true;
452 }
453 
454 
455 void
456 BMenuWindow::_ScrollBy(const float& step)
457 {
458 	if (step > 0) {
459 		if (fValue == 0) {
460 			fUpperScroller->SetEnabled(true);
461 			fUpperScroller->Invalidate();
462 		}
463 
464 		if (fValue + step >= fLimit) {
465 			// If we reached the limit, only scroll to the end
466 			fMenu->ScrollBy(0, fLimit - fValue);
467 			fValue = fLimit;
468 			fLowerScroller->SetEnabled(false);
469 			fLowerScroller->Invalidate();
470 		} else {
471 			fMenu->ScrollBy(0, step);
472 			fValue += step;
473 		}
474 	} else if (step < 0) {
475 		if (fValue == fLimit) {
476 			fLowerScroller->SetEnabled(true);
477 			fLowerScroller->Invalidate();
478 		}
479 
480 		if (fValue + step <= 0) {
481 			fMenu->ScrollBy(0, -fValue);
482 			fValue = 0;
483 			fUpperScroller->SetEnabled(false);
484 			fUpperScroller->Invalidate();
485 		} else {
486 			fMenu->ScrollBy(0, step);
487 			fValue += step;
488 		}
489 	}
490 }
491 
492