1 // This is core/vgui/impl/win32/vgui_win32_utils.cxx
2
3 #include <iostream>
4 #include <cstddef>
5 #include "vgui_win32_utils.h"
6 #include <cassert>
7 #ifdef _MSC_VER
8 # include "vcl_msvc_warnings.h"
9 #endif
10
11 // (Create if necessary and) return singleton instance of this class.
12 vgui_win32_utils *
instance()13 vgui_win32_utils::instance()
14 {
15 static vgui_win32_utils instance_;
16 return &instance_;
17 }
18
19
20 // Convert a vgui_menu to equivalent MENUTEMPLATE structure used by
21 // Win32 function LoadMenuIndirect.
22 HMENU
vgui_menu_to_win32(vgui_menu const & vguimenu,std::vector<vgui_command_sptr> & callbacks_,HACCEL * hAccel,bool isPopup)23 vgui_win32_utils::vgui_menu_to_win32(vgui_menu const & vguimenu,
24 std::vector<vgui_command_sptr> & callbacks_,
25 HACCEL * hAccel,
26 bool isPopup)
27 {
28 HMENU hMenu;
29 MENUITEMTEMPLATEHEADER * pMenuHeader;
30 MENUITEMTEMPLATE * pMenuItem;
31
32 // Note that the MENUITEMTEMPLATE structures are variable in length,
33 // Therefore we allocate capacity*sizeof(char) initially.
34 menu_capacity = 1024; // in unit of unsigned char
35 item_count = 0;
36 callbacks.clear();
37
38 pAccel = 0;
39 accel_capacity = 16;
40 accel_count = 0;
41
42 pMenu = (unsigned char *)malloc(sizeof(MENUITEMTEMPLATEHEADER) + menu_capacity);
43 if (pMenu == NULL)
44 {
45 std::cerr << "Memory allocation error\n";
46 return NULL;
47 }
48
49 pAccel = (ACCEL *)malloc(sizeof(ACCEL) * accel_capacity);
50 if (pAccel == NULL)
51 std::cerr << "Memory allocation error\n";
52
53 // Fill up the MENUITEMTEMPLATEHEADER structure.
54 pMenuHeader = (MENUITEMTEMPLATEHEADER *)pMenu;
55 pMenuHeader->versionNumber = 0; // This member must be zero.
56 pMenuHeader->offset = 0; // The menu item list follows immediately after the header.
57 int offset = sizeof(MENUITEMTEMPLATEHEADER);
58
59 if (isPopup)
60 {
61 // Add a dumb header so that TrackPopMenu shows the popup menu properly.
62 pMenuItem = (MENUITEMTEMPLATE *)(pMenu + offset);
63 pMenuItem->mtOption = MF_END | MF_POPUP;
64 offset += sizeof(pMenuItem->mtOption);
65 pMenuItem->mtID = 0;
66 offset += sizeof(WCHAR);
67 }
68
69 addMenuItems(vguimenu, offset, isPopup);
70
71 hMenu = LoadMenuIndirect(pMenu);
72 if (!hMenu)
73 ShowErrorMessage(GetLastError());
74
75 if (!isPopup) // create an accelerator table for non-popup menu.
76 *hAccel = CreateAcceleratorTable(pAccel, accel_count);
77
78 free(pMenu);
79 free(pAccel);
80
81 callbacks_ = callbacks;
82 return hMenu;
83 }
84
85
86 int
addMenuItems(vgui_menu const & vguimenu,int offset_in,bool is_popup)87 vgui_win32_utils::addMenuItems(vgui_menu const & vguimenu, int offset_in, bool is_popup)
88 {
89 static unsigned int max_menuitem_size = 256;
90
91 MENUITEMTEMPLATE * pMenuItem;
92 WCHAR * pMenuItemText;
93 std::string menuItemText;
94 int stride, offset; // in unit of unsigned char
95
96 // Loop over all menu items and convert them into MENUITEMTEMPLATE
97 // Note that there are four types of menu item in vgui_menu:
98 // command, submenu, toggle button, and separator.
99 offset = offset_in;
100 for (unsigned int i = 0; i < vguimenu.size(); ++i)
101 {
102 pMenuItem = (MENUITEMTEMPLATE *)(pMenu + offset);
103
104 // MENUITEMTEMPLATE does not have separator.
105 if (vguimenu[i].is_separator() || vguimenu[i].is_toggle_button())
106 continue;
107
108 stride = 0;
109 pMenuItem->mtOption = 0;
110 stride += sizeof(pMenuItem->mtOption);
111 if (i == vguimenu.size() - 1)
112 pMenuItem->mtOption |= MF_END; // indiate this is the last menu item
113
114 if (vguimenu[i].is_command())
115 {
116 int menuItemID = is_popup ? POPUPMENU_ID_START + item_count++ : MENU_ID_START + item_count++;
117 pMenuItem->mtID = menuItemID;
118 stride += sizeof(pMenuItem->mtID);
119
120 menuItemText = vguimenu[i].name;
121 addAccelerator(menuItemText, vguimenu[i], menuItemID);
122
123 // Copy the menu item text.
124 std::size_t j;
125 pMenuItemText = pMenuItem->mtString;
126 for (j = 0; j < menuItemText.size(); j++)
127 *(pMenuItemText + j) = (WCHAR)menuItemText.c_str()[j];
128 *(pMenuItemText + j) = 0;
129 stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
130
131 // Add the associated callback function pointer to the callback list.
132 callbacks.push_back(vguimenu[i].cmnd);
133 }
134 else if (vguimenu[i].is_submenu())
135 {
136 pMenuItem->mtOption |= MF_POPUP;
137
138 menuItemText = vguimenu[i].name;
139 std::size_t j;
140 // Note that the MENUITEMTEMPLATE structure for an item that opens a
141 // drop-down menu or submenu does not contain the mtID member.
142 pMenuItemText = (WCHAR *)&pMenuItem->mtID;
143 for (j = 0; j < menuItemText.size(); j++)
144 *(pMenuItemText + j) = menuItemText.c_str()[j];
145 *(pMenuItemText + j) = 0;
146 stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
147
148 // Call itself recursively for submenu
149 stride += addMenuItems(*vguimenu[i].menu, offset + stride, is_popup);
150 }
151
152 offset += stride;
153 // Check for illegal buffer access. We'are too late at this point.
154 // Increase maximum size of a menu item "max_menuitem_size" (default 256)
155 // to avoid this error.
156 assert(offset < menu_capacity);
157
158 // Deal with overflow.
159 if (offset > menu_capacity - (int)max_menuitem_size)
160 {
161 menu_capacity <<= 1; // double the capacity.
162 pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUITEMTEMPLATEHEADER) + menu_capacity);
163 if (pMenu == NULL)
164 {
165 std::cerr << "Memory allocation error\n";
166 return 0;
167 }
168 }
169 }
170
171 return offset - offset_in;
172 }
173
174 // Convert a vgui_menu to equivalent extended MENUTEMPLATE structure used by
175 // Win32 function LoadMenuIndirect.
176 HMENU
vgui_menu_to_win32ex(vgui_menu const & vguimenu,std::vector<vgui_command_sptr> & callbacks_,HACCEL * hAccel,bool isPopup)177 vgui_win32_utils::vgui_menu_to_win32ex(vgui_menu const & vguimenu,
178 std::vector<vgui_command_sptr> & callbacks_,
179 HACCEL * hAccel,
180 bool isPopup)
181 {
182 HMENU hMenu;
183 MENUEX_TEMPLATE_HEADER * pMenuHeader;
184 MENUEX_TEMPLATE_ITEM * pMenuItem;
185
186 // Note that the MENUEX_TEMPLATE_ITEM structures are variable in length,
187 // but are aligned on DWORD boundaries. Therefore we allocate
188 // capacity*sizeof(WORD) initially.
189 menu_capacity = 1024;
190 item_count = 0;
191 callbacks.clear();
192
193 pAccel = 0;
194 accel_capacity = 16;
195 accel_count = 0;
196
197 pMenu = (unsigned char *)malloc(sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
198 if (pMenu == NULL)
199 {
200 std::cerr << "Memory allocation error\n";
201 return NULL;
202 }
203
204 pAccel = (ACCEL *)malloc(sizeof(ACCEL) * accel_capacity);
205 if (pAccel == NULL)
206 std::cerr << "Memory allocation error\n";
207
208 // Fill up the MENUEX_TEMPLATE_HEADER structure.
209 pMenuHeader = (MENUEX_TEMPLATE_HEADER *)pMenu;
210 pMenuHeader->wVersion = 1; // This member must be 1.
211 pMenuHeader->wOffset = 4; // The menu item list follows immediately after the header.
212 pMenuHeader->dwHelpId = 0;
213 int offset = sizeof(MENUEX_TEMPLATE_HEADER);
214
215 if (isPopup)
216 {
217 // Add a dumb header so that TrackPopMenu shows the popup menu properly.
218 pMenuItem = (MENUEX_TEMPLATE_ITEM *)(pMenu + offset);
219 pMenuItem->dwType = MFT_STRING;
220 offset += sizeof(pMenuItem->dwType);
221 pMenuItem->dwState = MFS_ENABLED;
222 offset += sizeof(pMenuItem->dwState);
223 pMenuItem->menuId = 0;
224 offset += sizeof(pMenuItem->menuId);
225 pMenuItem->bResInfo = 0x01 | 0x80;
226 offset += sizeof(pMenuItem->bResInfo);
227 pMenuItem->szText = 0;
228 offset += sizeof(pMenuItem->szText);
229 pMenuItem->dwHelpId = 0;
230 offset += sizeof(pMenuItem->dwHelpId);
231 }
232
233 addMenuItemsEx(vguimenu, offset, isPopup);
234
235 hMenu = LoadMenuIndirect(pMenu);
236 if (!hMenu)
237 ShowErrorMessage(GetLastError());
238
239 if (!isPopup) // create an accelerator table for non-popup menu.
240 *hAccel = CreateAcceleratorTable(pAccel, accel_count);
241
242 free(pMenu);
243 free(pAccel);
244
245 callbacks_ = callbacks;
246 return hMenu;
247 }
248
249 int
addMenuItemsEx(vgui_menu const & vguimenu,int offset_in,bool is_popup)250 vgui_win32_utils::addMenuItemsEx(vgui_menu const & vguimenu, int offset_in, bool is_popup)
251 {
252 static unsigned int max_menuitem_size = 256;
253
254 MENUEX_TEMPLATE_ITEM * pMenuItem;
255 WCHAR * pMenuItemText;
256 std::string menuItemText;
257 int stride, offset;
258 bool last_item;
259
260 // Loop over all menu items and convert them into MENUTEMPLATE
261 // Note that there are four types of menu item in vgui_menu:
262 // command, submenu, toggle button, and separator.
263 offset = offset_in;
264 for (unsigned int i = 0; i < vguimenu.size(); ++i)
265 {
266 pMenuItem = (MENUEX_TEMPLATE_ITEM *)(pMenu + offset);
267 stride = 0;
268
269 // indiate this is the last menu item
270 last_item = (i == vguimenu.size() - 1) ? true : false;
271
272 pMenuItem->dwType = MFT_STRING;
273 stride += sizeof(pMenuItem->dwType);
274 pMenuItem->dwState = MFS_ENABLED;
275 stride += sizeof(pMenuItem->dwState);
276 pMenuItem->menuId = 0;
277 stride += sizeof(pMenuItem->menuId);
278
279 if (vguimenu[i].is_separator())
280 {
281 pMenuItem->dwType = MFT_SEPARATOR;
282 pMenuItem->bResInfo = last_item ? 0x80 : 0;
283 stride += sizeof(pMenuItem->bResInfo);
284 pMenuItem->szText = 0;
285 stride += sizeof(pMenuItem->szText);
286 if (stride % 4)
287 { // aligned on DWORD boundary.
288 stride += 4;
289 stride &= ~3;
290 }
291 }
292 else if (vguimenu[i].is_command())
293 {
294 int menuItemID = is_popup ? POPUPMENU_ID_START + item_count++ : MENU_ID_START + item_count++;
295 pMenuItem->menuId = menuItemID;
296
297 pMenuItem->bResInfo = last_item ? 0x80 : 0;
298 stride += sizeof(pMenuItem->bResInfo);
299
300 menuItemText = vguimenu[i].name;
301 addAccelerator(menuItemText, vguimenu[i], menuItemID);
302
303 // Copy the menu item text.
304 pMenuItemText = (WCHAR *)&pMenuItem->szText;
305 std::size_t j;
306 for (j = 0; j < menuItemText.size(); j++)
307 *(pMenuItemText + j) = menuItemText.c_str()[j];
308 *(pMenuItemText + j) = 0;
309
310 stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
311
312 if (stride % 4)
313 { // aligned on DWORD boundary.
314 stride += 4;
315 stride &= ~3;
316 }
317
318 // Add the associated callback function pointer to the callback list.
319 callbacks.push_back(vguimenu[i].cmnd);
320 }
321 else if (vguimenu[i].is_submenu())
322 {
323 pMenuItem->bResInfo = last_item ? 0x80 | 0x01 : 0x01;
324 stride += sizeof(pMenuItem->bResInfo);
325
326 menuItemText = vguimenu[i].name;
327 pMenuItemText = (WCHAR *)&pMenuItem->szText;
328 std::size_t j;
329 for (j = 0; j < menuItemText.size(); j++)
330 *(pMenuItemText + j) = menuItemText.c_str()[j];
331 *(pMenuItemText + j) = 0;
332 stride += sizeof(WCHAR) * (wcslen(pMenuItemText) + 1);
333
334 if (stride % 4)
335 { // aligned on DWORD boundary.
336 stride += 4;
337 stride &= ~3;
338 }
339
340 DWORD * dwHelpId = (DWORD *)(pMenu + offset + stride);
341 *dwHelpId = 0;
342 stride += sizeof(pMenuItem->dwHelpId);
343
344 // Call itself recursively for submenu
345 stride += addMenuItemsEx(*vguimenu[i].menu, offset + stride, is_popup);
346 }
347 else if (vguimenu[i].is_toggle_button())
348 {
349 std::cerr << "vgui_win32_utils: toggle button is not converted\n";
350 }
351
352 offset += stride;
353 // Check for illegal buffer access. We'are too late at this point.
354 // Increase maximum size of a menu item "max_menuitem_size" (default 256)
355 // to avoid this error.
356 assert(offset < menu_capacity);
357
358 // Deal with buffer overflow.
359 if (offset > menu_capacity - (int)max_menuitem_size)
360 {
361 menu_capacity <<= 1; // double the capacity.
362 pMenu = (unsigned char *)realloc(pMenu, sizeof(MENUEX_TEMPLATE_HEADER) + menu_capacity);
363 if (pMenu == NULL)
364 {
365 std::cerr << "Memory allocation error\n";
366 return NULL;
367 }
368 }
369 }
370
371 return offset - offset_in;
372 }
373
374 inline void
addAccelerator(std::string & menuItemText,vgui_menu_item const & vguimenu,int menuItemId)375 vgui_win32_utils::addAccelerator(std::string & menuItemText, vgui_menu_item const & vguimenu, int menuItemId)
376 {
377 ACCEL * pa = pAccel + accel_count;
378 pa->cmd = menuItemId;
379 // Give a virtual key, even if there is no modifier.
380 pa->fVirt = FVIRTKEY;
381
382 if (vguimenu.short_cut.mod != vgui_MODIFIER_NULL || vguimenu.short_cut.key != vgui_KEY_NULL)
383 menuItemText += "\t"; // A tab key is required.
384
385 if (vguimenu.short_cut.mod & vgui_CTRL)
386 {
387 menuItemText += "Ctrl+";
388 pa->fVirt |= FCONTROL;
389 }
390 if (vguimenu.short_cut.mod & vgui_SHIFT)
391 {
392 menuItemText += "Shift+";
393 pa->fVirt |= FSHIFT;
394 }
395 if (vguimenu.short_cut.mod & vgui_ALT)
396 {
397 menuItemText += "Alt+";
398 pa->fVirt |= FALT;
399 }
400
401 vgui_key key = vguimenu.short_cut.key;
402 if (key != vgui_KEY_NULL)
403 {
404 if (key >= 'A' && key <= 'Z' || key >= 'a' && key <= 'z')
405 {
406 if (key >= 'a' && key <= 'z')
407 key = vgui_key(key + 'A' - 'a');
408 menuItemText += key;
409 pa->key = key;
410 }
411 else
412 {
413 menuItemText += vgui_key_to_string(key);
414 pa->key = vgui_key_to_virt_key(key);
415 }
416 // Deal with the case of buffer overflow.
417 if (++accel_count >= accel_capacity)
418 {
419 accel_capacity <<= 1; // double the capacity.
420 pAccel = (ACCEL *)realloc(pAccel, sizeof(ACCEL) * accel_capacity);
421 if (pAccel == NULL)
422 std::cerr << "Memory allocation error\n";
423 }
424 }
425 }
426
427
428 inline std::string
vgui_key_to_string(vgui_key key)429 vgui_win32_utils::vgui_key_to_string(vgui_key key)
430 {
431 std::string str;
432
433 switch (key)
434 {
435 // Function keys
436 case vgui_F1:
437 str = "F1";
438 break;
439 case vgui_F2:
440 str = "F2";
441 break;
442 case vgui_F3:
443 str = "F3";
444 break;
445 case vgui_F4:
446 str = "F4";
447 break;
448 case vgui_F5:
449 str = "F5";
450 break;
451 case vgui_F6:
452 str = "F6";
453 break;
454 case vgui_F7:
455 str = "F7";
456 break;
457 case vgui_F8:
458 str = "F8";
459 break;
460 case vgui_F9:
461 str = "F9";
462 break;
463 case vgui_F10:
464 str = "F10";
465 break;
466 case vgui_F11:
467 str = "F11";
468 break;
469 case vgui_F12:
470 str = "F12";
471 break;
472 case vgui_CURSOR_LEFT:
473 str = "Left";
474 break;
475 case vgui_CURSOR_UP:
476 str = "Up";
477 break;
478 case vgui_CURSOR_RIGHT:
479 str = "Right";
480 break;
481 case vgui_CURSOR_DOWN:
482 str = "Down";
483 break;
484 case vgui_PAGE_UP:
485 str = "PageUp";
486 break;
487 case vgui_PAGE_DOWN:
488 str = "PageDown";
489 break;
490 case vgui_HOME:
491 str = "Home";
492 break;
493 case vgui_END:
494 str = "End";
495 break;
496 case vgui_DELETE:
497 str = "Del";
498 break;
499 case vgui_INSERT:
500 str = "Ins";
501 break;
502 default:
503 str = "";
504 break;
505 }
506
507 return str;
508 }
509
510 UINT
vgui_key_to_virt_key(vgui_key key)511 vgui_win32_utils::vgui_key_to_virt_key(vgui_key key)
512 {
513 UINT virt_key;
514
515 switch (key)
516 {
517 // Function keys
518 case vgui_F1:
519 case vgui_F2:
520 case vgui_F3:
521 case vgui_F4:
522 case vgui_F5:
523 case vgui_F6:
524 case vgui_F7:
525 case vgui_F8:
526 case vgui_F9:
527 case vgui_F10:
528 case vgui_F11:
529 case vgui_F12:
530 virt_key = VK_F1 + key - vgui_F1;
531 break;
532 case vgui_CURSOR_LEFT:
533 virt_key = VK_LEFT;
534 break;
535 case vgui_CURSOR_UP:
536 virt_key = VK_UP;
537 break;
538 case vgui_CURSOR_RIGHT:
539 virt_key = VK_RIGHT;
540 break;
541 case vgui_CURSOR_DOWN:
542 virt_key = VK_DOWN;
543 break;
544 case vgui_PAGE_UP:
545 virt_key = VK_PRIOR;
546 break;
547 case vgui_PAGE_DOWN:
548 virt_key = VK_NEXT;
549 break;
550 case vgui_HOME:
551 virt_key = VK_HOME;
552 break;
553 case vgui_END:
554 virt_key = VK_END;
555 break;
556 case vgui_DELETE:
557 virt_key = VK_DELETE;
558 break;
559 case vgui_INSERT:
560 virt_key = VK_INSERT;
561 break;
562 default: // undefined
563 virt_key = 0x07;
564 break;
565 }
566
567 return virt_key;
568 }
569
570 void
ShowErrorMessage(DWORD dwErrorNo)571 vgui_win32_utils::ShowErrorMessage(DWORD dwErrorNo)
572 {
573 LPSTR lpBuffer;
574 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM,
575 NULL,
576 dwErrorNo,
577 LANG_NEUTRAL,
578 (LPTSTR)&lpBuffer,
579 0,
580 NULL);
581 std::cerr << lpBuffer;
582 }
583