xref: /haiku/src/kits/interface/MenuWindow.cpp (revision 5ac9b506412b11afb993bb52d161efe7666958a5)
1 /*
2  * Copyright 2001-2015, 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 	SetViewUIColor(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 		BRect rect(Bounds());
207 		be_control_look->DrawMenuBackground(this, rect, updateRect,
208 			ui_color(B_MENU_BACKGROUND_COLOR));
209 		SetDrawingMode(B_OP_OVER);
210 
211 		// TODO: Review this as it's a bit hacky.
212 		// Since there are no items in this menu, its size is 0x0.
213 		// To show an empty BMenu, we use BMenuFrame to draw an empty item.
214 		// It would be nice to simply add a real "empty" item, but in that case
215 		// we couldn't tell if the item was added by us or not, and applications
216 		// could break (because CountItems() would return 1 for an empty BMenu).
217 		// See also BMenu::UpdateWindowViewSize()
218 		font_height height;
219 		GetFontHeight(&height);
220 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
221 			B_DISABLED_LABEL_TINT));
222 		BPoint where(
223 			(Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2,
224 			ceilf(height.ascent + 1));
225 		DrawString(kEmptyMenuLabel, where);
226 	}
227 }
228 
229 
230 
231 //	#pragma mark -
232 
233 
234 BMenuWindow::BMenuWindow(const char *name)
235 	// The window will be resized by BMenu, so just pass a dummy rect
236 	:
237 	BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
238 		B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS
239 			| kAcceptKeyboardFocusFlag),
240 	fMenu(NULL),
241 	fMenuFrame(NULL),
242 	fUpperScroller(NULL),
243 	fLowerScroller(NULL),
244 	fScrollStep(19)
245 {
246 	SetSizeLimits(2, 10000, 2, 10000);
247 }
248 
249 
250 BMenuWindow::~BMenuWindow()
251 {
252 	DetachMenu();
253 }
254 
255 
256 void
257 BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
258 {
259 	BWindow::DispatchMessage(message, handler);
260 }
261 
262 
263 void
264 BMenuWindow::AttachMenu(BMenu *menu)
265 {
266 	if (fMenuFrame)
267 		debugger("BMenuWindow: a menu is already attached!");
268 	if (menu != NULL) {
269 		fMenuFrame = new BMenuFrame(menu);
270 		AddChild(fMenuFrame);
271 		menu->MakeFocus(true);
272 		fMenu = menu;
273 	}
274 }
275 
276 
277 void
278 BMenuWindow::DetachMenu()
279 {
280 	DetachScrollers();
281 	if (fMenuFrame) {
282 		RemoveChild(fMenuFrame);
283 		delete fMenuFrame;
284 		fMenuFrame = NULL;
285 		fMenu = NULL;
286 	}
287 }
288 
289 
290 void
291 BMenuWindow::AttachScrollers()
292 {
293 	// We want to attach a scroller only if there's a
294 	// menu frame already existing.
295 	if (!fMenu || !fMenuFrame)
296 		return;
297 
298 	fMenu->MakeFocus(true);
299 
300 	BRect frame = Bounds();
301 
302 	if (fUpperScroller == NULL) {
303 		fUpperScroller = new UpperScroller(
304 			BRect(0, 0, frame.right, kScrollerHeight - 1));
305 		AddChild(fUpperScroller);
306 	}
307 
308 	if (fLowerScroller == NULL) {
309 		fLowerScroller = new LowerScroller(
310 			BRect(0, frame.bottom - kScrollerHeight + 1, frame.right,
311 				frame.bottom));
312 		AddChild(fLowerScroller);
313 	}
314 
315 	fUpperScroller->SetEnabled(false);
316 	fLowerScroller->SetEnabled(true);
317 
318 	fMenuFrame->ResizeBy(0, -2 * kScrollerHeight);
319 	fMenuFrame->MoveBy(0, kScrollerHeight);
320 
321 	fValue = 0;
322 	fLimit = fMenu->Bounds().Height() - (frame.Height() - 2 * kScrollerHeight);
323 }
324 
325 
326 void
327 BMenuWindow::DetachScrollers()
328 {
329 	// BeOS doesn't remember the position where the last scrolling ended,
330 	// so we just scroll back to the beginning.
331 	if (fMenu)
332 		fMenu->ScrollTo(0, 0);
333 
334 	if (fLowerScroller) {
335 		RemoveChild(fLowerScroller);
336 		delete fLowerScroller;
337 		fLowerScroller = NULL;
338 	}
339 
340 	if (fUpperScroller) {
341 		RemoveChild(fUpperScroller);
342 		delete fUpperScroller;
343 		fUpperScroller = NULL;
344 	}
345 }
346 
347 
348 void
349 BMenuWindow::SetSmallStep(float step)
350 {
351 	fScrollStep = step;
352 }
353 
354 
355 void
356 BMenuWindow::GetSteps(float* _smallStep, float* _largeStep) const
357 {
358 	if (_smallStep != NULL)
359 		*_smallStep = fScrollStep;
360 	if (_largeStep != NULL) {
361 		if (fMenuFrame != NULL)
362 			*_largeStep = fMenuFrame->Bounds().Height() - fScrollStep;
363 		else
364 			*_largeStep = fScrollStep * 2;
365 	}
366 }
367 
368 
369 bool
370 BMenuWindow::HasScrollers() const
371 {
372 	return fMenuFrame != NULL && fUpperScroller != NULL
373 		&& fLowerScroller != NULL;
374 }
375 
376 
377 bool
378 BMenuWindow::CheckForScrolling(const BPoint &cursor)
379 {
380 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
381 		return false;
382 
383 	return _Scroll(cursor);
384 }
385 
386 
387 bool
388 BMenuWindow::TryScrollBy(const float& step)
389 {
390 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
391 		return false;
392 
393 	_ScrollBy(step);
394 	return true;
395 }
396 
397 
398 bool
399 BMenuWindow::TryScrollTo(const float& where)
400 {
401 	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
402 		return false;
403 
404 	_ScrollBy(where - fValue);
405 	return true;
406 }
407 
408 
409 bool
410 BMenuWindow::_Scroll(const BPoint& where)
411 {
412 	ASSERT((fLowerScroller != NULL));
413 	ASSERT((fUpperScroller != NULL));
414 
415 	const BPoint cursor = ConvertFromScreen(where);
416 	const BRect &lowerFrame = fLowerScroller->Frame();
417 	const BRect &upperFrame = fUpperScroller->Frame();
418 
419 	int32 delta = 0;
420 	if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor))
421 		delta = 1;
422 	else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor))
423 		delta = -1;
424 
425 	if (delta == 0)
426 		return false;
427 
428 	float smallStep;
429 	GetSteps(&smallStep, NULL);
430 	_ScrollBy(smallStep * delta);
431 
432 	snooze(5000);
433 
434 	return true;
435 }
436 
437 
438 void
439 BMenuWindow::_ScrollBy(const float& step)
440 {
441 	if (step > 0) {
442 		if (fValue == 0) {
443 			fUpperScroller->SetEnabled(true);
444 			fUpperScroller->Invalidate();
445 		}
446 
447 		if (fValue + step >= fLimit) {
448 			// If we reached the limit, only scroll to the end
449 			fMenu->ScrollBy(0, fLimit - fValue);
450 			fValue = fLimit;
451 			fLowerScroller->SetEnabled(false);
452 			fLowerScroller->Invalidate();
453 		} else {
454 			fMenu->ScrollBy(0, step);
455 			fValue += step;
456 		}
457 	} else if (step < 0) {
458 		if (fValue == fLimit) {
459 			fLowerScroller->SetEnabled(true);
460 			fLowerScroller->Invalidate();
461 		}
462 
463 		if (fValue + step <= 0) {
464 			fMenu->ScrollBy(0, -fValue);
465 			fValue = 0;
466 			fUpperScroller->SetEnabled(false);
467 			fUpperScroller->Invalidate();
468 		} else {
469 			fMenu->ScrollBy(0, step);
470 			fValue += step;
471 		}
472 	}
473 }
474 
475