#include "foobar2000.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<=Nget_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_idxget_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 || idget_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); } } } } } } #endif 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); } bool contextmenu_manager::execute_by_id(unsigned id) { contextmenu_node * ptr = find_by_id(id); if (ptr == NULL) return false; ptr->execute(); return true; } #ifdef WIN32 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 & data,const POINT * pt,unsigned flags) { service_ptr_t 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 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; while(ptr = strchr(ptr,'&')) { 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) { mnemonic_manager mgr; unsigned n, m = GetMenuItemCount(menu); pfc::string8_fastalloc temp,temp2; for(n=0;n & 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 & 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(UINT 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 & 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(msg->wParam, GetHotkeyModifierFlags()); }