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