core/vgui/impl/win32/vgui_win32_utils.cxx
Go to the documentation of this file.
00001 // This is core/vgui/impl/win32/vgui_win32_utils.cxx
00002 
00003 #include "vgui_win32_utils.h"
00004 #include <vcl_iostream.h>
00005 #include <vcl_cassert.h>
00006 #include <vcl_cstddef.h> // for vcl_size_t
00007 
00008 // (Create if necessary and) return singleton instance of this class.
00009 vgui_win32_utils* vgui_win32_utils::instance()
00010 {
00011   static vgui_win32_utils instance_;
00012   return &instance_;
00013 }
00014 
00015 
00016 // Convert a vgui_menu to equivalent MENUTEMPLATE structure used by
00017 // Win32 function LoadMenuIndirect.
00018 HMENU vgui_win32_utils::vgui_menu_to_win32(vgui_menu const &vguimenu,
00019                                            vcl_vector<vgui_command_sptr> &callbacks_, HACCEL *hAccel, bool isPopup)
00020 {
00021   HMENU hMenu;
00022   MENUITEMTEMPLATEHEADER *pMenuHeader;
00023   MENUITEMTEMPLATE *pMenuItem;
00024 
00025   // Note that the MENUITEMTEMPLATE structures are variable in length,
00026   // Therefore we allocate capacity*sizeof(char) initially.
00027   menu_capacity = 1024;    // in unit of unsigned char
00028   item_count = 0;
00029   callbacks.clear();
00030 
00031   pAccel = 0;
00032   accel_capacity = 16;
00033   accel_count = 0;
00034 
00035   pMenu = (unsigned char *)malloc(sizeof(MENUITEMTEMPLATEHEADER) + menu_capacity);
00036   if ( pMenu == NULL ) {
00037     vcl_cerr << "Memory allocation error\n";
00038     return NULL;
00039   }
00040 
00041   pAccel = (ACCEL *)malloc(sizeof(ACCEL)*accel_capacity);
00042   if ( pAccel == NULL )
00043     vcl_cerr << "Memory allocation error\n";
00044 
00045   // Fill up the MENUITEMTEMPLATEHEADER structure.
00046   pMenuHeader = (MENUITEMTEMPLATEHEADER *)pMenu;
00047   pMenuHeader->versionNumber = 0; // This member must be zero.
00048   pMenuHeader->offset = 0;  // The menu item list follows immediately after the header.
00049   int offset = sizeof(MENUITEMTEMPLATEHEADER);
00050 
00051   if ( isPopup ) {
00052     // Add a dumb header so that TrackPopMenu shows the popup menu properly.
00053     pMenuItem = (MENUITEMTEMPLATE*)(pMenu + offset);
00054     pMenuItem->mtOption = MF_END | MF_POPUP;
00055     offset += sizeof(pMenuItem->mtOption);
00056     pMenuItem->mtID = 0;
00057     offset += sizeof(WCHAR);
00058   }
00059 
00060   addMenuItems(vguimenu, offset, isPopup);
00061 
00062   hMenu = LoadMenuIndirect(pMenu);
00063   if ( !hMenu )
00064      ShowErrorMessage(GetLastError());
00065 
00066   if ( !isPopup ) // create an accelerator table for non-popup menu.
00067     *hAccel = CreateAcceleratorTable(pAccel, accel_count);
00068 
00069   free(pMenu);
00070   free(pAccel);
00071 
00072   callbacks_ = callbacks;
00073   return hMenu;
00074 }
00075 
00076 
00077 int vgui_win32_utils::addMenuItems(vgui_menu const &vguimenu, int offset_in, bool is_popup)
00078 {
00079   static unsigned int max_menuitem_size = 256;
00080 
00081   MENUITEMTEMPLATE *pMenuItem;
00082   WCHAR *pMenuItemText;
00083   vcl_string menuItemText;
00084   int stride, offset; // in unit of unsigned char
00085 
00086   // Loop over all menu items and convert them into MENUITEMTEMPLATE
00087   // Note that there are four types of menu item in vgui_menu:
00088   // command, submenu, toggle button, and separator.
00089   offset = offset_in;
00090   for ( unsigned int i=0; i < vguimenu.size(); ++i ) {
00091     pMenuItem = (MENUITEMTEMPLATE*)(pMenu + offset);
00092 
00093     // MENUITEMTEMPLATE does not have separator.
00094     if (vguimenu[i].is_separator() ||
00095         vguimenu[i].is_toggle_button())
00096       continue;
00097 
00098     stride = 0;
00099     pMenuItem->mtOption = 0;
00100     stride += sizeof(pMenuItem->mtOption);
00101     if ( i == vguimenu.size()-1 )
00102       pMenuItem->mtOption |= MF_END; // indiate this is the last menu item
00103 
00104     if (vguimenu[i].is_command()) {
00105       int menuItemID = is_popup ? POPUPMENU_ID_START+item_count++ : MENU_ID_START+item_count++;
00106       pMenuItem->mtID = menuItemID;
00107       stride += sizeof(pMenuItem->mtID);
00108 
00109       menuItemText = vguimenu[i].name;
00110       addAccelerator(menuItemText, vguimenu[i], menuItemID);
00111 
00112       // Copy the menu item text.
00113       vcl_size_t j;
00114       pMenuItemText = pMenuItem->mtString;
00115       for ( j = 0; j < menuItemText.size(); j++ )
00116         *(pMenuItemText+j) = (WCHAR)menuItemText.c_str()[j];
00117       *(pMenuItemText+j) = 0;
00118       stride += sizeof(WCHAR) * (wcslen(pMenuItemText)+1);
00119 
00120       // Add the associated callback function pointer to the callback list.
00121       callbacks.push_back(vguimenu[i].cmnd);
00122     }
00123     else if (vguimenu[i].is_submenu()) {
00124       pMenuItem->mtOption |= MF_POPUP;
00125 
00126       menuItemText = vguimenu[i].name;
00127       vcl_size_t j;
00128       // Note that the MENUITEMTEMPLATE structure for an item that opens a
00129       // drop-down menu or submenu does not contain the mtID member.
00130       pMenuItemText = (WCHAR *)&pMenuItem->mtID;
00131       for ( j = 0; j < menuItemText.size(); j++ )
00132         *(pMenuItemText+j) = menuItemText.c_str()[j];
00133       *(pMenuItemText+j) = 0;
00134       stride += sizeof(WCHAR) * (wcslen(pMenuItemText)+1);
00135 
00136       // Call itself recursively for submenu
00137       stride += addMenuItems(*vguimenu[i].menu, offset+stride, is_popup);
00138     }
00139 
00140     offset += stride;
00141     // Check for illegal buffer access. We'are too late at this point.
00142     // Increase maximum size of a menu item "max_menuitem_size" (default 256)
00143     // to avoid this error.
00144     assert(offset < menu_capacity);
00145 
00146     // Deal with overflow.
00147     if ( offset > menu_capacity-(int)max_menuitem_size ) {
00148       menu_capacity <<= 1; // double the capacity.
00149       pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUITEMTEMPLATEHEADER)+menu_capacity);
00150       if ( pMenu == NULL ) {
00151         vcl_cerr << "Memory allocation error\n";
00152         return 0;
00153       }
00154     }
00155   }
00156 
00157   return offset - offset_in;
00158 }
00159 
00160 // Convert a vgui_menu to equivalent extended MENUTEMPLATE structure used by
00161 // Win32 function LoadMenuIndirect.
00162 HMENU vgui_win32_utils::vgui_menu_to_win32ex(vgui_menu const &vguimenu,
00163                                              vcl_vector<vgui_command_sptr> &callbacks_, HACCEL *hAccel, bool isPopup)
00164 {
00165   HMENU hMenu;
00166   MENUEX_TEMPLATE_HEADER *pMenuHeader;
00167   MENUEX_TEMPLATE_ITEM *pMenuItem;
00168 
00169   // Note that the MENUEX_TEMPLATE_ITEM structures are variable in length,
00170   // but are aligned on DWORD boundaries. Therefore we allocate
00171   // capacity*sizeof(WORD) initially.
00172   menu_capacity = 1024;
00173   item_count = 0;
00174   callbacks.clear();
00175 
00176   pAccel = 0;
00177   accel_capacity = 16;
00178   accel_count = 0;
00179 
00180   pMenu = (unsigned char *)malloc(sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
00181   if ( pMenu == NULL ) {
00182     vcl_cerr << "Memory allocation error\n";
00183     return NULL;
00184   }
00185 
00186   pAccel = (ACCEL *)malloc(sizeof(ACCEL)*accel_capacity);
00187   if ( pAccel == NULL )
00188     vcl_cerr << "Memory allocation error\n";
00189 
00190   // Fill up the MENUEX_TEMPLATE_HEADER structure.
00191   pMenuHeader = (MENUEX_TEMPLATE_HEADER *)pMenu;
00192   pMenuHeader->wVersion = 1; // This member must be 1.
00193   pMenuHeader->wOffset = 4;  // The menu item list follows immediately after the header.
00194   pMenuHeader->dwHelpId = 0;
00195   int offset = sizeof(MENUEX_TEMPLATE_HEADER);
00196 
00197   if ( isPopup ) {
00198     // Add a dumb header so that TrackPopMenu shows the popup menu properly.
00199     pMenuItem = (MENUEX_TEMPLATE_ITEM*)(pMenu+offset);
00200     pMenuItem->dwType = MFT_STRING;
00201     offset += sizeof(pMenuItem->dwType);
00202     pMenuItem->dwState = MFS_ENABLED;
00203     offset += sizeof(pMenuItem->dwState);
00204     pMenuItem->menuId = 0;
00205     offset += sizeof(pMenuItem->menuId);
00206     pMenuItem->bResInfo = 0x01 | 0x80;
00207     offset += sizeof(pMenuItem->bResInfo);
00208     pMenuItem->szText = 0;
00209     offset += sizeof(pMenuItem->szText);
00210     pMenuItem->dwHelpId = 0;
00211     offset += sizeof(pMenuItem->dwHelpId);
00212   }
00213 
00214   addMenuItemsEx(vguimenu, offset, isPopup);
00215 
00216   hMenu = LoadMenuIndirect(pMenu);
00217   if ( !hMenu )
00218      ShowErrorMessage(GetLastError());
00219 
00220   if ( !isPopup ) // create an accelerator table for non-popup menu.
00221     *hAccel = CreateAcceleratorTable(pAccel, accel_count);
00222 
00223   free(pMenu);
00224   free(pAccel);
00225 
00226   callbacks_ = callbacks;
00227   return hMenu;
00228 }
00229 
00230 int vgui_win32_utils::addMenuItemsEx(vgui_menu const &vguimenu, int offset_in, bool is_popup)
00231 {
00232   static unsigned int max_menuitem_size = 256;
00233 
00234   MENUEX_TEMPLATE_ITEM *pMenuItem;
00235   WCHAR *pMenuItemText;
00236   vcl_string menuItemText;
00237   int stride, offset;
00238   bool last_item;
00239 
00240   // Loop over all menu items and convert them into MENUTEMPLATE
00241   // Note that there are four types of menu item in vgui_menu:
00242   // command, submenu, toggle button, and separator.
00243   offset = offset_in;
00244   for ( unsigned int i=0; i < vguimenu.size(); ++i ) {
00245     pMenuItem = (MENUEX_TEMPLATE_ITEM*)(pMenu + offset);
00246     stride = 0;
00247 
00248     // indiate this is the last menu item
00249     last_item = (i == vguimenu.size()-1) ? true : false;
00250 
00251     pMenuItem->dwType = MFT_STRING;
00252     stride += sizeof(pMenuItem->dwType);
00253     pMenuItem->dwState = MFS_ENABLED;
00254     stride += sizeof(pMenuItem->dwState);
00255     pMenuItem->menuId = 0;
00256     stride += sizeof(pMenuItem->menuId);
00257 
00258     if (vguimenu[i].is_separator()) {
00259       pMenuItem->dwType = MFT_SEPARATOR;
00260       pMenuItem->bResInfo = last_item ? 0x80 : 0;
00261       stride += sizeof(pMenuItem->bResInfo);
00262       pMenuItem->szText = 0;
00263       stride += sizeof(pMenuItem->szText);
00264       if ( stride % 4 ) { // aligned on DWORD boundary.
00265         stride += 4;
00266         stride &= ~3;
00267       }
00268     }
00269     else if (vguimenu[i].is_command()) {
00270       int menuItemID = is_popup ? POPUPMENU_ID_START+item_count++ : MENU_ID_START+item_count++;
00271       pMenuItem->menuId = menuItemID;
00272 
00273       pMenuItem->bResInfo = last_item ? 0x80 : 0;
00274       stride += sizeof(pMenuItem->bResInfo);
00275 
00276       menuItemText = vguimenu[i].name;
00277       addAccelerator(menuItemText, vguimenu[i], menuItemID);
00278 
00279       // Copy the menu item text.
00280       pMenuItemText = (WCHAR *)&pMenuItem->szText;
00281       vcl_size_t j;
00282       for ( j = 0; j < menuItemText.size(); j++ )
00283         *(pMenuItemText+j) = menuItemText.c_str()[j];
00284       *(pMenuItemText+j) = 0;
00285 
00286       stride += sizeof(WCHAR) * (wcslen(pMenuItemText)+1);
00287 
00288       if ( stride % 4 ) { // aligned on DWORD boundary.
00289         stride += 4;
00290         stride &= ~3;
00291       }
00292 
00293       // Add the associated callback function pointer to the callback list.
00294       callbacks.push_back(vguimenu[i].cmnd);
00295     }
00296     else if (vguimenu[i].is_submenu()) {
00297       pMenuItem->bResInfo = last_item ? 0x80 | 0x01 : 0x01;
00298       stride += sizeof(pMenuItem->bResInfo);
00299 
00300       menuItemText = vguimenu[i].name;
00301       pMenuItemText = (WCHAR *)&pMenuItem->szText;
00302       vcl_size_t j;
00303       for ( j = 0; j < menuItemText.size(); j++ )
00304         *(pMenuItemText+j) = menuItemText.c_str()[j];
00305       *(pMenuItemText+j) = 0;
00306       stride += sizeof(WCHAR) * (wcslen(pMenuItemText)+1);
00307 
00308       if ( stride % 4 ) { // aligned on DWORD boundary.
00309         stride += 4;
00310         stride &= ~3;
00311       }
00312 
00313       DWORD* dwHelpId = (DWORD *)(pMenu+offset+stride);
00314       *dwHelpId = 0;
00315       stride += sizeof(pMenuItem->dwHelpId);
00316 
00317       // Call itself recursively for submenu
00318       stride += addMenuItemsEx(*vguimenu[i].menu, offset+stride, is_popup);
00319     }
00320     else if (vguimenu[i].is_toggle_button()) {
00321       vcl_cerr << "vgui_win32_utils: toggle button is not converted\n";
00322     }
00323 
00324     offset += stride;
00325     // Check for illegal buffer access. We'are too late at this point.
00326     // Increase maximum size of a menu item "max_menuitem_size" (default 256)
00327     // to avoid this error.
00328     assert(offset < menu_capacity);
00329 
00330     // Deal with buffer overflow.
00331     if ( offset > menu_capacity-(int)max_menuitem_size ) {
00332       menu_capacity <<= 1; // double the capacity.
00333       pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
00334       if ( pMenu == NULL ) {
00335         vcl_cerr << "Memory allocation error\n";
00336         return NULL;
00337       }
00338     }
00339   }
00340 
00341   return offset-offset_in;
00342 }
00343 
00344 inline void vgui_win32_utils::addAccelerator(vcl_string &menuItemText,
00345                                              vgui_menu_item const &vguimenu, int menuItemId)
00346 {
00347   ACCEL *pa = pAccel+accel_count;
00348   pa->cmd = menuItemId;
00349   // Give a virtual key, even if there is no modifier.
00350   pa->fVirt = FVIRTKEY;
00351 
00352   if ( vguimenu.short_cut.mod != vgui_MODIFIER_NULL ||
00353        vguimenu.short_cut.key != vgui_KEY_NULL )
00354     menuItemText += "\t"; // A tab key is required.
00355 
00356   if ( vguimenu.short_cut.mod & vgui_CTRL ) {
00357     menuItemText += "Ctrl+";
00358     pa->fVirt |= FCONTROL;
00359   }
00360   if ( vguimenu.short_cut.mod & vgui_SHIFT ) {
00361     menuItemText += "Shift+";
00362     pa->fVirt |= FSHIFT;
00363   }
00364   if ( vguimenu.short_cut.mod & vgui_ALT ) {
00365     menuItemText += "Alt+";
00366     pa->fVirt |= FALT;
00367   }
00368 
00369   vgui_key key = vguimenu.short_cut.key;
00370   if ( key != vgui_KEY_NULL ) {
00371     if ( key >= 'A' && key <= 'Z'|| key >= 'a' && key <= 'z') {
00372        if ( key >= 'a' && key <= 'z')
00373           key = vgui_key(key + 'A' - 'a');
00374        menuItemText += key;
00375        pa->key = key;
00376     }
00377     else {
00378       menuItemText += vgui_key_to_string(key);
00379       pa->key = vgui_key_to_virt_key(key);
00380     }
00381     // Deal with the case of buffer overflow.
00382     if (++accel_count >= accel_capacity) {
00383       accel_capacity <<= 1; // double the capacity.
00384       pAccel = (ACCEL *)realloc(pAccel, sizeof(ACCEL)*accel_capacity);
00385       if ( pAccel == NULL )
00386         vcl_cerr << "Memory allocation error\n";
00387     }
00388   }
00389 }
00390 
00391 
00392 inline vcl_string vgui_win32_utils::vgui_key_to_string(vgui_key key)
00393 {
00394   vcl_string str;
00395 
00396   switch ( key ) {
00397     // Function keys
00398     case vgui_F1:
00399       str = "F1"; break;
00400     case vgui_F2:
00401       str = "F2"; break;
00402     case vgui_F3:
00403       str = "F3"; break;
00404     case vgui_F4:
00405       str = "F4"; break;
00406     case vgui_F5:
00407       str = "F5"; break;
00408     case vgui_F6:
00409       str = "F6"; break;
00410     case vgui_F7:
00411       str = "F7"; break;
00412     case vgui_F8:
00413       str = "F8"; break;
00414     case vgui_F9:
00415       str = "F9"; break;
00416     case vgui_F10:
00417       str = "F10"; break;
00418     case vgui_F11:
00419       str = "F11"; break;
00420     case vgui_F12:
00421       str = "F12"; break;
00422     case vgui_CURSOR_LEFT:
00423       str = "Left"; break;
00424     case vgui_CURSOR_UP:
00425       str = "Up"; break;
00426     case vgui_CURSOR_RIGHT:
00427       str = "Right"; break;
00428     case vgui_CURSOR_DOWN:
00429       str = "Down"; break;
00430     case vgui_PAGE_UP:
00431       str = "PageUp"; break;
00432     case vgui_PAGE_DOWN:
00433       str = "PageDown"; break;
00434     case vgui_HOME:
00435       str = "Home"; break;
00436     case vgui_END:
00437       str = "End"; break;
00438     case vgui_DELETE:
00439       str = "Del"; break;
00440     case vgui_INSERT:
00441       str = "Ins"; break;
00442     default:
00443       str = ""; break;
00444   }
00445 
00446   return str;
00447 }
00448 
00449 UINT vgui_win32_utils::vgui_key_to_virt_key(vgui_key key)
00450 {
00451   UINT virt_key;
00452 
00453   switch ( key ) {
00454     // Function keys
00455     case vgui_F1:
00456     case vgui_F2:
00457     case vgui_F3:
00458     case vgui_F4:
00459     case vgui_F5:
00460     case vgui_F6:
00461     case vgui_F7:
00462     case vgui_F8:
00463     case vgui_F9:
00464     case vgui_F10:
00465     case vgui_F11:
00466     case vgui_F12:
00467       virt_key = VK_F1 + key - vgui_F1;
00468       break;
00469     case vgui_CURSOR_LEFT:
00470       virt_key = VK_LEFT; break;
00471     case vgui_CURSOR_UP:
00472       virt_key = VK_UP; break;
00473     case vgui_CURSOR_RIGHT:
00474       virt_key = VK_RIGHT; break;
00475     case vgui_CURSOR_DOWN:
00476       virt_key = VK_DOWN; break;
00477     case vgui_PAGE_UP:
00478       virt_key = VK_PRIOR; break;
00479     case vgui_PAGE_DOWN:
00480       virt_key = VK_NEXT; break;
00481     case vgui_HOME:
00482       virt_key = VK_HOME; break;
00483     case vgui_END:
00484       virt_key = VK_END; break;
00485     case vgui_DELETE:
00486       virt_key = VK_DELETE; break;
00487     case vgui_INSERT:
00488       virt_key = VK_INSERT; break;
00489     default: // undefined
00490       virt_key = 0x07; break;
00491   }
00492 
00493   return virt_key;
00494 }
00495 
00496 void vgui_win32_utils::ShowErrorMessage(DWORD dwErrorNo)
00497 {
00498    LPSTR lpBuffer;
00499    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER  |
00500                  FORMAT_MESSAGE_IGNORE_INSERTS  |
00501                  FORMAT_MESSAGE_FROM_SYSTEM,
00502                  NULL,
00503                  dwErrorNo,
00504                  LANG_NEUTRAL,
00505                  (LPTSTR) & lpBuffer,
00506                  0 ,
00507                  NULL);
00508   vcl_cerr << lpBuffer;
00509 }