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