foobar2000 SDK  2015-08-03
menu_manager.cpp
Go to the documentation of this file.
1 #include "foobar2000.h"
2 
3 #ifdef WIN32
4 
5 static void fix_ampersand(const char * src,pfc::string_base & out)
6 {
7  out.reset();
8  unsigned ptr = 0;
9  while(src[ptr])
10  {
11  if (src[ptr]=='&')
12  {
13  out.add_string("&&");
14  ptr++;
15  while(src[ptr]=='&')
16  {
17  out.add_string("&&");
18  ptr++;
19  }
20  }
21  else out.add_byte(src[ptr++]);
22  }
23 }
24 
25 static unsigned flags_to_win32(unsigned flags)
26 {
27  unsigned ret = 0;
28  if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) {/* dealt with elsewhere */}
29  else if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED;
30  if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED;
31  if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED;
32  return ret;
33 }
34 
35 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)
36 {
37  if (parent!=0 && parent->get_type()==contextmenu_item_node::TYPE_POPUP)
38  {
40  t_size child_idx,child_num = parent->get_num_children();
41  for(child_idx=0;child_idx<child_num;child_idx++)
42  {
43  contextmenu_node * child = parent->get_child(child_idx);
44  if (child)
45  {
46  const char * name = child->get_name();
47  if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;}
50  {
51  HMENU new_menu = CreatePopupMenu();
52  uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name);
53  win32_build_menu(new_menu,child,base_id,max_id);
54  }
56  {
57  uAppendMenu(menu,MF_SEPARATOR,0,0);
58  }
60  {
61  int id = child->get_id();
62  if (id>=0 && (max_id<0 || id<max_id))
63  {
64  const unsigned flags = child->get_display_flags();
65  const UINT ID = base_id+id;
66  uAppendMenu(menu,MF_STRING | flags_to_win32(flags),ID,name);
67  if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) CheckMenuRadioItem(menu,ID,ID,ID,MF_BYCOMMAND);
68  }
69  }
70  }
71  }
72  }
73 }
74 
75 #endif
76 
78  contextmenu_node * ptr = find_by_id(id);
79  if (ptr == NULL) return false;
80  return ptr->get_description(out);
81 }
83  contextmenu_node * ptr = find_by_id(id);
84  if (ptr == NULL) return false;
85  ptr->execute();
86  return true;
87 }
88 
89 #ifdef WIN32
90 
91 void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt)
92 {
93  enum {ID_CUSTOM_BASE = 1};
94 
95  int cmd;
96 
97  {
98  POINT p;
99  if (pt) p = *pt;
100  else GetCursorPos(&p);
101 
102  HMENU hmenu = CreatePopupMenu();
103  try {
104 
105  win32_build_menu(hmenu,ID_CUSTOM_BASE,-1);
107 
108  cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0);
109  } catch(...) {DestroyMenu(hmenu); throw;}
110 
111  DestroyMenu(hmenu);
112  }
113 
114  if (cmd>0)
115  {
116  if (cmd>=ID_CUSTOM_BASE)
117  {
118  execute_by_id(cmd - ID_CUSTOM_BASE);
119  }
120  }
121 }
122 
123 void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data,const POINT * pt,unsigned flags)
124 {
127  manager->init_context(data,flags);
128  manager->win32_run_menu_popup(parent,pt);
129 }
130 
131 void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags)
132 {
135  manager->init_context_playlist(flags);
136  manager->win32_run_menu_popup(parent,pt);
137 }
138 
139 
140 namespace {
141  class mnemonic_manager
142  {
144  bool is_used(unsigned c)
145  {
146  char temp[8];
147  temp[pfc::utf8_encode_char(uCharLower(c),temp)]=0;
148  return !!strstr(used,temp);
149  }
150 
151  static bool is_alphanumeric(char c)
152  {
153  return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9');
154  }
155 
156 
157 
158 
159  void insert(const char * src,unsigned idx,pfc::string_base & out)
160  {
161  out.reset();
162  out.add_string(src,idx);
163  out.add_string("&");
164  out.add_string(src+idx);
165  used.add_char(uCharLower(src[idx]));
166  }
167  public:
168  bool check_string(const char * src)
169  {//check for existing mnemonics
170  const char * ptr = src;
171  while(ptr = strchr(ptr,'&'))
172  {
173  if (ptr[1]=='&') ptr+=2;
174  else
175  {
176  unsigned c = 0;
177  if (pfc::utf8_decode_char(ptr+1,c)>0)
178  {
179  if (!is_used(c)) used.add_char(uCharLower(c));
180  }
181  return true;
182  }
183  }
184  return false;
185  }
186  bool process_string(const char * src,pfc::string_base & out)//returns if changed
187  {
188  if (check_string(src)) {out=src;return false;}
189  unsigned idx=0;
190  while(src[idx]==' ') idx++;
191  while(src[idx])
192  {
193  if (is_alphanumeric(src[idx]) && !is_used(src[idx]))
194  {
195  insert(src,idx,out);
196  return true;
197  }
198 
199  while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++;
200  if (src[idx]=='\t') break;
201  while(src[idx]==' ') idx++;
202  }
203 
204  //no success picking first letter of one of words
205  idx=0;
206  while(src[idx])
207  {
208  if (src[idx]=='\t') break;
209  if (is_alphanumeric(src[idx]) && !is_used(src[idx]))
210  {
211  insert(src,idx,out);
212  return true;
213  }
214  idx++;
215  }
216 
217  //giving up
218  out = src;
219  return false;
220  }
221  };
222 }
223 
225 {
226  mnemonic_manager mgr;
227  unsigned n, m = GetMenuItemCount(menu);
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 #endif
261 
262 static bool test_key(unsigned k)
263 {
264  return (GetKeyState(k) & 0x8000) ? true : false;
265 }
266 
267 #define F_SHIFT (HOTKEYF_SHIFT<<8)
268 #define F_CTRL (HOTKEYF_CONTROL<<8)
269 #define F_ALT (HOTKEYF_ALT<<8)
270 #define F_WIN (HOTKEYF_EXT<<8)
271 
272 static t_uint32 get_key_code(WPARAM wp) {
273  t_uint32 code = (t_uint32)(wp & 0xFF);
274  if (test_key(VK_CONTROL)) code|=F_CTRL;
275  if (test_key(VK_SHIFT)) code|=F_SHIFT;
276  if (test_key(VK_MENU)) code|=F_ALT;
277  if (test_key(VK_LWIN) || test_key(VK_RWIN)) code|=F_WIN;
278  return code;
279 }
280 
281 static t_uint32 get_key_code(WPARAM wp, t_uint32 mods) {
282  t_uint32 code = (t_uint32)(wp & 0xFF);
283  if (mods & MOD_CONTROL) code|=F_CTRL;
284  if (mods & MOD_SHIFT) code|=F_SHIFT;
285  if (mods & MOD_ALT) code|=F_ALT;
286  if (mods & MOD_WIN) code|=F_WIN;
287  return code;
288 }
289 
291 {
292  if (type==TYPE_CONTEXT) return false;
293  metadb_handle_list dummy;
294  return process_keydown(type,dummy,get_key_code(wp));
295 }
296 
298 {
299  if (data.get_count()==0) return false;
300  return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller);
301 }
302 
304 {
305  if (on_keydown(TYPE_MAIN,wp)) return true;
306  if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true;
307  if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true;
308  return false;
309 }
310 
312 {
313  metadb_handle_list data;
315  api->activeplaylist_get_selected_items(data);
316  return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist);
317 }
318 
320 {
321  if (on_keydown_context(data,wp,caller)) return true;
322  else return on_keydown_auto(wp);
323 }
324 
325 static bool should_relay_key_restricted(UINT p_key) {
326  switch(p_key) {
327  case VK_LEFT:
328  case VK_RIGHT:
329  case VK_UP:
330  case VK_DOWN:
331  return false;
332  default:
333  return (p_key >= VK_F1 && p_key <= VK_F24) || IsKeyPressed(VK_CONTROL) || IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN);
334  }
335 }
336 
338  if (!should_relay_key_restricted(wp)) return false;
339  return on_keydown_auto(wp);
340 }
342  if (!should_relay_key_restricted(wp)) return false;
343  return on_keydown_auto_playlist(wp);
344 }
346  if (!should_relay_key_restricted(wp)) return false;
347  return on_keydown_auto_context(data,wp,caller);
348 }
349 
350 static bool filterTypableWindowMessage(const MSG * msg, t_uint32 modifiers) {
351  if (keyboard_shortcut_manager::is_typing_key_combo((t_uint32)msg->wParam, modifiers)) {
352  try {
353  if (static_api_ptr_t<ui_element_typable_window_manager>()->is_registered(msg->hwnd)) return false;
354  } catch(exception_service_not_found) {}
355  }
356  return true;
357 }
358 
359 bool keyboard_shortcut_manager_v2::pretranslate_message(const MSG * msg, HWND thisPopupWnd) {
360  switch(msg->message) {
361  case WM_KEYDOWN:
362  case WM_SYSKEYDOWN:
363  if (thisPopupWnd != NULL && FindOwningPopup(msg->hwnd) == thisPopupWnd) {
364  const t_uint32 modifiers = GetHotkeyModifierFlags();
365  if (filterTypableWindowMessage(msg, modifiers)) {
366  if (process_keydown_simple(get_key_code(msg->wParam,modifiers))) return true;
367  }
368  }
369  return false;
370  default:
371  return false;
372  }
373 }
374 
376  return vkCode == VK_SPACE
377  || (vkCode >= '0' && vkCode < 0x40)
378  || (vkCode > 0x40 && vkCode < VK_LWIN)
379  || (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE)
380  || (vkCode >= VK_OEM_1 && vkCode <= VK_OEM_3)
381  || (vkCode >= VK_OEM_4 && vkCode <= VK_OEM_8)
382  ;
383 }
384 
386  return is_text_key(vkCode)
387  || vkCode == VK_BACK
388  || vkCode == VK_RETURN
389  || vkCode == VK_INSERT
390  || (vkCode > VK_SPACE && vkCode < '0');
391 }
392 
394  if (!is_typing_modifier(modifiers)) return false;
395  return is_typing_key(vkCode);
396 }
397 
399  flags &= ~MOD_SHIFT;
400  return flags == 0 || flags == (MOD_ALT | MOD_CONTROL);
401 }
402 
403 bool keyboard_shortcut_manager::is_typing_message(HWND editbox, const MSG * msg) {
404  if (msg->hwnd != editbox) return false;
405  return is_typing_message(msg);
406 }
408  if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN) return false;
409  return is_typing_key_combo(msg->wParam, GetHotkeyModifierFlags());
410 }
static bool is_typing_modifier(t_uint32 flags)
virtual contextmenu_node * get_child(t_size n)=0
bool on_keydown_context(const pfc::list_base_const_t< metadb_handle_ptr > &data, WPARAM wp, const GUID &caller)
Definition: pfc.h:71
virtual void add_string(const char *p_string, t_size p_length=~0)=0
virtual t_size get_num_children()=0
static void win32_run_menu_context(HWND parent, metadb_handle_list_cref data, const POINT *pt=0, unsigned flags=0)
static void g_create(service_ptr_t< contextmenu_manager > &p_out)
virtual void execute()=0
bool on_keydown_restricted_auto(WPARAM wp)
void win32_auto_mnemonics(HMENU menu)
static bool is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers)
t_size utf8_encode_char(unsigned c, char *out)
Definition: utf8.cpp:113
bool on_keydown_restricted_auto_playlist(WPARAM wp)
UINT SHARED_EXPORT uGetMenuItemType(HMENU menu, UINT position)
bool IsKeyPressed(unsigned vk)
virtual contextmenu_item_node::t_type get_type()=0
virtual const char * get_name()=0
bool pretranslate_message(const MSG *msg, HWND thisPopupWnd)
Helper for use with message filters.
bool get_description_by_id(unsigned id, pfc::string_base &out)
void win32_run_menu_popup(HWND parent, const POINT *pt=0)
bool on_keydown(shortcut_type type, WPARAM wp)
size_t t_size
Definition: int_types.h:48
bool on_keydown_restricted_auto_context(const pfc::list_base_const_t< metadb_handle_ptr > &data, WPARAM wp, const GUID &caller)
virtual unsigned get_id()=0
virtual t_size get_count() const =0
unsigned GetHotkeyModifierFlags()
Returns current modifier keys pressed, using win32 MOD_* flags.
UINT SHARED_EXPORT uCharLower(UINT c)
BOOL SHARED_EXPORT uAppendMenu(HMENU menu, UINT flags, UINT_PTR id, const char *content)
static bool is_text_key(t_uint32 vkCode)
static void win32_build_menu(HMENU menu, contextmenu_node *parent, int base_id, int max_id)
static const GUID caller_playlist
Deprecated - use caller_active_playlist_selection instead.
Definition: contextmenu.h:136
t_size utf8_decode_char(const char *src, unsigned &out, t_size src_bytes)
Definition: utf8.cpp:64
bool execute_by_id(unsigned id)
virtual contextmenu_node * find_by_id(unsigned id)=0
static void win32_run_menu_context_playlist(HWND parent, const POINT *pt=0, unsigned flags=0)
Autopointer class to be used with all services. Manages reference counter calls behind-the-scenes.
Definition: service.h:55
BOOL SHARED_EXPORT uModifyMenu(HMENU menu, UINT id, UINT flags, UINT newitem, const char *data)
virtual unsigned get_display_flags()=0
void add_byte(char c)
Definition: string_base.h:42
BOOL SHARED_EXPORT uGetMenuString(HMENU menu, UINT id, pfc::string_base &out, UINT flag)
virtual bool get_description(pfc::string_base &out)=0
bool on_keydown_auto_context(const pfc::list_base_const_t< metadb_handle_ptr > &data, WPARAM wp, const GUID &caller)
Helper template used to easily access core services. Usage: static_api_ptr_t<myclass> api; api->doso...
Definition: service.h:533
HWND SHARED_EXPORT FindOwningPopup(HWND p_wnd)
bool on_keydown_auto_playlist(WPARAM wp)
static bool is_typing_key(t_uint32 vkCode)
string8_t< pfc::alloc_fast_aggressive > string8_fastalloc
Definition: string_base.h:436
static bool is_typing_message(HWND editbox, const MSG *msg)
bool on_keydown_auto(WPARAM wp)
uint32_t t_uint32
Definition: int_types.h:5