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
22struct 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
39BPopUpMenu::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
55BPopUpMenu::BPopUpMenu(BMessage* archive)
56	:
57	BMenu(archive),
58	fUseWhere(false),
59	fAutoDestruct(false),
60	fTrackThread(-1)
61{
62}
63
64
65BPopUpMenu::~BPopUpMenu()
66{
67	if (fTrackThread >= 0) {
68		status_t status;
69		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
70			;
71	}
72}
73
74
75status_t
76BPopUpMenu::Archive(BMessage* data, bool deep) const
77{
78	return BMenu::Archive(data, deep);
79}
80
81
82BArchivable*
83BPopUpMenu::Instantiate(BMessage* data)
84{
85	if (validate_instantiation(data, "BPopUpMenu"))
86		return new BPopUpMenu(data);
87
88	return NULL;
89}
90
91
92BMenuItem*
93BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway, bool async)
94{
95	return _Go(where, deliversMessage, openAnyway, NULL, async);
96}
97
98
99BMenuItem*
100BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway,
101	BRect clickToOpen, bool async)
102{
103	return _Go(where, deliversMessage, openAnyway, &clickToOpen, async);
104}
105
106
107void
108BPopUpMenu::MessageReceived(BMessage* message)
109{
110	BMenu::MessageReceived(message);
111}
112
113
114void
115BPopUpMenu::MouseDown(BPoint point)
116{
117	BView::MouseDown(point);
118}
119
120
121void
122BPopUpMenu::MouseUp(BPoint point)
123{
124	BView::MouseUp(point);
125}
126
127
128void
129BPopUpMenu::MouseMoved(BPoint point, uint32 code, const BMessage* message)
130{
131	BView::MouseMoved(point, code, message);
132}
133
134
135void
136BPopUpMenu::AttachedToWindow()
137{
138	BMenu::AttachedToWindow();
139}
140
141
142void
143BPopUpMenu::DetachedFromWindow()
144{
145	BMenu::DetachedFromWindow();
146}
147
148
149void
150BPopUpMenu::FrameMoved(BPoint newPosition)
151{
152	BMenu::FrameMoved(newPosition);
153}
154
155
156void
157BPopUpMenu::FrameResized(float newWidth, float newHeight)
158{
159	BMenu::FrameResized(newWidth, newHeight);
160}
161
162
163BHandler*
164BPopUpMenu::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
171status_t
172BPopUpMenu::GetSupportedSuites(BMessage* data)
173{
174	return BMenu::GetSupportedSuites(data);
175}
176
177
178status_t
179BPopUpMenu::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
234void
235BPopUpMenu::ResizeToPreferred()
236{
237	BMenu::ResizeToPreferred();
238}
239
240
241void
242BPopUpMenu::GetPreferredSize(float* _width, float* _height)
243{
244	BMenu::GetPreferredSize(_width, _height);
245}
246
247
248void
249BPopUpMenu::MakeFocus(bool state)
250{
251	BMenu::MakeFocus(state);
252}
253
254
255void
256BPopUpMenu::AllAttached()
257{
258	BMenu::AllAttached();
259}
260
261
262void
263BPopUpMenu::AllDetached()
264{
265	BMenu::AllDetached();
266}
267
268
269void
270BPopUpMenu::SetAsyncAutoDestruct(bool on)
271{
272	fAutoDestruct = on;
273}
274
275
276bool
277BPopUpMenu::AsyncAutoDestruct() const
278{
279	return fAutoDestruct;
280}
281
282
283BPoint
284BPopUpMenu::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
313void BPopUpMenu::_ReservedPopUpMenu1() {}
314void BPopUpMenu::_ReservedPopUpMenu2() {}
315void BPopUpMenu::_ReservedPopUpMenu3() {}
316
317
318BPopUpMenu&
319BPopUpMenu::operator=(const BPopUpMenu& other)
320{
321	return *this;
322}
323
324
325BMenuItem*
326BPopUpMenu::_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 */
390int32
391BPopUpMenu::_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
422BMenuItem*
423BPopUpMenu::_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
458BMenuItem*
459BPopUpMenu::_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