| 351 | |
| 352 | |
| 353 | UINT Script::GetFreeMenuItemID() |
| 354 | // Returns an unused menu item ID, or 0 if all IDs are used. |
| 355 | { |
| 356 | // Need to find a menuID that isn't already in use by one of the other menu items. |
| 357 | // But also need to conserve menu items since only a relatively small number of IDs is available. |
| 358 | // Can't simply use ID_USER_FIRST + mMenuItemCount because: 1) There might be more than one |
| 359 | // user defined menu; 2) a menu item in the middle of the list may have been deleted, |
| 360 | // in which case that value would already be in use by the last item. |
| 361 | // Update: Now using caching of last successfully found free-ID to greatly improve avg. |
| 362 | // performance, especially for menus that contain thousands of items and submenus, such as |
| 363 | // ones that are built to mirror an entire nested directory structure. Caching should |
| 364 | // improve performance even after all menu IDs within the available range have been |
| 365 | // allocated once (via adding and deleting menus + menu items) since large blocks of free IDs |
| 366 | // should be free, and on average, the caching will exploit these large free blocks. However, |
| 367 | // if large amounts of menus and menu items are continually deleted and re-added by a script, |
| 368 | // the pool of free IDs will become fragmented over time, which will reduce performance. |
| 369 | // Since that kind of script behavior seems very rare, no attempt is made to "defragment". |
| 370 | // If more performance is needed in the future (seems unlikely for 99.9999% of scripts), |
| 371 | // could maintain an field of ~64000 bits, each bit representing whether a menu item ID is |
| 372 | // free. Then, every time a menu or one or more of its IDs is deleted or added, the corresponding |
| 373 | // ID could be marked as free/taken. That would add quite a bit of complexity to the menu |
| 374 | // delete code, however, and it would reduce the overall maintainability. So it definitely |
| 375 | // doesn't seem worth it, especially since Windows XP seems to have trouble even displaying |
| 376 | // menus larger than around 15000-25000 items. |
| 377 | static UINT sLastFreeID = ID_USER_FIRST - 1; |
| 378 | // Increment by one for each new search, both due to the above line and because the |
| 379 | // last-found free ID has a high likelihood of still being in use: |
| 380 | ++sLastFreeID; |
| 381 | bool id_in_use; |
| 382 | // Note that the i variable is used to force the loop to complete exactly one full |
| 383 | // circuit through all available IDs, regardless of where the starting/cached value: |
| 384 | for (int i = 0; i < (ID_USER_LAST - ID_USER_FIRST + 1); ++i, ++sLastFreeID) // FOR EACH ID |
| 385 | { |
| 386 | if (sLastFreeID > ID_USER_LAST) |
| 387 | sLastFreeID = ID_USER_FIRST; // Wrap around to the beginning so that one complete circuit is made. |
| 388 | id_in_use = false; // Reset the default each iteration (overridden if the below finds a match). |
| 389 | for (UserMenu *m = mFirstMenu; m; m = m->mNextMenu) // FOR EACH MENU |
| 390 | { |
| 391 | for (UserMenuItem *mi = m->mFirstMenuItem; mi; mi = mi->mNextMenuItem) // FOR EACH MENU ITEM |
| 392 | { |
| 393 | if (mi->mMenuID == sLastFreeID) |
| 394 | { |
| 395 | id_in_use = true; |
| 396 | break; |
| 397 | } |
| 398 | } |
| 399 | if (id_in_use) // No point in searching the other menus, since it's now known to be in use. |
| 400 | break; |
| 401 | } |
| 402 | if (!id_in_use) // Break before the loop increments sLastFreeID. |
| 403 | break; |
| 404 | } |
| 405 | return id_in_use ? 0 : sLastFreeID; |
| 406 | } |
| 407 | |
| 408 | |
| 409 |
no outgoing calls
no test coverage detected