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