xref: /haiku/src/kits/interface/PopUpMenu.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
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_INVALIDATE_LAYOUT:
215 		{
216 			perform_data_invalidate_layout* data
217 				= (perform_data_invalidate_layout*)_data;
218 			BPopUpMenu::InvalidateLayout(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 	if (fTrackThread >= B_OK) {
321 		// we already have an active menu, wait for it to go away before
322 		// spawning another
323 		status_t unused;
324 		while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED)
325 			;
326 	}
327 	popup_menu_data *data = new (std::nothrow) popup_menu_data;
328 	if (!data)
329 		return NULL;
330 
331 	sem_id sem = create_sem(0, "window close lock");
332 	if (sem < B_OK) {
333 		delete data;
334 		return NULL;
335 	}
336 
337 	// Get a pointer to the window from which Go() was called
338 	BWindow *window = dynamic_cast<BWindow *>(BLooper::LooperForThread(find_thread(NULL)));
339 	data->window = window;
340 
341 	// Asynchronous menu: we set the BWindow menu's semaphore
342 	// and let BWindow block when needed
343 	if (async && window != NULL) {
344 		_set_menu_sem_(window, sem);
345 	}
346 
347 	data->object = this;
348 	data->autoInvoke = autoInvoke;
349 	data->useRect = _specialRect != NULL;
350 	if (_specialRect != NULL)
351 		data->rect = *_specialRect;
352 	data->async = async;
353 	data->where = where;
354 	data->startOpened = startOpened;
355 	data->selected = NULL;
356 	data->lock = sem;
357 
358 	// Spawn the tracking thread
359 	fTrackThread = spawn_thread(_thread_entry, "popup", B_DISPLAY_PRIORITY, data);
360 	if (fTrackThread < B_OK) {
361 		// Something went wrong. Cleanup and return NULL
362 		delete_sem(sem);
363 		if (async && window != NULL)
364 			_set_menu_sem_(window, B_BAD_SEM_ID);
365 		delete data;
366 		return NULL;
367 	}
368 
369 	resume_thread(fTrackThread);
370 
371 	if (!async)
372 		return _WaitMenu(data);
373 
374 	return 0;
375 }
376 
377 
378 /* static */
379 int32
380 BPopUpMenu::_thread_entry(void *arg)
381 {
382 	popup_menu_data *data = static_cast<popup_menu_data *>(arg);
383 	BPopUpMenu *menu = data->object;
384 	BRect *rect = NULL;
385 
386 	if (data->useRect)
387 		rect = &data->rect;
388 
389 	data->selected = menu->_StartTrack(data->where, data->autoInvoke, data->startOpened, rect);
390 
391 	// Reset the window menu semaphore
392 	if (data->async && data->window)
393 		_set_menu_sem_(data->window, B_BAD_SEM_ID);
394 
395 	delete_sem(data->lock);
396 
397 	// Commit suicide if needed
398 	if (data->async && menu->fAutoDestruct) {
399 		menu->fTrackThread = -1;
400 		delete menu;
401 	}
402 
403 	if (data->async)
404 		delete data;
405 
406 	return 0;
407 }
408 
409 
410 BMenuItem *
411 BPopUpMenu::_StartTrack(BPoint where, bool autoInvoke, bool startOpened, BRect *_specialRect)
412 {
413 	fWhere = where;
414 
415 	// I know, this doesn't look senseful, but don't be fooled,
416 	// fUseWhere is used in ScreenLocation(), which is a virtual
417 	// called by BMenu::Track()
418 	fUseWhere = true;
419 
420 	// Show the menu's window
421 	Show();
422 	BMenuItem *result = Track(startOpened, _specialRect);
423 	if (result != NULL && autoInvoke)
424 		result->Invoke();
425 
426 	fUseWhere = false;
427 
428 	Hide();
429 	be_app->ShowCursor();
430 
431 	return result;
432 }
433 
434 
435 BMenuItem *
436 BPopUpMenu::_WaitMenu(void *_data)
437 {
438 	popup_menu_data *data = (popup_menu_data *)_data;
439 	BWindow *window = data->window;
440 	sem_id sem = data->lock;
441 	if (window != NULL) {
442 		while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID)
443 			window->UpdateIfNeeded();
444 	}
445 
446  	status_t unused;
447 	while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED)
448 		;
449 
450 	fTrackThread = -1;
451 
452 	BMenuItem *selected = data->selected;
453 		// data->selected is filled by the tracking thread
454 
455 	delete data;
456 
457 	return selected;
458 }
459