1%%
2%%  wings_hotkey.erl --
3%%
4%%     This modules translates hotkeys.
5%%
6%%  Copyright (c) 2001-2011 Bjorn Gustavsson
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%     $Id$
12%%
13
14-module(wings_hotkey).
15-export([event/1,event/2,matching/1,bind_unicode/3,bind_virtual/4,
16	 command/2, bind_from_event/2,unbind/1,hotkeys_by_commands/1,bindkey/2,
17	 set_default/0,listing/0,handle_error/2,
18	 format_hotkey/2]).
19
20-define(NEED_ESDL, 1).
21-include("wings.hrl").
22
23-import(lists, [foldl/3,sort/1,foreach/2,map/2,reverse/1]).
24
25-compile({parse_transform,ms_transform}).
26
27-define(KL, wings_state).
28
29%%%
30%%% Format hotkeys.
31%%%
32
33
34%% format_hotkey(Hotkey, wx|pretty) -> String.
35format_hotkey({bindkey,Hotkey}, Style) ->
36    format_hotkey(Hotkey, Style);
37format_hotkey({bindkey,_Mode,Hotkey}, Style) ->
38    format_hotkey(Hotkey, Style);
39format_hotkey(Hotkey, Style) ->
40    case Hotkey of
41	[] -> [];
42	[_|_] -> Hotkey;
43	{C,Mods} ->
44	    modname(Mods, Style) ++ vkeyname(C);
45	_ ->
46	    keyname(Hotkey, Style)
47    end.
48
49%%%
50%%% Hotkey lookup and translation.
51%%%
52
53event(Ev) ->
54    event_1(Ev, none).
55
56event(Ev, #st{sel=[]}) ->
57    event_1(Ev, none);
58event(Ev, #st{selmode=Mode}=St) ->
59    case wings_light:is_any_light_selected(St) of
60	true -> event_1(Ev, light);
61	false -> event_1(Ev, Mode)
62    end;
63event(Ev, Cmd) -> event_1(Ev, Cmd).
64
65event_1(#keyboard{}=Ev, SelMode) ->
66    case lookup(Ev, SelMode) of
67	next -> lookup(Ev, none);
68	other_mode -> next;
69	Action -> Action
70    end;
71event_1(_, _) -> next.
72
73lookup(Ev, {Menu,_}=Cmd) ->
74    Mode = suitable_mode(Menu),
75    BKey = bindkey(Ev, Cmd),
76    KeyInfo =
77	case ets:lookup(?KL, BKey) of
78	    [] ->  %% It can happens if we are bind a main menu item
79		case BKey of
80		    {B1,{_,_}=B2} -> ets:match_object(?KL, {{B1,'_',B2},'_','_'});
81		    _ -> []
82		end;
83	    KInfo -> KInfo
84	end,
85    case KeyInfo of
86	[{_,Action,_}] when not Mode -> Action;
87	[{_,{Menu,_}=Action,_}] -> Action;
88	[] -> next;
89	_ -> other_mode
90    end;
91lookup(Ev, Cmd) ->
92    case ets:lookup(?KL, bindkey(Ev, Cmd)) of
93	[{_,Action,_}] ->
94	    Action;
95	[] -> next
96    end.
97
98%%%
99%%% Binding and unbinding of keys.
100%%%
101
102-record(cs, {info, st, sc, op, action}).
103
104command(Cmd0, St) ->
105    Str = ?__(1, "Select an menu item to bind/unbind a hotkey"),
106    {Cmd, Sc} =
107    case Cmd0 of
108	{_,_} -> Cmd0;
109	_ -> {Cmd0, none}
110    end,
111    Cs = #cs{info=Str, st=St, op=Cmd, sc=Sc},
112    wings_wm:message(Str),
113    {push, fun(Ev) -> event_handler(Ev, Cs) end}.
114
115event_handler(redraw, CS=#cs{st=St}) ->
116    wings:redraw("", St),
117    {replace,fun(Ev) -> event_handler(Ev, CS) end};
118
119event_handler({action, Action}, #cs{op=unbind}) ->
120    do_unbind(Action);
121
122event_handler(Ev=#keyboard{which=menubar}, #cs{op=unbind}) ->
123    do_unbind(event(Ev));
124
125event_handler({action, Action}, CS=#cs{op=bind}) ->
126    wings_wm:message(hotkey_key_message(Action)),
127    wings_wm:dirty(),
128    {replace,fun(Ev) -> event_handler(Ev, CS#cs{action=Action}) end};
129
130event_handler(Ev0=#keyboard{which=menubar},
131	      CS=#cs{op=bind, action=undefined}) ->
132    Action = event(Ev0),
133    wings_wm:message(hotkey_key_message(Action)),
134    wings_wm:dirty(),
135    {replace,fun(Ev) -> event_handler(Ev, CS#cs{action=Action}) end};
136
137event_handler(Ev0= #mousebutton{}, #cs{info=Str, st=St, sc=Sc}) ->
138    case wings_menu:is_popup_event(Ev0) of
139        no ->
140	    wings_wm:message(Str),
141	    wings_wm:dirty(),
142	    keep;
143        {yes,Xglobal,Yglobal,Mod} ->
144            TweakBits = wings_msg:free_rmb_modifier(),
145            case Mod band TweakBits =/= 0 of
146		true ->  case Sc of
147			     none -> wings_tweak:menu(Xglobal, Yglobal);
148			     _ -> wpc_sculpt:sculpt_menu(Xglobal, Yglobal, Sc)
149			 end;
150		false -> wings:popup_menu(Xglobal, Yglobal, St)
151            end
152    end;
153
154event_handler(#mousemotion{}, _) -> keep;
155event_handler(#keyboard{sym=27}, _) ->
156    wings_wm:dirty(),
157    pop;
158
159event_handler(Ev = #keyboard{}, #cs{op=bind, action=Cmd})
160  when Cmd =/= undefined ->
161    case disallow_bind(Ev) of
162        true ->
163            keep;
164        false ->
165            case event(Ev, Cmd) of
166                next ->
167                    case hotkeys_by_commands([Cmd]) of
168                        [] -> do_bind(Ev, Cmd);
169                        Hotkeys ->
170                            HKs = ["[" ++ Hotkey ++"]" || {_, Hotkey, _, _} <- Hotkeys],
171                            Q = ?__(3,"This command is already bound to ") ++ string:join(HKs, ", ") ++
172                                ?__(4," hotkey. Do you want to re-define it?"),
173                            wings_u:yes_no(Q, fun() ->
174                                                      [wings_hotkey:unbind(Key) || {Key, _, _, _} <- Hotkeys],
175                                                      do_bind(Ev, Cmd)
176                                              end)
177                    end;
178                OtherCmd ->
179                    C = wings_util:stringify(OtherCmd),
180                    Q = ?__(1,"This key is already bound to the ") ++ C ++
181                        ?__(2," command. Do you want to re-define it?"),
182                    wings_u:yes_no(Q, fun() ->
183					    case hotkeys_by_commands([OtherCmd]) of
184						[] -> none;
185						HotKeys ->
186						    [wings_hotkey:unbind(Key) || {Key, _, _, _} <- HotKeys],
187						    wings_menu:update_menu_hotkey(OtherCmd, "")
188					    end,
189					    do_bind(Ev, Cmd)
190				      end)
191            end,
192            wings_wm:dirty(),
193            pop
194    end;
195
196event_handler(_Ev, _) ->
197    keep.
198
199disallow_bind(#keyboard{unicode=UC}) when UC =/= 0 -> false;
200disallow_bind(#keyboard{sym=Sym}) when ?SDLK_F1 =< Sym, Sym =< ?SDLK_F15 -> false;
201disallow_bind(#keyboard{sym=Sym}) when ?SDLK_KP0 =< Sym, Sym =< ?SDLK_KP_EQUALS -> false;
202disallow_bind(#keyboard{sym=Sym}) when ?SDLK_HOME =< Sym, Sym =< ?SDLK_PAGEDOWN -> false;
203disallow_bind(_) -> true.
204
205do_unbind(Action) ->
206    case hotkeys_by_commands([Action]) of
207	[] -> none;	%No hotkeys for this entry.
208	Hotkeys ->
209	    hotkey_delete_dialog(Hotkeys, Action)
210    end,
211    wings_wm:dirty(),
212    pop.
213
214do_bind(Ev, Cmd) ->
215    Keyname = bind_from_event(Ev, Cmd),
216    wings_menu:update_menu_hotkey(Cmd, Keyname),
217    ignore.
218
219hotkey_delete_dialog(Hotkeys, Action) ->
220    Fun = fun(Res) ->
221		  wings_menu:update_menu_hotkey(Action, ""),
222		  [wings_hotkey:unbind(K) || {K,true} <- Res],
223		  ignore
224	  end,
225    Dialog = mk_dialog(Hotkeys),
226    wings_dialog:dialog(?__(1,"Delete Hotkeys"), Dialog, Fun).
227
228mk_dialog([{Key,Keyname,Cmd,Src}|T]) ->
229    [mk_key_item(Key, Keyname, Cmd, Src)|mk_dialog(T)];
230mk_dialog([]) ->
231    [separator,{label,?__(1,"Check all hotkeys to be deleted.")}].
232
233mk_key_item(Key, Keyname, Cmd, _Src) when is_tuple(Key) ->
234    {Keyname ++ ": " ++ Cmd,false,[{key,Key}]}.
235
236hotkey_key_message(Cmd) ->
237    [?__(1,"Press the key to bind the \""),
238     wings_util:stringify(Cmd),
239     ?__(2,"\" command to.")].
240
241
242bind_from_event(Ev, Cmd) ->
243    Bkey = bindkey(Ev, Cmd),
244    Key = case Bkey of
245	    {bindkey, Key1} -> Key1;
246	    {bindkey, _Mode, Key1} -> Key1
247    end,
248    ets:insert(?KL, {Bkey,Cmd,user}),
249    format_hotkey(Key, wx).
250
251
252unbind({Key}) ->
253    unbind(Key);
254unbind(Key) ->
255    ets:delete(?KL, Key).
256
257hotkeys_by_commands(Cs) ->
258    hotkeys_by_commands_1(Cs, []).
259
260hotkeys_by_commands_1([C|Cs], Acc) ->
261    Ms = ets:match_object(?KL, {'_',C,'_'}),
262    hotkeys_by_commands_1(Cs, Ms++Acc);
263hotkeys_by_commands_1([], Acc) ->
264    hotkeys_by_commands_2(sort(Acc)).
265
266hotkeys_by_commands_2([{Key0,Cmd,Src}|T]) ->
267    Key = case Key0 of
268	      {bindkey,Key1} -> Key1;
269	      {bindkey,_Mode,Key1} -> Key1
270	  end,
271    Info = {Key0,format_hotkey(Key, wx),wings_util:stringify(Cmd),Src},
272    [Info|hotkeys_by_commands_2(T)];
273hotkeys_by_commands_2([]) -> [].
274
275bind_unicode(Key, Cmd, Source) ->
276    Bkey = bkey(Key, Cmd),
277    ets:insert(?KL, {Bkey,Cmd,Source}),
278    Bkey.
279
280bind_virtual(Key, Mods, Cmd, Source) ->
281    Bkey = bkey({Key,sort(Mods)}, Cmd),
282    ets:insert(?KL, {Bkey,Cmd,Source}),
283    Bkey.
284
285bindkey(#keyboard{sym=?SDLK_TAB=C,mod=Mod}, Cmd) ->
286    bkey({C,sort(modifiers(Mod))}, Cmd);
287bindkey(#keyboard{sym=Sym,mod=Mod,unicode=C}, Cmd) ->
288    case modifiers(Mod) of
289	[] when C =/= 0 ->
290	    bkey(fix_bksp_and_del(Sym, C), Cmd);
291	[shift] when C =/= 0 ->
292	    bkey(C, Cmd);
293	Mods when Sym =/= 0 ->
294	    bkey({Sym,sort(Mods)}, Cmd);
295	Mods ->
296	    bkey({C,sort(Mods)}, Cmd)
297    end.
298
299bkey(Key, {Mode,_}) ->
300    bkey(Key, Mode);
301bkey(Key, Mode) ->
302    case suitable_mode(Mode) of
303	true -> {bindkey,Mode,Key};
304	false -> {bindkey,Key}
305    end.
306
307matching(Names) ->
308    M0 = matching_global(Names) ++ matching_mode(Names),
309    M = wings_util:rel2fam(M0),
310    [{Name,Key} || {Name,[{_,Key}|_]} <- M].
311
312matching_global(Names) ->
313    Spec0 = foldl(fun(N, A) -> {N,A} end, '$1', Names),
314    Spec = [{{{bindkey,'$2'},Spec0,'$3'},
315	     [],
316	     [{{'$1',{{'$3','$2'}}}}]}],
317    [{Name,m_sortkey(Key)} || {Name,Key} <- ets:select(?KL, Spec)].
318
319matching_mode(Names) ->
320    Mode = lists:last(Names),
321    case suitable_mode(Mode) of
322	false -> [];
323	true ->
324	    Spec0 = foldl(fun(N, A) -> {N,A} end, '$1', Names),
325	    Spec = [{{{bindkey,Mode,'$2'},Spec0,'$3'},
326		     [],
327		     [{{'$1',{{'$3','$2'}}}}]}],
328	    [{Name,m_sortkey(Key)} || {Name,Key} <- ets:select(?KL, Spec)]
329    end.
330
331m_sortkey({user,K}) -> {1,K};
332m_sortkey({default,K}) -> {2,K};
333m_sortkey({plugin,K}) -> {3,K}.
334
335
336suitable_mode(light) -> true;
337suitable_mode(vertex) -> true;
338suitable_mode(edge) -> true;
339suitable_mode(face) -> true;
340suitable_mode(body) -> true;
341suitable_mode(Menu) ->
342    case Menu of
343      shape -> false;
344      file -> false;
345      edit -> false;
346      view -> false;
347      select -> false;
348      tools -> false;
349      windows -> false;
350      help -> false;
351      tweak -> false;
352      none -> false;
353      _ -> true
354    end.
355
356%%%
357%%% Make a listing of all hotkeys.
358%%%
359
360listing() ->
361    MatchSpec = ets:fun2ms(fun({{bindkey,K},Cmd,Src}) ->
362				   {all,{K,Cmd,Src}};
363			      ({{bindkey,Mode,K},Cmd,Src}) ->
364				   {Mode,{K,Cmd,Src}}
365			   end),
366    Keys = wings_util:rel2fam(ets:select(?KL, MatchSpec)),
367    listing_1(Keys, []).
368
369listing_1([{Mode,Keys}|T], Acc0) ->
370    Acc = [{table, 2, list_header(Mode), list_keys(Keys)}|Acc0],
371    listing_1(T, Acc);
372listing_1([], Acc) -> reverse(Acc).
373
374list_header(all) ->?STR(list_header,1,"Hotkeys in all modes");
375list_header(body) ->?STR(list_header,2,"Hotkeys in object mode");
376list_header(edge) -> ?STR(list_header,3,"Hotkeys in edge mode");
377list_header(face) -> ?STR(list_header,4,"Hotkeys in face mode");
378list_header(light) -> ?STR(list_header,5,"Hotkeys for lights");
379list_header(vertex) -> ?STR(list_header,6,"Hotkeys for vertices");
380list_header(A) -> atom_to_list(A).
381
382list_keys([{Key,Cmd,Src}|T]) ->
383    KeyStr = format_hotkey(Key, pretty),
384    SrcStr = case Src of
385		 default -> "";
386		 user -> ?STR(list_keys,1," (user-defined)");
387		 plugin -> ?STR(list_keys,2," (plug-in-defined)")
388	     end,
389    [[KeyStr,wings_util:stringify(Cmd) ++ SrcStr]|list_keys(T)];
390list_keys([]) -> [].
391
392
393%%%
394%%% Error handling.
395%%%
396handle_error(Ev, Cmd) ->
397    Key = bindkey(Ev, Cmd),
398    KeyName = format_hotkey(Key, pretty),
399    CmdStr = wings_util:stringify(Cmd),
400    Msg1 = "Executing the command \"" ++ CmdStr ++ "\"\nbound to the hotkey " ++
401	KeyName ++ " caused an error.",
402    Msg2 = "Possible causes:",
403    Msg3 = "The hotkey was defined in a previous version of Wings,"
404	"and the command that it refers to has been changed,"
405	"removed, or renamed in this version of Wings.",
406    Msg4 = "The hotkey refers to a command in a "
407	" plug-in that is currently disabled,"
408	"or to a previous version of a plug-in.",
409    Msg5 = "A bug in the command itself. Try executing the command"
410	"from the menu directly (i.e. not through a hotkey) -"
411	"if it crashes it IS a bug. (Please report it.)",
412    Msg6 = "Delete Hotkey: " ++ KeyName ++ " or press cancel to avoid any changes",
413    Qs = {vframe_dialog,
414	  [{label,Msg1}, separator,
415	   {label,Msg2},
416	   {label,Msg3},
417	   {label,Msg4},
418	   {label,Msg5}, separator,
419	   {label,Msg6}],
420	  [{buttons, [ok, cancel], {key, result}}]},
421    wings_dialog:dialog("Delete HotKey", Qs,
422			fun([{result, ok}]) -> unbind(Key);
423			   (_) -> ignore
424			end).
425
426%%%
427%%% Local functions.
428%%%
429
430%% For the benefit of Mac OS X, but does no harm on other platforms.
431fix_bksp_and_del(?SDLK_DELETE, _) -> ?SDLK_DELETE;
432fix_bksp_and_del(?SDLK_BACKSPACE, _) -> ?SDLK_BACKSPACE;
433fix_bksp_and_del(_, C) -> C.
434
435modifiers(Mod) ->
436    modifiers(Mod, []).
437modifiers(Mod, Acc) when Mod band ?CTRL_BITS =/= 0 ->
438    Pressed = Mod band ?CTRL_BITS,
439    modifiers(Mod bxor Pressed, [ctrl|Acc]);
440modifiers(Mod, Acc) when Mod band ?ALT_BITS =/= 0 ->
441    Pressed = Mod band ?ALT_BITS,
442    modifiers(Mod bxor Pressed, [alt|Acc]);
443modifiers(Mod, Acc) when Mod band ?SHIFT_BITS =/= 0 ->
444    Pressed = Mod band ?SHIFT_BITS,
445    modifiers(Mod bxor Pressed, [shift|Acc]);
446modifiers(Mod, Acc) when Mod band ?KMOD_META =/= 0 ->
447    Pressed = Mod band ?KMOD_META,
448    modifiers(Mod bxor Pressed, [command|Acc]);
449modifiers(_, Acc) -> lists:sort(Acc).
450
451
452%%%
453%%% Format hotkeys.
454%%%
455
456modname(Mods, Style) ->
457    case os:type() of
458	{unix,darwin} ->
459	    case Style of
460		wx -> mac_modname_wx(Mods);
461		pretty -> mac_modname(Mods)
462	    end;
463	_ ->
464	    modname_1(Mods)
465    end.
466
467modname_1([command|T]) -> "Meta+" ++modname_1(T);
468modname_1([Mod|T]) -> wings_s:modkey(Mod) ++ "+" ++modname_1(T);
469modname_1([]) -> [].
470
471mac_modname_wx([ctrl|T]) -> "rawctrl+" ++ mac_modname_wx(T);
472mac_modname_wx([command|T]) -> "Ctrl+" ++ mac_modname_wx(T);
473mac_modname_wx([alt|T]) -> "Alt+" ++ mac_modname_wx(T);
474mac_modname_wx([shift|T]) -> "Shift+" ++ mac_modname_wx(T);
475mac_modname_wx([]) -> [].
476
477mac_modname(Mods0) ->
478    Mods1 = [{mac_mod_sortkey(M),M} || M <- Mods0],
479    Mods2 = sort(Mods1),
480    [wings_s:mac_mod(M) || {_,M} <- Mods2].
481
482mac_mod_sortkey(shift) -> 1;
483mac_mod_sortkey(alt) -> 2;
484mac_mod_sortkey(ctrl) -> 3;
485mac_mod_sortkey(command) -> 4.
486
487keyname($\b, _Style) -> ?STR(keyname,1,"Bksp");
488keyname($\t, _Style) -> ?STR(keyname,2,"Tab");
489keyname($\s, _Style) -> ?STR(keyname,3,"Space");
490keyname(C, _Style) when $a =< C, C =< $z -> [C-32];
491keyname(C, wx) when $A =< C, C =< $Z ->
492    ?STR(keyname,4,"Shift+") ++ [C];
493keyname(C, pretty) when $A =< C, C =< $Z ->
494    case os:type() of
495	{unix,darwin} ->
496	    wings_s:modkey(shift)++[C];
497	_ ->
498	    ?STR(keyname,4,"Shift+") ++ [C]
499    end;
500keyname(C, _Style) when is_integer(C), C < 256 -> [C];
501keyname(C, _Style) when is_integer(C), 63236 =< C, C =< 63247 ->
502    [$F|integer_to_list(C-63235)];
503keyname(C, _Style) -> [C].
504
505vkeyname(?SDLK_BACKSPACE) -> ?STR(vkeyname,1,"Bksp");
506vkeyname(?SDLK_TAB) -> ?STR(vkeyname,2,"Tab");
507vkeyname(?SDLK_RETURN) -> ?STR(vkeyname,3,"Enter");
508vkeyname(?SDLK_PAUSE) -> ?STR(vkeyname,4,"Pause");
509vkeyname(?SDLK_ESCAPE) ->?STR(vkeyname,5,"Esc");
510vkeyname(?SDLK_SPACE) -> ?STR(vkeyname,6,"Space");
511vkeyname(?SDLK_DELETE) -> ?STR(vkeyname,7,"Delete");
512vkeyname(C) when $a =< C, C =< $z-> [C-32];
513vkeyname(C) when $\s < C, C < 256 -> [C];
514vkeyname(C) when ?SDLK_KP0 < C, C < ?SDLK_KP9 -> [C-?SDLK_KP0+$0];
515vkeyname(C) when ?SDLK_F1 =< C, C =< ?SDLK_F15 ->
516    [$F|integer_to_list(C-?SDLK_F1+1)];
517vkeyname(?SDLK_KP_PERIOD) -> ?STR(vkeyname,8,"Del");
518vkeyname(?SDLK_KP_DIVIDE) -> ?STR(vkeyname,9,"Div");
519vkeyname(?SDLK_KP_MULTIPLY) -> ?STR(vkeyname,10,"Mul");
520vkeyname(?SDLK_KP_MINUS) -> ?STR(vkeyname,11,"-");
521vkeyname(?SDLK_KP_PLUS) -> ?STR(vkeyname,12,"+");
522vkeyname(?SDLK_KP_ENTER) -> ?STR(vkeyname,13,"Enter");
523vkeyname(?SDLK_KP_EQUALS) ->?STR(vkeyname,14,"=");
524vkeyname(?SDLK_UP) -> ?STR(vkeyname,15,"Up");
525vkeyname(?SDLK_DOWN) -> ?STR(vkeyname,16,"Down");
526vkeyname(?SDLK_RIGHT) -> ?STR(vkeyname,17,"Right");
527vkeyname(?SDLK_LEFT) -> ?STR(vkeyname,18,"Left");
528vkeyname(?SDLK_INSERT) -> ?STR(vkeyname,19,"Insert");
529vkeyname(?SDLK_HOME) -> ?STR(vkeyname,20,"Home");
530vkeyname(?SDLK_END) -> ?STR(vkeyname,21,"End");
531vkeyname(?SDLK_PAGEUP) -> ?STR(vkeyname,22,"Page Up");
532vkeyname(?SDLK_PAGEDOWN) ->?STR(vkeyname,23,"Page Down");
533vkeyname(C) -> [C].
534
535%%%
536%%% Default keybindings.
537%%%
538
539set_default() ->
540    foreach(
541      fun({{Key,List0},Action}) when is_integer(Key) ->
542	      List = convert_modifiers(List0),
543	      ets:insert(wings_state, {{bindkey,{Key,sort(List)}},
544				       Action,default});
545	 ({Key,Action}) when is_integer(Key) ->
546	      ets:insert(wings_state, {{bindkey,Key},
547				       Action,default});
548	 ({Mode,{Key,List0},Action}) when is_integer(Key) ->
549	      List = convert_modifiers(List0),
550	      ets:insert(wings_state, {{bindkey,Mode,{Key,sort(List)}},
551				       Action,default});
552	 ({Mode,Key,Action}) when is_integer(Key) ->
553	      ets:insert(wings_state, {{bindkey,Mode,Key},
554				       Action,default})
555      end, default_keybindings()).
556
557convert_modifiers(Mod) ->
558    case os:type() of
559	{unix,darwin} ->
560	    map(fun(ctrl) -> command;
561		   (Other) -> Other end, Mod);
562	_ -> Mod
563    end.
564
565default_keybindings() ->
566    [{{$a,[ctrl]},          {select,all}},
567     {{$i,[ctrl,shift]},    {select,inverse}},
568     {{$l,[ctrl]},          {file,merge}},
569     {{$n,[ctrl]},          {file,new}},
570     {{$o,[ctrl]},          {file,open}},
571     {{$q,[ctrl]},          {file,quit}},
572     {{$s,[ctrl,shift]},    {file,save_as}},
573     {{$s,[ctrl]},          {file,save}},
574     {{$z,[alt,ctrl]},      {edit,undo}},
575     {{$z,[ctrl,shift]},    {edit,redo}},
576     {{$z,[ctrl]},          {edit,undo_toggle}},
577     {{?SDLK_KP_PLUS,[]},   {select,more}},
578     {{?SDLK_KP_MINUS,[]},  {select,less}},
579     {{?SDLK_F1,[]},        {tweak,{axis_constraint,x}}},
580     {{?SDLK_F2,[]},        {tweak,{axis_constraint,y}}},
581     {{?SDLK_F3,[]},        {tweak,{axis_constraint,z}}},
582     {{?SDLK_F6,[]},        {select,{edge_loop,prev_edge_loop}}},
583     {{?SDLK_F7,[]},        {select,{edge_loop,next_edge_loop}}},
584     {{?SDLK_F5,[]},        {select,{by,{faces_with,5}}}},
585     {{?SDLK_TAB,[]},       {view,workmode}},
586     {{?SDLK_TAB,[shift]},  {view,quick_preview}},
587
588     {{?SDLK_INSERT,[ctrl]}, {hotkey, bind}},
589     {{$8, [ctrl, alt]},     {hotkey, bind}},   %% Swedish keyboards
590     {{$8, [ctrl]},          {hotkey, bind}},
591     {{?SDLK_DELETE,[ctrl]}, {hotkey, unbind}},
592     {{$9, [ctrl, alt]},     {hotkey, unbind}}, %% Swedish keyboards
593     {{$9, [ctrl]},          {hotkey, unbind}},
594
595     {$\s,              {select,deselect}},
596     {$a,               {view,highlight_aim}},
597     {$A,               {view,frame}},
598     {$b,               {select,body}},
599     {{$d,[ctrl]},      {edit,repeat}},
600     {$d,               {edit,repeat_args}},
601     {$D,               {edit,repeat_drag}},
602     {$e,               {select,edge}},
603
604     {$f,               {select,face}},
605     {$g,               {select,{edge_loop,edge_ring}}},
606     {{$g,[alt]},       {select,{edge_loop,edge_ring_incr}}},
607     {{$g,[alt,ctrl]},  {select,{edge_loop,edge_ring_decr}}},
608     {$i,               {select,similar}},
609     {$l,               {select,{edge_loop,edge_loop}}},
610     {$L,		{select,{edge_loop,edge_loop_to_region}}},
611     {{$l,[alt]},       {select,{edge_loop,edge_link_incr}}},
612     {{$l,[alt,ctrl]},  {select,{edge_loop,edge_link_decr}}},
613     {$o,               {view,orthogonal_view}},
614     {$r,               {view,reset}},
615     {$u,               {view,auto_rotate}},
616     {$v,               {select,vertex}},
617     {$w,               {view,toggle_wireframe}},
618     {$x,               {view,{along,x}}},
619     {$y,               {view,{along,y}}},
620     {$z,               {view,{along,z}}},
621     {$X,               {view,{along,neg_x}}},
622     {$Y,               {view,{along,neg_y}}},
623     {$Z,               {view,{along,neg_z}}},
624     {$+,               {select,more}},
625     {$=,               {select,more}},
626     {$-,               {select,less}},
627
628     %% Mode-specific bindings.
629     {edge,$2,		{edge,{cut,2}}},
630     {edge,$3,		{edge,{cut,3}}},
631     {edge,$4,		{edge,{cut,4}}},
632     {edge,$5,		{edge,{cut,5}}},
633     {edge,$6,		{edge,{cut,6}}},
634     {edge,$7,		{edge,{cut,7}}},
635     {edge,$8,		{edge,{cut,8}}},
636     {edge,$9,		{edge,{cut,9}}},
637     {edge,$0,		{edge,{cut,10}}},
638
639     {vertex,$c,	{vertex,connect}},
640     {edge,$c,		{edge,connect}},
641
642     {vertex,$\b,	{vertex,collapse}},
643     {edge,$\b,		{edge,dissolve}},
644     {face,$\b,		{face,dissolve}},
645     {body,$\b,		{body,delete}},
646
647     {vertex,{?SDLK_DELETE,[]},	{vertex,dissolve}},
648     {edge,{?SDLK_DELETE,[]},	{edge,dissolve}},
649     {face,{?SDLK_DELETE,[]},	{face,dissolve}},
650     {body,{?SDLK_DELETE,[]},	{body,delete}},
651
652     {vertex,{?SDLK_KP_PERIOD,[]},{vertex,dissolve}},
653     {edge,{?SDLK_KP_PERIOD,[]},  {edge,dissolve}},
654     {face,{?SDLK_KP_PERIOD,[]},  {face,dissolve}},
655     {body,{?SDLK_KP_PERIOD,[]},  {body,delete}},
656
657     {light,$\b,	{light,delete}},
658     {light,{?SDLK_DELETE,[]},	{light,delete}},
659     {light,{?SDLK_KP_PERIOD,[]},  {light,delete}},
660
661     {face,$s,		{face,smooth}},
662     {body,$s,		{body,smooth}}
663    ].
664