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