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