Mercurial > foo_out_sdl
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/SDK/menu_manager.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,425 @@ +#include "foobar2000-sdk-pch.h" + +#include "contextmenu_manager.h" +#include "menu_helpers.h" +#include "playlist.h" + +#ifdef WIN32 + +#include "ui_element_typable_window_manager.h" + +static void fix_ampersand(const char * src,pfc::string_base & out) +{ + out.reset(); + unsigned ptr = 0; + while(src[ptr]) + { + if (src[ptr]=='&') + { + out.add_string("&&"); + ptr++; + while(src[ptr]=='&') + { + out.add_string("&&"); + ptr++; + } + } + else out.add_byte(src[ptr++]); + } +} + +static unsigned flags_to_win32(unsigned flags) +{ + unsigned ret = 0; + if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) {/* dealt with elsewhere */} + else if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED; + if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED; + if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED; + return ret; +} + +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) +{ + if (parent!=0 && parent->get_type()==contextmenu_item_node::TYPE_POPUP) + { + pfc::string8_fastalloc temp; + t_size child_idx,child_num = parent->get_num_children(); + for(child_idx=0;child_idx<child_num;child_idx++) + { + contextmenu_node * child = parent->get_child(child_idx); + if (child) + { + const char * name = child->get_name(); + if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;} + contextmenu_item_node::t_type type = child->get_type(); + if (type==contextmenu_item_node::TYPE_POPUP) + { + HMENU new_menu = CreatePopupMenu(); + uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name); + win32_build_menu(new_menu,child,base_id,max_id); + } + else if (type==contextmenu_item_node::TYPE_SEPARATOR) + { + uAppendMenu(menu,MF_SEPARATOR,0,0); + } + else if (type==contextmenu_item_node::TYPE_COMMAND) + { + int id = child->get_id(); + if (id>=0 && (max_id<0 || id<max_id)) + { + const unsigned flags = child->get_display_flags(); + const UINT ID = base_id+id; + uAppendMenu(menu,MF_STRING | flags_to_win32(flags),ID,name); + if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) CheckMenuRadioItem(menu,ID,ID,ID,MF_BYCOMMAND); + } + } + } + } + } +} + + +bool contextmenu_manager::get_description_by_id(unsigned id,pfc::string_base & out) { + contextmenu_node * ptr = find_by_id(id); + if (ptr == NULL) return false; + return ptr->get_description(out); +} + +void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt) +{ + enum {ID_CUSTOM_BASE = 1}; + + int cmd; + + { + POINT p; + if (pt) p = *pt; + else GetCursorPos(&p); + + HMENU hmenu = CreatePopupMenu(); + try { + + win32_build_menu(hmenu,ID_CUSTOM_BASE,-1); + menu_helpers::win32_auto_mnemonics(hmenu); + + cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0); + } catch(...) {DestroyMenu(hmenu); throw;} + + DestroyMenu(hmenu); + } + + if (cmd>0) + { + if (cmd>=ID_CUSTOM_BASE) + { + execute_by_id(cmd - ID_CUSTOM_BASE); + } + } +} + +void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data,const POINT * pt,unsigned flags) +{ + service_ptr_t<contextmenu_manager> manager; + contextmenu_manager::g_create(manager); + manager->init_context(data,flags); + manager->win32_run_menu_popup(parent,pt); +} + +void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags) +{ + service_ptr_t<contextmenu_manager> manager; + contextmenu_manager::g_create(manager); + manager->init_context_playlist(flags); + manager->win32_run_menu_popup(parent,pt); +} + + +namespace { + class mnemonic_manager + { + pfc::string8_fastalloc used; + bool is_used(unsigned c) + { + char temp[8]; + temp[pfc::utf8_encode_char(uCharLower(c),temp)]=0; + return !!strstr(used,temp); + } + + static bool is_alphanumeric(char c) + { + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); + } + + + + + void insert(const char * src,unsigned idx,pfc::string_base & out) + { + out.reset(); + out.add_string(src,idx); + out.add_string("&"); + out.add_string(src+idx); + used.add_char(uCharLower(src[idx])); + } + public: + bool check_string(const char * src) + {//check for existing mnemonics + const char * ptr = src; + for( ;; ) { + ptr = strchr(ptr, '&'); + if (ptr == nullptr) break; + if (ptr[1]=='&') ptr+=2; + else + { + unsigned c = 0; + if (pfc::utf8_decode_char(ptr+1,c)>0) + { + if (!is_used(c)) used.add_char(uCharLower(c)); + } + return true; + } + } + return false; + } + bool process_string(const char * src,pfc::string_base & out)//returns if changed + { + if (check_string(src)) {out=src;return false;} + unsigned idx=0; + while(src[idx]==' ') idx++; + while(src[idx]) + { + if (is_alphanumeric(src[idx]) && !is_used(src[idx])) + { + insert(src,idx,out); + return true; + } + + while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++; + if (src[idx]=='\t') break; + while(src[idx]==' ') idx++; + } + + //no success picking first letter of one of words + idx=0; + while(src[idx]) + { + if (src[idx]=='\t') break; + if (is_alphanumeric(src[idx]) && !is_used(src[idx])) + { + insert(src,idx,out); + return true; + } + idx++; + } + + //giving up + out = src; + return false; + } + }; +} + +void menu_helpers::win32_auto_mnemonics(HMENU menu) +{ + PFC_ASSERT(IsMenu(menu)); + mnemonic_manager mgr; + int n, m = GetMenuItemCount(menu); + PFC_ASSERT(m >= 0); + pfc::string8_fastalloc temp,temp2; + for(n=0;n<m;n++)//first pass, check existing mnemonics + { + unsigned type = uGetMenuItemType(menu,n); + if (type==MFT_STRING) + { + uGetMenuString(menu,n,temp,MF_BYPOSITION); + mgr.check_string(temp); + } + } + + for(n=0;n<m;n++) + { + HMENU submenu = GetSubMenu(menu,n); + if (submenu) win32_auto_mnemonics(submenu); + + { + unsigned type = uGetMenuItemType(menu,n); + if (type==MFT_STRING) + { + unsigned state = submenu ? 0 : GetMenuState(menu,n,MF_BYPOSITION); + unsigned id = GetMenuItemID(menu,n); + uGetMenuString(menu,n,temp,MF_BYPOSITION); + if (mgr.process_string(temp,temp2)) + { + uModifyMenu(menu,n,MF_BYPOSITION|MF_STRING|state,id,temp2); + } + } + } + } +} + + +static bool test_key(unsigned k) +{ + return (GetKeyState(k) & 0x8000) ? true : false; +} + +#define F_SHIFT (HOTKEYF_SHIFT<<8) +#define F_CTRL (HOTKEYF_CONTROL<<8) +#define F_ALT (HOTKEYF_ALT<<8) +#define F_WIN (HOTKEYF_EXT<<8) + +static t_uint32 get_key_code(WPARAM wp) { + t_uint32 code = (t_uint32)(wp & 0xFF); + if (test_key(VK_CONTROL)) code|=F_CTRL; + if (test_key(VK_SHIFT)) code|=F_SHIFT; + if (test_key(VK_MENU)) code|=F_ALT; + if (test_key(VK_LWIN) || test_key(VK_RWIN)) code|=F_WIN; + return code; +} + +static t_uint32 get_key_code(WPARAM wp, t_uint32 mods) { + t_uint32 code = (t_uint32)(wp & 0xFF); + if (mods & MOD_CONTROL) code|=F_CTRL; + if (mods & MOD_SHIFT) code|=F_SHIFT; + if (mods & MOD_ALT) code|=F_ALT; + if (mods & MOD_WIN) code|=F_WIN; + return code; +} + +bool keyboard_shortcut_manager::on_keydown(shortcut_type type,WPARAM wp) +{ + if (type==TYPE_CONTEXT) return false; + metadb_handle_list dummy; + return process_keydown(type,dummy,get_key_code(wp)); +} + +bool keyboard_shortcut_manager::on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) +{ + if (data.get_count()==0) return false; + return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller); +} + +bool keyboard_shortcut_manager::on_keydown_auto(WPARAM wp) +{ + if (on_keydown(TYPE_MAIN,wp)) return true; + if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true; + if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true; + return false; +} + +bool keyboard_shortcut_manager::on_keydown_auto_playlist(WPARAM wp) +{ + metadb_handle_list data; + playlist_manager::get()->activeplaylist_get_selected_items(data); + return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist); +} + +bool keyboard_shortcut_manager::on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) +{ + if (on_keydown_context(data,wp,caller)) return true; + else return on_keydown_auto(wp); +} + +static bool should_relay_key_restricted(WPARAM p_key) { + switch(p_key) { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + return false; + default: + return (p_key >= VK_F1 && p_key <= VK_F24) || IsKeyPressed(VK_CONTROL) || IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN); + } +} + +bool keyboard_shortcut_manager::on_keydown_restricted_auto(WPARAM wp) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto(wp); +} +bool keyboard_shortcut_manager::on_keydown_restricted_auto_playlist(WPARAM wp) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto_playlist(wp); +} +bool keyboard_shortcut_manager::on_keydown_restricted_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) { + if (!should_relay_key_restricted(wp)) return false; + return on_keydown_auto_context(data,wp,caller); +} + +static bool filterTypableWindowMessage(const MSG * msg, t_uint32 modifiers) { + if (keyboard_shortcut_manager::is_typing_key_combo((t_uint32)msg->wParam, modifiers)) { + const DWORD mask = DLGC_HASSETSEL | DLGC_WANTCHARS | DLGC_WANTARROWS; + auto status = ::SendMessage(msg->hwnd, WM_GETDLGCODE,0, 0); + if ( (status & mask) == mask ) return false; + + ui_element_typable_window_manager::ptr api; + if (ui_element_typable_window_manager::tryGet(api)) { + if (api->is_registered(msg->hwnd)) return false; + } + } + return true; +} + +bool keyboard_shortcut_manager_v2::pretranslate_message(const MSG * msg, HWND thisPopupWnd) { + switch(msg->message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if (thisPopupWnd != NULL && FindOwningPopup(msg->hwnd) == thisPopupWnd) { + const t_uint32 modifiers = GetHotkeyModifierFlags(); + if (filterTypableWindowMessage(msg, modifiers)) { + if (process_keydown_simple(get_key_code(msg->wParam,modifiers))) return true; + } + } + return false; + default: + return false; + } +} + +bool keyboard_shortcut_manager::is_text_key(t_uint32 vkCode) { + return vkCode == VK_SPACE + || (vkCode >= '0' && vkCode < 0x40) + || (vkCode > 0x40 && vkCode < VK_LWIN) + || (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE) + || (vkCode >= VK_OEM_1 && vkCode <= VK_OEM_3) + || (vkCode >= VK_OEM_4 && vkCode <= VK_OEM_8) + ; +} + +bool keyboard_shortcut_manager::is_typing_key(t_uint32 vkCode) { + return is_text_key(vkCode) + || vkCode == VK_BACK + || vkCode == VK_RETURN + || vkCode == VK_INSERT + || (vkCode > VK_SPACE && vkCode < '0'); +} + +bool keyboard_shortcut_manager::is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers) { + if (!is_typing_modifier(modifiers)) return false; + return is_typing_key(vkCode); +} + +bool keyboard_shortcut_manager::is_typing_modifier(t_uint32 flags) { + flags &= ~MOD_SHIFT; + return flags == 0 || flags == (MOD_ALT | MOD_CONTROL); +} + +bool keyboard_shortcut_manager::is_typing_message(HWND editbox, const MSG * msg) { + if (msg->hwnd != editbox) return false; + return is_typing_message(msg); +} +bool keyboard_shortcut_manager::is_typing_message(const MSG * msg) { + if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN) return false; + return is_typing_key_combo((uint32_t)msg->wParam, GetHotkeyModifierFlags()); +} + +#endif // _WIN32 + +bool contextmenu_manager::execute_by_id(unsigned id) noexcept { + contextmenu_node * ptr = find_by_id(id); + if (ptr == NULL) return false; + ptr->execute(); + return true; +} + +void contextmenu_manager::g_create(service_ptr_t<contextmenu_manager>& p_out) { standard_api_create_t(p_out); } +service_ptr_t<contextmenu_manager> contextmenu_manager::g_create() { service_ptr_t<contextmenu_manager> ret; standard_api_create_t(ret); return ret; }
