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* name, bool radioMode, bool labelFromMarked, 40 menu_layout layout) 41 : 42 BMenu(name, layout), 43 fUseWhere(false), 44 fAutoDestruct(false), 45 fTrackThread(-1) 46 { 47 if (radioMode) 48 SetRadioMode(true); 49 50 if (labelFromMarked) 51 SetLabelFromMarked(true); 52 } 53 54 55 BPopUpMenu::BPopUpMenu(BMessage* archive) 56 : 57 BMenu(archive), 58 fUseWhere(false), 59 fAutoDestruct(false), 60 fTrackThread(-1) 61 { 62 } 63 64 65 BPopUpMenu::~BPopUpMenu() 66 { 67 if (fTrackThread >= 0) { 68 status_t status; 69 while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED) 70 ; 71 } 72 } 73 74 75 status_t 76 BPopUpMenu::Archive(BMessage* data, bool deep) const 77 { 78 return BMenu::Archive(data, deep); 79 } 80 81 82 BArchivable* 83 BPopUpMenu::Instantiate(BMessage* data) 84 { 85 if (validate_instantiation(data, "BPopUpMenu")) 86 return new BPopUpMenu(data); 87 88 return NULL; 89 } 90 91 92 BMenuItem* 93 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway, bool async) 94 { 95 return _Go(where, deliversMessage, openAnyway, NULL, async); 96 } 97 98 99 BMenuItem* 100 BPopUpMenu::Go(BPoint where, bool deliversMessage, bool openAnyway, 101 BRect clickToOpen, bool async) 102 { 103 return _Go(where, deliversMessage, openAnyway, &clickToOpen, async); 104 } 105 106 107 void 108 BPopUpMenu::MessageReceived(BMessage* message) 109 { 110 BMenu::MessageReceived(message); 111 } 112 113 114 void 115 BPopUpMenu::MouseDown(BPoint point) 116 { 117 BView::MouseDown(point); 118 } 119 120 121 void 122 BPopUpMenu::MouseUp(BPoint point) 123 { 124 BView::MouseUp(point); 125 } 126 127 128 void 129 BPopUpMenu::MouseMoved(BPoint point, uint32 code, const BMessage* message) 130 { 131 BView::MouseMoved(point, code, message); 132 } 133 134 135 void 136 BPopUpMenu::AttachedToWindow() 137 { 138 BMenu::AttachedToWindow(); 139 } 140 141 142 void 143 BPopUpMenu::DetachedFromWindow() 144 { 145 BMenu::DetachedFromWindow(); 146 } 147 148 149 void 150 BPopUpMenu::FrameMoved(BPoint newPosition) 151 { 152 BMenu::FrameMoved(newPosition); 153 } 154 155 156 void 157 BPopUpMenu::FrameResized(float newWidth, float newHeight) 158 { 159 BMenu::FrameResized(newWidth, newHeight); 160 } 161 162 163 BHandler* 164 BPopUpMenu::ResolveSpecifier(BMessage* message, int32 index, 165 BMessage* specifier, int32 form, const char* property) 166 { 167 return BMenu::ResolveSpecifier(message, index, specifier, form, property); 168 } 169 170 171 status_t 172 BPopUpMenu::GetSupportedSuites(BMessage* data) 173 { 174 return BMenu::GetSupportedSuites(data); 175 } 176 177 178 status_t 179 BPopUpMenu::Perform(perform_code code, void* _data) 180 { 181 switch (code) { 182 case PERFORM_CODE_MIN_SIZE: 183 ((perform_data_min_size*)_data)->return_value 184 = BPopUpMenu::MinSize(); 185 return B_OK; 186 case PERFORM_CODE_MAX_SIZE: 187 ((perform_data_max_size*)_data)->return_value 188 = BPopUpMenu::MaxSize(); 189 return B_OK; 190 case PERFORM_CODE_PREFERRED_SIZE: 191 ((perform_data_preferred_size*)_data)->return_value 192 = BPopUpMenu::PreferredSize(); 193 return B_OK; 194 case PERFORM_CODE_LAYOUT_ALIGNMENT: 195 ((perform_data_layout_alignment*)_data)->return_value 196 = BPopUpMenu::LayoutAlignment(); 197 return B_OK; 198 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 199 ((perform_data_has_height_for_width*)_data)->return_value 200 = BPopUpMenu::HasHeightForWidth(); 201 return B_OK; 202 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 203 { 204 perform_data_get_height_for_width* data 205 = (perform_data_get_height_for_width*)_data; 206 BPopUpMenu::GetHeightForWidth(data->width, &data->min, &data->max, 207 &data->preferred); 208 return B_OK; 209 } 210 case PERFORM_CODE_SET_LAYOUT: 211 { 212 perform_data_set_layout* data = (perform_data_set_layout*)_data; 213 BPopUpMenu::SetLayout(data->layout); 214 return B_OK; 215 } 216 case PERFORM_CODE_LAYOUT_INVALIDATED: 217 { 218 perform_data_layout_invalidated* data 219 = (perform_data_layout_invalidated*)_data; 220 BPopUpMenu::LayoutInvalidated(data->descendants); 221 return B_OK; 222 } 223 case PERFORM_CODE_DO_LAYOUT: 224 { 225 BPopUpMenu::DoLayout(); 226 return B_OK; 227 } 228 } 229 230 return BMenu::Perform(code, _data); 231 } 232 233 234 void 235 BPopUpMenu::ResizeToPreferred() 236 { 237 BMenu::ResizeToPreferred(); 238 } 239 240 241 void 242 BPopUpMenu::GetPreferredSize(float* _width, float* _height) 243 { 244 BMenu::GetPreferredSize(_width, _height); 245 } 246 247 248 void 249 BPopUpMenu::MakeFocus(bool state) 250 { 251 BMenu::MakeFocus(state); 252 } 253 254 255 void 256 BPopUpMenu::AllAttached() 257 { 258 BMenu::AllAttached(); 259 } 260 261 262 void 263 BPopUpMenu::AllDetached() 264 { 265 BMenu::AllDetached(); 266 } 267 268 269 void 270 BPopUpMenu::SetAsyncAutoDestruct(bool on) 271 { 272 fAutoDestruct = on; 273 } 274 275 276 bool 277 BPopUpMenu::AsyncAutoDestruct() const 278 { 279 return fAutoDestruct; 280 } 281 282 283 BPoint 284 BPopUpMenu::ScreenLocation() 285 { 286 // This case is when the BPopUpMenu is standalone 287 if (fUseWhere) 288 return fWhere; 289 290 // This case is when the BPopUpMenu is inside a BMenuField 291 BMenuItem* superItem = Superitem(); 292 BMenu* superMenu = Supermenu(); 293 BMenuItem* selectedItem = FindItem(superItem->Label()); 294 BPoint point = superItem->Frame().LeftTop(); 295 296 superMenu->ConvertToScreen(&point); 297 298 if (selectedItem != NULL) { 299 while (selectedItem->Menu() != this 300 && selectedItem->Menu()->Superitem() != NULL) { 301 selectedItem = selectedItem->Menu()->Superitem(); 302 } 303 point.y -= selectedItem->Frame().top; 304 } 305 306 return point; 307 } 308 309 310 // #pragma mark - private methods 311 312 313 void BPopUpMenu::_ReservedPopUpMenu1() {} 314 void BPopUpMenu::_ReservedPopUpMenu2() {} 315 void BPopUpMenu::_ReservedPopUpMenu3() {} 316 317 318 BPopUpMenu& 319 BPopUpMenu::operator=(const BPopUpMenu& other) 320 { 321 return *this; 322 } 323 324 325 BMenuItem* 326 BPopUpMenu::_Go(BPoint where, bool autoInvoke, bool startOpened, 327 BRect* _specialRect, bool async) 328 { 329 if (fTrackThread >= B_OK) { 330 // we already have an active menu, wait for it to go away before 331 // spawning another 332 status_t unused; 333 while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED) 334 ; 335 } 336 337 popup_menu_data* data = new (std::nothrow) popup_menu_data; 338 if (data == NULL) 339 return NULL; 340 341 sem_id sem = create_sem(0, "window close lock"); 342 if (sem < B_OK) { 343 delete data; 344 return NULL; 345 } 346 347 // Get a pointer to the window from which Go() was called 348 BWindow* window 349 = dynamic_cast<BWindow*>(BLooper::LooperForThread(find_thread(NULL))); 350 data->window = window; 351 352 // Asynchronous menu: we set the BWindow menu's semaphore 353 // and let BWindow block when needed 354 if (async && window != NULL) 355 _set_menu_sem_(window, sem); 356 357 data->object = this; 358 data->autoInvoke = autoInvoke; 359 data->useRect = _specialRect != NULL; 360 if (_specialRect != NULL) 361 data->rect = *_specialRect; 362 data->async = async; 363 data->where = where; 364 data->startOpened = startOpened; 365 data->selected = NULL; 366 data->lock = sem; 367 368 // Spawn the tracking thread 369 fTrackThread = spawn_thread(_thread_entry, "popup", B_DISPLAY_PRIORITY, 370 data); 371 if (fTrackThread < B_OK) { 372 // Something went wrong. Cleanup and return NULL 373 delete_sem(sem); 374 if (async && window != NULL) 375 _set_menu_sem_(window, B_BAD_SEM_ID); 376 delete data; 377 return NULL; 378 } 379 380 resume_thread(fTrackThread); 381 382 if (!async) 383 return _WaitMenu(data); 384 385 return 0; 386 } 387 388 389 /* static */ 390 int32 391 BPopUpMenu::_thread_entry(void* menuData) 392 { 393 popup_menu_data* data = static_cast<popup_menu_data*>(menuData); 394 BPopUpMenu* menu = data->object; 395 BRect* rect = NULL; 396 397 if (data->useRect) 398 rect = &data->rect; 399 400 data->selected = menu->_StartTrack(data->where, data->autoInvoke, 401 data->startOpened, rect); 402 403 // Reset the window menu semaphore 404 if (data->async && data->window) 405 _set_menu_sem_(data->window, B_BAD_SEM_ID); 406 407 delete_sem(data->lock); 408 409 // Commit suicide if needed 410 if (data->async && menu->fAutoDestruct) { 411 menu->fTrackThread = -1; 412 delete menu; 413 } 414 415 if (data->async) 416 delete data; 417 418 return 0; 419 } 420 421 422 BMenuItem* 423 BPopUpMenu::_StartTrack(BPoint where, bool autoInvoke, bool startOpened, 424 BRect* _specialRect) 425 { 426 // I know, this doesn't look senseful, but don't be fooled, 427 // fUseWhere is used in ScreenLocation(), which is a virtual 428 // called by BMenu::Track() 429 fWhere = where; 430 fUseWhere = true; 431 432 // Determine when mouse-down-up will be taken as a 'press', 433 // rather than a 'click' 434 bigtime_t clickMaxTime = 0; 435 get_click_speed(&clickMaxTime); 436 clickMaxTime += system_time(); 437 438 // Show the menu's window 439 Show(); 440 snooze(50000); 441 BMenuItem* result = Track(startOpened, _specialRect); 442 443 // If it was a click, keep the menu open and tracking 444 if (system_time() <= clickMaxTime) 445 result = Track(true, _specialRect); 446 if (result != NULL && autoInvoke) 447 result->Invoke(); 448 449 fUseWhere = false; 450 451 Hide(); 452 be_app->ShowCursor(); 453 454 return result; 455 } 456 457 458 BMenuItem* 459 BPopUpMenu::_WaitMenu(void* _data) 460 { 461 popup_menu_data* data = static_cast<popup_menu_data*>(_data); 462 BWindow* window = data->window; 463 sem_id sem = data->lock; 464 if (window != NULL) { 465 while (acquire_sem_etc(sem, 1, B_TIMEOUT, 50000) != B_BAD_SEM_ID) 466 window->UpdateIfNeeded(); 467 } 468 469 status_t unused; 470 while (wait_for_thread(fTrackThread, &unused) == B_INTERRUPTED) 471 ; 472 473 fTrackThread = -1; 474 475 BMenuItem* selected = data->selected; 476 // data->selected is filled by the tracking thread 477 478 delete data; 479 480 return selected; 481 } 482