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