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