xref: /haiku/src/kits/interface/PopUpMenu.cpp (revision 683cbefe9ec156fe9587b9a64a5e1b666a21654d)
1 //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2005, Haiku, Inc.
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		PopUpMenu.cpp
23 //	Author:			Marc Flerackers (mflerackers@androme.be)
24 //					Stefano Ceccherini (burton666@libero.it)
25 //	Description:	BPopUpMenu represents a menu that pops up when you
26 //                  activate it.
27 //------------------------------------------------------------------------------
28 #include <Application.h>
29 #include <Looper.h>
30 #include <MenuItem.h>
31 #include <PopUpMenu.h>
32 #include <Window.h>
33 
34 
35 struct popup_menu_data
36 {
37 	BPopUpMenu *object;
38 	BWindow *window;
39 	BMenuItem *selected;
40 
41 	BPoint where;
42 	BRect rect;
43 
44 	bool async;
45 	bool autoInvoke;
46 	bool startOpened;
47 	bool useRect;
48 
49 	sem_id lock;
50 };
51 
52 
53 BPopUpMenu::BPopUpMenu(const char *title, bool radioMode, bool autoRename,
54 						menu_layout layout)
55 	:
56 	BMenu(title, layout),
57 	fUseWhere(false),
58 	fAutoDestruct(false),
59 	fTrackThread(-1)
60 {
61 	if (radioMode)
62 		SetRadioMode(true);
63 
64 	if (autoRename)
65 		SetLabelFromMarked(true);
66 }
67 
68 
69 BPopUpMenu::BPopUpMenu(BMessage *archive)
70 	:
71 	BMenu(archive),
72 	fUseWhere(false),
73 	fAutoDestruct(false),
74 	fTrackThread(-1)
75 {
76 }
77 
78 
79 BPopUpMenu::~BPopUpMenu()
80 {
81 	if (fTrackThread >= 0) {
82 		status_t status;
83 		while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED)
84 			;
85 
86 	}
87 }
88 
89 
90 status_t
91 BPopUpMenu::Archive(BMessage *data, bool deep) const
92 {
93 	return BMenu::Archive(data, deep);
94 }
95 
96 
97 BArchivable *
98 BPopUpMenu::Instantiate(BMessage *data)
99 {
100 	if (validate_instantiation(data, "BPopUpMenu"))
101 		return new BPopUpMenu(data);
102 
103 	return NULL;
104 }
105 
106 
107 BMenuItem *
108 BPopUpMenu::Go(BPoint where, bool delivers_message, bool open_anyway, bool async)
109 {
110 	return _go(where, delivers_message, open_anyway, NULL, async);
111 }
112 
113 
114 BMenuItem *
115 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway,
116 	BRect clickToOpen, bool async)
117 {
118 	return _go(where, deliversMessage, openAnyway, &clickToOpen, async);
119 }
120 
121 
122 void
123 BPopUpMenu::MessageReceived(BMessage *msg)
124 {
125 	BMenu::MessageReceived(msg);
126 }
127 
128 
129 void
130 BPopUpMenu::MouseDown(BPoint point)
131 {
132 	BView::MouseDown(point);
133 }
134 
135 
136 void
137 BPopUpMenu::MouseUp(BPoint point)
138 {
139 	BView::MouseUp(point);
140 }
141 
142 
143 void
144 BPopUpMenu::MouseMoved(BPoint point, uint32 code, const BMessage *msg)
145 {
146 	BView::MouseMoved(point, code, msg);
147 }
148 
149 
150 void
151 BPopUpMenu::AttachedToWindow()
152 {
153 	BMenu::AttachedToWindow();
154 }
155 
156 
157 void
158 BPopUpMenu::DetachedFromWindow()
159 {
160 	BMenu::DetachedFromWindow();
161 }
162 
163 
164 void
165 BPopUpMenu::FrameMoved(BPoint newPosition)
166 {
167 	BMenu::FrameMoved(newPosition);
168 }
169 
170 
171 void
172 BPopUpMenu::FrameResized(float newWidth, float newHeight)
173 {
174 	BMenu::FrameResized(newWidth, newHeight);
175 }
176 
177 
178 BHandler *
179 BPopUpMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier,
180 	int32 form, const char *property)
181 {
182 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
183 }
184 
185 
186 status_t
187 BPopUpMenu::GetSupportedSuites(BMessage *data)
188 {
189 	return BMenu::GetSupportedSuites(data);
190 }
191 
192 
193 status_t
194 BPopUpMenu::Perform(perform_code d, void *arg)
195 {
196 	return BMenu::Perform(d, arg);
197 }
198 
199 
200 void
201 BPopUpMenu::ResizeToPreferred()
202 {
203 	BMenu::ResizeToPreferred();
204 }
205 
206 
207 void
208 BPopUpMenu::GetPreferredSize(float *_width, float *_height)
209 {
210 	BMenu::GetPreferredSize(_width, _height);
211 }
212 
213 
214 void
215 BPopUpMenu::MakeFocus(bool state)
216 {
217 	BMenu::MakeFocus(state);
218 }
219 
220 
221 void
222 BPopUpMenu::AllAttached()
223 {
224 	BMenu::AllAttached();
225 }
226 
227 
228 void
229 BPopUpMenu::AllDetached()
230 {
231 	BMenu::AllDetached();
232 }
233 
234 
235 void
236 BPopUpMenu::SetAsyncAutoDestruct(bool state)
237 {
238 	fAutoDestruct = state;
239 }
240 
241 
242 bool
243 BPopUpMenu::AsyncAutoDestruct() const
244 {
245 	return fAutoDestruct;
246 }
247 
248 
249 BPoint
250 BPopUpMenu::ScreenLocation()
251 {
252 	if (fUseWhere)
253 		return fWhere;
254 
255 	BMenuItem *item = Superitem();
256 	BMenu *menu = Supermenu();
257 	BMenuItem *selectedItem = FindItem(item->Label());
258 	BRect rect = item->Frame();
259 	BPoint point = rect.LeftTop();
260 
261 	menu->ConvertToScreen(&point);
262 
263 	if (selectedItem)
264 		point.y -= selectedItem->Frame().top;
265 
266 	return point;
267 }
268 
269 
270 //	#pragma mark -
271 //	private methods
272 
273 
274 void BPopUpMenu::_ReservedPopUpMenu1() {}
275 void BPopUpMenu::_ReservedPopUpMenu2() {}
276 void BPopUpMenu::_ReservedPopUpMenu3() {}
277 
278 
279 BPopUpMenu &
280 BPopUpMenu::operator=(const BPopUpMenu &)
281 {
282 	return *this;
283 }
284 
285 
286 BMenuItem *
287 BPopUpMenu::_go(BPoint where, bool autoInvoke, bool startOpened,
288 		BRect *_specialRect, bool async)
289 {
290 	BMenuItem *selected = NULL;
291 
292 	// Can't use Window(), as the BPopUpMenu isn't attached
293 	BLooper *looper = BLooper::LooperForThread(find_thread(NULL));
294 	BWindow *window = dynamic_cast<BWindow *>(looper);
295 
296 	if (window == NULL)
297 		return NULL;
298 
299 	popup_menu_data *data = new popup_menu_data;
300 	sem_id sem = create_sem(0, "window close lock");
301 
302 	// Asynchronous menu: we set the BWindow menu's semaphore
303 	// and let BWindow block when needed
304 	if (async) {
305 		data->window = window;
306 		_set_menu_sem_(window, sem);
307 	}
308 
309 	data->object = this;
310 	data->autoInvoke = autoInvoke;
311 	data->useRect = _specialRect != NULL;
312 	if (_specialRect != NULL)
313 		data->rect = *_specialRect;
314 	data->async = async;
315 	data->where = where;
316 	data->startOpened = startOpened;
317 	data->window = NULL;
318 	data->selected = selected;
319 	data->lock = sem;
320 
321 	// Spawn the tracking thread
322 	thread_id thread = spawn_thread(entry, "popup", B_NORMAL_PRIORITY, data);
323 
324 	if (thread >= 0)
325 		resume_thread(thread);
326 	else {
327 		// Something went wrong. Cleanup and return NULL
328 		delete_sem(sem);
329 		if (async)
330 			_set_menu_sem_(window, B_NO_MORE_SEMS);
331 		delete data;
332 		return NULL;
333 	}
334 
335 	// Synchronous menu: we block on the sem till
336 	// the other thread deletes it.
337 	if (!async) {
338 		if (window) {
339 			// TODO: usually it's not a good idea to check for a particular error
340 			// code. Though here we just want to wait till the semaphore is deleted
341 			// (it will return B_BAD_SEM_ID in that case), not provide locking or whatever.
342 			while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID)
343 				window->UpdateIfNeeded();
344 		}
345 
346 		status_t unused;
347 		while (wait_for_thread(thread, &unused) == B_INTERRUPTED)
348 			;
349 
350 		selected = data->selected;
351 
352 		delete data;
353 	}
354 
355 	return selected;
356 }
357 
358 
359 int32
360 BPopUpMenu::entry(void *arg)
361 {
362 	popup_menu_data *data = static_cast<popup_menu_data *>(arg);
363 	BPopUpMenu *menu = data->object;
364 
365 	BPoint where = data->where;
366 	BRect *rect = NULL;
367 	bool autoInvoke = data->autoInvoke;
368 	bool startOpened = data->startOpened;
369 
370 	if (data->useRect)
371 		rect = &data->rect;
372 
373 	BMenuItem *selected = menu->start_track(where, autoInvoke,
374 								startOpened, rect);
375 
376 	// Put the selected item in the shared struct.
377 	data->selected = selected;
378 
379 	delete_sem(data->lock);
380 
381 	// Reset the window menu semaphore
382 	if (data->window)
383 		_set_menu_sem_(data->window, B_NO_MORE_SEMS);
384 
385 	// Commit suicide if needed
386 	if (menu->fAutoDestruct)
387 		delete menu;
388 
389 	if (data->async)
390 		delete data;
391 
392 	return 0;
393 }
394 
395 
396 BMenuItem *
397 BPopUpMenu::start_track(BPoint where, bool autoInvoke,
398 		bool startOpened, BRect *_specialRect)
399 {
400 	BMenuItem *result = NULL;
401 
402 	fWhere = where;
403 
404 	// I know, this doesn't look senseful, but don't be fooled,
405 	// fUseWhere is used in ScreenLocation(), which is a virtual
406 	// called by BMenu::Track()
407 	fUseWhere = true;
408 
409 	// Show the menu's window
410 	Show();
411 
412 	// Wait some time then track the menu
413 	snooze(50000);
414 	result = Track(startOpened, _specialRect);
415 	if (result != NULL && autoInvoke)
416 		result->Invoke();
417 
418 	fUseWhere = false;
419 
420 	Hide();
421 
422 	return result;
423 }
424 
425