xref: /haiku/src/kits/interface/PopUpMenu.cpp (revision a4f6a81235ca2522c01f532de13cad9b729d4029)
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 *superItem = Superitem();
256 	BMenu *superMenu = Supermenu();
257 	BMenuItem *selectedItem = FindItem(superItem->Label());
258 	BPoint point = superItem->Frame().LeftTop();
259 
260 	superMenu->ConvertToScreen(&point);
261 
262 	if (selectedItem != NULL)
263 		point.y -= selectedItem->Frame().top;
264 
265 	return point;
266 }
267 
268 
269 //	#pragma mark -
270 //	private methods
271 
272 
273 void BPopUpMenu::_ReservedPopUpMenu1() {}
274 void BPopUpMenu::_ReservedPopUpMenu2() {}
275 void BPopUpMenu::_ReservedPopUpMenu3() {}
276 
277 
278 BPopUpMenu &
279 BPopUpMenu::operator=(const BPopUpMenu &)
280 {
281 	return *this;
282 }
283 
284 
285 BMenuItem *
286 BPopUpMenu::_go(BPoint where, bool autoInvoke, bool startOpened,
287 		BRect *_specialRect, bool async)
288 {
289 	BMenuItem *selected = NULL;
290 
291 	// Can't use Window(), as the BPopUpMenu isn't attached
292 	BLooper *looper = BLooper::LooperForThread(find_thread(NULL));
293 	BWindow *window = dynamic_cast<BWindow *>(looper);
294 
295 	if (window == NULL)
296 		return NULL;
297 
298 	popup_menu_data *data = new popup_menu_data;
299 	sem_id sem = create_sem(0, "window close lock");
300 
301 	// Asynchronous menu: we set the BWindow menu's semaphore
302 	// and let BWindow block when needed
303 	if (async) {
304 		data->window = window;
305 		_set_menu_sem_(window, sem);
306 	}
307 
308 	data->object = this;
309 	data->autoInvoke = autoInvoke;
310 	data->useRect = _specialRect != NULL;
311 	if (_specialRect != NULL)
312 		data->rect = *_specialRect;
313 	data->async = async;
314 	data->where = where;
315 	data->startOpened = startOpened;
316 	data->window = NULL;
317 	data->selected = selected;
318 	data->lock = sem;
319 
320 	// Spawn the tracking thread
321 	thread_id thread = spawn_thread(entry, "popup", B_NORMAL_PRIORITY, data);
322 
323 	if (thread >= 0)
324 		resume_thread(thread);
325 	else {
326 		// Something went wrong. Cleanup and return NULL
327 		delete_sem(sem);
328 		if (async)
329 			_set_menu_sem_(window, B_NO_MORE_SEMS);
330 		delete data;
331 		return NULL;
332 	}
333 
334 	// Synchronous menu: we block on the sem till
335 	// the other thread deletes it.
336 	if (!async) {
337 		if (window) {
338 			// TODO: usually it's not a good idea to check for a particular error
339 			// code. Though here we just want to wait till the semaphore is deleted
340 			// (it will return B_BAD_SEM_ID in that case), not provide locking or whatever.
341 			while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID)
342 				window->UpdateIfNeeded();
343 		}
344 
345 		status_t unused;
346 		while (wait_for_thread(thread, &unused) == B_INTERRUPTED)
347 			;
348 
349 		selected = data->selected;
350 
351 		delete data;
352 	}
353 
354 	return selected;
355 }
356 
357 
358 int32
359 BPopUpMenu::entry(void *arg)
360 {
361 	popup_menu_data *data = static_cast<popup_menu_data *>(arg);
362 	BPopUpMenu *menu = data->object;
363 
364 	BPoint where = data->where;
365 	BRect *rect = NULL;
366 	bool autoInvoke = data->autoInvoke;
367 	bool startOpened = data->startOpened;
368 
369 	if (data->useRect)
370 		rect = &data->rect;
371 
372 	BMenuItem *selected = menu->start_track(where, autoInvoke,
373 								startOpened, rect);
374 
375 	// Put the selected item in the shared struct.
376 	data->selected = selected;
377 
378 	delete_sem(data->lock);
379 
380 	// Reset the window menu semaphore
381 	if (data->window)
382 		_set_menu_sem_(data->window, B_NO_MORE_SEMS);
383 
384 	// Commit suicide if needed
385 	if (menu->fAutoDestruct)
386 		delete menu;
387 
388 	if (data->async)
389 		delete data;
390 
391 	return 0;
392 }
393 
394 
395 BMenuItem *
396 BPopUpMenu::start_track(BPoint where, bool autoInvoke,
397 		bool startOpened, BRect *_specialRect)
398 {
399 	BMenuItem *result = NULL;
400 
401 	fWhere = where;
402 
403 	// I know, this doesn't look senseful, but don't be fooled,
404 	// fUseWhere is used in ScreenLocation(), which is a virtual
405 	// called by BMenu::Track()
406 	fUseWhere = true;
407 
408 	// Show the menu's window
409 	Show();
410 
411 	// Wait some time then track the menu
412 	snooze(50000);
413 	result = Track(startOpened, _specialRect);
414 	if (result != NULL && autoInvoke)
415 		result->Invoke();
416 
417 	fUseWhere = false;
418 
419 	Hide();
420 
421 	return result;
422 }
423 
424