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; }