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