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