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