Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/SDK/menu_manager.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "foobar2000-sdk-pch.h" | |
| 2 | |
| 3 #include "contextmenu_manager.h" | |
| 4 #include "menu_helpers.h" | |
| 5 #include "playlist.h" | |
| 6 | |
| 7 #ifdef WIN32 | |
| 8 | |
| 9 #include "ui_element_typable_window_manager.h" | |
| 10 | |
| 11 static void fix_ampersand(const char * src,pfc::string_base & out) | |
| 12 { | |
| 13 out.reset(); | |
| 14 unsigned ptr = 0; | |
| 15 while(src[ptr]) | |
| 16 { | |
| 17 if (src[ptr]=='&') | |
| 18 { | |
| 19 out.add_string("&&"); | |
| 20 ptr++; | |
| 21 while(src[ptr]=='&') | |
| 22 { | |
| 23 out.add_string("&&"); | |
| 24 ptr++; | |
| 25 } | |
| 26 } | |
| 27 else out.add_byte(src[ptr++]); | |
| 28 } | |
| 29 } | |
| 30 | |
| 31 static unsigned flags_to_win32(unsigned flags) | |
| 32 { | |
| 33 unsigned ret = 0; | |
| 34 if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) {/* dealt with elsewhere */} | |
| 35 else if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED; | |
| 36 if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED; | |
| 37 if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED; | |
| 38 return ret; | |
| 39 } | |
| 40 | |
| 41 void contextmenu_manager::win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id)//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped) | |
| 42 { | |
| 43 if (parent!=0 && parent->get_type()==contextmenu_item_node::TYPE_POPUP) | |
| 44 { | |
| 45 pfc::string8_fastalloc temp; | |
| 46 t_size child_idx,child_num = parent->get_num_children(); | |
| 47 for(child_idx=0;child_idx<child_num;child_idx++) | |
| 48 { | |
| 49 contextmenu_node * child = parent->get_child(child_idx); | |
| 50 if (child) | |
| 51 { | |
| 52 const char * name = child->get_name(); | |
| 53 if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;} | |
| 54 contextmenu_item_node::t_type type = child->get_type(); | |
| 55 if (type==contextmenu_item_node::TYPE_POPUP) | |
| 56 { | |
| 57 HMENU new_menu = CreatePopupMenu(); | |
| 58 uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name); | |
| 59 win32_build_menu(new_menu,child,base_id,max_id); | |
| 60 } | |
| 61 else if (type==contextmenu_item_node::TYPE_SEPARATOR) | |
| 62 { | |
| 63 uAppendMenu(menu,MF_SEPARATOR,0,0); | |
| 64 } | |
| 65 else if (type==contextmenu_item_node::TYPE_COMMAND) | |
| 66 { | |
| 67 int id = child->get_id(); | |
| 68 if (id>=0 && (max_id<0 || id<max_id)) | |
| 69 { | |
| 70 const unsigned flags = child->get_display_flags(); | |
| 71 const UINT ID = base_id+id; | |
| 72 uAppendMenu(menu,MF_STRING | flags_to_win32(flags),ID,name); | |
| 73 if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) CheckMenuRadioItem(menu,ID,ID,ID,MF_BYCOMMAND); | |
| 74 } | |
| 75 } | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 | |
| 82 bool contextmenu_manager::get_description_by_id(unsigned id,pfc::string_base & out) { | |
| 83 contextmenu_node * ptr = find_by_id(id); | |
| 84 if (ptr == NULL) return false; | |
| 85 return ptr->get_description(out); | |
| 86 } | |
| 87 | |
| 88 void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt) | |
| 89 { | |
| 90 enum {ID_CUSTOM_BASE = 1}; | |
| 91 | |
| 92 int cmd; | |
| 93 | |
| 94 { | |
| 95 POINT p; | |
| 96 if (pt) p = *pt; | |
| 97 else GetCursorPos(&p); | |
| 98 | |
| 99 HMENU hmenu = CreatePopupMenu(); | |
| 100 try { | |
| 101 | |
| 102 win32_build_menu(hmenu,ID_CUSTOM_BASE,-1); | |
| 103 menu_helpers::win32_auto_mnemonics(hmenu); | |
| 104 | |
| 105 cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0); | |
| 106 } catch(...) {DestroyMenu(hmenu); throw;} | |
| 107 | |
| 108 DestroyMenu(hmenu); | |
| 109 } | |
| 110 | |
| 111 if (cmd>0) | |
| 112 { | |
| 113 if (cmd>=ID_CUSTOM_BASE) | |
| 114 { | |
| 115 execute_by_id(cmd - ID_CUSTOM_BASE); | |
| 116 } | |
| 117 } | |
| 118 } | |
| 119 | |
| 120 void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data,const POINT * pt,unsigned flags) | |
| 121 { | |
| 122 service_ptr_t<contextmenu_manager> manager; | |
| 123 contextmenu_manager::g_create(manager); | |
| 124 manager->init_context(data,flags); | |
| 125 manager->win32_run_menu_popup(parent,pt); | |
| 126 } | |
| 127 | |
| 128 void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags) | |
| 129 { | |
| 130 service_ptr_t<contextmenu_manager> manager; | |
| 131 contextmenu_manager::g_create(manager); | |
| 132 manager->init_context_playlist(flags); | |
| 133 manager->win32_run_menu_popup(parent,pt); | |
| 134 } | |
| 135 | |
| 136 | |
| 137 namespace { | |
| 138 class mnemonic_manager | |
| 139 { | |
| 140 pfc::string8_fastalloc used; | |
| 141 bool is_used(unsigned c) | |
| 142 { | |
| 143 char temp[8]; | |
| 144 temp[pfc::utf8_encode_char(uCharLower(c),temp)]=0; | |
| 145 return !!strstr(used,temp); | |
| 146 } | |
| 147 | |
| 148 static bool is_alphanumeric(char c) | |
| 149 { | |
| 150 return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); | |
| 151 } | |
| 152 | |
| 153 | |
| 154 | |
| 155 | |
| 156 void insert(const char * src,unsigned idx,pfc::string_base & out) | |
| 157 { | |
| 158 out.reset(); | |
| 159 out.add_string(src,idx); | |
| 160 out.add_string("&"); | |
| 161 out.add_string(src+idx); | |
| 162 used.add_char(uCharLower(src[idx])); | |
| 163 } | |
| 164 public: | |
| 165 bool check_string(const char * src) | |
| 166 {//check for existing mnemonics | |
| 167 const char * ptr = src; | |
| 168 for( ;; ) { | |
| 169 ptr = strchr(ptr, '&'); | |
| 170 if (ptr == nullptr) break; | |
| 171 if (ptr[1]=='&') ptr+=2; | |
| 172 else | |
| 173 { | |
| 174 unsigned c = 0; | |
| 175 if (pfc::utf8_decode_char(ptr+1,c)>0) | |
| 176 { | |
| 177 if (!is_used(c)) used.add_char(uCharLower(c)); | |
| 178 } | |
| 179 return true; | |
| 180 } | |
| 181 } | |
| 182 return false; | |
| 183 } | |
| 184 bool process_string(const char * src,pfc::string_base & out)//returns if changed | |
| 185 { | |
| 186 if (check_string(src)) {out=src;return false;} | |
| 187 unsigned idx=0; | |
| 188 while(src[idx]==' ') idx++; | |
| 189 while(src[idx]) | |
| 190 { | |
| 191 if (is_alphanumeric(src[idx]) && !is_used(src[idx])) | |
| 192 { | |
| 193 insert(src,idx,out); | |
| 194 return true; | |
| 195 } | |
| 196 | |
| 197 while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++; | |
| 198 if (src[idx]=='\t') break; | |
| 199 while(src[idx]==' ') idx++; | |
| 200 } | |
| 201 | |
| 202 //no success picking first letter of one of words | |
| 203 idx=0; | |
| 204 while(src[idx]) | |
| 205 { | |
| 206 if (src[idx]=='\t') break; | |
| 207 if (is_alphanumeric(src[idx]) && !is_used(src[idx])) | |
| 208 { | |
| 209 insert(src,idx,out); | |
| 210 return true; | |
| 211 } | |
| 212 idx++; | |
| 213 } | |
| 214 | |
| 215 //giving up | |
| 216 out = src; | |
| 217 return false; | |
| 218 } | |
| 219 }; | |
| 220 } | |
| 221 | |
| 222 void menu_helpers::win32_auto_mnemonics(HMENU menu) | |
| 223 { | |
| 224 PFC_ASSERT(IsMenu(menu)); | |
| 225 mnemonic_manager mgr; | |
| 226 int n, m = GetMenuItemCount(menu); | |
| 227 PFC_ASSERT(m >= 0); | |
| 228 pfc::string8_fastalloc temp,temp2; | |
| 229 for(n=0;n<m;n++)//first pass, check existing mnemonics | |
| 230 { | |
| 231 unsigned type = uGetMenuItemType(menu,n); | |
| 232 if (type==MFT_STRING) | |
| 233 { | |
| 234 uGetMenuString(menu,n,temp,MF_BYPOSITION); | |
| 235 mgr.check_string(temp); | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 for(n=0;n<m;n++) | |
| 240 { | |
| 241 HMENU submenu = GetSubMenu(menu,n); | |
| 242 if (submenu) win32_auto_mnemonics(submenu); | |
| 243 | |
| 244 { | |
| 245 unsigned type = uGetMenuItemType(menu,n); | |
| 246 if (type==MFT_STRING) | |
| 247 { | |
| 248 unsigned state = submenu ? 0 : GetMenuState(menu,n,MF_BYPOSITION); | |
| 249 unsigned id = GetMenuItemID(menu,n); | |
| 250 uGetMenuString(menu,n,temp,MF_BYPOSITION); | |
| 251 if (mgr.process_string(temp,temp2)) | |
| 252 { | |
| 253 uModifyMenu(menu,n,MF_BYPOSITION|MF_STRING|state,id,temp2); | |
| 254 } | |
| 255 } | |
| 256 } | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 | |
| 261 static bool test_key(unsigned k) | |
| 262 { | |
| 263 return (GetKeyState(k) & 0x8000) ? true : false; | |
| 264 } | |
| 265 | |
| 266 #define F_SHIFT (HOTKEYF_SHIFT<<8) | |
| 267 #define F_CTRL (HOTKEYF_CONTROL<<8) | |
| 268 #define F_ALT (HOTKEYF_ALT<<8) | |
| 269 #define F_WIN (HOTKEYF_EXT<<8) | |
| 270 | |
| 271 static t_uint32 get_key_code(WPARAM wp) { | |
| 272 t_uint32 code = (t_uint32)(wp & 0xFF); | |
| 273 if (test_key(VK_CONTROL)) code|=F_CTRL; | |
| 274 if (test_key(VK_SHIFT)) code|=F_SHIFT; | |
| 275 if (test_key(VK_MENU)) code|=F_ALT; | |
| 276 if (test_key(VK_LWIN) || test_key(VK_RWIN)) code|=F_WIN; | |
| 277 return code; | |
| 278 } | |
| 279 | |
| 280 static t_uint32 get_key_code(WPARAM wp, t_uint32 mods) { | |
| 281 t_uint32 code = (t_uint32)(wp & 0xFF); | |
| 282 if (mods & MOD_CONTROL) code|=F_CTRL; | |
| 283 if (mods & MOD_SHIFT) code|=F_SHIFT; | |
| 284 if (mods & MOD_ALT) code|=F_ALT; | |
| 285 if (mods & MOD_WIN) code|=F_WIN; | |
| 286 return code; | |
| 287 } | |
| 288 | |
| 289 bool keyboard_shortcut_manager::on_keydown(shortcut_type type,WPARAM wp) | |
| 290 { | |
| 291 if (type==TYPE_CONTEXT) return false; | |
| 292 metadb_handle_list dummy; | |
| 293 return process_keydown(type,dummy,get_key_code(wp)); | |
| 294 } | |
| 295 | |
| 296 bool keyboard_shortcut_manager::on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) | |
| 297 { | |
| 298 if (data.get_count()==0) return false; | |
| 299 return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller); | |
| 300 } | |
| 301 | |
| 302 bool keyboard_shortcut_manager::on_keydown_auto(WPARAM wp) | |
| 303 { | |
| 304 if (on_keydown(TYPE_MAIN,wp)) return true; | |
| 305 if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true; | |
| 306 if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true; | |
| 307 return false; | |
| 308 } | |
| 309 | |
| 310 bool keyboard_shortcut_manager::on_keydown_auto_playlist(WPARAM wp) | |
| 311 { | |
| 312 metadb_handle_list data; | |
| 313 playlist_manager::get()->activeplaylist_get_selected_items(data); | |
| 314 return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist); | |
| 315 } | |
| 316 | |
| 317 bool keyboard_shortcut_manager::on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) | |
| 318 { | |
| 319 if (on_keydown_context(data,wp,caller)) return true; | |
| 320 else return on_keydown_auto(wp); | |
| 321 } | |
| 322 | |
| 323 static bool should_relay_key_restricted(WPARAM p_key) { | |
| 324 switch(p_key) { | |
| 325 case VK_LEFT: | |
| 326 case VK_RIGHT: | |
| 327 case VK_UP: | |
| 328 case VK_DOWN: | |
| 329 return false; | |
| 330 default: | |
| 331 return (p_key >= VK_F1 && p_key <= VK_F24) || IsKeyPressed(VK_CONTROL) || IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN); | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 bool keyboard_shortcut_manager::on_keydown_restricted_auto(WPARAM wp) { | |
| 336 if (!should_relay_key_restricted(wp)) return false; | |
| 337 return on_keydown_auto(wp); | |
| 338 } | |
| 339 bool keyboard_shortcut_manager::on_keydown_restricted_auto_playlist(WPARAM wp) { | |
| 340 if (!should_relay_key_restricted(wp)) return false; | |
| 341 return on_keydown_auto_playlist(wp); | |
| 342 } | |
| 343 bool keyboard_shortcut_manager::on_keydown_restricted_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) { | |
| 344 if (!should_relay_key_restricted(wp)) return false; | |
| 345 return on_keydown_auto_context(data,wp,caller); | |
| 346 } | |
| 347 | |
| 348 static bool filterTypableWindowMessage(const MSG * msg, t_uint32 modifiers) { | |
| 349 if (keyboard_shortcut_manager::is_typing_key_combo((t_uint32)msg->wParam, modifiers)) { | |
| 350 const DWORD mask = DLGC_HASSETSEL | DLGC_WANTCHARS | DLGC_WANTARROWS; | |
| 351 auto status = ::SendMessage(msg->hwnd, WM_GETDLGCODE,0, 0); | |
| 352 if ( (status & mask) == mask ) return false; | |
| 353 | |
| 354 ui_element_typable_window_manager::ptr api; | |
| 355 if (ui_element_typable_window_manager::tryGet(api)) { | |
| 356 if (api->is_registered(msg->hwnd)) return false; | |
| 357 } | |
| 358 } | |
| 359 return true; | |
| 360 } | |
| 361 | |
| 362 bool keyboard_shortcut_manager_v2::pretranslate_message(const MSG * msg, HWND thisPopupWnd) { | |
| 363 switch(msg->message) { | |
| 364 case WM_KEYDOWN: | |
| 365 case WM_SYSKEYDOWN: | |
| 366 if (thisPopupWnd != NULL && FindOwningPopup(msg->hwnd) == thisPopupWnd) { | |
| 367 const t_uint32 modifiers = GetHotkeyModifierFlags(); | |
| 368 if (filterTypableWindowMessage(msg, modifiers)) { | |
| 369 if (process_keydown_simple(get_key_code(msg->wParam,modifiers))) return true; | |
| 370 } | |
| 371 } | |
| 372 return false; | |
| 373 default: | |
| 374 return false; | |
| 375 } | |
| 376 } | |
| 377 | |
| 378 bool keyboard_shortcut_manager::is_text_key(t_uint32 vkCode) { | |
| 379 return vkCode == VK_SPACE | |
| 380 || (vkCode >= '0' && vkCode < 0x40) | |
| 381 || (vkCode > 0x40 && vkCode < VK_LWIN) | |
| 382 || (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE) | |
| 383 || (vkCode >= VK_OEM_1 && vkCode <= VK_OEM_3) | |
| 384 || (vkCode >= VK_OEM_4 && vkCode <= VK_OEM_8) | |
| 385 ; | |
| 386 } | |
| 387 | |
| 388 bool keyboard_shortcut_manager::is_typing_key(t_uint32 vkCode) { | |
| 389 return is_text_key(vkCode) | |
| 390 || vkCode == VK_BACK | |
| 391 || vkCode == VK_RETURN | |
| 392 || vkCode == VK_INSERT | |
| 393 || (vkCode > VK_SPACE && vkCode < '0'); | |
| 394 } | |
| 395 | |
| 396 bool keyboard_shortcut_manager::is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers) { | |
| 397 if (!is_typing_modifier(modifiers)) return false; | |
| 398 return is_typing_key(vkCode); | |
| 399 } | |
| 400 | |
| 401 bool keyboard_shortcut_manager::is_typing_modifier(t_uint32 flags) { | |
| 402 flags &= ~MOD_SHIFT; | |
| 403 return flags == 0 || flags == (MOD_ALT | MOD_CONTROL); | |
| 404 } | |
| 405 | |
| 406 bool keyboard_shortcut_manager::is_typing_message(HWND editbox, const MSG * msg) { | |
| 407 if (msg->hwnd != editbox) return false; | |
| 408 return is_typing_message(msg); | |
| 409 } | |
| 410 bool keyboard_shortcut_manager::is_typing_message(const MSG * msg) { | |
| 411 if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN) return false; | |
| 412 return is_typing_key_combo((uint32_t)msg->wParam, GetHotkeyModifierFlags()); | |
| 413 } | |
| 414 | |
| 415 #endif // _WIN32 | |
| 416 | |
| 417 bool contextmenu_manager::execute_by_id(unsigned id) noexcept { | |
| 418 contextmenu_node * ptr = find_by_id(id); | |
| 419 if (ptr == NULL) return false; | |
| 420 ptr->execute(); | |
| 421 return true; | |
| 422 } | |
| 423 | |
| 424 void contextmenu_manager::g_create(service_ptr_t<contextmenu_manager>& p_out) { standard_api_create_t(p_out); } | |
| 425 service_ptr_t<contextmenu_manager> contextmenu_manager::g_create() { service_ptr_t<contextmenu_manager> ret; standard_api_create_t(ret); return ret; } |
