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
23namespace BPrivate {
24
25class BMenuScroller : public BView {
26public:
27							BMenuScroller(BRect frame);
28
29			bool			IsEnabled() const;
30			void			SetEnabled(bool enabled);
31
32private:
33			bool			fEnabled;
34};
35
36
37class BMenuFrame : public BView {
38public:
39							BMenuFrame(BMenu* menu);
40
41	virtual	void			AttachedToWindow();
42	virtual	void			DetachedFromWindow();
43	virtual	void			Draw(BRect updateRect);
44
45private:
46	friend class BMenuWindow;
47
48			BMenu*			fMenu;
49};
50
51
52class UpperScroller : public BMenuScroller {
53public:
54							UpperScroller(BRect frame);
55
56	virtual	void			Draw(BRect updateRect);
57};
58
59
60class LowerScroller : public BMenuScroller {
61public:
62							LowerScroller(BRect frame);
63
64	virtual	void			Draw(BRect updateRect);
65};
66
67
68}	// namespace BPrivate
69
70
71using namespace BPrivate;
72
73
74const int kScrollerHeight = 12;
75
76
77BMenuScroller::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
86bool
87BMenuScroller::IsEnabled() const
88{
89	return fEnabled;
90}
91
92
93void
94BMenuScroller::SetEnabled(bool enabled)
95{
96	fEnabled = enabled;
97}
98
99
100//	#pragma mark -
101
102
103UpperScroller::UpperScroller(BRect frame)
104	:
105	BMenuScroller(frame)
106{
107}
108
109
110void
111UpperScroller::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
135LowerScroller::LowerScroller(BRect frame)
136	:
137	BMenuScroller(frame)
138{
139}
140
141
142void
143LowerScroller::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
169BMenuFrame::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
177void
178BMenuFrame::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
194void
195BMenuFrame::DetachedFromWindow()
196{
197	if (fMenu != NULL)
198		RemoveChild(fMenu);
199}
200
201
202void
203BMenuFrame::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
251BMenuWindow::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
267BMenuWindow::~BMenuWindow()
268{
269	DetachMenu();
270}
271
272
273void
274BMenuWindow::DispatchMessage(BMessage *message, BHandler *handler)
275{
276	BWindow::DispatchMessage(message, handler);
277}
278
279
280void
281BMenuWindow::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
294void
295BMenuWindow::DetachMenu()
296{
297	DetachScrollers();
298	if (fMenuFrame) {
299		RemoveChild(fMenuFrame);
300		delete fMenuFrame;
301		fMenuFrame = NULL;
302		fMenu = NULL;
303	}
304}
305
306
307void
308BMenuWindow::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
343void
344BMenuWindow::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
365void
366BMenuWindow::SetSmallStep(float step)
367{
368	fScrollStep = step;
369}
370
371
372void
373BMenuWindow::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
386bool
387BMenuWindow::HasScrollers() const
388{
389	return fMenuFrame != NULL && fUpperScroller != NULL
390		&& fLowerScroller != NULL;
391}
392
393
394bool
395BMenuWindow::CheckForScrolling(const BPoint &cursor)
396{
397	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
398		return false;
399
400	return _Scroll(cursor);
401}
402
403
404bool
405BMenuWindow::TryScrollBy(const float& step)
406{
407	if (!fMenuFrame || !fUpperScroller || !fLowerScroller)
408		return false;
409
410	_ScrollBy(step);
411	return true;
412}
413
414
415bool
416BMenuWindow::_Scroll(const BPoint& where)
417{
418	ASSERT((fLowerScroller != NULL));
419	ASSERT((fUpperScroller != NULL));
420
421	const BPoint cursor = ConvertFromScreen(where);
422	const BRect &lowerFrame = fLowerScroller->Frame();
423	const BRect &upperFrame = fUpperScroller->Frame();
424
425	int32 delta = 0;
426	if (fLowerScroller->IsEnabled() && lowerFrame.Contains(cursor))
427		delta = 1;
428	else if (fUpperScroller->IsEnabled() && upperFrame.Contains(cursor))
429		delta = -1;
430
431	if (delta == 0)
432		return false;
433
434	float smallStep;
435	GetSteps(&smallStep, NULL);
436	_ScrollBy(smallStep * delta);
437
438	snooze(5000);
439
440	return true;
441}
442
443
444void
445BMenuWindow::_ScrollBy(const float& step)
446{
447	if (step > 0) {
448		if (fValue == 0) {
449			fUpperScroller->SetEnabled(true);
450			fUpperScroller->Invalidate();
451		}
452
453		if (fValue + step >= fLimit) {
454			// If we reached the limit, only scroll to the end
455			fMenu->ScrollBy(0, fLimit - fValue);
456			fValue = fLimit;
457			fLowerScroller->SetEnabled(false);
458			fLowerScroller->Invalidate();
459		} else {
460			fMenu->ScrollBy(0, step);
461			fValue += step;
462		}
463	} else if (step < 0) {
464		if (fValue == fLimit) {
465			fLowerScroller->SetEnabled(true);
466			fLowerScroller->Invalidate();
467		}
468
469		if (fValue + step <= 0) {
470			fMenu->ScrollBy(0, -fValue);
471			fValue = 0;
472			fUpperScroller->SetEnabled(false);
473			fUpperScroller->Invalidate();
474		} else {
475			fMenu->ScrollBy(0, step);
476			fValue += step;
477		}
478	}
479}
480
481