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
11 #include <Application.h>
12 #include <Looper.h>
13 #include <MenuItem.h>
14 #include <PopUpMenu.h>
15 #include <Window.h>
16
17 #include <new>
18
19 #include <binary_compatibility/Interface.h>
20
21
22 struct popup_menu_data {
23 BPopUpMenu* object;
24 BWindow* window;
25 BMenuItem* selected;
26
27 BPoint where;
28 BRect rect;
29
30 bool async;
31 bool autoInvoke;
32 bool startOpened;
33 bool useRect;
34
35 sem_id lock;
36 };
37
38
BPopUpMenu(const char * name,bool radioMode,bool labelFromMarked,menu_layout layout)39 BPopUpMenu::BPopUpMenu(const char* name, bool radioMode, bool labelFromMarked,
40 menu_layout layout)
41 :
42 BMenu(name, layout),
43 fUseWhere(false),
44 fAutoDestruct(false),
45 fTrackThread(-1)
46 {
47 if (radioMode)
48 SetRadioMode(true);
49
50 if (labelFromMarked)
51 SetLabelFromMarked(true);
52 }
53
54
BPopUpMenu(BMessage * archive)55 BPopUpMenu::BPopUpMenu(BMessage* archive)
56 :
57 BMenu(archive),
58 fUseWhere(false),
59 fAutoDestruct(false),
60 fTrackThread(-1)
61 {
62 }
63
64
~BPopUpMenu()65 BPopUpMenu::~BPopUpMenu()
66 {
67 if (fTrackThread >= 0) {
68 status_t status;
69 while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
70 ;
71 }
72 }
73
74
75 status_t
Archive(BMessage * data,bool deep) const76 BPopUpMenu::Archive(BMessage* data, bool deep) const
77 {
78 return BMenu::Archive(data, deep);
79 }
80
81
82 BArchivable*
Instantiate(BMessage * data)83 BPopUpMenu::Instantiate(BMessage* data)
84 {
85 if (validate_instantiation(data, "BPopUpMenu"))
86 return new BPopUpMenu(data);
87
88 return NULL;
89 }
90
91
92 BMenuItem*
Go(BPoint where,bool deliversMessage,bool openAnyway,bool async)93 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway, bool async)
94 {
95 return _Go(where, deliversMessage, openAnyway, NULL, async);
96 }
97
98
99 BMenuItem*
Go(BPoint where,bool deliversMessage,bool openAnyway,BRect clickToOpen,bool async)100 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway,
101 BRect clickToOpen, bool async)
102 {
103 return _Go(where, deliversMessage, openAnyway, &clickToOpen, async);
104 }
105
106
107 void
MessageReceived(BMessage * message)108 BPopUpMenu::MessageReceived(BMessage* message)
109 {
110 BMenu::MessageReceived(message);
111 }
112
113
114 void
MouseDown(BPoint point)115 BPopUpMenu::MouseDown(BPoint point)
116 {
117 BView::MouseDown(point);
118 }
119
120
121 void
MouseUp(BPoint point)122 BPopUpMenu::MouseUp(BPoint point)
123 {
124 BView::MouseUp(point);
125 }
126
127
128 void
MouseMoved(BPoint point,uint32 code,const BMessage * message)129 BPopUpMenu::MouseMoved(BPoint point, uint32 code, const BMessage* message)
130 {
131 BView::MouseMoved(point, code, message);
132 }
133
134
135 void
AttachedToWindow()136 BPopUpMenu::AttachedToWindow()
137 {
138 BMenu::AttachedToWindow();
139 }
140
141
142 void
DetachedFromWindow()143 BPopUpMenu::DetachedFromWindow()
144 {
145 BMenu::DetachedFromWindow();
146 }
147
148
149 void
FrameMoved(BPoint newPosition)150 BPopUpMenu::FrameMoved(BPoint newPosition)
151 {
152 BMenu::FrameMoved(newPosition);
153 }
154
155
156 void
FrameResized(float newWidth,float newHeight)157 BPopUpMenu::FrameResized(float newWidth, float newHeight)
158 {
159 BMenu::FrameResized(newWidth, newHeight);
160 }
161
162
163 BHandler*
ResolveSpecifier(BMessage * message,int32 index,BMessage * specifier,int32 form,const char * property)164 BPopUpMenu::ResolveSpecifier(BMessage* message, int32 index,
165 BMessage* specifier, int32 form, const char* property)
166 {
167 return BMenu::ResolveSpecifier(message, index, specifier, form, property);
168 }
169
170
171 status_t
GetSupportedSuites(BMessage * data)172 BPopUpMenu::GetSupportedSuites(BMessage* data)
173 {
174 return BMenu::GetSupportedSuites(data);
175 }
176
177
178 status_t
Perform(perform_code code,void * _data)179 BPopUpMenu::Perform(perform_code code, void* _data)
180 {
181 switch (code) {
182 case PERFORM_CODE_MIN_SIZE:
183 ((perform_data_min_size*)_data)->return_value
184 = BPopUpMenu::MinSize();
185 return B_OK;
186 case PERFORM_CODE_MAX_SIZE:
187 ((perform_data_max_size*)_data)->return_value
188 = BPopUpMenu::MaxSize();
189 return B_OK;
190 case PERFORM_CODE_PREFERRED_SIZE:
191 ((perform_data_preferred_size*)_data)->return_value
192 = BPopUpMenu::PreferredSize();
193 return B_OK;
194 case PERFORM_CODE_LAYOUT_ALIGNMENT:
195 ((perform_data_layout_alignment*)_data)->return_value
196 = BPopUpMenu::LayoutAlignment();
197 return B_OK;
198 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
199 ((perform_data_has_height_for_width*)_data)->return_value
200 = BPopUpMenu::HasHeightForWidth();
201 return B_OK;
202 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
203 {
204 perform_data_get_height_for_width* data
205 = (perform_data_get_height_for_width*)_data;
206 BPopUpMenu::GetHeightForWidth(data->width, &data->min, &data->max,
207 &data->preferred);
208 return B_OK;
209 }
210 case PERFORM_CODE_SET_LAYOUT:
211 {
212 perform_data_set_layout* data = (perform_data_set_layout*)_data;
213 BPopUpMenu::SetLayout(data->layout);
214 return B_OK;
215 }
216 case PERFORM_CODE_LAYOUT_INVALIDATED:
217 {
218 perform_data_layout_invalidated* data
219 = (perform_data_layout_invalidated*)_data;
220 BPopUpMenu::LayoutInvalidated(data->descendants);
221 return B_OK;
222 }
223 case PERFORM_CODE_DO_LAYOUT:
224 {
225 BPopUpMenu::DoLayout();
226 return B_OK;
227 }
228 }
229
230 return BMenu::Perform(code, _data);
231 }
232
233
234 void
ResizeToPreferred()235 BPopUpMenu::ResizeToPreferred()
236 {
237 BMenu::ResizeToPreferred();
238 }
239
240
241 void
GetPreferredSize(float * _width,float * _height)242 BPopUpMenu::GetPreferredSize(float* _width, float* _height)
243 {
244 BMenu::GetPreferredSize(_width, _height);
245 }
246
247
248 void
MakeFocus(bool state)249 BPopUpMenu::MakeFocus(bool state)
250 {
251 BMenu::MakeFocus(state);
252 }
253
254
255 void
AllAttached()256 BPopUpMenu::AllAttached()
257 {
258 BMenu::AllAttached();
259 }
260
261
262 void
AllDetached()263 BPopUpMenu::AllDetached()
264 {
265 BMenu::AllDetached();
266 }
267
268
269 void
SetAsyncAutoDestruct(bool on)270 BPopUpMenu::SetAsyncAutoDestruct(bool on)
271 {
272 fAutoDestruct = on;
273 }
274
275
276 bool
AsyncAutoDestruct() const277 BPopUpMenu::AsyncAutoDestruct() const
278 {
279 return fAutoDestruct;
280 }
281
282
283 BPoint
ScreenLocation()284 BPopUpMenu::ScreenLocation()
285 {
286 // This case is when the BPopUpMenu is standalone
287 if (fUseWhere)
288 return fWhere;
289
290 // This case is when the BPopUpMenu is inside a BMenuField
291 BMenuItem* superItem = Superitem();
292 BMenu* superMenu = Supermenu();
293 BMenuItem* selectedItem = FindItem(superItem->Label());
294 BPoint point = superItem->Frame().LeftTop();
295
296 superMenu->ConvertToScreen(&point);
297
298 if (selectedItem != NULL) {
299 while (selectedItem->Menu() != this
300 && selectedItem->Menu()->Superitem() != NULL) {
301 selectedItem = selectedItem->Menu()->Superitem();
302 }
303 point.y -= selectedItem->Frame().top;
304 }
305
306 return point;
307 }
308
309
310 // #pragma mark - private methods
311
312
_ReservedPopUpMenu1()313 void BPopUpMenu::_ReservedPopUpMenu1() {}
_ReservedPopUpMenu2()314 void BPopUpMenu::_ReservedPopUpMenu2() {}
_ReservedPopUpMenu3()315 void BPopUpMenu::_ReservedPopUpMenu3() {}
316
317
318 BPopUpMenu&
operator =(const BPopUpMenu & other)319 BPopUpMenu::operator=(const BPopUpMenu& other)
320 {
321 return *this;
322 }
323
324
325 BMenuItem*
_Go(BPoint where,bool autoInvoke,bool startOpened,BRect * _specialRect,bool async)326 BPopUpMenu::_Go(BPoint where, bool autoInvoke, bool startOpened,
327 BRect* _specialRect, bool async)
328 {
329 if (fTrackThread >= B_OK) {
330 // we already have an active menu, wait for it to go away before
331 // spawning another
332 status_t unused;
333 while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED)
334 ;
335 }
336
337 popup_menu_data* data = new (std::nothrow) popup_menu_data;
338 if (data == NULL)
339 return NULL;
340
341 sem_id sem = create_sem(0, "window close lock");
342 if (sem < B_OK) {
343 delete data;
344 return NULL;
345 }
346
347 // Get a pointer to the window from which Go() was called
348 BWindow* window
349 = dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL)));
350 data->window = window;
351
352 // Asynchronous menu: we set the BWindow menu's semaphore
353 // and let BWindow block when needed
354 if (async && window != NULL)
355 _set_menu_sem_(window, sem);
356
357 data->object = this;
358 data->autoInvoke = autoInvoke;
359 data->useRect = _specialRect != NULL;
360 if (_specialRect != NULL)
361 data->rect = *_specialRect;
362 data->async = async;
363 data->where = where;
364 data->startOpened = startOpened;
365 data->selected = NULL;
366 data->lock = sem;
367
368 // Spawn the tracking thread
369 fTrackThread = spawn_thread(_thread_entry, "popup", B_DISPLAY_PRIORITY,
370 data);
371 if (fTrackThread < B_OK) {
372 // Something went wrong. Cleanup and return NULL
373 delete_sem(sem);
374 if (async && window != NULL)
375 _set_menu_sem_(window, B_BAD_SEM_ID);
376 delete data;
377 return NULL;
378 }
379
380 resume_thread(fTrackThread);
381
382 if (!async)
383 return _WaitMenu(data);
384
385 return 0;
386 }
387
388
389 /* static */
390 int32
_thread_entry(void * menuData)391 BPopUpMenu::_thread_entry(void* menuData)
392 {
393 popup_menu_data* data = static_cast<popup_menu_data*>(menuData);
394 BPopUpMenu* menu = data->object;
395 BRect* rect = NULL;
396
397 if (data->useRect)
398 rect = &data->rect;
399
400 data->selected = menu->_StartTrack(data->where, data->autoInvoke,
401 data->startOpened, rect);
402
403 // Reset the window menu semaphore
404 if (data->async && data->window)
405 _set_menu_sem_(data->window, B_BAD_SEM_ID);
406
407 delete_sem(data->lock);
408
409 // Commit suicide if needed
410 if (data->async && menu->fAutoDestruct) {
411 menu->fTrackThread = -1;
412 delete menu;
413 }
414
415 if (data->async)
416 delete data;
417
418 return 0;
419 }
420
421
422 BMenuItem*
_StartTrack(BPoint where,bool autoInvoke,bool startOpened,BRect * _specialRect)423 BPopUpMenu::_StartTrack(BPoint where, bool autoInvoke, bool startOpened,
424 BRect* _specialRect)
425 {
426 // I know, this doesn't look senseful, but don't be fooled,
427 // fUseWhere is used in ScreenLocation(), which is a virtual
428 // called by BMenu::Track()
429 fWhere = where;
430 fUseWhere = true;
431
432 // Determine when mouse-down-up will be taken as a 'press',
433 // rather than a 'click'
434 bigtime_t clickMaxTime = 0;
435 get_click_speed(&clickMaxTime);
436 clickMaxTime += system_time();
437
438 // Show the menu's window
439 Show();
440 snooze(50000);
441 BMenuItem* result = Track(startOpened, _specialRect);
442
443 // If it was a click, keep the menu open and tracking
444 if (system_time() <= clickMaxTime)
445 result = Track(true, _specialRect);
446 if (result != NULL && autoInvoke)
447 result->Invoke();
448
449 fUseWhere = false;
450
451 Hide();
452 be_app->ShowCursor();
453
454 return result;
455 }
456
457
458 BMenuItem*
_WaitMenu(void * _data)459 BPopUpMenu::_WaitMenu(void* _data)
460 {
461 popup_menu_data* data = static_cast<popup_menu_data*>(_data);
462 BWindow* window = data->window;
463 sem_id sem = data->lock;
464 if (window != NULL) {
465 while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID)
466 window->UpdateIfNeeded();
467 }
468
469 status_t unused;
470 while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED)
471 ;
472
473 fTrackThread = -1;
474
475 BMenuItem* selected = data->selected;
476 // data->selected is filled by the tracking thread
477
478 delete data;
479
480 return selected;
481 }
482