xref: /haiku/src/kits/interface/MenuWindow.cpp (revision 9b33a309dab39f04474bc441d8d35e7f402b40ca)
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 
BMenuScroller(BRect frame)77 BMenuScroller::BMenuScroller(BRect frame)
78 	:
79 	BView(frame, "menu scroller", 0, B_WILL_DRAW | B_FRAME_EVENTS
80 		| B_FULL_UPDATE_ON_RESIZE),
81 	fEnabled(false)
82 {
83 	SetViewUIColor(B_MENU_BACKGROUND_COLOR);
84 }
85 
86 
87 bool
IsEnabled() const88 BMenuScroller::IsEnabled() const
89 {
90 	return fEnabled;
91 }
92 
93 
94 void
SetEnabled(bool enabled)95 BMenuScroller::SetEnabled(bool enabled)
96 {
97 	fEnabled = enabled;
98 }
99 
100 
101 //	#pragma mark -
102 
103 
UpperScroller(BRect frame)104 UpperScroller::UpperScroller(BRect frame)
105 	:
106 	BMenuScroller(frame)
107 {
108 }
109 
110 
111 void
Draw(BRect updateRect)112 UpperScroller::Draw(BRect updateRect)
113 {
114 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
115 	float middle = Bounds().right / 2;
116 
117 	// Draw the upper arrow.
118 	if (IsEnabled())
119 		SetHighColor(0, 0, 0);
120 	else {
121 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
122 			B_DARKEN_2_TINT));
123 	}
124 
125 	FillRect(Bounds(), B_SOLID_LOW);
126 
127 	FillTriangle(BPoint(middle, (kScrollerHeight / 2) - 3),
128 		BPoint(middle + 5, (kScrollerHeight / 2) + 2),
129 		BPoint(middle - 5, (kScrollerHeight / 2) + 2));
130 }
131 
132 
133 //	#pragma mark -
134 
135 
LowerScroller(BRect frame)136 LowerScroller::LowerScroller(BRect frame)
137 	:
138 	BMenuScroller(frame)
139 {
140 }
141 
142 
143 void
Draw(BRect updateRect)144 LowerScroller::Draw(BRect updateRect)
145 {
146 	SetLowColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
147 
148 	BRect frame = Bounds();
149 	// Draw the lower arrow.
150 	if (IsEnabled())
151 		SetHighColor(0, 0, 0);
152 	else {
153 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
154 			B_DARKEN_2_TINT));
155 	}
156 
157 	FillRect(frame, B_SOLID_LOW);
158 
159 	float middle = Bounds().right / 2;
160 
161 	FillTriangle(BPoint(middle, frame.bottom - (kScrollerHeight / 2) + 3),
162 		BPoint(middle + 5, frame.bottom - (kScrollerHeight / 2) - 2),
163 		BPoint(middle - 5, frame.bottom - (kScrollerHeight / 2) - 2));
164 }
165 
166 
167 //	#pragma mark -
168 
169 
BMenuFrame(BMenu * menu)170 BMenuFrame::BMenuFrame(BMenu *menu)
171 	:
172 	BView(BRect(0, 0, 1, 1), "menu frame", B_FOLLOW_ALL_SIDES, B_WILL_DRAW),
173 	fMenu(menu)
174 {
175 }
176 
177 
178 void
AttachedToWindow()179 BMenuFrame::AttachedToWindow()
180 {
181 	BView::AttachedToWindow();
182 
183 	if (fMenu != NULL)
184 		AddChild(fMenu);
185 
186 	ResizeTo(Window()->Bounds().Width(), Window()->Bounds().Height());
187 	if (fMenu != NULL) {
188 		BFont font;
189 		fMenu->GetFont(&font);
190 		SetFont(&font);
191 	}
192 }
193 
194 
195 void
DetachedFromWindow()196 BMenuFrame::DetachedFromWindow()
197 {
198 	if (fMenu != NULL)
199 		RemoveChild(fMenu);
200 }
201 
202 
203 void
Draw(BRect updateRect)204 BMenuFrame::Draw(BRect updateRect)
205 {
206 	if (fMenu != NULL && fMenu->CountItems() == 0) {
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 
212 		// TODO: Review this as it's a bit hacky.
213 		// Since there are no items in this menu, its size is 0x0.
214 		// To show an empty BMenu, we use BMenuFrame to draw an empty item.
215 		// It would be nice to simply add a real "empty" item, but in that case
216 		// we couldn't tell if the item was added by us or not, and applications
217 		// could break (because CountItems() would return 1 for an empty BMenu).
218 		// See also BMenu::UpdateWindowViewSize()
219 		font_height height;
220 		GetFontHeight(&height);
221 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR),
222 			B_DISABLED_LABEL_TINT));
223 		BPoint where(
224 			(Bounds().Width() - fMenu->StringWidth(kEmptyMenuLabel)) / 2,
225 			ceilf(height.ascent + 1));
226 		DrawString(kEmptyMenuLabel, where);
227 	}
228 }
229 
230 
231 
232 //	#pragma mark -
233 
234 
BMenuWindow(const char * name)235 BMenuWindow::BMenuWindow(const char *name)
236 	// The window will be resized by BMenu, so just pass a dummy rect
237 	:
238 	BWindow(BRect(0, 0, 0, 0), name, B_BORDERED_WINDOW_LOOK, kMenuWindowFeel,
239 		B_NOT_MOVABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FOCUS
240 			| kAcceptKeyboardFocusFlag),
241 	fMenu(NULL),
242 	fMenuFrame(NULL),
243 	fUpperScroller(NULL),
244 	fLowerScroller(NULL),
245 	fScrollStep(19)
246 {
247 	SetSizeLimits(2, 10000, 2, 10000);
248 }
249 
250 
~BMenuWindow()251 BMenuWindow::~BMenuWindow()
252 {
253 	DetachMenu();
254 }
255 
256 
257 void
DispatchMessage(BMessage * message,BHandler * handler)258 BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
259 {
260 	BWindow::DispatchMessage(message, handler);
261 }
262 
263 
264 void
AttachMenu(BMenu * menu)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 		fMenu = menu;
274 	}
275 }
276 
277 
278 void
DetachMenu()279 BMenuWindow::DetachMenu()
280 {
281 	DetachScrollers();
282 	if (fMenuFrame) {
283 		RemoveChild(fMenuFrame);
284 		delete fMenuFrame;
285 		fMenuFrame = NULL;
286 		fMenu = NULL;
287 	}
288 }
289 
290 
291 void
AttachScrollers()292 BMenuWindow::AttachScrollers()
293 {
294 	// We want to attach a scroller only if there's a
295 	// menu frame already existing.
296 	if (!fMenu || !fMenuFrame)
297 		return;
298 
299 	fMenu->MakeFocus(true);
300 
301 	BRect frame = Bounds();
302 	float newLimit = fMenu->Bounds().Height()
303 		- (frame.Height() - 2 * kScrollerHeight);
304 
305 	if (!HasScrollers())
306 		fValue = 0;
307 	else if (fValue > newLimit)
308 		_ScrollBy(newLimit - fValue);
309 
310 	fLimit = newLimit;
311 
312 	if (fUpperScroller == NULL) {
313 		fUpperScroller = new UpperScroller(
314 			BRect(0, 0, frame.right, kScrollerHeight - 1));
315 		AddChild(fUpperScroller);
316 	}
317 
318 	if (fLowerScroller == NULL) {
319 		fLowerScroller = new LowerScroller(
320 			BRect(0, frame.bottom - kScrollerHeight + 1, frame.right,
321 				frame.bottom));
322 		AddChild(fLowerScroller);
323 	}
324 
325 	fUpperScroller->ResizeTo(frame.right, kScrollerHeight - 1);
326 	fLowerScroller->ResizeTo(frame.right, kScrollerHeight - 1);
327 
328 	fUpperScroller->SetEnabled(fValue > 0);
329 	fLowerScroller->SetEnabled(fValue < fLimit);
330 
331 	fMenuFrame->ResizeTo(frame.Width(), frame.Height() - 2 * kScrollerHeight);
332 	fMenuFrame->MoveTo(0, kScrollerHeight);
333 }
334 
335 
336 void
DetachScrollers()337 BMenuWindow::DetachScrollers()
338 {
339 	// BeOS doesn't remember the position where the last scrolling ended,
340 	// so we just scroll back to the beginning.
341 	if (fMenu)
342 		fMenu->ScrollTo(0, 0);
343 
344 	if (fLowerScroller) {
345 		RemoveChild(fLowerScroller);
346 		delete fLowerScroller;
347 		fLowerScroller = NULL;
348 	}
349 
350 	if (fUpperScroller) {
351 		RemoveChild(fUpperScroller);
352 		delete fUpperScroller;
353 		fUpperScroller = NULL;
354 	}
355 
356 	BRect frame = Bounds();
357 
358 	if (fMenuFrame != NULL) {
359 		fMenuFrame->ResizeTo(frame.Width(), frame.Height());
360 		fMenuFrame->MoveTo(0, 0);
361 	}
362 }
363 
364 
365 void
SetSmallStep(float step)366 BMenuWindow::SetSmallStep(float step)
367 {
368 	fScrollStep = step;
369 }
370 
371 
372 void
GetSteps(float * _smallStep,float * _largeStep) const373 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
HasScrollers() const387 BMenuWindow::HasScrollers() const
388 {
389 	return fMenuFrame != NULL && fUpperScroller != NULL
390 		&& fLowerScroller != NULL;
391 }
392 
393 
394 bool
CheckForScrolling(const BPoint & cursor)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
TryScrollBy(const float & step)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
TryScrollTo(const float & where)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
_Scroll(const BPoint & where)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
_ScrollBy(const float & step)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