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