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