%% %% wings_hotkey.erl -- %% %% This modules translates hotkeys. %% %% Copyright (c) 2001-2004 Bjorn Gustavsson %% %% See the file "license.terms" for information on usage and redistribution %% of this file, and for a DISCLAIMER OF ALL WARRANTIES. %% %% $Id: wings_hotkey.erl,v 1.49 2004/11/14 12:21:08 bjorng Exp $ %% -module(wings_hotkey). -export([event/1,event/2,matching/1,bind_unicode/3,bind_virtual/4, bind_from_event/2,delete_by_command/1,set_default/0, listing/0]). -define(NEED_ESDL, 1). -include("wings.hrl"). -import(lists, [foldl/3,sort/1,foreach/2,map/2,last/1,reverse/1]). -compile({parse_transform,ms_transform}). -define(KL, wings_state). %%% %%% Hotkey lookup and translation. %%% event(Ev) -> event_1(Ev, none). event(Ev, #st{sel=[]}) -> event_1(Ev, none); event(Ev, #st{selmode=Mode}=St) -> case wings_light:is_any_light_selected(St) of true -> event_1(Ev, light); false -> event_1(Ev, Mode) end; event(Ev, Cmd) -> event_1(Ev, Cmd). event_1(#keyboard{}=Ev, SelMode) -> case lookup(Ev, SelMode) of next -> lookup(Ev, none); Action -> Action end; event_1(_, _) -> next. lookup(Ev, Cmd) -> case ets:lookup(?KL, bindkey(Ev, Cmd)) of [{_,Action,_}] -> Action; [] -> next end. %%% %%% Binding and unbinding of keys. %%% bind_from_event(Ev, Cmd) -> Bkey = bindkey(Ev, Cmd), ets:insert(?KL, {Bkey,Cmd,user}), keyname(Bkey). delete_by_command(Cmd) -> case sort(ets:match_object(?KL, {'_',Cmd,'_'})) of [{Key,_,_}] -> ets:delete(?KL, Key), []; [{Key,_,_},{Next,_,_}|_] -> ets:delete(?KL, Key), keyname(Next); [] -> [] end. bind_unicode(Key, Cmd, Source) -> Bkey = bkey(Key, Cmd), ets:insert(?KL, {Bkey,Cmd,Source}), Bkey. bind_virtual(Key, Mods, Cmd, Source) -> Bkey = bkey({Key,sort(Mods)}, Cmd), ets:insert(?KL, {Bkey,Cmd,Source}), Bkey. bindkey(#keyboard{sym=?SDLK_TAB=C,mod=Mod}, Cmd) -> bkey({C,sort(modifiers(Mod))}, Cmd); bindkey(#keyboard{sym=Sym,mod=Mod,unicode=C}, Cmd) -> case modifiers(Mod) of [] when C =/= 0 -> bkey(fix_bksp_and_del(Sym, C), Cmd); [shift] when C =/= 0 -> bkey(C, Cmd); Mods -> bkey({Sym,sort(Mods)}, Cmd) end. bkey(Key, {Mode,_}) -> bkey(Key, Mode); bkey(Key, Mode) -> case suitable_mode(Mode) of true -> {bindkey,Mode,Key}; false -> {bindkey,Key} end. matching(Names) -> M0 = matching_global(Names) ++ matching_mode(Names), M = wings_util:rel2fam(M0), [{Name,Key} || {Name,[{_,Key}|_]} <- M]. matching_global(Names) -> Spec0 = foldl(fun(N, A) -> {N,A} end, '$1', Names), Spec = [{{{bindkey,'$2'},Spec0,'$3'}, [], [{{'$1',{{'$3','$2'}}}}]}], [{Name,mkeyname(Key)} || {Name,Key} <- ets:select(?KL, Spec)]. matching_mode(Names) -> Mode = lists:last(Names), case suitable_mode(Mode) of false -> []; true -> Spec0 = foldl(fun(N, A) -> {N,A} end, '$1', Names), Spec = [{{{bindkey,Mode,'$2'},Spec0,'$3'}, [], [{{'$1',{{'$3','$2'}}}}]}], [{Name,mkeyname(Key)} || {Name,Key} <- ets:select(?KL, Spec)] end. mkeyname({user,K}) -> {1,keyname(K)}; mkeyname({default,K}) -> {2,keyname(K)}; mkeyname({plugin,K}) -> {3,keyname(K)}. suitable_mode(light) -> true; suitable_mode(vertex) -> true; suitable_mode(edge) -> true; suitable_mode(face) -> true; suitable_mode(body) -> true; suitable_mode(_) -> false. %%% %%% Make a listing of all hotkeys. %%% listing() -> MatchSpec = ets:fun2ms(fun({{bindkey,K},Cmd,Src}) -> {all,{{bindkey,K},Cmd,Src}}; ({{bindkey,Mode,K},Cmd,Src}) -> {Mode,{{bindkey,K},Cmd,Src}} end), Keys = wings_util:rel2fam(ets:select(?KL, MatchSpec)), listing_1(Keys, []). listing_1([{Mode,Keys}|T], Acc0) -> Acc = [list_keys(Keys),list_header(Mode)|Acc0], listing_1(T, Acc); listing_1([], Acc) -> reverse(Acc). list_header(all) ->?STR(list_header,1,"Hotkeys in all modes"); list_header(body) ->?STR(list_header,2,"Hotkeys in object mode"); list_header(edge) -> ?STR(list_header,3,"Hotkeys in edge mode"); list_header(face) -> ?STR(list_header,4,"Hotkeys in face mode"); list_header(light) -> ?STR(list_header,5,"Hotkeys for lights"); list_header(vertex) -> ?STR(list_header,6,"Hotkeys for vertices"); list_header(A) -> atom_to_list(A). list_keys([{Key,Cmd,Src}|T]) -> KeyStr = keyname(Key), SrcStr = case Src of default -> ""; user -> ?STR(list_keys,1," (user-defined)"); plugin -> ?STR(list_keys,2," (plug-in-defined)") end, KeyStr ++ ": " ++ wings_util:stringify(Cmd) ++ SrcStr ++ "\n" ++ list_keys(T); list_keys([]) -> []. %%% %%% Local functions. %%% %% For the benefit of Mac OS X, but does no harm on other platforms. fix_bksp_and_del(?SDLK_DELETE, _) -> ?SDLK_DELETE; fix_bksp_and_del(?SDLK_BACKSPACE, _) -> ?SDLK_BACKSPACE; fix_bksp_and_del(_, C) -> C. modifiers(Mod) -> modifiers(Mod, []). modifiers(Mod, Acc) when Mod band ?CTRL_BITS =/= 0 -> Pressed = Mod band ?CTRL_BITS, modifiers(Mod bxor Pressed, [ctrl|Acc]); modifiers(Mod, Acc) when Mod band ?ALT_BITS =/= 0 -> Pressed = Mod band ?ALT_BITS, modifiers(Mod bxor Pressed, [alt|Acc]); modifiers(Mod, Acc) when Mod band ?SHIFT_BITS =/= 0 -> Pressed = Mod band ?SHIFT_BITS, modifiers(Mod bxor Pressed, [shift|Acc]); modifiers(Mod, Acc) when Mod band ?KMOD_META =/= 0 -> Pressed = Mod band ?KMOD_META, modifiers(Mod bxor Pressed, [command|Acc]); modifiers(_, Acc) -> lists:sort(Acc). keyname({bindkey,Key}) -> keyname(Key); keyname({bindkey,_Mode,Key}) -> keyname(Key); keyname({C,Mods}) -> modname(Mods) ++ vkeyname(C); keyname($\b) -> ?STR(keyname,1,"Bksp"); keyname($\t) -> ?STR(keyname,2,"Tab"); keyname($\s) -> ?STR(keyname,3,"Space"); keyname(C) when $a =< C, C =< $z -> [C-32]; keyname(C) when $A =< C, C =< $Z -> case get(wings_os_type) of {unix,darwin} -> [shift,C]; _ -> ?STR(keyname,4,"Shift+")++ [C] end; keyname(C) when is_integer(C), C < 256 -> [C]; keyname(C) when is_integer(C), 63236 =< C, C =< 63247 -> [$F|integer_to_list(C-63235)]; keyname(C) -> [$<|integer_to_list(C)++">"]. modname(Mods) -> case get(wings_os_type) of {unix,darwin} -> mac_modname(Mods, []); _ -> modname_1(Mods) end. modname_1([command|T]) -> "Meta+" ++modname_1(T); modname_1([Mod|T]) -> wings_s:modkey(Mod) ++ "+" ++modname_1(T); modname_1([]) -> []. mac_modname([ctrl|T], Acc) -> [$^|mac_modname(T, Acc)]; mac_modname([shift|T], Acc) -> mac_modname(T, [shift|Acc]); mac_modname([alt|T], Acc) -> mac_modname(T, [option|Acc]); mac_modname([command|T], Acc) -> mac_modname(T, Acc++[command]); mac_modname([], Acc) -> Acc. vkeyname(?SDLK_BACKSPACE) -> ?STR(vkeyname,1,"Bksp"); vkeyname(?SDLK_TAB) -> ?STR(vkeyname,2,"Tab"); vkeyname(?SDLK_RETURN) -> ?STR(vkeyname,3,"Enter"); vkeyname(?SDLK_PAUSE) -> ?STR(vkeyname,4,"Pause"); vkeyname(?SDLK_ESCAPE) ->?STR(vkeyname,5,"Esc"); vkeyname(?SDLK_SPACE) -> ?STR(vkeyname,6,"Space"); vkeyname(?SDLK_DELETE) -> ?STR(vkeyname,7,"Delete"); vkeyname(C) when $a =< C, C =< $z-> [C-32]; vkeyname(C) when $\s < C, C < 256 -> [C]; vkeyname(C) when ?SDLK_KP0 < C, C < ?SDLK_KP9 -> [C-?SDLK_KP0+$0]; vkeyname(C) when ?SDLK_F1 =< C, C =< ?SDLK_F15 -> [$F|integer_to_list(C-?SDLK_F1+1)]; vkeyname(?SDLK_KP_PERIOD) -> ?STR(vkeyname,8,"Del"); vkeyname(?SDLK_KP_DIVIDE) -> ?STR(vkeyname,9,"Div"); vkeyname(?SDLK_KP_MULTIPLY) -> ?STR(vkeyname,10,"Mul"); vkeyname(?SDLK_KP_MINUS) -> ?STR(vkeyname,11,"-"); vkeyname(?SDLK_KP_PLUS) -> ?STR(vkeyname,12,"+"); vkeyname(?SDLK_KP_ENTER) -> ?STR(vkeyname,13,"Enter"); vkeyname(?SDLK_KP_EQUALS) ->?STR(vkeyname,14,"="); vkeyname(?SDLK_UP) -> ?STR(vkeyname,15,"Up"); vkeyname(?SDLK_DOWN) -> ?STR(vkeyname,16,"Down"); vkeyname(?SDLK_RIGHT) -> ?STR(vkeyname,17,"Right"); vkeyname(?SDLK_LEFT) -> ?STR(vkeyname,18,"Left"); vkeyname(?SDLK_INSERT) -> ?STR(vkeyname,19,"Insert"); vkeyname(?SDLK_HOME) -> ?STR(vkeyname,20,"Home"); vkeyname(?SDLK_END) -> ?STR(vkeyname,21,"End"); vkeyname(?SDLK_PAGEUP) -> ?STR(vkeyname,22,"Page Up"); vkeyname(?SDLK_PAGEDOWN) ->?STR(vkeyname,23,"Page Down"); vkeyname(_) -> ?STR(vkeyname,24,"UKEY"). %%% %%% Default keybindings. %%% set_default() -> foreach( fun({{Key,List0},Action}) when is_integer(Key) -> List = convert_modifiers(List0), ets:insert(wings_state, {{bindkey,{Key,sort(List)}}, Action,default}); ({Key,Action}) when is_integer(Key) -> ets:insert(wings_state, {{bindkey,Key}, Action,default}); ({Mode,{Key,List0},Action}) when is_integer(Key) -> List = convert_modifiers(List0), ets:insert(wings_state, {{bindkey,Mode,{Key,sort(List)}}, Action,default}); ({Mode,Key,Action}) when is_integer(Key) -> ets:insert(wings_state, {{bindkey,Mode,Key}, Action,default}) end, default_keybindings()). convert_modifiers(Mod) -> case get(wings_os_type) of {unix,darwin} -> map(fun(ctrl) -> command; (Other) -> Other end, Mod); _ -> Mod end. default_keybindings() -> [{{$a,[ctrl]}, {select,all}}, {{$i,[ctrl,shift]}, {select,inverse}}, {{$l,[ctrl]}, {file,merge}}, {{$n,[ctrl]}, {file,new}}, {{$o,[ctrl]}, {file,open}}, {{$q,[ctrl]}, {file,quit}}, {{$s,[ctrl,shift]}, {file,save_as}}, {{$s,[ctrl]}, {file,save}}, {{$z,[alt,ctrl]}, {edit,undo}}, {{$z,[ctrl,shift]}, {edit,redo}}, {{$z,[ctrl]}, {edit,undo_toggle}}, {{?SDLK_KP_PLUS,[]}, {select,more}}, {{?SDLK_KP_MINUS,[]}, {select,less}}, {{?SDLK_F3,[]}, {select,{edge_loop,prev_edge_loop}}}, {{?SDLK_F4,[]}, {select,{edge_loop,next_edge_loop}}}, {{?SDLK_F5,[]}, {select,{by,{faces_with,5}}}}, {{?SDLK_TAB,[]}, {view,workmode}}, {{?SDLK_TAB,[shift]}, {view,quick_preview}}, {$\s, {select,deselect}}, {$a, {view,aim}}, {$A, {view,frame}}, {$b, {select,body}}, {{$d,[ctrl]}, {edit,repeat}}, {$d, {edit,repeat_args}}, {$D, {edit,repeat_drag}}, {$e, {select,edge}}, {$f, {select,face}}, {$g, {select,{edge_loop,edge_ring}}}, {{$g,[alt]}, {select,{edge_loop,edge_ring_incr}}}, {{$g,[alt,ctrl]}, {select,{edge_loop,edge_ring_decr}}}, {$i, {select,similar}}, {$l, {select,{edge_loop,edge_loop}}}, {$L, {select,{edge_loop,edge_loop_to_region}}}, {{$l,[alt]}, {select,{edge_loop,edge_link_incr}}}, {{$l,[alt,ctrl]}, {select,{edge_loop,edge_link_decr}}}, {$o, {view,orthogonal_view}}, {$r, {view,reset}}, {$u, {view,auto_rotate}}, {$v, {select,vertex}}, {$w, {view,toggle_wireframe}}, {$x, {view,{along,x}}}, {$y, {view,{along,y}}}, {$z, {view,{along,z}}}, {$X, {view,{along,neg_x}}}, {$Y, {view,{along,neg_y}}}, {$Z, {view,{along,neg_z}}}, {$+, {select,more}}, {$=, {select,more}}, {$-, {select,less}}, %% Mode-specific bindings. {edge,$2, {edge,{cut,2}}}, {edge,$3, {edge,{cut,3}}}, {edge,$4, {edge,{cut,4}}}, {edge,$5, {edge,{cut,5}}}, {edge,$6, {edge,{cut,6}}}, {edge,$7, {edge,{cut,7}}}, {edge,$8, {edge,{cut,8}}}, {edge,$9, {edge,{cut,9}}}, {edge,$0, {edge,{cut,10}}}, {vertex,$c, {vertex,connect}}, {edge,$c, {edge,connect}}, {vertex,$\b, {vertex,collapse}}, {edge,$\b, {edge,dissolve}}, {face,$\b, {face,dissolve}}, {body,$\b, {body,delete}}, {vertex,{?SDLK_DELETE,[]}, {vertex,dissolve}}, {edge,{?SDLK_DELETE,[]}, {edge,dissolve}}, {face,{?SDLK_DELETE,[]}, {face,dissolve}}, {body,{?SDLK_DELETE,[]}, {body,delete}}, {vertex,{?SDLK_KP_PERIOD,[]},{vertex,dissolve}}, {edge,{?SDLK_KP_PERIOD,[]}, {edge,dissolve}}, {face,{?SDLK_KP_PERIOD,[]}, {face,dissolve}}, {body,{?SDLK_KP_PERIOD,[]}, {body,delete}}, {light,$\b, {light,delete}}, {light,{?SDLK_DELETE,[]}, {light,delete}}, {light,{?SDLK_KP_PERIOD,[]}, {light,delete}}, {face,$s, {face,smooth}}, {body,$s, {body,smooth}} ].