1%% 2%% wings_menu.erl -- 3%% 4%% Implementation of pulldown and popup menus. 5%% 6%% Copyright (c) 2001-2011 Bjorn Gustavsson 7%% 2014 Dan Gudmundsson 8%% 9%% See the file "license.terms" for information on usage and redistribution 10%% of this file, and for a DISCLAIMER OF ALL WARRANTIES. 11%% 12%% $Id$ 13%% 14 15-module(wings_menu). 16-export([is_popup_event/1,popup_menu/4,build_command/2, 17 kill_menus/0, predefined_item/2]). 18-export([setup_menus/2, id_to_name/1, check_item/1, str_clean/1]). 19-export([update_menu/3, update_menu/4, 20 update_menu_enabled/3, update_menu_hotkey/2]). 21 22%% Reuse in tweak windows 23-export([normalize_menu_wx/3, calc_min_sizes/4, 24 format_hotkeys/2, setup_popup/7, 25 setup_colors/3, 26 entry_msg/2, entry_cmd/2, entry_wins/1, set_entry_id/2]). 27 28-define(NEED_ESDL, 1). 29-include("wings.hrl"). 30-import(lists, [foldl/3,reverse/1]). 31 32-define(REPEAT, 99). 33-define(REPEAT_ARGS, 98). 34-define(REPEAT_DRAG, 97). 35-define(SEL_VERTEX, 96). 36-define(SEL_EDGE, 95). 37-define(SEL_FACE, 94). 38-define(SEL_BODY, 93). 39-define(VIEW_WORKMODE, 92). 40-define(VIEW_ORTHO, 91). 41-define(VIEW_AXES, 90). 42-define(VIEW_GROUND, 89). 43-define(CHECK_MARK, [10003,32]). % 10003 = unicode for check mark character 44 45%% menu entries, the name, type and opts are used differently 46%% depending on if they are used in top_level menus or pop menus 47%% 48%% The Help field is normalized to 49%% String for plain (pull-down) menus 50%% {L,M,R} for pop-up menus 51%% 52-record(menu, {wxid, % wxId number 53 object, % wxObject 54 name, % menu_name atom or fun {atom, fun} 55 type=menu, % menu | submenu | separator 56 desc, % Displayed string 57 help = [], % Strings 58 opts = [], % options 59 hk=[]}). % hotkey 60 61is_popup_event(#mousebutton{button=3,x=X,y=Y,state=?SDL_RELEASED,mod=Mod}) -> 62 {yes,X,Y,Mod}; 63is_popup_event(#wx{obj=Win, event=#wxMouse{type=right_up, x=X0, y=Y0}}) -> 64 {yes, wxWindow:clientToScreen(Win, X0, Y0)}; 65is_popup_event(#wx{event=#wxCommand{type=command_right_click}}) -> 66 {yes, wx_misc:getMousePosition()}; 67is_popup_event(_Event) -> 68 no. 69 70popup_menu(X, Y, Name, Menu) %% Should be removed, the next should be used !! 71 when is_number(X), is_number(Y) -> 72 Win = wings_wm:this_win(), 73 wx_popup_menu_init(Win, wxWindow:clientToScreen(Win, X, Y), [Name], Menu); 74popup_menu(Parent, {_,_} = GlobalPos, Name, Menu) -> 75 wx_popup_menu_init(Parent, GlobalPos, [Name], Menu). 76 77kill_menus() -> 78 case wings_wm:is_window(menu_killer) of 79 true -> wings_wm:send(menu_killer, kill_menus); 80 false -> ok 81 end. 82 83match_hotkey([Cmd1, Cmd2, Cmd3], HotKeys, Opt) -> 84 [match_hotkey(Cmd1, HotKeys, Opt), 85 match_hotkey(Cmd2, HotKeys, Opt), 86 match_hotkey(Cmd3, HotKeys, Opt)]; 87match_hotkey(Name, [{{_,Name},Key}|_], false) -> Key; 88match_hotkey(Name, [{Name,Key}|_], false) -> Key; 89match_hotkey(Name, [{{Name,false},Key}|_], true) -> Key; 90match_hotkey(Name, [{{Name,true},Key}|_], true) -> Key; 91match_hotkey(Name, [_|T], OptionBox) -> 92 match_hotkey(Name, T, OptionBox); 93match_hotkey(_N, [], _) -> 94 []. 95 96reduce_name([Cmd1, Cmd2, Cmd3]) -> 97 [reduce_name(Cmd1), 98 reduce_name(Cmd2), 99 reduce_name(Cmd3)]; 100reduce_name({'ASK',_}=Ask) -> Ask; 101reduce_name({tweak,Val}) -> Val; 102reduce_name({Key,{_,_}=Tuple}) when is_atom(Key) -> 103 reduce_name(Tuple); 104reduce_name({Key,Val}) when is_atom(Key) -> Val; 105reduce_name(Name) -> Name. 106 107is_magnet_active(Ps, Flags) when is_list(Flags) -> 108 have_magnet(Ps) andalso have_magnet(Flags); 109is_magnet_active(Ps, Bool) when is_boolean(Bool) -> 110 Bool andalso have_magnet(Ps). 111 112%% key(#keyboard{sym=27}) -> cancel; 113%% key(#keyboard{sym=?SDLK_INSERT}) -> insert; 114%% key(#keyboard{unicode=$/}) -> insert; 115%% key(#keyboard{sym=?SDLK_DELETE}) -> delete; 116%% key(#keyboard{unicode=$\\}) -> delete; 117%% key(_) -> none. 118 119insert_magnet_flags(Action, true) -> 120 insert_magnet_flags_0(Action); 121insert_magnet_flags(Action,_) -> Action. 122 123insert_magnet_flags_0({'ASK',{PickList,Done,Flags}}=Cmd) -> 124 case have_magnet(Flags) of 125 false -> Cmd; 126 true -> {'ASK',{PickList++[magnet],Done,Flags}} 127 end; 128insert_magnet_flags_0(Tuple0) when is_tuple(Tuple0) -> 129 Tuple = [insert_magnet_flags_0(El) || El <- tuple_to_list(Tuple0)], 130 list_to_tuple(Tuple); 131insert_magnet_flags_0(Term) -> Term. 132 133build_command(Name, Names) -> 134 foldl(fun(N, Use={N, _}) -> Use; 135 (N, A) -> {N,A} end, 136 Name, Names). 137build_command(Name, Names, true) -> 138 build_command({'ASK',{[magnet],[Name]}}, Names); 139build_command(Name, Names, false) -> 140 build_command(Name, Names). 141 142build_names(Term, Acc) 143 when not is_tuple(Term) -> 144 lists:reverse([Term|Acc]); 145build_names({Term,Term2}, Acc) 146 when is_boolean(Term2) -> 147 lists:reverse([Term|Acc]); 148build_names({Term1,Term2}, Acc) -> 149 build_names(Term2, [Term1|Acc]). 150 151have_option_box(Ps) -> 152 proplists:is_defined(option, Ps). 153 154have_color(Ps) -> 155 proplists:is_defined(color, Ps). 156 157have_magnet(Ps) -> 158 proplists:is_defined(magnet, Ps). 159 160have_magnet(_, true) -> activated; 161have_magnet(Ps, _) -> 162 proplists:is_defined(magnet, Ps). 163 164wx_popup_menu_init(Parent,GlobalPos,Names,Menus0) -> 165 case wings_wm:grabbed_focus_window() of 166 dialog_blanket -> 167 wings_wm:send(dialog_blanket, user_attention); 168 _ -> 169 Owner = wings_wm:this(), 170 Entries = wx_popup_menu(Parent,GlobalPos,Names,Menus0,false,dialog_blanket), 171 {TopW,TopH} = wings_wm:top_size(), 172 Op = {push, fun(Ev) -> popup_event_handler(Ev, {Parent,Owner}, Entries) end}, 173 wings_wm:new(dialog_blanket, {0,0,highest}, {TopW,TopH}, Op), 174 wings_wm:grab_focus(dialog_blanket) 175 end, 176 keep. 177 178wx_popup_menu(Parent,Pos,Names,Menus0,Magnet,Owner) -> 179 Entries0 = make_entries(Names, Menus0, pretty), 180 {Entries1,_} = lists:foldl(fun(ME, {List, Id}) -> 181 {[ME#menu{wxid=Id}, 182 ME#menu{wxid=Id+1, type=opt}|List], 183 Id+2} 184 end, {[],500}, Entries0), 185 Entries = reverse(Entries1), 186 MEs = [ME#menu{name=undefined} || ME <- Entries], 187 MenuData = wx:batch(fun() -> setup_dialog(Parent, MEs, Magnet, Pos, ?GET(menu_cache)) end), 188 Env = wx:get_env(), 189 spawn_link(fun() -> 190 try 191 wx:set_env(Env), 192 register(wings_menu_process, self()), 193 send_enter_window(MenuData, Pos), 194 popup_events(MenuData, Magnet, undefined, Names, Owner), 195 close_menu_frame(MenuData), 196 wxWindow:setFocus(Parent) 197 catch _:Reason:ST -> 198 io:format("CRASH ~p ~p~n",[Reason, ST]) 199 end, 200 normal 201 end), 202 Entries. 203 204setup_dialog(Parent, Entries, Magnet, ScreenPos, ignore) -> 205 do_setup_dialog(Parent, Entries, Magnet, ScreenPos); 206setup_dialog(Parent, Entries, Magnet, ScreenPos, undefined) -> 207 setup_dialog(Parent, Entries, Magnet, ScreenPos, #{}); 208setup_dialog(Parent, Entries, Magnet, {X,Y} = ScreenPos, Cache) -> 209 TopParent = get_toplevel(Parent), 210 case maps:get({Entries, TopParent}, Cache, undefined) of 211 undefined -> 212 MenuData = do_setup_dialog(TopParent, Entries, Magnet, ScreenPos), 213 case maps:get(overlay, MenuData) of 214 none -> 215 %% Note we leak popup windows menus here, 216 %% like autouv, should it be cleaned up? 217 ?SET(menu_cache, Cache#{{Entries, TopParent} => MenuData}); 218 _ -> %% Only for popuptransient windows 219 ?SET(menu_cache,ignore) 220 end, 221 MenuData; 222 #{frame := Frame} = MenuData -> 223 Pos = fit_menu_on_display(Frame,{X-25,Y-15}), 224 wxWindow:move(Frame, Pos), 225 wxPopupTransientWindow:popup(Frame), 226 MenuData 227 end. 228 229do_setup_dialog(TopParent, Entries0, Magnet, {X0,Y0}=ScreenPos) -> 230 {Overlay, Frame, KbdFocus} = make_menu_frame(TopParent, ScreenPos), 231 X1 = X0-25, 232 Y1 = Y0-15, 233 Panel = wxPanel:new(Frame), 234 wxWindow:setFont(Panel, ?GET(system_font_wx)), 235 {{R,G,B,A},FG} = {colorB(menu_color),colorB(menu_text)}, 236 Cols = {{R,G,B,A}, FG}, 237 catch wxFrame:setTransparent(Frame, 240), 238 wxWindow:setBackgroundColour(Frame, {R,G,B, 240}), 239 wxWindow:setBackgroundColour(Panel, {R,G,B, 240}), 240 Main = wxBoxSizer:new(?wxHORIZONTAL), 241 Sizer = wxBoxSizer:new(?wxVERTICAL), 242 MinHSzs = calc_min_sizes(Entries0, Panel, 5, 5), 243 Entries = setup_popup(Entries0, Sizer, MinHSzs, Cols, Panel, Magnet, []), 244 wxSizer:setMinSize(Sizer, 225, -1), 245 wxSizer:addSpacer(Main, 5), 246 wxSizer:add(Main, Sizer, [{proportion, 1}, {border, 5}, {flag, ?wxEXPAND bor ?wxALL}]), 247 wxSizer:addSpacer(Main, 5), 248 wxPanel:setSizer(Panel, Main), 249 wxSizer:fit(Main, Panel), 250 wxWindow:setClientSize(Frame, wxWindow:getSize(Panel)), 251 wxWindow:move(Frame, fit_menu_on_display(Frame,{X1,Y1})), 252 show_menu_frame(Overlay, Frame, KbdFocus), 253 #{overlay=>Overlay, frame=>Frame, panel=>Panel, entries=>Entries, colors=>Cols}. 254 255get_toplevel(Win) -> 256 Parent = try wxWindow:isTopLevel(Win) of 257 true -> wx:null(); 258 false -> wxWindow:getParent(Win) 259 catch _:_ -> 260 no_exists 261 end, 262 case wx:is_null(Parent) of 263 true -> Win; 264 false -> get_toplevel(Parent) 265 end. 266 267show_menu_frame(none, Frame, _Focus) -> 268 wxPopupTransientWindow:popup(Frame); 269show_menu_frame(_, Frame, KbdFocus) -> 270 KbdFocus(), %% Set keyboard focus so we can catch ESC on mac 271 wxFrame:show(Frame). 272 273close_menu_frame(#{overlay:=none, frame:=Frame}) -> 274 wxPopupTransientWindow:dismiss(Frame); 275close_menu_frame(#{overlay:=Overlay, frame:=Frame}) -> 276 wxWindow:hide(Frame), 277 wxFrame:destroy(Overlay). 278 279send_enter_window(#{panel:=Panel}, ScreenPos) -> 280 %% Color active menuitem 281 {MX, MY} = wxWindow:screenToClient(Panel, ScreenPos), 282 case find_active_panel(Panel, MX, MY) of 283 {false,_} -> ignore; 284 {ActId, ActPanel} -> 285 self() ! #wx{id=ActId, obj=ActPanel, 286 event=#wxMouse{type=enter_window,x=0,y=0, 287 leftDown=false,middleDown=false,rightDown=false, 288 controlDown=false,shiftDown=false,altDown=false,metaDown=false, 289 wheelRotation=0, wheelDelta=0, linesPerAction=0}} 290 end. 291 292make_menu_frame(Parent, Pos) -> 293 case os:type() of 294 {_, darwin} -> 295 %% PopupTransientWindow did not work on Mac on wxWidgets 3.1.3 atleast 296 %% So we make our on own overlay handling there 297 %% on the other transparent windows don't work on some linux'es 298 %% So we can't use this for all OS's 299 make_overlay(Parent,Pos); 300 _ -> 301 Frame = wxPopupTransientWindow:new(Parent, [{style, ?wxBORDER_SIMPLE}]), 302 EvH = fun(Ev, _) -> catch wings_menu_process ! Ev end, 303 wxPopupTransientWindow:connect(Frame, show, [{callback, EvH}]), 304 {none, Frame, none} 305 end. 306 307make_overlay(Parent, ScreenPos) -> 308 OL = wxFrame:new(), 309 Flags = ?wxFRAME_TOOL_WINDOW bor ?wxFRAME_FLOAT_ON_PARENT bor ?wxFRAME_NO_TASKBAR, 310 TCol = case {os:type(), {?wxMAJOR_VERSION, ?wxMINOR_VERSION}} of 311 {{_, linux}, Ver} when Ver > {3,0} -> 312 wxFrame:setBackgroundStyle(OL, 3), %% ?wxBG_STYLE_TRANSPARENT 313 0; 314 {{_, darwin}, _} -> 315 13; %% No events received if completely transparent ?? 316 _ -> 317 1 318 end, 319 DisplayID = wxDisplay:getFromPoint(ScreenPos), 320 Display = wxDisplay_new(DisplayID), 321 {DX,DY,DW,DH} = wxDisplay:getClientArea(Display), 322 true = wxFrame:create(OL, Parent, -1, "", [{pos,{DX,DY}},{size, {DW,DH}},{style, Flags}]), 323 wxFrame:setBackgroundColour(OL, {0,0,0,TCol}), 324 catch wxFrame:setTransparent(OL, TCol), 325 Panel = wxWindow:new(OL, -1, [{size, {DW,DH}}, {style, ?wxWANTS_CHARS}]), 326 EvH = fun(#wx{event=#wxKey{keyCode=Key}}, _) -> 327 if Key =:= ?WXK_ESCAPE -> wings_menu_process ! cancel; 328 true -> ok 329 end; 330 (#wx{event=#wxMouse{type=right_up,y=Y, x=X}}, _) -> 331 wings_menu_process ! {move, wxWindow:clientToScreen(OL,{X,Y})}; 332 (_Ev, Obj) -> 333 wxEvent:skip(Obj), 334 %% ?dbg("Cancel menu: ~w~n",[_Ev]), 335 catch wings_menu_process ! cancel 336 end, 337 [wxWindow:connect(Panel, Ev, [{callback, EvH}]) || 338 Ev <- [left_up, middle_up, right_up, char, char_hook]], 339 wxFrame:show(OL), 340 341 FrameFlags = case os:type() of 342 {_, linux} -> ?wxSTAY_ON_TOP; %% Hmm needed for some reason 343 _ -> ?wxFRAME_FLOAT_ON_PARENT 344 end, 345 Flags = ?wxFRAME_TOOL_WINDOW bor FrameFlags bor ?wxFRAME_NO_TASKBAR, 346 Frame = wxFrame:new(OL, -1, "", [{style, FrameFlags}]), 347 348 {OL, Frame, fun() -> wxWindow:setFocus(Panel) end}. 349 350wxDisplay_new(DisplayID) -> 351 New = wings_u:id(new), 352 try %% new 353 wxDisplay:New(DisplayID) 354 catch _:_ -> %% old 355 wxDisplay:New([{n, DisplayID}]) 356 end. 357 358popup_events(MenuData, Magnet, Previous, Ns, Owner) -> 359 receive 360 #wx{id=Id, obj=Obj,event=#wxMouse{type=enter_window}} -> 361 Set = fun() -> 362 if Obj =/= Previous -> 363 {BG,FG} = maps:get(colors, MenuData), 364 setup_colors(Previous, BG, FG), 365 setup_colors(Obj, colorB(menu_hilite),colorB(menu_hilited_text)), 366 Obj; 367 true -> 368 Previous 369 end 370 end, 371 Line = wx:batch(Set), 372 wings_status:message(Owner, entry_msg(Id, maps:get(entries, MenuData)), ""), 373 popup_events(MenuData, Magnet, Line, Ns, Owner); 374 #wx{id=Id0, event=Ev=#wxMouse{y=Y, x=X}} -> 375 Id = case Id0 > 0 orelse find_active_panel(maps:get(panel, MenuData), X, Y) of 376 true -> Id0; 377 {false, _} = No -> No; 378 {AId, _} -> AId 379 end, 380 %% ?dbg("Ev: ~w ~w ~w~n",[Ev, Id0, Id]), 381 case Id of 382 {false, outside} -> 383 {BG,FG} = maps:get(colors, MenuData), 384 setup_colors(Previous, BG, FG), 385 wings_wm:psend(Owner, cancel); 386 {false, inside} -> 387 popup_events(MenuData, Magnet, Previous, Ns, Owner); 388 Active when is_integer(Active) -> 389 {BG,FG} = maps:get(colors, MenuData), 390 setup_colors(Previous, BG, FG), 391 MagnetClick = Magnet orelse 392 magnet_pressed(wings_msg:free_rmb_modifier(), Ev), 393 wings_wm:psend(Owner, {click, Id, {mouse_button(Ev), MagnetClick}, Ns}) 394 end; 395 cancel -> 396 {BG,FG} = maps:get(colors, MenuData), 397 setup_colors(Previous, BG, FG), 398 wings_wm:psend(Owner, cancel); 399 {move, {X,Y}} -> 400 Frame = maps:get(frame, MenuData), 401 Pos = fit_menu_on_display(Frame,{X-25,Y-15}), 402 wxWindow:move(Frame, Pos), 403 wings_wm:psend(Owner, redraw), 404 popup_events(MenuData, Magnet, Previous, Ns, Owner); 405 #wx{event=#wxShow{show=false}} -> 406 {BG,FG} = maps:get(colors, MenuData), 407 setup_colors(Previous, BG, FG), 408 wings_wm:psend(Owner, cancel); 409 _Ev -> 410 %% ?dbg("Got Ev ~p ~n", [_Ev]), 411 popup_events(MenuData, Magnet, Previous, Ns, Owner) 412 end. 413 414fit_menu_on_display(Frame,{MX,MY} = Pos) -> 415 {WW,WH} = wxWindow:getSize(Frame), 416 DisplayID = wxDisplay:getFromPoint(Pos), 417 Display = wxDisplay_new(DisplayID), 418 {DX,DY,DW,DH} = wxDisplay:getClientArea(Display), 419 MaxW = (DX+DW), 420 PX = if (MX+WW) > MaxW -> MaxW-WW-2; 421 true -> MX 422 end, 423 MaxH = (DY+DH), 424 PY = if (MY+WH) > MaxH -> MaxH-WH-2; 425 true -> MY 426 end, 427 wxDisplay:destroy(Display), 428 {PX,PY}. 429 430%% If the mouse is not moved after popping up the menu, the menu entry 431%% is not active, find_active_panel finds the active row. 432find_active_panel(Panel, MX, MY) -> 433 {_,_,WinW,WinH} = wxWindow:getRect(Panel), 434 case MX > 0 andalso MX < WinW andalso MY > 0 andalso MY < WinH of 435 true -> find_active_panel_1(Panel, MY); 436 false -> {false, outside} 437 end. 438 439find_active_panel_1(Panel, MY) -> 440 MainSizer = wxWindow:getSizer(Panel), 441 [_,SizerItem,_] = wxSizer:getChildren(MainSizer), 442 Sizer = wxSizerItem:getSizer(SizerItem), 443 Children = wxSizer:getChildren(Sizer), 444 Active = fun(SItem, _) -> 445 {_, SY, _, SH} = wxSizerItem:getRect(SItem), 446 case MY > (SY) andalso MY < (SY+SH) of 447 false -> {false, outside}; 448 true -> 449 Active = wxSizerItem:getWindow(SItem), 450 case wx:is_null(Active) orelse wxWindow:getId(Active) of 451 true -> {false, inside}; 452 Id when Id < 0 -> {false, inside}; 453 Id -> throw({Id, wx:typeCast(Active,wxPanel)}) 454 end 455 end 456 end, 457 try 458 lists:foldl(Active, {false, inside}, Children) 459 catch Found -> 460 Found 461 end. 462 463magnet_pressed(?CTRL_BITS, #wxMouse{controlDown=true}) -> true; 464magnet_pressed(?ALT_BITS, #wxMouse{altDown=true}) -> true; 465magnet_pressed(?META_BITS, #wxMouse{metaDown=true}) -> true; 466magnet_pressed(_, _) -> false. 467 468mouse_index(left_up) -> 1; 469mouse_index(middle_up) -> 2; 470mouse_index(right_up) -> 3. 471 472mouse_button(#wxMouse{type=What, controlDown = Ctrl, altDown = Alt, metaDown = Meta}) -> 473 case wings_pref:get_value(num_buttons) of 474 1 -> 475 case {What,Alt,Ctrl} of 476 {left_up,true,false} -> middle_up; 477 {left_up,false,true} -> right_up; 478 _ -> What 479 end; 480 2 -> 481 case {What,(Ctrl or Meta)} of 482 {right_up,true} -> middle_up; 483 _ -> What 484 end; 485 _ -> What 486 end. 487 488popup_event_handler(cancel, _, _) -> 489 wings_wm:release_focus(), 490 delete; 491popup_event_handler({click, Id, Click, Ns}, {Parent,Owner}, Entries0) -> 492 case popup_result(lists:keyfind(Id, 2, Entries0), Click, Ns, Owner) of 493 pop -> 494 wings_wm:release_focus(), 495 delete; 496 {submenu, Names, Menus, MagnetClick} -> 497 {_, X,Y} = wings_io:get_mouse_state(), 498 Entries = wx_popup_menu(Parent, {X,Y}, Names, Menus, MagnetClick, dialog_blanket), 499 {replace, fun(Ev) -> popup_event_handler(Ev, {Parent,Owner}, Entries) end} 500 end; 501popup_event_handler(redraw,_,_) -> 502 keep; 503popup_event_handler(#keyboard{sym=?SDLK_ESCAPE}, {_, _}, _) -> 504 wings_menu_process ! cancel, %% Keyboard focus fails on mac wxWidgets-3.1.3 505 keep; 506popup_event_handler(#mousemotion{}, _, _) -> 507 keep; 508popup_event_handler(_Ev,_,_) -> 509 %% io:format("Hmm ~p ~n",[_Ev]), 510 keep. 511 512popup_result(#menu{type=submenu, name={Name, Menus}, opts=Opts}, {What, MagnetClick}, Names0, Owner) -> 513 Names = [Name|Names0], 514 case is_function(Menus) of 515 true -> 516 case Menus(mouse_index(What), Names) of 517 ignore -> 518 {submenu, Names, Menus(1, Names), MagnetClick}; 519 Next when is_list(Next) -> 520 {submenu, Names, Next, MagnetClick}; 521 Action when is_tuple(Action); is_atom(Action) -> 522 Magnet = is_magnet_active(Opts, MagnetClick), 523 Cmd = insert_magnet_flags(Action, Magnet), 524 Cmd =:= ignore orelse wings_wm:send_after_redraw(Owner, {action,Cmd}), 525 pop 526 end; 527 false -> 528 {submenu, Names, Menus, MagnetClick} 529 end; 530 531popup_result(#menu{type=opt,name=Name}, _Click, Names, Owner) -> 532 Cmd = build_command({Name,true}, Names), 533 Cmd =:= ignore orelse wings_wm:send_after_redraw(Owner, {action,Cmd}), 534 pop; 535popup_result(#menu{type=menu,name=CmdFun,opts=Opts}, {Click,MagnetClick}, Names, Owner) 536 when is_function(CmdFun) -> 537 Cmd0 = CmdFun(mouse_index(Click), Names), 538 Magnet = is_magnet_active(Opts, MagnetClick), 539 Cmd = insert_magnet_flags(Cmd0, Magnet), 540 Cmd =:= ignore orelse wings_wm:send_after_redraw(Owner, {action,Cmd}), 541 pop; 542popup_result(#menu{type=menu,name=Name,opts=Opts}, {Click,MagnetClick}, Names, Owner) -> 543 Cmd = case {have_option_box(Opts), Name} of 544 {true,_} -> build_command({Name,Click=/=left_up}, Names); 545 {false, {'VALUE', Cmd0}} -> 546 Magnet = is_magnet_active(Opts, MagnetClick), 547 build_command(Cmd0, Names, Magnet); 548 {false, {_Name, Cmd0}} when is_tuple(Cmd0) -> 549 Magnet = is_magnet_active(Opts, MagnetClick), 550 insert_magnet_flags(Cmd0, Magnet); 551 {false, _} -> 552 Magnet = is_magnet_active(Opts, MagnetClick), 553 build_command(Name, Names, Magnet) 554 end, 555 Cmd =:= ignore orelse wings_wm:send_after_redraw(Owner, {action,Cmd}), 556 pop. 557 558calc_min_sizes([#menu{type=Type, desc=Desc, opts=Ps, hk=HK}|Es], Win, C1, C2) 559 when Type=:=menu;Type=:=submenu -> 560 case proplists:get_value(crossmark, Ps) of 561 true -> {WChk, _, _, _} = wxWindow:getTextExtent(Win, ?CHECK_MARK); 562 _ -> WChk = 0 563 end, 564 {WStr, _, _, _} = wxWindow:getTextExtent(Win, Desc), 565 {WHK, _, _, _} = wxWindow:getTextExtent(Win, get_hotkey(1,HK)), 566 calc_min_sizes(Es, Win, max(WChk+WStr+5, C1), max(WHK+5, C2)); 567calc_min_sizes([#menu{}|Es], Win, C1, C2) -> 568 calc_min_sizes(Es, Win, C1, C2); 569calc_min_sizes([], _, C1, C2) -> 570 {C1, C2}. 571 572setup_popup([#menu{type=separator}|Es], Sizer, Sz, Cs, Parent, Magnet, Acc) -> 573 Line = wxStaticLine:new(Parent, [{size,{-1,2}}]), 574 wxSizer:addSpacer(Sizer, 4), 575 wxSizer:add(Sizer, Line, [{border, 10}, {proportion, 0}, 576 {flag, ?wxEXPAND bor ?wxLEFT bor ?wxRIGHT}]), 577 wxSizer:addSpacer(Sizer, 4), 578 setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, Acc); 579setup_popup([#menu{type=submenu, wxid=Id, desc=Desc, help=Help0, opts=Ps, hk=HK}=ME|Es], 580 Sizer, Sz = {Sz1,Sz2}, Cs, Parent, Magnet, Acc) -> 581 Panel = wxPanel:new(Parent, [{winid, Id}]), 582 setup_colors([Panel], Cs), 583 Line = wxBoxSizer:new(?wxHORIZONTAL), 584 wxSizer:addSpacer(Line, 3), 585 Controls = 586 case get_hotkey(1,HK) of 587 [] -> 588 wxSizer:add(Line, T1 = wxStaticText:new(Panel, Id, Desc), [{proportion, 1},{flag, ?wxALIGN_CENTER}]), 589 [Panel,T1]; 590 HK0 -> 591 wxSizer:add(Line, T1 = wxStaticText:new(Panel, Id, Desc), [{proportion, 0},{flag, ?wxALIGN_CENTER}]), 592 wxSizer:setItemMinSize(Line, T1, Sz1, -1), 593 wxSizer:addSpacer(Line, 10), 594 wxSizer:addStretchSpacer(Line), 595 wxSizer:add(Line, T2 = wxStaticText:new(Panel, Id, HK0), [{proportion, 0},{flag, ?wxALIGN_CENTER}]), 596 wxSizer:setItemMinSize(Line, T2, Sz2, -1), 597 wxSizer:addSpacer(Line, 10), 598 wxSizer:add(Line, 16, 16), 599 [Panel,T1,T2] 600 end, 601 Help = if Help0 =:= [] -> Desc ++ ?__(1," submenu"); 602 true -> Help0 603 end, 604 {TipMsg, CmdMsg} = tooltip(Help, false, have_magnet(Ps, Magnet), HK), 605 [wxWindow:setToolTip(Win, wxToolTip:new(TipMsg)) || Win <- Controls], 606 wxPanel:setSizerAndFit(Panel, Line), 607 wxSizer:add(Sizer, Panel, [{flag, ?wxEXPAND},{proportion, 1}]), 608 menu_connect(Controls, [left_up, middle_up, right_up, enter_window]), 609 setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, [ME#menu{help=CmdMsg}|Acc]); 610setup_popup([#menu{type=menu, wxid=Id, desc=Desc, help=Help, opts=Props, hk=HK}=ME|Es], 611 Sizer, Sz = {Sz1,Sz2}, Cs, Parent, Magnet, Acc) -> 612 Panel = wxPanel:new(Parent, [{winid, Id}]), 613 setup_colors([Panel], Cs), 614 Line = wxBoxSizer:new(?wxHORIZONTAL), 615 Checked = proplists:get_value(crossmark, Props) =:= true, 616 wxSizer:addSpacer(Line, 3), 617 ChkM = if Checked -> ?CHECK_MARK; true -> "" end, 618 wxSizer:add(Line, T1 = wxStaticText:new(Panel, Id, ChkM++Desc),[{proportion, 0},{flag, ?wxALIGN_CENTER}]), 619 wxSizer:setItemMinSize(Line, T1, Sz1, -1), 620 wxSizer:addSpacer(Line, 10), 621 wxSizer:addStretchSpacer(Line), 622 wxSizer:add(Line, T2 = wxStaticText:new(Panel, Id, get_hotkey(1,HK)), [{proportion, 0},{flag, ?wxALIGN_CENTER}]), 623 wxSizer:setItemMinSize(Line, T2, Sz2, -1), 624 wxSizer:addSpacer(Line, 10), 625 BM = case {OpBox = have_option_box(Props),have_color(Props)} of 626 {true,_} -> 627 Bitmap = get_pref_bitmap(), 628 SBM = case os:type() of 629 {_, darwin} -> 630 wxBitmapButton:new(Panel, Id+1, Bitmap, 631 [{style,?wxNO_BORDER}]); 632 _ -> 633 wxStaticBitmap:new(Panel, Id+1, Bitmap) 634 end, 635 wxSizer:add(Line, SBM, [{flag, ?wxALIGN_CENTER}]), 636 [SBM]; 637 {false, true} -> 638 {_,H} = wxWindow:getSize(T1), 639 SBM = create_color_box(Id, Panel, H, Props), 640 wxSizer:add(Line, SBM, [{flag, ?wxALIGN_CENTER}]), 641 [SBM]; 642 {false, false} -> 643 wxSizer:add(Line, 16, 16), 644 [] 645 end, 646 wxSizer:addSpacer(Line, 3), 647 %% Windows doesn't catch enter_window on Panel below statictext 648 %% so we need to set tooltip on all sub-windows 649 {TipMsg, CmdMsg} = tooltip(Help, OpBox, have_magnet(Props, Magnet), HK), 650 [wxWindow:setToolTip(Win, wxToolTip:new(TipMsg)) || Win <- [Panel,T1,T2|BM]], 651 wxPanel:setSizerAndFit(Panel, Line), 652 wxSizer:add(Sizer, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), 653 menu_connect([Panel,T1,T2|BM], [left_up, middle_up, right_up, enter_window]), 654 Win = #{panel=>Panel, label=>T1, hotkey=>T2}, 655 Pop = ME#menu{wxid=Id, help=CmdMsg, object=Win}, 656 setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, [Pop|Acc]); 657setup_popup([#menu{type=opt}=ME|Es], Sizer, Sz, Cs, Parent, Magnet, Acc) -> 658 setup_popup(Es, Sizer, Sz, Cs, Parent, Magnet, [ME|Acc]); 659setup_popup([], _, _, _, _, _, Acc) -> lists:reverse(Acc). 660 661menu_connect(Windows, Evs) -> 662 EvH = fun(Ev, _) -> wings_menu_process ! Ev end, 663 [ [wxWindow:connect(Win, Ev, [{callback, EvH}]) || Ev <- Evs] || Win <- Windows]. 664 665get_pref_bitmap() -> 666 case ?GET(small_pref_bm) of 667 undefined -> 668 Images = wings_frame:get_icon_images(), 669 {_, _Sz, Img} = lists:keyfind(small_pref, 1, Images), 670 BM = wxBitmap:new(wxImage:copy(Img)), 671 ?SET(small_pref_bm, BM), 672 BM; 673 BM -> 674 BM 675 end. 676 677create_color_box(Id, Panel, H, Props) -> 678 {R,G,B} = wings_color:rgb3bv(proplists:get_value(color, Props)), 679 Image = wxImage:new(1,1,<<R,G,B>>), 680 wxImage:rescale(Image,10,H-2), 681 Bitmap = wxBitmap:new(Image), 682 SBM = wxStaticBitmap:new(Panel, Id+1, Bitmap), 683 wxImage:destroy(Image), 684 wxBitmap:destroy(Bitmap), 685 SBM. 686 687entry_msg(Id, Entries) -> 688 #menu{help=Msg} = lists:keyfind(Id, 2, Entries), 689 Msg. 690 691entry_cmd(Id, Entries) -> 692 #menu{name=Cmd} = lists:keyfind(Id, 2, Entries), 693 Cmd. 694entry_wins(#menu{object=Win}) -> 695 Win. 696 697set_entry_id(Id, ME) -> 698 ME#menu{wxid=Id}. 699 700setup_colors(Windows, {BG, FG}) -> 701 setup_colors(Windows, BG, FG). 702 703setup_colors(Windows, Background, Foreground) when is_list(Windows) -> 704 [wxWindow:setBackgroundColour(Win, Background) || Win <- Windows], 705 [wxWindow:setForegroundColour(Win, Foreground) || Win <- Windows], 706 ok; 707setup_colors(undefined, _, _) -> ok; 708setup_colors(Window, Background, Foreground) -> 709 case wx:getObjectType(Window) of 710 wxPanel -> 711 setup_colors([Window|wxWindow:getChildren(Window)], Background, Foreground), 712 wxWindow:refresh(Window), 713 Window; 714 _ -> %% Get Parent who is a Panel 715 setup_colors(wx:typeCast(wxWindow:getParent(Window), wxPanel), Background, Foreground) 716 end. 717 718tooltip("", false, false, _) -> {"",""}; 719tooltip(Help, OptBox, Magnet, HK) when is_list(Help) -> 720 tooltip(Help, "", opt_help(OptBox), Magnet, HK); 721tooltip({Help}, OptBox, Magnet, HK) -> 722 tooltip(Help, "", opt_help(OptBox), Magnet, HK); 723tooltip({HelpL, HelpM}, OptBox, Magnet, HK) -> 724 tooltip(HelpL, HelpM, opt_help(OptBox), Magnet, HK); 725tooltip({HelpL, HelpM, ""}, OptBox, Magnet, HK) -> 726 tooltip(HelpL, HelpM, opt_help(OptBox), Magnet, HK); 727tooltip({HelpL, HelpM, HelpR}, _, Magnet, HK) -> 728 tooltip(HelpL, HelpM, HelpR, Magnet, HK). 729 730tooltip("", "", "", Magnet, _) -> 731 Str = magnet_help(str, Magnet), 732 {Str, Str}; 733tooltip(HelpL, "", "", Magnet, _) -> 734 {str_clean(HelpL) ++ magnet_help(tip, Magnet), 735 wings_msg:join(HelpL,magnet_help(str, Magnet))}; 736tooltip(HelpL, HelpM, "", Magnet, HK) -> 737 {io_lib:format(?__(1, "Left mouse button") ++ ": ~ts" ++ tooltip_hk(1,HK) ++ "~n" ++ 738 ?__(2, "Middle mouse button") ++ ": ~ts" ++ tooltip_hk(2,HK), 739 [HelpL, HelpM]) ++ magnet_help(tip, Magnet), 740 wings_msg:join(wings_msg:button_format(HelpL, HelpM, ""),magnet_help(str, Magnet))}; 741tooltip(HelpL, "", HelpR, Magnet, HK) -> 742 {io_lib:format(?__(1, "Left mouse button") ++ ": ~ts" ++ tooltip_hk(1,HK) ++ "~n" ++ 743 ?__(3, "Right mouse button") ++ ": ~ts" ++ tooltip_hk(3,HK), 744 [HelpL, HelpR]) ++ magnet_help(tip, Magnet), 745 wings_msg:join(wings_msg:button_format(HelpL, "", HelpR), magnet_help(str, Magnet))}; 746tooltip(HelpL, HelpM, HelpR, Magnet, HK) -> 747 {io_lib:format(?__(1, "Left mouse button") ++ ": ~ts" ++ tooltip_hk(1,HK) ++ "~n" ++ 748 ?__(2, "Middle mouse button") ++ ": ~ts" ++ tooltip_hk(2,HK) ++ "~n" ++ 749 ?__(3, "Right mouse button") ++ ": ~ts" ++ tooltip_hk(3,HK), 750 [HelpL, HelpM, HelpR]) ++ magnet_help(tip, Magnet), 751 wings_msg:join(wings_msg:button_format(HelpL, HelpM, HelpR), magnet_help(str, Magnet))}. 752 753tooltip_hk(Mb, HK) -> 754 case get_hotkey(Mb, HK) of 755 [] -> ""; 756 HKStr -> io_lib:format(" | ~ts", [HKStr]) 757 end. 758 759str_clean([Char|Cs]) when is_integer(Char) -> 760 [Char|str_clean(Cs)]; 761str_clean([List|Cs]) when is_list(List) -> 762 [str_clean(List)|str_clean(Cs)]; 763str_clean([{_,Str}|Cs]) -> 764 ["\n",str_clean(Str)|str_clean(Cs)]; 765str_clean([]) -> []. 766 767 768 769opt_help(true) -> ?__(1, "Open option dialog"); 770opt_help(false) -> "". 771 772magnet_help(str, activated) -> wings_magnet:info_string(); 773magnet_help(Type, true) -> 774 ModRmb = wings_msg:free_rmb_modifier(), 775 ModName = wings_msg:mod_name(ModRmb), 776 case Type of 777 str -> "+" ++ ?__(2,"Click for Magnet") ++ ModName; 778 tip -> "\n+" ++ ?__(2,"Click for Magnet") ++ ModName 779 end; 780magnet_help(_, _) -> "". 781 782submenu_help([], Fun, Ns) when is_function(Fun) -> 783 Fun(help, Ns); 784submenu_help(Help, _, _) -> 785 Help. 786 787check_item(Name) -> 788 case ets:match_object(wings_menus, #menu{name=Name, type=?wxITEM_CHECK, _ = '_'}) of 789 [] -> ok; 790 [#menu{object=MenuItem}] -> %% Toggle checkmark 791 Checked = wxMenuItem:isChecked(MenuItem), 792 wxMenuItem:check(MenuItem, [{check, not Checked}]) 793 end. 794 795update_menu(Menu, Item, Cmd) -> 796 update_menu(Menu, Item, Cmd, undefined). 797 798update_menu(file, Item = {recent_file, _}, delete, _) -> 799 Id = menu_item_id(file, Item), 800 FileId = predefined_item(menu, file), 801 [#menu{object=File, type=submenu}] = ets:lookup(wings_menus, FileId), 802 true = wxMenu:delete(File, Id), 803 ets:delete(wings_menus, Id), 804 ok; 805update_menu(Menu, Item, delete, _) -> 806 case menu_item_id(Menu, Item) of 807 false -> ok; 808 Id -> 809 case ets:lookup(wings_menus, Id) of 810 [#menu{type=submenu}=SubMenu] -> 811 remove_submenu(SubMenu), 812 ok; 813 [#menu{object=MenuItem}] -> 814 ParentMenu = wxMenuItem:getMenu(MenuItem), 815 true = wxMenu:delete(ParentMenu, Id), 816 ets:delete(wings_menus, Id), 817 ok; 818 _ -> 819 ok 820 end 821 end; 822update_menu(Menu, Item, {append, Pos0, Cmd0}, Help) -> 823 update_menu(Menu, Item, {append, Pos0, Cmd0, []}, Help); 824update_menu(Menu, Item, {append, Pos0, Cmd0, Props}, Help) -> 825 case menu_item_id(Menu, Item) of 826 false -> 827 AddItem = 828 fun(SubMenu, Name) -> 829 Pos = 830 if Pos0 >= 0 -> min(wxMenu:getMenuItemCount(SubMenu), Pos0); 831 true -> wxMenu:getMenuItemCount(SubMenu) 832 end, 833 {Type,Check} = case proplists:get_value(crossmark, Props) of 834 undefined -> {?wxITEM_NORMAL, false}; 835 false -> {?wxITEM_CHECK, false}; 836 _ -> {?wxITEM_CHECK, true} %% grey or true 837 end, 838 MO = wxMenu:insert(SubMenu, Pos, -1, [{text, Cmd0},{kind, Type}]), 839 Id = wxMenuItem:getId(MO), 840 ME=#menu{name=Name, object=MO, 841 wxid=Id, type=Type}, 842 true = ets:insert(wings_menus, ME), 843 Cmd = setup_hotkey(MO, Cmd0), 844 wxMenuItem:setText(MO, Cmd), 845 (Type==?wxITEM_CHECK) andalso wxMenuItem:check(MO,[{check, Check}]), 846 is_list(Help) andalso wxMenuItem:setHelp(MO, Help) 847 end, 848 849 Names0 = build_names(Item, [Menu]), 850 Names = lists:droplast(Names0), 851 case ets:match_object(wings_menus, #menu{name=Names, _ = '_'}) of 852 [#menu{object=SubMenu}] -> 853 AddItem(SubMenu, build_command(Item, [Menu])); 854 _ -> 855 io:format("update_menu: Item rejected (~p|~p)\n",[Menu,Item]) 856 end; 857 _ -> 858 ok 859 end; 860update_menu(Menu, Item, Cmd0, Help) -> 861 Id = menu_item_id(Menu, Item), 862 MI = case ets:lookup(wings_menus, Id) of 863 [#menu{object=MO}] -> 864 MO; 865 [] when Menu =:= file, element(1, Item) =:= load_pref -> 866 Names0 = build_names(Item, [Menu]), 867 Names = lists:droplast(Names0), 868 [#menu{object=CustomTheme, type=submenu}] = ets:match_object(wings_menus, #menu{name=Names, _ = '_'}), 869 N = wxMenu:getMenuItemCount(CustomTheme), 870 MO = wxMenu:insert(CustomTheme, N, ?wxITEM_NORMAL, [{text, Cmd0}]), 871 ME=#menu{name=build_command(Item,[Menu]), object=MO, 872 wxid=Id, type=?wxITEM_NORMAL}, 873 true = ets:insert(wings_menus, ME), 874 wxMenu:insertSeparator(CustomTheme, N), 875 MO; 876 [] when Menu =:= file, element(1, Item) =:= recent_file -> 877 FileId = predefined_item(menu, Menu), 878 [#menu{object=File, type=submenu}] = ets:lookup(wings_menus, FileId), 879 N = wxMenu:getMenuItemCount(File), 880 MO = wxMenu:insert(File, N-2, Id, [{text, Cmd0}]), 881 ME=#menu{name=build_command(Item,[file]), object=MO, 882 wxid=Id, type=?wxITEM_NORMAL}, 883 true = ets:insert(wings_menus, ME), 884 Id =:= ?wxID_FILE1 andalso wxMenu:insertSeparator(File, N-2), 885 MO 886 end, 887 Cmd = setup_hotkey(MI, Cmd0), 888 wxMenuItem:setText(MI, Cmd), 889 is_list(Help) andalso wxMenuItem:setHelp(MI, Help). 890 891update_menu_enabled(Menu, Item, Enabled) 892 when is_boolean(Enabled) -> 893 case menu_item_id(Menu, Item) of 894 false -> ignore; 895 Id -> 896 [#menu{object=MI}] = ets:lookup(wings_menus, Id), 897 case wxMenuItem:isCheckable(MI) of 898 true -> wxMenuItem:check(MI, [{check,Enabled}]); 899 false -> wxMenuItem:enable(MI, [{enable, Enabled}]) 900 end 901 end; 902update_menu_enabled(_Menu, _Item, _Enabled) -> 903 ignore. 904 905update_menu_hotkey(Action, HotKeyStr) -> 906 case ets:match_object(wings_menus, #menu{name=Action, _='_'}) of 907 [] -> ok; %% Ignore it is a popupmenu entry 908 [#menu{object=MI}] -> 909 Label = wxMenuItem:getLabel(MI), 910 case HotKeyStr of 911 "" -> wxMenuItem:setText(MI, Label); 912 _ -> wxMenuItem:setText(MI, Label ++ "\t" ++ HotKeyStr) 913 end 914 end. 915 916parent_menu(This) when is_list(This) -> 917 Parent = lists:droplast(This), 918 [#menu{}=PMenu] = ets:match_object(wings_menus, #menu{name=Parent,type=submenu, _='_'}), 919 PMenu. 920 921menu_item_id(Menu, Item) -> 922 case predefined_item(Menu, Item) of 923 Id when is_integer(Id) -> 924 Id; 925 false -> 926 case ets:match_object(wings_menus, #menu{name={Menu,Item}, _='_'}) of 927 [#menu{wxid=Id}] -> Id; 928 [] when Menu =:= view -> %% Auto find {view, {show, Item}} used for toolbar 929 case ets:match_object(wings_menus, #menu{name={view,{show,Item}}, _='_'}) of 930 [#menu{wxid=Id}] -> Id; 931 [] -> false 932 end; 933 [] -> %% find for submenu root (level 0) 934 MenuPath = build_names(Item, [Menu]), 935 case ets:match_object(wings_menus, #menu{name=MenuPath,type=submenu, _='_'}) of 936 [#menu{wxid=Id}] -> Id; 937 _ -> false 938 end 939 end 940 end. 941 942remove_submenu(#menu{object=SubMenu, name=Name, wxid=SubId}) -> 943 #menu{object=ParentMenu} = parent_menu(Name), 944 wxMenu:delete(ParentMenu, SubId), 945 ets:delete(wings_menus, SubId), 946 wxMenu:destroy(SubMenu). 947 948setup_hotkey(MI, Cmd) -> 949 case lists:member($\t, Cmd) of 950 true -> %% Already have one use the new one 951 Cmd; 952 false -> 953 try wxMenuItem:getText(MI) of 954 Old -> 955 case string:chr(Old, $\t) of 956 0 -> Cmd; %% Old string have no hotkey 957 Idx -> 958 HotKeyStr = string:substr(Old, Idx), 959 string:concat(Cmd, HotKeyStr) 960 end 961 catch _:Reason -> 962 io:format("~p:~p GetTextFailed ~p ~p~n",[?MODULE,?LINE, MI, Reason]), 963 Cmd 964 end 965 end. 966 967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 968 969setup_menus(MB, Menus) -> 970 Enter = fun({Str, Name, List}, Id) -> 971 {Menu, NextId} = setup_menu([Name], Id, List), 972 wxMenuBar:append(MB, Menu, Str), 973 MenuId = predefined_item(menu, Name, NextId), 974 ME=#menu{name=[Name], object=Menu, wxid=MenuId, type=submenu}, 975 true = ets:insert(wings_menus, ME), 976 NextId+1 977 end, 978 lists:foldl(Enter, 200, Menus). 979 980 981id_to_name(?SEL_VERTEX) -> {select, vertex}; 982id_to_name(?SEL_EDGE) -> {select, edge}; 983id_to_name(?SEL_FACE) -> {select, face}; 984id_to_name(?SEL_BODY) -> {select, body}; 985id_to_name(Id) -> 986 [#menu{name=Name}] = ets:lookup(wings_menus, Id), 987 Name. 988 989setup_menu(Names, Id, Menus) -> 990 Menu = wxMenu:new(), 991 Entries = make_entries(Names, Menus, wx), 992 Next = create_menu(Entries, Id, Names, Menu), 993 {Menu, Next}. 994 995make_entries(Names, Menus0, Style) when is_list(Menus0) -> 996 Menus1 = wings_plugin:menu(list_to_tuple(reverse(Names)), Menus0), 997 HotKeys = wings_hotkey:matching(Names), 998 Menus2 = [normalize_menu_wx(Entry, HotKeys, Names) || Entry <- lists:flatten(Menus1)], 999 format_hotkeys(Menus2, Style). 1000 1001normalize_menu_wx(separator, _, _) -> 1002 #menu{type=separator}; 1003normalize_menu_wx({S,Fun,Help,Ps}, Hotkeys, Ns) when is_function(Fun) -> 1004 HK = case proplists:get_value(hotkey, Ps) of 1005 undefined -> match_hotkey(reduce_name([Fun(1, Ns),Fun(2, Ns),Fun(3, Ns)]), Hotkeys, have_option_box(Ps)); 1006 String -> String 1007 end, 1008 #menu{type=menu, desc=S, name=Fun, help=Help, opts=Ps, hk=HK}; 1009normalize_menu_wx({S, {Name, SubMenu}}, Hotkeys, Ns) 1010 when is_list(SubMenu); is_function(SubMenu) -> 1011 Name0 = name_for_hotkey(Name, Ns, SubMenu), 1012 HK = match_hotkey(reduce_name(Name0), Hotkeys, false), 1013 #menu{type=submenu, desc=S, name={Name, SubMenu}, 1014 help=submenu_help("", SubMenu, [Name|Ns]), hk=HK}; 1015normalize_menu_wx({S, {Name, SubMenu}, Ps}, Hotkeys, Ns) 1016 when is_list(SubMenu); is_function(SubMenu) -> 1017 Name0 = name_for_hotkey(Name, Ns, SubMenu), 1018 HK = match_hotkey(reduce_name(Name0), Hotkeys, false), 1019 #menu{type=submenu, desc=S, name={Name, SubMenu}, 1020 help=submenu_help("", SubMenu, [Name|Ns]), opts=Ps, hk=HK}; 1021normalize_menu_wx({S,{Name,Fun},Help,Ps}, Hotkeys, Ns) 1022 when is_function(Fun); is_list(Fun) -> 1023 Name0 = name_for_hotkey(Name, Ns, Fun), 1024 HK = match_hotkey(reduce_name(Name0), Hotkeys, have_option_box(Ps)), 1025 #menu{type=submenu, desc=S, name={Name, Fun}, 1026 help=submenu_help(Help, Fun, [Name|Ns]), opts=Ps, hk=HK}; 1027normalize_menu_wx({S,Name,Help,Ps}, Hotkeys, _Ns) -> 1028 HK = case proplists:get_value(hotkey, Ps) of 1029 undefined -> match_hotkey(reduce_name(Name), Hotkeys, have_option_box(Ps)); 1030 String -> String 1031 end, 1032 #menu{desc=S, name=Name, help=Help, opts=Ps, hk=HK}; 1033normalize_menu_wx({S,Name}, Hotkeys, _Ns) -> 1034 HK = match_hotkey(reduce_name(Name), Hotkeys, false), 1035 #menu{desc=S, name=Name, hk=HK}; 1036normalize_menu_wx({S,Name,[C|_]=Help}, Hotkeys, _Ns) 1037 when is_integer(C) -> 1038 HK = match_hotkey(reduce_name(Name), Hotkeys, false), 1039 #menu{desc=S,name=Name,help=Help,hk=HK}; 1040normalize_menu_wx({S,Name,Help}, Hotkeys, _Ns) 1041 when is_tuple(Help), tuple_size(Help) =< 3 -> 1042 HK = match_hotkey(reduce_name(Name), Hotkeys, false), 1043 #menu{desc=S,name=Name,help=Help,hk=HK}; 1044normalize_menu_wx({S,Name,Ps},Hotkeys, _Ns) -> 1045 HK = case proplists:get_value(hotkey, Ps) of 1046 undefined -> match_hotkey(reduce_name(Name), Hotkeys, have_option_box(Ps)); 1047 String -> String 1048 end, 1049 #menu{desc=S,name=Name,opts=Ps,hk=HK}. 1050 1051name_for_hotkey(Name, Ns, Fun) when is_function(Fun) -> 1052 case Fun(1, Ns) of 1053 SM when is_tuple(SM) -> [SM, Fun(2, Ns), Fun(3, Ns)]; 1054 _ -> Name 1055 end; 1056name_for_hotkey(Name, _, _) -> Name. 1057 1058get_hotkey(1,{HK1, _, _}) -> HK1; 1059get_hotkey(2,{_, HK2, _}) -> HK2; 1060get_hotkey(3,{_, _, HK3}) -> HK3; 1061get_hotkey(1,HK) -> HK; 1062get_hotkey(_,_) -> "". 1063 1064format_hotkeys([#menu{type=separator}=H|T], Style) -> 1065 [H|format_hotkeys(T, Style)]; 1066format_hotkeys([#menu{hk=[HK1,HK2,HK3]}=H|T], Style) -> 1067 Hotkey1 = wings_hotkey:format_hotkey(HK1, Style), 1068 Hotkey2 = wings_hotkey:format_hotkey(HK2, Style), 1069 Hotkey3 = wings_hotkey:format_hotkey(HK3, Style), 1070 [H#menu{hk={Hotkey1, Hotkey2, Hotkey3}}|format_hotkeys(T, Style)]; 1071format_hotkeys([#menu{hk=HK0}=H|T], Style) -> 1072 Hotkey = wings_hotkey:format_hotkey(HK0, Style), 1073 [H#menu{hk=Hotkey}|format_hotkeys(T, Style)]; 1074format_hotkeys([], _Style) -> []. 1075 1076create_menu([#menu{type=separator}|Rest], Id, Names, Menu) -> 1077 wxMenu:appendSeparator(Menu), 1078 create_menu(Rest, Id, Names, Menu); 1079create_menu([#menu{type=submenu, desc=Desc, name={Name,SubMenu0}, help=Help}=ME0|Rest], Id, Names, Menu) 1080 when is_list(SubMenu0) -> 1081 {SMenu, NextId} = setup_menu([Name|Names], Id, SubMenu0), 1082 wxMenu:append(Menu, NextId, Desc, SMenu, [{help, Help}]), 1083 ME=ME0#menu{name=lists:reverse([Name|Names]), object=SMenu, wxid=NextId}, 1084 true = ets:insert(wings_menus, ME), 1085 create_menu(Rest, NextId+1, Names, Menu); 1086create_menu([MenuEntry|Rest], Id, Names, Menu) -> 1087 {MenuItem, Check} = menu_item(MenuEntry, Menu, Id, Names), 1088 wxMenu:append(Menu, MenuItem), 1089 Check andalso wxMenuItem:check(MenuItem), %% Can not check until appended to menu.. 1090 create_menu(Rest, Id+1, Names, Menu); 1091create_menu([], NextId, _, _) -> 1092 NextId. 1093 1094menu_item(#menu{desc=Desc0, name=Name, help=Help, opts=Props, hk=HotKey}=ME, Parent, Id, Names) -> 1095 Desc = menu_item_desc(Desc0, HotKey), 1096 MenuId = predefined_item(hd(Names),Name, Id), 1097 Command = case have_option_box(Props) of 1098 true -> 1099 case lists:reverse(Desc0) of 1100 "..." ++ _ -> ok; 1101 _ -> 1102 io:format("Menu have option box ~p ~ts~n",[Name, Desc0]), 1103 io:format(" it should be marked with ...~n",[]) 1104 end, 1105 {Name, true}; 1106 false -> 1107 Name 1108 end, 1109 {Type,Check} = case proplists:get_value(crossmark, Props) of 1110 undefined -> {?wxITEM_NORMAL, false}; 1111 false -> {?wxITEM_CHECK, false}; 1112 _ -> {?wxITEM_CHECK, true} %% grey or true 1113 end, 1114 MI = wxMenuItem:new([{parentMenu, Parent}, {id,MenuId}, 1115 {text,Desc}, {kind, Type}, {help,Help}]), 1116 Cmd = case is_function(Command) of 1117 true -> Command(1, #st{}); 1118 false -> build_command(Command, Names) 1119 end, 1120 true = ets:insert(wings_menus, ME#menu{name=Cmd,object=MI, wxid=MenuId, type=Type}), 1121 {MI, Check}. 1122 1123menu_item_desc(Desc, {[],[],[]}) -> Desc; 1124menu_item_desc(Desc, []) -> Desc; 1125menu_item_desc(Desc, HotKey) -> 1126 %% Quote to avoid Windows stealing keys. 1127 case os:type() of 1128 {win32, _} -> Desc ++ "\t'" ++ HotKey ++ "'"; 1129 _ -> Desc ++ "\t" ++ HotKey 1130 end. 1131 1132%% We want to use the predefined id where they exist (mac) needs for it's 1133%% specialized menus but we want our shortcuts hmm. 1134%% We also get little predefined icons for OS's that have that. 1135predefined_item(Menu, Item, DefId) -> 1136 case predefined_item(Menu,Item) of 1137 false -> DefId; 1138 PId -> PId 1139 end. 1140 1141predefined_item(help, about) -> ?wxID_ABOUT; 1142predefined_item(help, help) -> ?wxID_HELP; 1143predefined_item(menu, file) -> ?wxID_FILE; 1144predefined_item(file, quit) -> ?wxID_EXIT; 1145predefined_item(file, new) -> ?wxID_NEW; 1146predefined_item(file, open) -> ?wxID_OPEN; 1147predefined_item(file, save) -> ?wxID_SAVE; 1148predefined_item(file, save_as) -> ?wxID_SAVEAS; 1149predefined_item(file, revert) -> ?wxID_REVERT; 1150predefined_item(file, {recent_file,N}) -> ?wxID_FILE + N; %% Zero numbered 1151predefined_item(menu, edit) -> ?wxID_EDIT; 1152predefined_item(edit, undo) -> ?wxID_UNDO; 1153predefined_item(edit, redo) -> ?wxID_REDO; 1154predefined_item(edit, preferences) -> ?wxID_PREFERENCES; 1155predefined_item(edit, Fun) when is_function(Fun) -> ?wxID_PREFERENCES; 1156%% Make it easy to find repeat 1157predefined_item(edit, repeat) -> ?REPEAT; 1158predefined_item(edit, repeat_args) -> ?REPEAT_ARGS; 1159predefined_item(edit, repeat_drag) -> ?REPEAT_DRAG; 1160%% Also all toolbar stuff (only once) 1161predefined_item(select, vertex) -> ?SEL_VERTEX; 1162predefined_item(select, edge) -> ?SEL_EDGE; 1163predefined_item(select, face) -> ?SEL_FACE; 1164predefined_item(select, body) -> ?SEL_BODY; 1165 1166predefined_item(view, workmode) -> ?VIEW_WORKMODE; 1167predefined_item(view, orthogonal_view) -> ?VIEW_ORTHO; 1168predefined_item(show, show_axes) -> ?VIEW_AXES; 1169predefined_item(show, show_groundplane) -> ?VIEW_GROUND; 1170 1171predefined_item(toolbar, open) -> predefined_item(file, open); 1172predefined_item(toolbar, save) -> predefined_item(file, save); 1173predefined_item(toolbar, undo) -> predefined_item(edit, undo); 1174predefined_item(toolbar, redo) -> predefined_item(edit, redo); 1175predefined_item(toolbar, pref) -> predefined_item(edit, preferences); 1176 1177predefined_item(toolbar, vertex) -> predefined_item(select, vertex); 1178predefined_item(toolbar, edge) -> predefined_item(select, edge); 1179predefined_item(toolbar, face) -> predefined_item(select, face); 1180predefined_item(toolbar, body) -> predefined_item(select, body); 1181 1182predefined_item(toolbar, workmode) -> predefined_item(view, workmode); 1183predefined_item(toolbar, orthogonal_view) -> predefined_item(view, orthogonal_view); 1184predefined_item(toolbar, show_groundplane) -> predefined_item(show, show_groundplane); 1185predefined_item(toolbar, show_axes) -> predefined_item(show, show_axes); 1186 1187predefined_item(_M, _C) -> 1188%% io:format("Ignore ~p ~p~n",[_M,_C]), 1189 false. 1190 1191colorB(Pref) when is_atom(Pref) -> 1192 wings_color:rgb4bv(wings_pref:get_value(Pref)). 1193 1194