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