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