xref: /haiku/src/kits/interface/PopUpMenu.cpp (revision 6f33360fac01f5ca0a27405219710521a64fbf71)
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