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