%%
%%  wings.erl --
%%
%%     The main module of Wings 3D.
%%
%%  Copyright (c) 2001-2004 Bjorn Gustavsson
%%
%%  See the file "license.terms" for information on usage and redistribution
%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%%     $Id: wings.erl,v 1.326 2004/11/22 19:13:11 bjorng Exp $
%%

-module(wings).
-export([start/0,start_halt/0,start_halt/1]).
-export([caption/1,redraw/1,redraw/2,init_opengl/1,command/2]).
-export([mode_restriction/1,clear_mode_restriction/0,get_mode_restriction/0]).
-export([ask/3]).
-export([init_menubar/0]).

-export([register_postdraw_hook/3,unregister_postdraw_hook/2]).

-define(NEED_OPENGL, 1).
-define(NEED_ESDL, 1).
-include("wings.hrl").

-import(lists, [foreach/2,map/2,filter/2,foldl/3,sort/1,
		keymember/3,reverse/1]).

start() ->
    do_spawn(none).

start_halt() ->
    spawn_halt(none).

start_halt([File|_]) ->
    spawn_halt(File).

spawn_halt(File) ->
    spawn(fun() ->
		  process_flag(trap_exit, true),
		  Wings = do_spawn(File, [link]),
		  halt_loop(Wings)
	  end).

halt_loop(Wings) ->
    receive
	{'EXIT',Wings,normal} ->
	    halt();
	{'EXIT',Wings,{window_crash,Name,Reason}} ->
	    Log = wings_util:crash_log(Name, Reason),
	    io:format("\n\n"),
	    io:format(?STR(halt_loop,1,"Fatal internal error - log written to ~s\n"),
		      [Log]),
	    ok;
	{'EXIT',Wings,Reason} ->
	    Log = wings_util:crash_log(?STR(halt_loop,2,"<Unknown Window Name>"),
				       Reason),
	    io:format("\n\n"),
	    io:format(?STR(halt_loop,3,"Fatal internal error - log written to ~s\n"),
		      [Log]),
	    ok;
	_Other ->				%Can't happen.
	    halt_loop(Wings)
    end.

do_spawn(File) ->
    do_spawn(File, []).

do_spawn(File, Flags) ->
    %% Set a minimal heap size to avoiding garbage-collecting
    %% all the time. Don't set it too high to avoid keeping binaries
    %% too long.
    Fun = fun() -> init(File) end,
    spawn_opt(erlang, apply, [Fun,[]],
	      [{fullsweep_after,16384},{min_heap_size,32*1204}|Flags]).

init(File) ->
    register(wings, self()),
    
    OsType = os:type(),
    put(wings_os_type, OsType),
    wings_pref:init(),
    wings_lang:init(),
    
    group_leader(wings_console:start(), self()),
    wings_init:init(),
    wings_text:init(),
    wings_image:init(),
    wings_plugin:init(),
    wings_color:init(),
    wings_io:init(),

    wings_camera:init(),
    wings_vec:init(),
        
    Empty = gb_trees:empty(),
    St0 = #st{shapes=Empty,
	      selmode=face,
	      sel=[],
	      ssels=Empty,
	      mat=wings_material:default(),
	      saved=true,
	      onext=1,
	      repeatable=ignore,
	      ask_args=none,
	      drag_args=none,
	      def={ignore,ignore}
	     },
    St1 = wings_sel:reset(St0),
    St = wings_undo:init(St1),
    wings_view:init(),
    wings_file:init(),
    caption(St),
    put(wings_hitbuf, sdl_util:alloc(?HIT_BUF_SIZE, ?GL_INT)),
    wings_wm:init(),
    wings_file:init_autosave(),
    init_menubar(),
    wings_pb:init(),
    wings_ask:init(),

    Op = main_loop_noredraw(St),		%Replace crash handler
						%with this handler.
    
    Props = initial_properties(),
    {{X,Y},{W,H}} = wings_wm:win_rect(desktop),
    wings_wm:toplevel(geom, geom_title(geom),
		      {X,Y,highest}, {W,H-80},
		      [resizable,{anchor,nw},
		       {toolbar,
			fun(A, B, C) -> wings_toolbar:create(A, B, C) end},
		       menubar,{properties,Props}],
		      Op),
    wings_wm:menubar(geom, get(wings_menu_template)),
    set_drag_filter(geom),

    open_file(File),
    restore_windows(St),
    case catch wings_wm:enter_event_loop() of
	{'EXIT',normal} ->
	    wings_pref:finish(),
	    erase(wings_hitbuf),
	    sdl:quit();
	{'EXIT',Reason} ->
	    io:format("~P\n", [Reason,20]),
	    erase(wings_hitbuf),
	    sdl:quit(),
	    exit(Reason)
    end.

new_viewer(St) ->
    {Pos,{W,H}} = wings_wm:win_rect(desktop),
    Size = {W div 2-40,H div 2-40},
    N = free_viewer_num(2),
    Active = wings_wm:this(),
    Props = wings_wm:get_props(Active),
    ToolbarHidden = wings_wm:is_hidden({toolbar,Active}),
    Name = {geom,N},
    new_viewer(Name, Pos, Size, Props, ToolbarHidden, St).

new_viewer(Name, {X,Y}, Size, Props, ToolbarHidden, St) ->
    Op = main_loop_noredraw(St),
    Title = geom_title(Name),
    wings_wm:toplevel(Name, Title, {X,Y,highest}, Size,
		      [resizable,closable,{anchor,nw},
		       {toolbar,
			fun(A, B, C) -> wings_toolbar:create(A, B, C) end},
		       menubar,
		       {properties,Props}],
		      Op),
    wings_wm:menubar(Name, get(wings_menu_template)),
    wings_wm:send({menubar,Name}, {current_state,St}),
    set_drag_filter(Name),
    if
	ToolbarHidden -> wings_wm:hide({toolbar,Name});
	true -> ok
    end,
    Name.

free_viewer_num(N) ->
    case wings_wm:is_window({geom,N}) of
	false -> N;
	true -> free_viewer_num(N+1)
    end.

open_file(none) -> ok;
open_file(Name) -> wings_wm:send(geom, {open_file,Name}).

init_opengl(St) ->
    wings_draw_util:init(),
    wings_dl:init(),
    wings_draw:refresh_dlists(St),
    keep.

redraw(St) ->
    redraw(info(St), St).

redraw(Info, St) ->
    wings_wm:clear_background(),
    wings_render:render(St),
    call_post_hook(St),
    case Info =/= [] andalso wings_wm:get_prop(show_info_text) of
	true -> wings_io:info(Info);
	false -> ok
    end.

call_post_hook(St) ->
    case wings_wm:lookup_prop(postdraw_hook) of
	none -> ok;
	{value,{_Id,Fun}} -> Fun(St)
    end.

register_postdraw_hook(Window, Id, Fun) ->
    case wings_wm:lookup_prop(Window, postdraw_hook) of
	none ->
	    wings_wm:set_prop(Window, postdraw_hook, {Id,Fun});
	{value,{Id,_}} ->
	    wings_wm:set_prop(Window, postdraw_hook, {Id,Fun});
	{value,{OtherId,_}} ->
	    erlang:fault({in_use_by,OtherId}, [Window,Id,Fun])
    end.

unregister_postdraw_hook(Window, Id) ->
    case wings_wm:lookup_prop(Window, postdraw_hook) of
	{value,{Id,_}} ->
	    wings_wm:erase_prop(Window, postdraw_hook);
	_ ->
	    ok
    end.

save_state(St0, St1) ->
    St2 = wings_undo:save(St0, St1),
    St = case St2 of
	     #st{saved=false} -> St2;
	     _Other -> caption(St2#st{saved=false})
	 end,
    main_loop(clear_temp_sel(St)).

ask(Ask, St, Cb) ->
    wings_vec:do_ask(Ask, St, Cb).

main_loop(St) ->
    ?VALIDATE_MODEL(St),
    clear_mode_restriction(),
    wings_draw:refresh_dlists(St),
    wings_wm:current_state(St),
    wings_wm:dirty(),
    main_loop_noredraw(St).

main_loop_noredraw(St) ->
    {replace,fun(Event) -> handle_event(Event, St) end}.

handle_event({crash,Crash}, St) ->
    crash_logger(Crash, St);
handle_event({crash_in_other_window,LogName}, St) ->
    get_crash_event(LogName, St);
handle_event({open_file,Name}, St0) ->
    case catch ?SLOW(wings_ff_wings:import(Name, St0)) of
	#st{}=St ->
	    wings_pref:set_value(current_directory, filename:dirname(Name)),
	    main_loop(caption(St#st{saved=true,file=Name}));
	{error,_} ->
	    main_loop(St0)
    end;
handle_event(Ev, St) ->
    case wings_camera:event(Ev, St) of
	next -> handle_event_0(Ev, St);
	Other -> Other
    end.

handle_event_0(#mousebutton{button=But,state=ButSt,mod=Mod}=Ev, St)
  when But < 3, Mod band ?CTRL_BITS =/= 0 ->
    case wings_pref:get_value(default_commands) of
	false ->
	    handle_event_1(Ev, St);
	true ->
	    if
		Mod band ?SHIFT_BITS =/= 0 ->
		    define_command(ButSt, But, St);
		true ->
		    use_command(Ev, St)
	    end
    end;
handle_event_0(Ev, St) -> handle_event_1(Ev, St).

handle_event_1(Ev, St) ->
    case wings_pick:event(Ev, St) of
	next -> handle_event_2(Ev, St);
	Other -> Other
    end.

handle_event_2(#mousebutton{x=X,y=Y}=Ev0, #st{sel=Sel}=St0) ->
    case wings_menu:is_popup_event(Ev0) of
	no ->
	    handle_event_3(Ev0, St0);
	{yes,Xglobal,Yglobal,_} ->
	    case Sel =:= [] andalso wings_pref:get_value(use_temp_sel) of
		false ->
		    popup_menu(Xglobal, Yglobal, St0);
		true ->
		    case wings_pick:do_pick(X, Y, St0) of
			{add,_,St} ->
			    Ev = wings_wm:local2global(Ev0),
			    wings_io:putback_event(Ev),
			    wings_wm:later({temporary_selection,St});
			_ ->
			    popup_menu(Xglobal, Yglobal, St0)
		    end
	    end;
	Other -> Other
    end;
handle_event_2(Ev, St) -> handle_event_3(Ev, St).
	    
handle_event_3(#keyboard{}=Ev, St0) ->
    case do_hotkey(Ev, St0) of
	next -> keep;
	{Cmd,St} -> do_command(Cmd, St)
    end;
handle_event_3({action,Callback}, _) when is_function(Callback) ->
    Callback();
handle_event_3({action,Cmd}, St) ->
    do_command(Cmd, St);
handle_event_3({vec_command,Command,St}, _) when is_function(Command) ->
    %% Use to execute command with vector arguments (see wings_vec.erl).
    command_response(Command(), none, St);
handle_event_3(#mousebutton{}, _St) -> keep;
handle_event_3(#mousemotion{}, _St) -> keep;
handle_event_3(init_opengl, St) ->
    wings_wm:current_state(St),
    init_opengl(St),
    wings_draw:refresh_dlists(St),
    keep;
handle_event_3(#expose{}, St) ->
    handle_event_3(redraw, St);
handle_event_3(resized, _) -> keep;
handle_event_3(close, _) ->
    Active = wings_wm:this(),
    wings_wm:delete({object,Active}),
    delete;
handle_event_3(redraw, St) ->
    redraw(St),
    main_loop_noredraw(St);
handle_event_3(quit, St) ->
    case wings_wm:this() of
	geom -> do_command({file,quit}, St);
	_ -> keep
    end;
handle_event_3({new_state,St}, St0) ->
    save_state(St0, St);
handle_event_3({temporary_selection,St}, St0) ->
    main_loop(set_temp_sel(St0, St));
handle_event_3({current_state,St}, _) ->
    main_loop_noredraw(St);
handle_event_3({current_state,_,_}, _) ->
    keep;
handle_event_3(revert_state, St) ->
    main_loop(clear_temp_sel(St));
handle_event_3(need_save, St) ->
    main_loop(caption(St#st{saved=false}));
handle_event_3({new_default_command,DefCmd}, St) ->
    main_loop_noredraw(St#st{def=DefCmd});
handle_event_3(got_focus, _) ->
    Msg1 = wings_util:button_format(?STR(handle_event_3,1,"Select")),
    Msg2 = wings_camera:help(),
    Msg3 = wings_util:button_format([], [], ?STR(handle_event_3,2,"Show menu")),
    Message = wings_util:join_msg([Msg1,Msg2,Msg3]),
    wings_wm:message(Message),
    wings_wm:dirty();
handle_event_3(lost_focus, _) -> keep;
handle_event_3({note,menu_aborted}, St) ->
    main_loop(clear_temp_sel(St));
handle_event_3({note,_}, _) ->
    keep;
handle_event_3({drop,Pos,DropData}, St) ->
    handle_drop(DropData, Pos, St);
handle_event_3(language_changed, _) ->
    This = wings_wm:this(),
    wings_wm:toplevel_title(This, geom_title(This)),
    wings_wm:menubar(This, get(wings_menu_template)),
    keep;
handle_event_3(ignore, _St) -> keep.

do_hotkey(Ev, #st{sel=[]}=St0) ->
    case wings_pref:get_value(use_temp_sel) of
	false ->
	    do_hotkey_1(Ev, St0);
	true ->
	    {_,X,Y} = wings_wm:local_mouse_state(),
	    case wings_pick:do_pick(X, Y, St0) of
		{add,_,St} ->
		    case wings_hotkey:event(Ev, St) of
			next -> next;
			Cmd ->
			    case highlight_sel_style(Cmd) of
				none -> {Cmd,St0};
				temporary -> {Cmd,set_temp_sel(St0, St)};
				permanent -> {Cmd,St}
			    end
		    end;
		_Other -> do_hotkey_1(Ev, St0)
	    end
    end;
do_hotkey(Ev, St) -> do_hotkey_1(Ev, St).

do_hotkey_1(Ev, St) ->
    case wings_hotkey:event(Ev, St) of
 	next -> next;
	Cmd -> {Cmd,St}
    end.

highlight_sel_style({vertex,_}) -> temporary;
highlight_sel_style({edge,_}) -> temporary;
highlight_sel_style({face,_}) -> temporary;
highlight_sel_style({body,_}) -> temporary;
highlight_sel_style({edit,repeat}) -> temporary;
highlight_sel_style({edit,repeat_args}) -> temporary;
highlight_sel_style({edit,repeat_drag}) -> temporary;
highlight_sel_style({select,vertex}) -> none;
highlight_sel_style({select,edge}) -> none;
highlight_sel_style({select,face}) -> none;
highlight_sel_style({select,body}) -> none;
highlight_sel_style({select,{adjacent,_}}) -> none;
highlight_sel_style({select,_}) -> permanent;
highlight_sel_style({view,align_to_selection}) -> temporary;
highlight_sel_style({view,aim}) -> temporary;
highlight_sel_style(_) -> none.

do_command(Cmd, St0) ->
    St = remember_command(Cmd, St0),
    {replace,
     fun(Ev) -> handle_event(Ev, St) end,
     fun() -> raw_command(Cmd, none, St) end}.

raw_command(Cmd, Args, St) ->
    command_response(do_command_1(Cmd, St), Args, St).

command_response(#st{}=St, _, _) ->
    main_loop(clear_temp_sel(St));
command_response({drag,Drag}, Args, _) ->
    wings_drag:do_drag(Drag, Args);
command_response({save_state,#st{}=St}, _, St0) ->
    save_state(St0, St);
command_response({saved,St}, _, _) ->
    main_loop(St);
command_response({new,St}, _, _) ->
    main_loop(caption(wings_undo:init(St)));
command_response({push,_}=Push, _, _) ->
    Push;
command_response({init,_,_}=Init, _, _) ->
    Init;
command_response({seq,_,_}=Seq, _, _) ->
    Seq;
command_response({replace,_}=Replace, _, _) ->
    Replace;
command_response({replace,_,_}=Replace, _, _) ->
    Replace;
command_response(keep, _, _) ->
    keep;
command_response(quit, _, _) ->
    save_windows(),
    exit(normal).

do_command_1(Cmd, St0) ->
    case wings_plugin:command(Cmd, St0) of
	next -> command(Cmd, St0);
	St0 -> St0;
	#st{}=St -> {save_state,St};
	Other -> Other
    end.

remember_command({C,_}=Cmd, St) when C =:= vertex; C =:= edge;
				     C =:= face; C =:= body ->
    St#st{repeatable=Cmd,ask_args=none,drag_args=none};
remember_command(_Cmd, St) -> St.

%% Test if the saved command can be safely repeated, and
%% rewrite it with the current selection mode if needed.
repeatable(Mode, Cmd) ->
    case Cmd of
	{Mode,_} -> Cmd;			%Same mode is always OK.

	%% Commands safe in all modes.
	{_,{move,normal}} when Mode == body -> no;
	{_,{move,_}=C} -> {Mode,C};
	{_,{rotate,normal}} when Mode == body -> no;
	{_,{rotate,_}=C} -> {Mode,C};
	{_,{scale,_}=C} -> {Mode,C};

	%% Some special cases.
	{_,tighten=C} when Mode == vertex; Mode == body -> {Mode,C};
	{_,smooth=C} when Mode == face; Mode == body -> {Mode,C};
	
	%% No more commands are safe in body mode.
	{_,_} when Mode == body -> no;
	{_,{flatten,_}=C} when Mode == vertex; Mode == face -> {Mode,C};
	{_,dissolve} when Mode == vertex -> no;
	{_,dissolve=C} -> {Mode,C};
	{_,bevel=C} -> {Mode,C};
	{_,{extrude,_}=C} -> {Mode,C};
	{_,collapse=C} -> {Mode,C};

	%% Other special commands.
	{_,connect} when Mode == face -> no;
	{_,connect=C} -> {Mode,C};

	%% Other commands only work in the saved mode.
	_ -> no
    end.

%% Vector and secondary-selection commands.
command({shape,Shape}, St0) ->
    case wings_shapes:command(Shape, St0) of
    	St0 -> St0;
	#st{}=St -> {save_state,St};
	Other -> Other
    end;
command({help,What}, St) ->
    wings_help:command(What, St);

%% Drag & drop.
command({drop,What}, St) ->
    drop_command(What, St);

%% File menu.
command({file,Command}, St) ->
    wings_file:command(Command, St);

%% Edit menu.
command({edit,undo_toggle}, St) ->
    caption(wings_undo:undo_toggle(St));
command({edit,undo}, St) ->
    caption(wings_undo:undo(St));
command({edit,redo}, St) ->
    caption(wings_undo:redo(St));
command({edit,repeat}, #st{sel=[]}=St) -> St;
command({edit,repeat}, #st{selmode=Mode,repeatable=Cmd0}=St) ->
    case repeatable(Mode, Cmd0) of
	no -> keep;
	Cmd when is_tuple(Cmd) -> raw_command(Cmd, none, St)
    end;
command({edit,repeat}, St) -> St;
command({edit,repeat_args}, #st{sel=[]}=St) -> St;
command({edit,repeat_args}, #st{selmode=Mode,repeatable=Cmd0,
				ask_args=AskArgs}=St) ->
    case repeatable(Mode, Cmd0) of
	no -> keep;
	Cmd1 when is_tuple(Cmd1) ->
	    Cmd = replace_ask(Cmd1, AskArgs),
	    raw_command(Cmd, none, St)
    end;
command({edit,repeat_args}, St) -> St;
command({edit,repeat_drag}, #st{sel=[]}=St) -> St;
command({edit,repeat_drag}, #st{selmode=Mode,repeatable=Cmd0,
				ask_args=AskArgs,drag_args=DragArgs}=St) ->
    case repeatable(Mode, Cmd0) of
	no -> keep;
	Cmd1 when is_tuple(Cmd1) ->
	    Cmd = replace_ask(Cmd1, AskArgs),
	    raw_command(Cmd, DragArgs, St)
    end;
command({edit,repeat_drag}, St) -> St;
command({edit,purge_undo}, St) ->
    purgo_undo(St);
command({edit,confirmed_purge_undo}, St) ->
    wings_undo:init(St);
command({edit,enable_patches}, St) ->
    wings_start:enable_patches(),
    St;
command({edit,disable_patches}, St) ->
    wings_start:disable_patches(),
    St;
command({edit,{preferences,Pref}}, St) ->
    wings_pref:command(Pref, St);

%% Select menu.
command({select,Command}, St) ->
    wings_sel_cmd:command(Command, St);

%% View menu.
command({view,Command}, St) ->
    wings_view:command(Command, St);

%% Window menu.
command({window,geom_viewer}, St) ->
    new_viewer(St),
    keep;
command({window,outliner}, St) ->
    wings_outliner:window(St);
command({window,object}, St) ->
    wings_shape:window(St);
command({window,palette}, St) ->
    wings_palette:window(St);
command({window,console}, _St) ->
    wings_console:window(),
    keep;
command({window,uv_editor_window}, St) ->
    wpc_autouv:window(St),
    keep;

%% Body menu.
command({body,Cmd}, St) ->
    wings_body:command(Cmd, St);

%% Face menu.
command({face,Cmd}, St) ->
    wings_face_cmd:command(Cmd, St);

%% Edge commands.
command({edge,Cmd}, St) ->
    wings_edge:command(Cmd, St);

%% Vertex menu.
command({vertex,Cmd}, St) ->
    wings_vertex_cmd:command(Cmd, St);

%% Light menu.
command({light,Cmd}, St) ->
    wings_light:command(Cmd, St);

%% Material commands.
command({material,Cmd}, St) ->
    wings_material:command(Cmd, St);

%% Tools menu.

command({tools,set_default_axis}, St) ->
    wings:ask({[axis,point],[]}, St,
	      fun({Axis,Point}, _) ->
		      wings_pref:set_value(default_axis, {Point,Axis}),
		      keep
	      end);
command({tools,{align,Dir}}, St) ->
    {save_state,wings_align:align(Dir, St)};
command({tools,{center,Dir}}, St) ->
    {save_state,wings_align:center(Dir, St)};
command({tools,save_bb}, St) ->
    wings_align:copy_bb(St);
command({tools,{scale_to_bb,Dir}}, St) ->
    {save_state,wings_align:scale_to_bb(Dir, St)};
command({tools,{scale_to_bb_prop,Dir}}, St) ->
    {save_state,wings_align:scale_to_bb_prop(Dir, St)};
command({tools,{move_to_bb,Dir}}, St) ->
    {save_state,wings_align:move_to_bb(Dir, St)};
command({tools,{virtual_mirror,Cmd}}, St) ->
    wings_view:virtual_mirror(Cmd, St);
command({tools, screenshot}, St) ->
    wings_image:screenshot(),
    St.

popup_menu(X, Y, #st{sel=[]}=St) ->
    wings_shapes:menu(X, Y, St);
popup_menu(X, Y, #st{selmode=Mode}=St) ->
    case wings_light:is_any_light_selected(St) of
	true -> wings_light:menu(X, Y, St);
	false ->
	    case Mode of
		vertex -> wings_vertex_cmd:menu(X, Y, St);
		edge -> wings_edge:menu(X, Y, St);
		face -> wings_face_cmd:menu(X, Y, St);
		body -> wings_body:menu(X, Y, St)
	    end
    end.

init_menubar() ->
   Menus = [{?STR(init_menubar,1,"File"),file,fun(St) -> wings_file:menu(St) end},
          {?STR(init_menubar,2,"Edit"),edit,fun edit_menu/1},
          {?STR(init_menubar,3,"View"),view,fun(St) -> wings_view:menu(St) end},
          {?STR(init_menubar,4,"Select"),select,fun(St) -> wings_sel_cmd:menu(St) end},
          {?STR(init_menubar,5,"Tools"),tools,fun tools_menu/1},
          {?STR(init_menubar,6,"Window"),window,fun window_menu/1},
          {?STR(init_menubar,7,"Help"),help,fun(St) -> wings_help:menu(St) end}],
    put(wings_menu_template, Menus).

edit_menu(St) ->
    UndoInfo = lists:flatten([?STR(edit_menu,1,
				   "Delete undo history to reclaim memory"),
			      " (",undo_info(St),")"]),
    [{?STR(edit_menu,3,"Undo/Redo"),undo_toggle,
      ?STR(edit_menu,4,"Undo or redo the last command")},
     {?STR(edit_menu,5,"Redo"),redo,
      ?STR(edit_menu,6,"Redo the last command that was undone")},
     {?STR(edit_menu,7,"Undo"),undo,
      ?STR(edit_menu,8,"Undo the last command")},
     separator,
     {command_name(?STR(edit_menu,9,"Repeat"), St),repeat},
     {command_name(?STR(edit_menu,10,"Repeat Args"), St),repeat_args},
     {command_name(?STR(edit_menu,11,"Repeat Drag"), St),repeat_drag},
     separator,
     wings_pref:menu(St),
     {?STR(edit_menu,12,"Plug-in Preferences"),{plugin_preferences,[]}},
     separator,
     {?STR(edit_menu,13,"Purge Undo History"),purge_undo,UndoInfo}|patches()].

undo_info(St) ->
    {Un,Rn} = wings_undo:info(St),
    Undo = case Un of
	       0 -> ?STR(undo_info,1,"there are no undo states");
	       1 -> ?STR(undo_info,2,"there is one undo state");
	       _ -> io_lib:format(?STR(undo_info,3,"there are ~p undo states"), [Un])
	   end,
    case Rn of
	0 -> Undo;
	1 -> [Undo|?STR(undo_info,4,"; one operation can be redone")];
	_ -> [Undo|io_lib:format(?STR(undo_info,5,"; ~p operations can be redone"), [Rn])]
    end.

tools_menu(_) ->
    Dirs = [{wings_s:dir(all),all},
	    {wings_s:dir(x),x},
	    {wings_s:dir(y),y},
	    {wings_s:dir(z),z},
	    {wings_s:dir(radial_x),radial_x},
	    {wings_s:dir(radial_y),radial_y},
	    {wings_s:dir(radial_z),radial_z}],
    [{?STR(tools_menu,8,"Align"),{align,Dirs}},
     {?STR(tools_menu,9,"Center"),{center,Dirs}},
     separator,
     {?STR(tools_menu,10,"Save Bounding Box"),save_bb},
     {?STR(tools_menu,11,"Scale to Saved BB"),{scale_to_bb,Dirs}},
     {?STR(tools_menu,12,"Scale to Saved BB Proportionally"),{scale_to_bb_prop,Dirs}},
     {?STR(tools_menu,13,"Move to Saved BB"),{move_to_bb,wings_menu_util:all_xyz()}},
     separator,
     {?STR(tools_menu,14,"Set Default Axis"),set_default_axis,
      ?STR(tools_menu,15,"Define and store axis (with ref. point) for later use with any ")++
      ?STR(tools_menu,16,"\"Default Axis\" command (e.g. Scale|Default Axis)")},
     separator,
     {?STR(tools_menu,17,"Virtual Mirror"),
      {virtual_mirror,
       [{?STR(tools_menu,18,"Create"),create,
	 ?STR(tools_menu,19,"Given a face selection, set up a virtual mirror")},
	{?STR(tools_menu,20,"Break"),break,
	 ?STR(tools_menu,21,"Remove virtual mirrors for all objects")},
	{?STR(tools_menu,22,"Freeze"),freeze,
	 ?STR(tools_menu,23,"Create real geometry from the virtual mirrors")}]}},
     separator,
     {?STR(tools_menu,24,"Screenshot"), screenshot, 
      ?STR(tools_menu,25,"Grab an image of the window (export it from the outliner)")}].

window_menu(_) ->
    Name = case wings_wm:this() of
	       {_,geom} ->
		   ?STR(window_menu,1,"Geometry Graph");
	       {_,{geom,N}} ->
		   ?STR(window_menu,2,"Geometry Graph #") ++ integer_to_list(N)
	   end,
    [{?STR(window_menu,3,"Outliner"),outliner,
      ?STR(window_menu,4,"Open the outliner window (showing materials and objects)")},
     {Name,object,
      ?STR(window_menu,5,"Open a Geometry Graph window (showing objects)")},
     {?STR(window_menu,6,"Palette"), palette,?STR(window_menu,7,"Open the color palette window")},
     separator,
     {?STR(window_menu,8,"New Geometry Window"),geom_viewer, ?STR(window_menu,9,"Open a new Geometry window")},
     {?STR(window_menu,10,"Console"),console,?STR(window_menu,11,"Open a console window for information messages")},
     separator,
     {?STR(window_menu,12,"UV Editor Window"),uv_editor_window,
      ?STR(window_menu,13,"Open a UV Editor window for each selected object")}].

patches() ->
    case wings_start:get_patches() of
	none -> [];
	{enabled,Desc} ->
	    [separator,{?STR(patches,1,"Use ")++Desc,disable_patches,[crossmark]}];
	{disabled,Desc} ->
	    [separator,{?STR(patches,1,"Use ")++Desc,enable_patches}]
    end.

set_temp_sel(#st{sh=Sh,selmode=Mode}, St) ->
    St#st{temp_sel={Mode,Sh}}.

clear_temp_sel(#st{temp_sel=none}=St) -> St;
clear_temp_sel(#st{temp_sel={Mode,Sh}}=St) ->
    St#st{temp_sel=none,selmode=Mode,sh=Sh,sel=[]}.


purgo_undo(St) ->
    This = wings_wm:this(),
    {Un,Rn} = wings_undo:info(St),
    Qs = {vframe,
         [{label,?STR(purgo_undo,1,"Undo states: ") ++ integer_to_list(Un)},
          {label,?STR(purgo_undo,2,"Redo states: ")  ++ integer_to_list(Rn)},
	   separator|
	   if
	       Un+Rn =:= 0 ->
		   [{label,?STR(purgo_undo,3,"Nothing to remove")},
		    {hframe,[{button,ok}]}];
	       true ->
		   [{label,?STR(purgo_undo,4,"Remove all states (NOT undoable)?")},
		    {hframe,[{button,wings_s:yes(),
			      fun(_) ->
				      Action = {action,{edit,confirmed_purge_undo}},
				      wings_wm:send(This, Action)
			      end},
			     {button,wings_s:no(),cancel,[cancel]}]}]
	   end]},
    wings_ask:dialog("", Qs, fun(_) -> ignore end).

info(#st{sel=[]}) -> [];
info(St) ->
    case wings_wm:get_prop(show_info_text) of
	false -> [];
	true -> info_1(St)
    end.
	    
info_1(#st{shapes=Shapes,selmode=body,sel=[{Id,_}]}) ->
    Sh = gb_trees:get(Id, Shapes),
    shape_info(Sh);
info_1(#st{shapes=Shapes,selmode=body,sel=Sel}) ->
    shape_info(Sel, Shapes);
info_1(#st{selmode=vertex,sel=[{_Id,Sel}]}=St) ->
    case gb_sets:size(Sel) of
	0 -> "";
	1 ->
	    [V] = gb_sets:to_list(Sel),
	    measure(io_lib:format(?STR(info_1,1,"Vertex ~p selected"), [V]), St);
	N when N < 5 ->
	    Vs = gb_sets:to_list(Sel),
	    measure(item_list(Vs, ?STR(info_1,2,"Vertices")), St);
	N ->
	    io_lib:format(?STR(info_1,3,"~p vertices selected"), [N])
    end;
info_1(#st{selmode=edge,sel=[{_,Sel}]}=St) ->
    case gb_sets:size(Sel) of
	0 -> "";
	1 ->
	    [Edge] = gb_sets:to_list(Sel),
	    measure(io_lib:format(?STR(info_1,4,"Edge ~p selected"), [Edge]), St);
	N when N < 5 ->
	    Edges = gb_sets:to_list(Sel),
	    item_list(Edges, ?STR(info_1,5,"Edges"));
	N ->
	    io_lib:format(?STR(info_1,6,"~p edges selected"), [N])
    end;
info_1(#st{selmode=face,sel=[{_,Sel}]}=St) ->
    case gb_sets:size(Sel) of
	0 -> "";
	1 ->
	    [Face] = gb_sets:to_list(Sel),
	    measure(io_lib:format(?STR(info_1,7,"Face ~p selected"), [Face]), St);
	N when N < 5 ->
	    Faces = gb_sets:to_list(Sel),
	    item_list(Faces,?STR(info_1,8,"Faces"));
	N ->
	    io_lib:format(?STR(info_1,9,"~p faces selected"), [N])
    end;
info_1(#st{selmode=Mode,sel=Sel}=St) ->
    On = length(Sel),
    N = foldl(fun({_,S}, A) -> A+gb_sets:size(S) end, 0, Sel),
    Str = case Mode of
	      vertex ->
		  io_lib:format(?STR(info_1,10,"~p vertices selected in ~p objects"), [N,On]);
	      edge ->
		  io_lib:format(?STR(info_1,11,"~p edges selected in ~p objects"), [N,On]);
	      face ->
		  io_lib:format(?STR(info_1,12,"~p faces selected in ~p objects"), [N,On])
	  end,
    measure(Str, St).

measure(Base, #st{selmode=vertex,sel=[{Id,Vs}],shapes=Shs}) ->
    case gb_sets:size(Vs) of
	1 ->
	    We = gb_trees:get(Id, Shs),
 	    [Va] = gb_sets:to_list(Vs),
	    {X,Y,Z} = wings_vertex:pos(Va, We),
	    [Base|io_lib:format(?STR(measure,1,". Position ~s ~s ~s"),
				[wings_util:nice_float(X),
				 wings_util:nice_float(Y),
				 wings_util:nice_float(Z)])];
	2 ->
	    We = gb_trees:get(Id, Shs),
 	    [Va,Vb] = gb_sets:to_list(Vs),
 	    Dist = e3d_vec:dist(wings_vertex:pos(Va, We),
				wings_vertex:pos(Vb, We)),
	    [Base|io_lib:format(?STR(measure,2,". Distance ~s"),
				[wings_util:nice_float(Dist)])];
	_ -> Base
    end;
measure(Base, #st{selmode=vertex,sel=[{IdA,VsA},{IdB,VsB}],shapes=Shs}) ->
    case gb_sets:size(VsA) == 1 andalso gb_sets:size(VsB) == 1 of
	false -> Base;
	true ->
	    WeA = gb_trees:get(IdA, Shs),
	    WeB = gb_trees:get(IdB, Shs),
 	    [Va] = gb_sets:to_list(VsA),
 	    [Vb] = gb_sets:to_list(VsB),
 	    Dist = e3d_vec:dist(wings_vertex:pos(Va, WeA),
				wings_vertex:pos(Vb, WeB)),
	    [Base|io_lib:format(?STR(measure,2,". Distance ~s"),
				[wings_util:nice_float(Dist)])]
    end;
measure(Base, #st{selmode=edge,sel=[{Id,Es}],shapes=Shs}) ->
    case gb_sets:size(Es) of
	1 ->
	    We = gb_trees:get(Id, Shs),
 	    [Edge] = gb_sets:to_list(Es),
	    #edge{vs=Va,ve=Vb} = gb_trees:get(Edge, We#we.es),
	    PosA = wings_vertex:pos(Va, We),
	    PosB = wings_vertex:pos(Vb, We),
 	    Dist = e3d_vec:dist(PosA, PosB),
	    {X,Y,Z} = e3d_vec:average([PosA,PosB]),
	    [Base|io_lib:format(?STR(measure,3,". Midpt ~s ~s ~s. Length ~s"),
				[wings_util:nice_float(X),
				 wings_util:nice_float(Y),
				 wings_util:nice_float(Z),
				 wings_util:nice_float(Dist)])];
	_ -> Base
    end;
measure(Base, #st{selmode=face,sel=[{Id,Fs}],shapes=Shs}) ->
    case gb_sets:size(Fs) of
	1 ->
	    We = gb_trees:get(Id, Shs),
 	    [Face] = gb_sets:to_list(Fs),
	    {X,Y,Z} = wings_face:center(Face, We),
	    Mat = wings_material:get(Face, We),
	    [Base|io_lib:format(?STR(measure,4,". Midpt ~s ~s ~s.\nMaterial ~s"),
				[wings_util:nice_float(X),
				 wings_util:nice_float(Y),
				 wings_util:nice_float(Z),
				 Mat])];
	_ -> Base
    end;
measure(Base, _) -> Base.

item_list(Items, Desc) ->
    item_list(Items, " ", Desc).

item_list([Item|Items], Sep, Desc) ->
    item_list(Items, ", ", [Desc,Sep|integer_to_list(Item)]);
item_list([], _Sep, Desc) -> [Desc|?STR(item_list,1," selected")].

shape_info(We) when ?IS_LIGHT(We) ->
    wings_light:info(We);
shape_info(#we{id=Id,name=Name,fs=Ftab,es=Etab,vp=Vtab,mode=Mode}) ->
    Faces = gb_trees:size(Ftab),
    Edges = gb_trees:size(Etab),
    Vertices = gb_trees:size(Vtab),
    io_lib:format(?STR(shape_info,1,
		       "Object ~p \"~s\" has ~p polygons, "
		       "~p edges, ~p vertices.\n"
		       "Mode is ~p"),
		  [Id,Name,Faces,Edges,Vertices,Mode]).

shape_info(Objs, Shs) ->
    shape_info(Objs, Shs, 0, 0, 0, 0).

shape_info([{Id,_}|Objs], Shs, On, Vn, En, Fn) ->
    #we{fs=Ftab,es=Etab,vp=Vtab} = gb_trees:get(Id, Shs),
    Faces = gb_trees:size(Ftab),
    Edges = gb_trees:size(Etab),
    Vertices = gb_trees:size(Vtab),
    shape_info(Objs, Shs, On+1, Vn+Vertices, En+Edges, Fn+Faces);
shape_info([], _Shs, N, Vertices, Edges, Faces) ->
    io_lib:format(?STR(shape_info,2,
		       "~p objects, ~p faces, ~p edges, ~p vertices"),
		  [N,Faces,Edges,Vertices]).

caption(#st{file=undefined}=St) ->
    Caption = wings(),
    sdl_video:wm_setCaption(Caption, Caption),
    St;
caption(#st{saved=true,file=Name}=St) ->
    Caption = wings() ++ " - " ++ filename:basename(Name),
    sdl_video:wm_setCaption(Caption, Caption),
    St;
caption(#st{saved=auto,file=Name}=St) ->
    Caption = wings() ++ " - " ++ filename:basename(Name) ++
	"* [" ++ ?STR(caption,1,"auto-saved") ++ "]",
    sdl_video:wm_setCaption(Caption, Caption),
    St;
caption(#st{file=Name}=St) ->
    Caption = wings() ++ " - " ++ filename:basename(Name) ++ "*",
    sdl_video:wm_setCaption(Caption, Caption),
    St.

command_name(_Repeat, #st{repeatable=ignore}) ->
    "("++cannot_repeat()++")";
command_name(Repeat, #st{repeatable={_,Cmd}}=St) ->
    CmdStr = wings_util:stringify(Cmd),
    command_name(Repeat, CmdStr, St).

command_name(_Repeat, CmdStr, #st{sel=[]}) ->
    cannot_repeat(CmdStr);
command_name(Repeat, CmdStr, #st{selmode=Mode,repeatable=Cmd}) ->
    case repeatable(Mode, Cmd) of
	no -> cannot_repeat(CmdStr);
	_ ->  lists:flatten([Repeat," ",wings_util:quote(CmdStr)])
    end.

cannot_repeat(Cmd) ->
    lists:flatten(["(",cannot_repeat(),wings_util:quote(Cmd),")"]).

cannot_repeat() -> ?STR(cannot_repeat,1,"Can't repeat").

replace_ask(Term, none) -> Term;
replace_ask({'ASK',_}, AskArgs) -> AskArgs;
replace_ask(Tuple0, AskArgs) when is_tuple(Tuple0) ->
    Tuple = [replace_ask(El, AskArgs) || El <- tuple_to_list(Tuple0)],
    list_to_tuple(Tuple);
replace_ask(Term, _) -> Term.

define_command(?SDL_RELEASED, N, #st{repeatable=Cmd,def=DefCmd0}) ->
    This = wings_wm:this(),
    CmdStr = wings_util:stringify(Cmd),
    Button = case N of
		 1 -> wings_s:lmb();
		 2 -> wings_s:mmb()
	     end,
    Q = lists:flatten([?STR(define_command,1,"Do you want to define"),
		       " ",wings_util:quote(CmdStr), " ",
		       ?STR(define_command,2,"as a default command"),
		       " (",wings_s:key(ctrl),"+",Button,")?"]),
    wings_util:yes_no(Q,
		      fun() ->
			      DefCmd = setelement(N, DefCmd0, Cmd),
			      wings_wm:send(This, {new_default_command,DefCmd}),
			      ignore
		      end, ignore);
define_command(_, _, _) -> keep.

use_command(#mousebutton{state=?SDL_RELEASED,button=N}=Ev,
	    #st{selmode=Mode,def=DefCmd}=St) ->
    case repeatable(Mode, element(N, DefCmd)) of
	no -> keep;
	Cmd when is_tuple(Cmd) ->
	    do_use_command(Ev, Cmd, St)
    end;
use_command(_, _) -> keep.

geom_title(geom) ->
    ?STR(geom_title,1,"Geometry");
geom_title({geom,N}) ->
    ?STR(geom_title,2,"Geometry #") ++ integer_to_list(N).

do_use_command(#mousebutton{x=X,y=Y}, Cmd0, #st{sel=[]}=St0) ->
    case wings_pref:get_value(use_temp_sel) of
	false ->
	    keep;
	true ->
	    case wings_pick:do_pick(X, Y, St0) of
		{add,_,#st{selmode=Mode}=St} ->
		    %% The selection mode may have changed.
		    %% Must check (and possibly convert) the command again.
		    case repeatable(Mode, Cmd0) of
			no -> keep;
			Cmd ->
			    wings_wm:later({action,Cmd}),
			    main_loop_noredraw(set_temp_sel(St0, St))
		    end;
		_Other -> keep
	    end
    end;
do_use_command(_, Cmd, _) -> wings_wm:later({action,Cmd}).

crash_logger(Crash, St) ->
    LogName = wings_util:crash_log(geom, Crash),
    get_crash_event(LogName, St).

get_crash_event(Log, St) ->
    wings_wm:dirty(),
    {replace,fun(Ev) -> crash_handler(Ev, Log, St) end}.

crash_handler(redraw, Log, _St) ->
    wings_wm:clear_background(),
    wings_io:ortho_setup(),
    wings_io:text_at(10, 2*?LINE_HEIGHT,
		     ?STR(crash_handler,1,
			  "Internal error - log written to") ++
		     " " ++ Log),
    wings_io:text_at(10, 4*?LINE_HEIGHT,
		     ?STR(crash_handler,2,
			  "Click a mouse button to continue working")),
    wings_util:button_message(?STR(crash_handler,3,"Continue working")),
    keep;
crash_handler(#mousebutton{}, _, St) ->
    wings_wm:message(""),
    wings_wm:menubar(wings_wm:this(), get(wings_menu_template)),
    main_loop(St);
crash_handler(_, Log, St) ->
    get_crash_event(Log, St).

-ifdef(DEBUG).
wings() -> "Wings 3D [debug]".
-else.
wings() -> "Wings 3D".
-endif.

%%%
%%% Drag & Drop.
%%%

set_drag_filter(Name) ->
    F = fun({material,_}) -> yes;
	   (_) -> no
	end,
    wings_wm:set_prop(Name, drag_filter, F).

handle_drop(DropData, {X0,Y0}, St) ->
    {X,Y} = wings_wm:local2global(X0, Y0),
    handle_drop_1(DropData, X, Y, St).

handle_drop_1(_, X, Y, #st{sel=[]}) ->
    wings_menu:popup_menu(X, Y, drop,
			  [{ ?STR(handle_drop_1,1,"No Selection"),cancel_drop, ?STR(handle_drop_1,2,"Cancel drop operation")}]);
handle_drop_1({material,Name}, X, Y, #st{selmode=face}) ->
    Menu = [{ ?STR(handle_drop_1,3,"Assign material to selected faces"),menu_cmd(assign_to_sel, Name),
	      ?STR(handle_drop_1,4,"Assign material \"")++Name++ ?STR(handle_drop_1,5,"\" only to selected faces")},
	    { ?STR(handle_drop_1,6,"Assign material to all faces"),
	     menu_cmd(assign_to_body, Name),
	      ?STR(handle_drop_1,7,"Assign material \"")++Name++
              ?STR(handle_drop_1,8,"\" to all faces in objects having a selection")}],
    wings_menu:popup_menu(X, Y, drop, Menu);
handle_drop_1({material,Name}, X, Y, _) ->
    Menu = [{ ?STR(handle_drop_1,9,"Assign material to all faces"),
	     menu_cmd(assign_to_body, Name),
	      ?STR(handle_drop_1,10,"Assign material \"")++Name++
	     ?STR(handle_drop_1,11,"\" to all faces in objects having a selection")}],
    wings_menu:popup_menu(X, Y, drop, Menu).
    
menu_cmd(Cmd, Id) ->
    {'VALUE',{Cmd,Id}}.

drop_command({assign_to_sel,Name}, St) ->
    wings_material:command({assign,Name}, St);
drop_command({assign_to_body,Name}, #st{selmode=Mode}=St0) ->
    St = wings_material:command({assign,Name}, St0#st{selmode=body}),
    St#st{selmode=Mode};
drop_command(cancel_drop, St) -> St.

%%%
%%% Saving and restoring of window layouts.
%%%

save_windows() ->
    Saved = save_windows_1(wings_wm:windows()),
    wings_pref:set_value(saved_windows, Saved).

save_windows_1([console|Ns]) ->
    save_window(console, Ns);
save_windows_1([palette|Ns]) ->
    save_window(palette, Ns);
save_windows_1([outliner|Ns]) ->
    save_window(outliner, Ns);
save_windows_1([{object,_}=N|Ns]) ->
    save_window(N, Ns);
save_windows_1([geom=N|Ns]) ->
    save_geom_window(N, Ns);
save_windows_1([{geom,_}=N|Ns]) ->
    save_geom_window(N, Ns);
save_windows_1([_|T]) -> save_windows_1(T);
save_windows_1([]) -> [].

save_window(Name, Ns) ->
    Pos = wings_wm:win_ur({controller,Name}),
    Size = wings_wm:win_size(Name),
    W = {Name,Pos,Size},
    [W|save_windows_1(Ns)].

save_geom_window(Name, Ns) ->
    {Pos,Size} = wings_wm:win_rect(Name),
    Ps0 = [{toolbar_hidden,wings_wm:is_hidden({toolbar,Name})}],
    Ps = save_geom_props(wings_wm:get_props(Name), Ps0),
    Geom = {Name,Pos,Size,Ps},
    [Geom|save_windows_1(Ns)].

save_geom_props([{show_axes,_}=P|T], Acc) ->
    save_geom_props(T, [P|Acc]);
save_geom_props([{show_groundplane,_}=P|T], Acc) ->
    save_geom_props(T, [P|Acc]);
save_geom_props([{current_view,View}|T], Acc) ->
    #view{fov=Fov,hither=Hither,yon=Yon} = View,
    save_geom_props(T, [{fov,Fov},{clipping_planes,Hither,Yon}|Acc]);
save_geom_props([{show_info_text,_}=P|T], Acc) ->
    save_geom_props(T, [P|Acc]);
save_geom_props([_|T], Acc) ->
    save_geom_props(T, Acc);
save_geom_props([], Acc) -> Acc.

restore_windows(St) ->
    %% Sort windows using names as keys to make sure we
    %% create the geometry windows before the object windows.
    %% (Because we set up links.)
    Windows0 = wings_pref:get_value(saved_windows, []),
    Windows1 = sort([{element(1, W),W} || W <- Windows0]),
    Windows = [W || {_,W} <- Windows1],
    restore_windows_1(Windows, St).

restore_windows_1([{geom,{_,_}=Pos0,{_,_}=Size,Ps0}|Ws], St) ->
    Ps = geom_props(Ps0),
    case proplists:get_bool(toolbar_hidden, Ps) of
	true -> wings_wm:hide({toolbar,geom});
	false -> ok
    end,
    Pos = geom_pos(Pos0),
    wings_wm:move(geom, Pos, Size),
    set_geom_props(Ps, geom),
    restore_windows_1(Ws, St);
restore_windows_1([{{geom,_}=Name,Pos0,Size,Ps0}|Ws], St) ->
    Ps = geom_props(Ps0),
    ToolbarHidden = proplists:get_bool(toolbar_hidden, Ps),
    new_viewer(Name, {0,0}, Size, initial_properties(), ToolbarHidden, St),
    Pos = geom_pos(Pos0),
    wings_wm:move(Name, Pos, Size),
    set_geom_props(Ps, Name),
    restore_windows_1(Ws, St);
restore_windows_1([{{object,_}=Name,{_,_}=Pos,{_,_}=Size}|Ws], St) ->
    wings_shape:window(Name, Pos, Size, St),
    restore_windows_1(Ws, St);
restore_windows_1([{outliner,{_,_}=Pos,{_,_}=Size}|Ws], St) ->
    wings_outliner:window(Pos, Size, St),
    restore_windows_1(Ws, St);
restore_windows_1([{console,{_,_}=Pos,{_,_}=Size}|Ws], St) ->
    wings_console:window(console, Pos, Size),
    restore_windows_1(Ws, St);
restore_windows_1([{palette,{_,_}=Pos,{_,_}=Size}|Ws], St) ->
    wings_palette:window(Pos,Size,St),
    restore_windows_1(Ws, St);
restore_windows_1([_|Ws], St) ->
    restore_windows_1(Ws, St);
restore_windows_1([], _) -> ok.

geom_pos({X,Y}=Pos) ->
    {_,Upper0} = wings_wm:win_ul(desktop),
    Upper1 = case wings_wm:is_hidden({toolbar,geom}) of
		 true -> Upper0;
		 false ->
		     {_,ToolbarH} = wings_wm:win_size({toolbar,geom}),
		     Upper0+ToolbarH
	     end,
    {_,TitleH} = wings_wm:win_size({controller,geom}),
    case Upper1 + TitleH of
	Upper when Y < Upper -> {X,Upper};
	_ -> Pos
    end.

geom_props(B) when B == false; B == true ->
    [{toolbar_hidden,B}];
geom_props(L) when is_list(L) -> L;
geom_props(_) -> [].

set_geom_props([{show_axes,B}|T], Name) ->
    wings_wm:set_prop(Name, show_axes, B),
    set_geom_props(T, Name);
set_geom_props([{show_groundplane,B}|T], Name) ->
    wings_wm:set_prop(Name, show_groundplane, B),
    set_geom_props(T, Name);
set_geom_props([{show_info_text,B}|T], Name) ->
    wings_wm:set_prop(Name, show_info_text, B),
    set_geom_props(T, Name);
set_geom_props([{fov,Fov}|T], Name) ->
    View = wings_wm:get_prop(Name, current_view),
    wings_wm:set_prop(Name, current_view, View#view{fov=Fov}),
    set_geom_props(T, Name);
set_geom_props([{clipping_planes,Hither,Yon}|T], Name)
  when Hither > 0, Hither < Yon  ->
    View = wings_wm:get_prop(Name, current_view),
    wings_wm:set_prop(Name, current_view, View#view{hither=Hither,yon=Yon}),
    set_geom_props(T, Name);
set_geom_props([_|T], Name) ->
    set_geom_props(T, Name);
set_geom_props([], _) -> ok.

initial_properties() ->
    [{display_lists,geom_display_lists}|wings_view:initial_properties()].

mode_restriction(Modes) ->
    Win = {toolbar,wings_wm:this()},
    wings_wm:send(Win, {mode_restriction,Modes}),
    case Modes of
	none ->
	    wings_wm:erase_prop(Win, mode_restriction);
	_ ->
	    wings_wm:set_prop(Win, mode_restriction, Modes)
    end.

clear_mode_restriction() ->
    mode_restriction(none).

get_mode_restriction() ->
    Name = wings_wm:this(),
    Toolbar = {toolbar,Name},
    case wings_wm:lookup_prop(Toolbar, mode_restriction) of
	none -> [edge,vertex,face,body];
	{value,Other} -> Other
    end.
