xref: /haiku/src/kits/interface/PopUpMenu.cpp (revision ba499cdc3336fb89429027418871bf263f1f5e14)
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 using std::nothrow;
20 
21 struct popup_menu_data {
22 	BPopUpMenu *object;
23 	BWindow *window;
24 	BMenuItem *selected;
25 
26 	BPoint where;
27 	BRect rect;
28 
29 	bool async;
30 	bool autoInvoke;
31 	bool startOpened;
32 	bool useRect;
33 
34 	sem_id lock;
35 };
36 
37 
38 BPopUpMenu::BPopUpMenu(const char *title, bool radioMode, bool autoRename,
39 		menu_layout layout)
40 	: BMenu(title, layout),
41 	fUseWhere(false),
42 	fAutoDestruct(false),
43 	fTrackThread(-1)
44 {
45 	if (radioMode)
46 		SetRadioMode(true);
47 
48 	if (autoRename)
49 		SetLabelFromMarked(true);
50 }
51 
52 
53 BPopUpMenu::BPopUpMenu(BMessage *archive)
54 	: BMenu(archive),
55 	fUseWhere(false),
56 	fAutoDestruct(false),
57 	fTrackThread(-1)
58 {
59 }
60 
61 
62 BPopUpMenu::~BPopUpMenu()
63 {
64 	if (fTrackThread >= 0) {
65 		status_t status;
66 		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
67 			;
68 	}
69 }
70 
71 
72 status_t
73 BPopUpMenu::Archive(BMessage *data, bool deep) const
74 {
75 	return BMenu::Archive(data, deep);
76 }
77 
78 
79 BArchivable *
80 BPopUpMenu::Instantiate(BMessage *data)
81 {
82 	if (validate_instantiation(data, "BPopUpMenu"))
83 		return new BPopUpMenu(data);
84 
85 	return NULL;
86 }
87 
88 
89 BMenuItem *
90 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway, bool async)
91 {
92 	return _Go(where, deliversMessage, openAnyway, NULL, async);
93 }
94 
95 
96 BMenuItem *
97 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway,
98 	BRect clickToOpen, bool async)
99 {
100 	return _Go(where, deliversMessage, openAnyway, &clickToOpen, async);
101 }
102 
103 
104 void
105 BPopUpMenu::MessageReceived(BMessage *msg)
106 {
107 	BMenu::MessageReceived(msg);
108 }
109 
110 
111 void
112 BPopUpMenu::MouseDown(BPoint point)
113 {
114 	BView::MouseDown(point);
115 }
116 
117 
118 void
119 BPopUpMenu::MouseUp(BPoint point)
120 {
121 	BView::MouseUp(point);
122 }
123 
124 
125 void
126 BPopUpMenu::MouseMoved(BPoint point, uint32 code, const BMessage *msg)
127 {
128 	BView::MouseMoved(point, code, msg);
129 }
130 
131 
132 void
133 BPopUpMenu::AttachedToWindow()
134 {
135 	BMenu::AttachedToWindow();
136 }
137 
138 
139 void
140 BPopUpMenu::DetachedFromWindow()
141 {
142 	BMenu::DetachedFromWindow();
143 }
144 
145 
146 void
147 BPopUpMenu::FrameMoved(BPoint newPosition)
148 {
149 	BMenu::FrameMoved(newPosition);
150 }
151 
152 
153 void
154 BPopUpMenu::FrameResized(float newWidth, float newHeight)
155 {
156 	BMenu::FrameResized(newWidth, newHeight);
157 }
158 
159 
160 BHandler *
161 BPopUpMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier,
162 	int32 form, const char *property)
163 {
164 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
165 }
166 
167 
168 status_t
169 BPopUpMenu::GetSupportedSuites(BMessage *data)
170 {
171 	return BMenu::GetSupportedSuites(data);
172 }
173 
174 
175 status_t
176 BPopUpMenu::Perform(perform_code d, void *arg)
177 {
178 	return BMenu::Perform(d, arg);
179 }
180 
181 
182 void
183 BPopUpMenu::ResizeToPreferred()
184 {
185 	BMenu::ResizeToPreferred();
186 }
187 
188 
189 void
190 BPopUpMenu::GetPreferredSize(float *_width, float *_height)
191 {
192 	BMenu::GetPreferredSize(_width, _height);
193 }
194 
195 
196 void
197 BPopUpMenu::MakeFocus(bool state)
198 {
199 	BMenu::MakeFocus(state);
200 }
201 
202 
203 void
204 BPopUpMenu::AllAttached()
205 {
206 	BMenu::AllAttached();
207 }
208 
209 
210 void
211 BPopUpMenu::AllDetached()
212 {
213 	BMenu::AllDetached();
214 }
215 
216 
217 void
218 BPopUpMenu::SetAsyncAutoDestruct(bool state)
219 {
220 	fAutoDestruct = state;
221 }
222 
223 
224 bool
225 BPopUpMenu::AsyncAutoDestruct() const
226 {
227 	return fAutoDestruct;
228 }
229 
230 
231 BPoint
232 BPopUpMenu::ScreenLocation()
233 {
234 	if (fUseWhere)
235 		return fWhere;
236 
237 	BMenuItem *superItem = Superitem();
238 	BMenu *superMenu = Supermenu();
239 	BMenuItem *selectedItem = FindItem(superItem->Label());
240 	BPoint point = superItem->Frame().LeftTop();
241 
242 	superMenu->ConvertToScreen(&point);
243 
244 	if (selectedItem != NULL)
245 		point.y -= selectedItem->Frame().top;
246 
247 	return point;
248 }
249 
250 
251 //	#pragma mark - private methods
252 
253 
254 void BPopUpMenu::_ReservedPopUpMenu1() {}
255 void BPopUpMenu::_ReservedPopUpMenu2() {}
256 void BPopUpMenu::_ReservedPopUpMenu3() {}
257 
258 
259 BPopUpMenu &
260 BPopUpMenu::operator=(const BPopUpMenu &)
261 {
262 	return *this;
263 }
264 
265 
266 BMenuItem *
267 BPopUpMenu::_Go(BPoint where, bool autoInvoke, bool startOpened,
268 		BRect *_specialRect, bool async)
269 {
270 	popup_menu_data *data = new (nothrow) popup_menu_data;
271 	if (!data)
272 		return NULL;
273 
274 	sem_id sem = create_sem(0, "window close lock");
275 	if (sem < B_OK) {
276 		delete data;
277 		return NULL;
278 	}
279 
280 	// Get a pointer to the window from which Go() was called
281 	BWindow *window = dynamic_cast<BWindow *>(BLooper::LooperForThread(find_thread(NULL)));
282 	data->window = window;
283 
284 	// Asynchronous menu: we set the BWindow menu's semaphore
285 	// and let BWindow block when needed
286 	if (async && window != NULL) {
287 		_set_menu_sem_(window, sem);
288 	}
289 
290 	data->object = this;
291 	data->autoInvoke = autoInvoke;
292 	data->useRect = _specialRect != NULL;
293 	if (_specialRect != NULL)
294 		data->rect = *_specialRect;
295 	data->async = async;
296 	data->where = where;
297 	data->startOpened = startOpened;
298 	data->selected = NULL;
299 	data->lock = sem;
300 
301 	// Spawn the tracking thread
302 	fTrackThread = spawn_thread(_thread_entry, "popup", B_DISPLAY_PRIORITY, data);
303 	if (fTrackThread < B_OK) {
304 		// Something went wrong. Cleanup and return NULL
305 		delete_sem(sem);
306 		if (async && window != NULL)
307 			_set_menu_sem_(window, B_BAD_SEM_ID);
308 		delete data;
309 		return NULL;
310 	}
311 
312 	resume_thread(fTrackThread);
313 
314 	if (!async)
315 		return _WaitMenu(data);
316 
317 	return 0;
318 }
319 
320 
321 /* static */
322 int32
323 BPopUpMenu::_thread_entry(void *arg)
324 {
325 	popup_menu_data *data = static_cast<popup_menu_data *>(arg);
326 	BPopUpMenu *menu = data->object;
327 	BRect *rect = NULL;
328 
329 	if (data->useRect)
330 		rect = &data->rect;
331 
332 	data->selected = menu->_StartTrack(data->where, data->autoInvoke, data->startOpened, rect);
333 
334 	// Reset the window menu semaphore
335 	if (data->async && data->window)
336 		_set_menu_sem_(data->window, B_BAD_SEM_ID);
337 
338 	delete_sem(data->lock);
339 
340 	// Commit suicide if needed
341 	if (menu->fAutoDestruct) {
342 		menu->fTrackThread = -1;
343 		delete menu;
344 	}
345 
346 	if (data->async)
347 		delete data;
348 
349 	return 0;
350 }
351 
352 
353 BMenuItem *
354 BPopUpMenu::_StartTrack(BPoint where, bool autoInvoke, bool startOpened, BRect *_specialRect)
355 {
356 	fWhere = where;
357 
358 	// I know, this doesn't look senseful, but don't be fooled,
359 	// fUseWhere is used in ScreenLocation(), which is a virtual
360 	// called by BMenu::Track()
361 	fUseWhere = true;
362 
363 	// Show the menu's window
364 	Show();
365 
366 	// Wait some time then track the menu
367 	snooze(50000);
368 	BMenuItem *result = Track(startOpened, _specialRect);
369 	if (result != NULL && autoInvoke)
370 		result->Invoke();
371 
372 	fUseWhere = false;
373 
374 	Hide();
375 	be_app->ShowCursor();
376 
377 	return result;
378 }
379 
380 
381 BMenuItem *
382 BPopUpMenu::_WaitMenu(void *_data)
383 {
384 	popup_menu_data *data = (popup_menu_data *)_data;
385 	BWindow *window = data->window;
386 	sem_id sem = data->lock;
387 	if (window != NULL) {
388 		while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID)
389 			window->UpdateIfNeeded();
390 	}
391 
392  	status_t unused;
393 	while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED)
394 		;
395 
396 	fTrackThread = -1;
397 
398 	BMenuItem *selected = data->selected;
399 		// data->selected is filled by the tracking thread
400 
401 	delete data;
402 
403 	return selected;
404 }
405