%%
%%  wings_pref.erl --
%%
%%     Preference management.
%%
%%  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_pref.erl,v 1.128 2004/10/15 15:02:54 bjorng Exp $
%%

-module(wings_pref).
-export([init/0,finish/0,
	 menu/1,command/2,
	 get_value/1,get_value/2,set_value/2,set_default/2,
	 delete_value/1,
	 get_scene_value/0,get_scene_value/1,get_scene_value/2,
	 set_scene_value/2,set_scene_default/2,
	 delete_scene_value/0,delete_scene_value/1]).

-define(NEED_ESDL, 1).    %% Some keybindings
-include("wings.hrl").
-import(lists, [foreach/2,keysearch/3,map/2,reverse/1,sort/1,last/1]).

-define(MAC_PREFS, "Library/Preferences/Wings 3D Preferences.txt").
-define(WIN32_OLD_PREFS, "Preferences").
-define(WIN32_PREFS, "Wings3D/Preferences.txt").

init() ->
    ets:new(wings_state, [named_table,public,ordered_set]),
    ets:new(wings_delayed_update, [named_table,public,ordered_set]),
    ets:new(wings_scene_prefs, [named_table,public,ordered_set]),
    ets:insert(wings_state, defaults()),
    wings_hotkey:set_default(),
    case old_pref_file() of
	none -> ok;
	PrefFile ->
            io:format("Reading preferences from: ~s\n", [PrefFile]),
	    case file:consult(PrefFile) of
		{ok,List0} ->
		    List = clean(List0),
		    catch ets:insert(wings_state, List);
		{error,_Reason} ->
		    ok
	    end
    end.

finish() ->
    foreach(fun({Key,Val}) ->
		    set_value(Key, Val)
	    end, ets:tab2list(wings_delayed_update)),
    PrefFile = new_pref_file(),
    List0 = ets:tab2list(wings_state),
    List = prune_defaults(List0),
    Format = "~p. \n",
    PostProcess = case os:type() of
                      {win32,_} -> fun insert_crs/1;
                      _ -> fun(L) -> L end
                  end,
    Write = fun({{bindkey,_},_,default}) -> [];
	       ({{bindkey,_},_,plugin}) -> [];
	       ({{bindkey,_,_},_,default}) -> [];
	       ({{bindkey,_,_},_,plugin}) -> [];
	       (Else) -> PostProcess(io_lib:format(Format, [Else]))
	    end,
    Str = lists:map(Write, List),
    catch file:write_file(PrefFile, Str),
    ok.

%% Insert CRs into a deep list to produce a correct Windows
%% text file.
insert_crs([H|T]) -> [insert_crs(H)|insert_crs(T)];
insert_crs([]) -> [];
insert_crs($\n) -> "\r\n";
insert_crs(C) when is_integer(C) -> C.

prune_defaults(List) ->
    List -- defaults().

menu(_St) ->
    {?STR(menu,1,"Preferences..."),
      fun(_, _) ->
	      {edit,{preferences,prefs}}
      end,?STR(menu,2,"Edit the preferences for Wings"),[]}.

command(prefs, St) ->
    PrefQs0 = [{?STR(command,1,"General"),gen_prefs()},
	       {?STR(command,2,"Camera"),{'VALUE',wings_camera:prefs()}},
	       {?STR(command,3,"Advanced"),advanced_prefs()},
	       {?STR(command,4,"User Interface"),ui_prefs()},
	       {?STR(command,5,"Misc"),misc_prefs()}],
    PrefQs = [{Lbl,make_query(Ps)} || {Lbl,Ps} <- PrefQs0],
    Qs = [{oframe,PrefQs,1,[{style,buttons}]}],
	wings_ask:dialog(?STR(command,6,"Preferences"), Qs,
		     fun([_|Res]) ->
			     Dl = wings_wm:get_prop(geom, display_lists),
			     wings_wm:set_prop(wings_wm:this(), display_lists, Dl),
			     set_values(Res, St)
		     end).

set_values([{Key,Val}|Kvs], St) ->
    smart_set_value(Key, Val, St),
    set_values(Kvs, St);
set_values([], _) -> ignore.


gen_prefs() ->
    {vframe,
     [{hframe,
       [{vframe,
	 [{label_column,
	   [{?STR(gen_prefs,1,"Unselected Size"),vertex_size,
	     [{info,?STR(gen_prefs,2,"Size in pixels of unselected vertices (0.0 means not shown)")}]},
	    {?STR(gen_prefs,3,"Selected Size"),selected_vertex_size,
	     [{info,?STR(gen_prefs,4,"Size in pixels of selected vertices")}]}]}],
	 [{title,?STR(gen_prefs,5,"Vertex Display")}]},
	{vframe,
	 [{label_column,
	   [{?STR(gen_prefs,6,"Unselected Width"),edge_width,
	     [{info,?STR(gen_prefs,7,"Width in pixels of unselected edges")}]},
	    {?STR(gen_prefs,8,"Selected Width"),selected_edge_width,
	     [{info,?STR(gen_prefs,9,"Width in pixels of selected edges")}]},
	    {color,?STR(gen_prefs,10,"Soft Edges"),edge_color,[{info,?STR(gen_prefs,11,"Color of soft edges")}]},
	    {color,?STR(gen_prefs,12,"Hard Edges"),hard_edge_color,[{info,?STR(gen_prefs,13,"Color of hard edges")}]}]}],
	 [{title,?STR(gen_prefs,14,"Edge Display")}]}]},
      {hframe,
       [{vframe,
	 [{hframe,
	   [{?STR(gen_prefs,15,"Vertices"),vertex_hilite},
	    {?STR(gen_prefs,16,"Edges"),edge_hilite},
	    {?STR(gen_prefs,17,"Faces"),face_hilite},
	    {?STR(gen_prefs,18,"Objects"),body_hilite}]},
	  {hframe,
	   [{label,?STR(gen_prefs,19,"Unselected")},{color,unselected_hlite},
	    {label,?STR(gen_prefs,20,"Selected")},{color,selected_hlite}]},
	  {?STR(gen_prefs,21,"Smart Highlighting"),smart_highlighting}],
	 [{title,?STR(gen_prefs,22,"Highlighting")}]},
	{vframe,
	 [{vradio,[{?STR(gen_prefs,23,"Solid Face Selections"),solid},
		   {?STR(gen_prefs,24,"Stippled Face Selections"),stippled}],
	   selection_style},
	  {hframe,[{label,?STR(gen_prefs,25,"Selection Color")},{color,selected_color}]} ],
	 [{title,?STR(gen_prefs,26,"Selection")}]}]},
      {vframe,
       [{label_column,
	 [{color,?STR(gen_prefs,27,"Text"),info_color,[{info,?STR(gen_prefs,28,"Color of information text")}]},
	  {color,?STR(gen_prefs,29,"Background"),info_background_color,
	   [{info,?STR(gen_prefs,30,"Color of background for information text (including transparency)")}]}]}],
       [{title,?STR(gen_prefs,31,"Information text")}]},
      {hframe,
       [{label,?STR(gen_prefs,32,"Color")},{color,grid_color},
	{?STR(gen_prefs,33,"Force Axis-Aligned Grid"),force_show_along_grid,
	 [{info,?STR(gen_prefs,34,"Always show the grid when the view is aligned along one of the major axes")}]},
	{?STR(gen_prefs,aa_ortho1,"Force Axis-Aligned Ortho"),
	 force_ortho_along_axis,
	[{info, ?STR(gen_prefs,aa_ortho2,"Always go into orthogonal mode when the view is aligned along one of the major axes")}]}],
      [{title,?STR(gen_prefs,35,"Grid")}]},
      {hframe,
       [{vframe,
	 [{?STR(gen_prefs,36,"Show Axis Letters"),show_axis_letters},
	  {hframe,
	   [{label_column,
	     [{color,?STR(gen_prefs,37,"+X Color"),x_color},
	      {color,?STR(gen_prefs,38,"+Y Color"),y_color},
	      {color,?STR(gen_prefs,39,"+Z Color"),z_color}]},
	    {label_column,
	     [{color,?STR(gen_prefs,40,"-X Color"),neg_x_color},
	      {color,?STR(gen_prefs,41,"-Y Color"),neg_y_color},
	      {color,?STR(gen_prefs,42,"-Z Color"),neg_z_color}]}]}],
	 [{title,?STR(gen_prefs,43,"Axes")}]}
       ]}]}.

advanced_prefs() ->
    DisableHook = fun (is_disabled, {_Var,_I,Store}) ->
			  not gb_trees:get(advanced_menus, Store);
		      (_, _) ->	void
		  end,
    SuperDisable = fun (is_disabled, {_Var,_I,Store}) ->
			   not gb_trees:get(advanced_menus, Store) orelse
			       not gb_trees:get(use_temp_sel, Store);
		       (_, _) ->	void
		  end,
    Flags = [{hook,DisableHook}],

    {vframe,
     [{?STR(advanced_prefs,1,"Default Commands"),default_commands,
       [{info,?STR(advanced_prefs,2,"Allow defining commands that can be invoked by Ctrl+L or Ctrl+M")}]},
      {?STR(advanced_prefs,3,"Use Highlight as Temporary Selection"),use_temp_sel,
       [{info,?STR(advanced_prefs,4,"If there is no selection, ")++
	 ?STR(advanced_prefs,5,"allow commands to act on the highlighted element")}]},
      {?STR(advanced_prefs,6,"Hide Selection While Dragging"),hide_sel_while_dragging,
       [{info,?STR(advanced_prefs,7,"Don't show the selection in any interactive command")}]},
      {?STR(advanced_prefs,8,"Hide Selection While Moving Camera"),hide_sel_in_camera_moves,
       [{info,?STR(advanced_prefs,9,"Don't show the selection when the camera is being moved")}]},
      panel,
      {?STR(advanced_prefs,10,"Advanced Menus"),advanced_menus,
       [{info,?STR(advanced_prefs,11,"More commands and more options, such as magnets")}]},
      {?STR(advanced_prefs,12,"Power-user temporary selections"),use_super_temp_sel,
       [{info,?STR(advanced_prefs,13,"In the secondary selection mode, RMB-clicking always add to the selection")},{hook,SuperDisable}]},
      {vframe,
       [{label_column,
	 [{?STR(advanced_prefs,14,"Length"),active_vector_size,
	   [{info,?STR(advanced_prefs,15,"Length of vector in secondary selections")},{range,{0.1,10.0}}|Flags]},
	   {?STR(advanced_prefs,16,"Width"),active_vector_width,
	   [{info,?STR(advanced_prefs,17,"Width of vector (in pixels)")},{range,{1.0,10.0}}|Flags]},
	  {color,?STR(advanced_prefs,18,"Color"),active_vector_color,
	   [{info,?STR(advanced_prefs,19,"Color of vector")}|Flags]}]}],
	[{title,?STR(advanced_prefs,20,"Vector Display")}]}
     ]}.

ui_prefs() ->
    Fonts = wings_text:fonts(),
    Langs0 = wings_lang:available_languages(),
    Langs = [{language_name(L),L} || L <- Langs0],
    {hframe,
     [{vframe,
       [{vframe,
	 [{menu,Fonts,system_font}],
	 [{title,?STR(ui_prefs,1,"System Font")}]},
	{hframe,
	 [{vframe,
	   [{label,?STR(ui_prefs,2,"Desktop/Geometry Background")},
	    {label,?STR(ui_prefs,3,"Menu Text")},
	    {label,?STR(ui_prefs,4,"Menu Highlight")},
	    {label,?STR(ui_prefs,5,"Menu Highlighted Text")},
	    {label,?STR(ui_prefs,6,"Menu Background")},
	    {label,?STR(ui_prefs,7,"Dialog Text")},
	    {label,?STR(ui_prefs,8,"Dialog (Disabled) Text")},
	    {label,?STR(ui_prefs,9,"Dialog Background")},
	    {label,?STR(ui_prefs,10,"Title Text")},
	    {label,?STR(ui_prefs,11,"Title (Passive) Background")},
	    {label,?STR(ui_prefs,12,"Title (Active) Background")}]},
	  {vframe,
	   [{color,background_color},
	    {color,menu_text},
	    {color,menu_hilite},
	    {color,menu_hilited_text},
	    {color,menu_color},
	    {color,dialog_text},
	    {color,dialog_disabled},
	    {color,dialog_color},
	    {color,title_text_color},
	    {color,title_passive_color},
	    {color,title_active_color}]}],
	 [{title,?STR(ui_prefs,13,"Colors")}]},
	{?STR(ui_prefs,14,"No Progress Bar"),no_progress_bar}
       ]},
      {vframe,
       [{vframe,
	 [{menu,Langs,language}],
	 [{title,?STR(ui_prefs,23,"Language")}]},
	{vframe,
	 [{menu,Fonts,console_font}],
	 [{title,?STR(ui_prefs,15,"Console Font")}]},
	{hframe,[{vframe,[{label,?STR(ui_prefs,16,"Width")},
			  {label,?STR(ui_prefs,17,"Height")},
			  {label,?STR(ui_prefs,18,"Save Lines")},
			  {label,?STR(ui_prefs,19,"Background")},
			  {label,?STR(ui_prefs,20,"Text")},
			  {label,?STR(ui_prefs,21,"Cursor")}]},
		 {vframe,[{text,console_width,[{range,{12,120}}]},
			  {text,console_height,[{range,{3,72}}]},
			  {text,console_save_lines,[{range,{0,10000}}]},
			  {color,console_color},
			  {color,console_text_color},
			  {color,console_cursor_color}]}],
	[{title,?STR(ui_prefs,22,"Console")}]}]}
     ]}.

language_name("en") -> "English";
language_name("de") -> "Deutsch";
language_name("es") -> "Español";
language_name("fr") -> "Français";
language_name("it") -> "Italiano";
language_name("pt") -> "Português";
language_name("sv") -> "Svenska";
language_name(Other) -> Other.

misc_prefs() ->
    Flags = case wings_util:is_gl_ext({1,2}, 'GL_ARB_imaging') of
		true -> [];
		false ->
		    [{hook,fun(is_disabled, _) -> true;
			      (_, _) -> void
			   end},
		     {info,?STR(misc_prefs,1,"Opacity settings not supported using this version of OpenGL")}]
	    end,
    AutoFun = fun(is_disabled, {_Var,_I,Store}) ->
		      not gb_trees:get(autosave, Store);
		 (_, _) -> void
	      end,
    {vframe,
     [{hframe,[{?STR(misc_prefs,2,"Save automatically every"),autosave},
	       {text,autosave_time,[{hook,AutoFun},{range,{1,1440}}]},
	       {label,?STR(misc_prefs,3,"minutes")}]},
      {hframe,[{label,?STR(misc_prefs,4,"Undo levels")},
	       {text,num_undo_levels,[{range,{10,128}}]}]},
      {vframe,
	 [{label_column,
	   [{?STR(misc_prefs,5,"Angle"),auto_rotate_angle},
	    {?STR(misc_prefs,6,"Delay (ms)"),auto_rotate_delay}]}],
	 [{title,?STR(misc_prefs,7,"Auto Rotate")}]},
      {vframe,
       [{vframe,
	 [{menu,[{?STR(misc_prefs,8,"Cage"),cage},
		 {?STR(misc_prefs,9,"Some Edges"),some},
		 {?STR(misc_prefs,10,"All Edges"),all}],
	   proxy_shaded_edge_style}],
	 [{title,?STR(misc_prefs,11,"Shaded Mode Edge Style")}]},
	{hframe,
	 [{vframe,
	   [{label,?STR(misc_prefs,12,"Stationary Opacity")},
	    {label,?STR(misc_prefs,13,"Moving Opacity")}]},
	  {vframe,
	   [{slider,{text,proxy_static_opacity,[{range,{0.0,1.0}}|Flags]}},
	    {slider,{text,proxy_moving_opacity,[{range,{0.0,1.0}}|Flags]}}]}]}],
       [{title,?STR(misc_prefs,14,"Proxy Mode")}]},
      {hframe,
       %% Note: text_display_lists is specially handled
       %% in make_query/1 and smart_set_value/3 to have its value inverted
       %% to keep preference files backward compatible.
       workaround([{text_display_lists,
		    ?STR(misc_prefs,15,"Text in menus and dialogs disappear"),
		    ?STR(misc_prefs,16,"Problem occurs on some Matrox cards")},
		   {dummy_axis_letter,
		    ?STR(misc_prefs,17,"Wings crashes if axes are turned off"),
		    ?STR(misc_prefs,18,"Problem occurs on some Matrox cards")},
		   {jumpy_camera,
		    ?STR(misc_prefs,19,"Camera moves and interactive commands are jumpy"),
		    ?STR(misc_prefs,20,"Problem occurs on Mac OS X 10.3 (Panther)")}
		  ]),
       [{title,?STR(misc_prefs,21,"Workarounds")}]}
     ]}.

workaround(L) ->
    workaround_1(L, [], []).

workaround_1([{Key,Str,BadGuy}|T], A0, B0) ->
    Bl = " ",
    Info = [{info,BadGuy}],
    A = [{label,Str,Info}|A0],
    B = [{Bl,Key,Info}|B0],
    workaround_1(T, A, B);
workaround_1([], A, B) ->
    [{vframe,[{label,?STR(workaround_1,1,"Problem")},separator|reverse(A)]},
     {vframe,[{label,?STR(workaround_1,2,"Use Workaround?")},separator|reverse(B)]}].

smart_set_value(text_display_lists=Key, Val, St) ->
    %% Reverse sense to keep backwards comptibility of preferences.
    smart_set_value_1(Key, not Val, St);
smart_set_value(Key, Val, St) ->
    smart_set_value_1(Key, Val, St).

smart_set_value_1(Key, Val, St) ->
    case ets:lookup(wings_state, Key) of
	[] -> set_value(Key, Val);
	[{Key,Val}] -> ok;
	[{Key,OldVal}] ->
	    set_value(Key, Val),
	    case Key of
		vertex_size ->
		    clear_vertex_dlist();
		background_color ->
		    {R,G,B} = Val,
		    gl:clearColor(R, G, B, 1.0);
		autosave_time ->
		    wings_file:init_autosave();
		proxy_shaded_edge_style ->
		    clear_proxy_edges(St);
		system_font ->
		    delayed_set_value(Key, OldVal, Val),
		    wings_util:message(?STR(smart_set_value_1,1,"The change to the system font will take effect the next time Wings 3D is started."));
		console_font ->
		    wings_console:window();
		camera_mode ->
		    wings_wm:translation_change();
		num_buttons ->
		    wings_wm:translation_change();
		no_progress_bar ->
		    wings_pb:init();
 		language ->
		    wings_lang:load_language(Val);
        	_Other -> ok
	    end
    end.

delayed_set_value(Key, OldVal, NewVal) ->
    set_value(Key, OldVal),
    ets:insert(wings_delayed_update, {Key,NewVal}).

clear_vertex_dlist() ->
    wings_dl:map(fun clear_vertex_dlist/2, []).

clear_vertex_dlist(D, _) -> D#dlo{vs=none}.

clear_proxy_edges(St) ->
    wings_dl:map(fun(D, _) -> clear_proxy_edges(D, St) end, []).

clear_proxy_edges(D, St) ->
    wings_subdiv:update(D#dlo{proxy_edges=none}, St).

make_query({'VALUE',Val}) ->
    Val;
make_query([_|_]=List)  ->
    [make_query(El) || El <- List];
make_query({color,Key}) ->
    Def = get_value(Key),
    {color,Def,[{key,Key}]};
make_query({color,[_|_]=Str,Key}) ->
    Def = get_value(Key),
    {Str,{color,Def,[{key,Key}]}};
make_query({color,[_|_]=Str,Key,Flags}) ->
    Def = get_value(Key),
    {Str,{color,Def,[{key,Key}|Flags]}};
make_query({[_|_]=Str,text_display_lists=Key}) ->
    %% Reverse sense to keep backwards comptibility of preferences.
    {Str,not get_value(Key),[{key,Key}]};
make_query({[_|_]=Str,text_display_lists=Key,Flags}) ->
    %% Reverse sense to keep backwards comptibility of preferences.
    {Str,not get_value(Key),[{key,Key}|Flags]};
make_query({[_|_]=Str,Key}) ->
    case get_value(Key) of
	Def when Def == true; Def == false ->
	    {Str,Def,[{key,Key}]};
	Def ->
	    {Str,{text,Def,[{key,Key}]}}
    end;
make_query({[_|_]=Str,Key,Flags}) ->
    case get_value(Key) of
	Def when Def == true; Def == false ->
	    {Str,Def,[{key,Key}|Flags]};
	Def ->
	    {Str,{text,Def,[{key,Key}|Flags]}}
    end;
make_query({alt,Key,Label,Val}) ->
    Def = get_value(Key),
    {key_alt,{Key,Def},Label,Val};
make_query({menu,List,Key}) ->
    Def = get_value(Key),
    {menu,List,Def,[{key,Key}]};
make_query({vradio,List,Key}) ->
    Def = get_value(Key),
    {vradio,List,Def,[{key,Key}]};
make_query({slider,Slider}) ->
    {slider,make_query(Slider)};
make_query({text,Key,Flags}) ->
    Def = get_value(Key),
    {text,Def,[{key,Key}|Flags]};
make_query({text,Key}) ->
    Def = get_value(Key),
    {text,Def,[{key,Key}]};
make_query(Tuple) when is_tuple(Tuple) ->
    list_to_tuple([make_query(El) || El <- tuple_to_list(Tuple)]);
make_query(Other) -> Other.

%%%
%%% Search for a pre-existing preference file.
%%%

old_pref_file() ->
    case os:type() of
	{unix,darwin} ->
	    case try_location(os:getenv("HOME"), ?MAC_PREFS) of
		none -> unix_pref();
		File -> File
	    end;
	{unix,_} -> unix_pref();
	{win32,_} -> win32_pref()
    end.

unix_pref() ->
    try_location(os:getenv("HOME"), ".wings").

win32_pref() ->
    {ok,R} = win32reg:open([read]),
    Res = win32_pref_1(R, ["AppData","Personal"]),
    ok = win32reg:close(R),
    Res.

%% Search for a preference file in "special folders", such as "AppData"
%% and "My Documents".
win32_pref_1(R, [FolderType|T]) ->
    case win32_special_folder(R, FolderType) of
        none -> win32_pref_1(R, T);
        Path ->
            case try_location(Path, ?WIN32_PREFS) of
                none -> win32_pref_1(R, T);
                File -> File
            end
    end;
win32_pref_1(R, []) ->
    case try_location(code:lib_dir(wings), ?WIN32_PREFS) of
        none -> win32_pref_2(R);
        File -> File
    end.
            
%% No preferences found so far. Search in old installations of
%% Wings for preference files.
win32_pref_2(R) ->
    case win32_9816(R) of
        none -> win32_pref_pre9816(R);
        File -> File
    end.

win32_9816(R) ->
    %% Search for a preference file in a Wings installation in 0.98.16.
    %% (Too bad... in a special place in this release only.)
    case win32reg:change_key(R, "\\hklm\\SOFTWARE\\Wings 3D") of
        ok ->
            case win32reg:sub_keys(R) of
                {ok,SubKeys0} ->
                    SubKeys = reverse(sort(SubKeys0)),
                    {ok,Curr} = win32reg:current_key(R),
                    win32_9816_1(R, SubKeys, Curr);
                {error,Error} ->
		    io:format(?STR(win32_9816,1,"Can't read sub keys for 'Wings 3D': ~p\n"),
                              [Error]),
                    none
            end;
        {error,Error} ->
		    io:format(?STR(win32_9816,2,"No 'Wings 3D' key (this is STRANGE): ~p\n"), [Error]),
            none
    end.

win32_9816_1(R, [K|Keys], Curr) ->
    ok = win32reg:change_key(R, K),
    Dir = reg_get_default(R),
    ok = win32reg:change_key(R, Curr),
    WingsDirs = filelib:wildcard(Dir++"/lib/wings-*"),
    case try_locations(WingsDirs, ?WIN32_OLD_PREFS) of
        none -> win32_9816_1(R, Keys, Curr);
        File -> File
    end;
win32_9816_1(_, [], _) -> none.

win32_pref_pre9816(R) ->
    %% Search for a preference file in a Wings installation older than 0.98.16
    %% using the uninstall string.
    case win32reg:change_key(R, "\\hklm\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Wings 3D") of
        ok ->
            case win32reg:value(R, "UninstallString") of
                {ok,Str0} ->
                    Str = strip_quotes(Str0),
                    try_location(filename:dirname(Str), ?WIN32_OLD_PREFS);
                {error,_} -> none
            end;
        {error,_} -> none
    end.

win32_special_folder(R, FolderType) ->
    Key = "\\hkcu\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
    case win32reg:change_key(R, Key) of
        ok ->
            case win32reg:value(R, FolderType) of
                {error,_} -> error;
                {ok,Value} -> Value
            end;
        _ -> error
    end.

reg_get_default(R) ->
    %% There seems to be a bug in win32reg:value/2 preventing
    %% us from retrieving the default value. Workaround follows.
    case win32reg:values(R) of
        {ok,Values} ->
            case keysearch(default, 1, Values) of
                {value,{default,Val}} -> Val;
                false -> ""
            end;
        {error,_} -> ""
    end.
    
strip_quotes([$"|T0]) ->
    case reverse(T0) of
        [$"|T] -> reverse(T);
        _ -> T0
    end;
strip_quotes(S) -> S.

%%%
%%% Return a suitable path for a new preference file.
%%%

new_pref_file() ->
    case os:type() of
	{unix,darwin} ->
	    filename:join(os:getenv("HOME"), ?MAC_PREFS);
	{unix,_} ->
	    filename:join(os:getenv("HOME"), ".wings");
	{win32,_} ->
            win32_new_pref()
    end.

win32_new_pref() ->
    {ok,R} = win32reg:open([read]),
    Res = win32_new_pref_1(R, ["AppData","Personal"]),
    ok = win32reg:close(R),
    Res.

win32_new_pref_1(R, [FolderType|T]) ->
    case win32_special_folder(R, FolderType) of
        none -> win32_pref_1(R, T);
        Path ->
            File = filename:join(Path, ?WIN32_PREFS),
            filelib:ensure_dir(File),
            File
    end;
win32_new_pref_1(_, []) ->
    %% Desperate fallback for very old Window systems.
    %% (No "My Documents" folder.)
    filename:join(code:lib_dir(wings), ?WIN32_PREFS).

%%%
%%% Utilities.
%%%

try_locations([D|Ds], File) ->
    case try_location(D, File) of
        none -> try_locations(Ds, File);
        Name -> Name
    end;
try_locations([], _) -> none.

try_location(Dir, File) ->
    Name = filename:join(Dir, File),
    case filelib:is_file(Name) of
	true -> Name;
	false -> none
    end.

get_value(Key) ->
    get_value(Key, undefined).

get_value(Key, Default) ->
    case ets:lookup(wings_state, Key) of
	[] -> Default;
	[{Key,Val}] -> Val
    end.

set_value(Key, Value) ->
    ets:insert(wings_state, {Key,Value}),
    ok.

set_default(Key, Value) ->
    case ets:member(wings_state, Key) of
	true -> ok;
	false ->
	    ets:insert(wings_state, {Key,Value}),
	    ok
    end.

delete_value(Key) ->
    ets:delete(wings_state, Key),
    ok.


%%% Scene prefs

get_scene_value() ->
    ets:tab2list(wings_scene_prefs).

get_scene_value(Key) ->
    get_value(Key, undefined).

get_scene_value(Key, Default) ->
    case ets:lookup(wings_scene_prefs, Key) of
	[] -> Default;
	[{Key,Val}] -> Val
    end.

set_scene_value(Key, Value) ->
    true = ets:insert(wings_scene_prefs, {Key,Value}),
    ok.

set_scene_default(Key, Value) ->
    case ets:member(wings_scene_prefs, Key) of
	true -> ok;
	false ->
	    ets:insert(wings_scene_prefs, {Key,Value}),
	    ok
    end.

delete_scene_value() ->
    true = ets:delete_all_objects(wings_scene_prefs),
    ok.

delete_scene_value(Key) ->
    true = ets:delete(wings_scene_prefs, Key),
    ok.

%%% End of scene prefs


defaults() ->
    [{background_color,{0.8,0.8,0.8}},
     {info_color,{0.0,0.0,0.0}},
     {info_background_color,{0.8,0.8,0.8,0.5}},
     {grid_color,{0.7,0.7,0.7}},
     {edge_color,{0.0,0.0,0.0}},
     {hard_edge_color,{0.0,0.5,0.0}},
     {selected_color,{0.65,0.0,0.0}},
     {unselected_hlite,{0.0,0.65,0.0}},
     {selected_hlite,{0.70,0.70,0.0}},
     {x_color,{0.6,0.0,0.0}},
     {y_color,{0.0,0.6,0.0}},
     {z_color,{0.0,0.0,0.6}},
     {neg_x_color,{0.6,0.6,0.6}},
     {neg_y_color,{0.6,0.6,0.6}},
     {neg_z_color,{0.6,0.6,0.6}},

     {vertex_size,4.0},
     {selected_vertex_size,5.0},
     {edge_width,2.0},
     {selected_edge_width,2.0},
     {show_axis_letters,true},
     {force_show_along_grid,false},
     {force_ortho_along_axis,false},
     {vertex_hilite,true},
     {edge_hilite,true},
     {face_hilite,true},
     {body_hilite,true},
     {auto_rotate_angle,1.0},
     {auto_rotate_delay,60},
     {autosave,true},
     {autosave_time,10},
     {active_vector_size,1.0},
     {active_vector_width,2.0},
     {active_vector_color,{0.0,0.0,0.65}},
     {smart_highlighting,false},

     {selection_style,solid},
     {hide_sel_while_dragging,false},
     {hide_sel_in_camera_moves,false},
     {negative_height,24},
     {negative_width,36},

     %% Compatibility preferences.
     {text_display_lists,true},
     {dummy_axis_letter,false},
     {jumpy_camera,case {os:type(),os:version()} of
		       {{unix,darwin},{Maj,Min,_}}
		       when Maj >= 7, Min >= 3 -> true;
		       _ -> false
		   end},

     %% Advanced features.
     {advanced_menus,false},
     {default_commands,false},
     {use_temp_sel,false},
     {use_super_temp_sel,false},

     %% Proxy preferences.
     {proxy_shaded_edge_style,some},
     {proxy_static_opacity,1.0},
     {proxy_moving_opacity,1.0},

     %% User interface preferences.
     {menu_text,{0.0,0.0,0.0}},
     {menu_hilite,{0.0,0.0,0.5}},
     {menu_hilited_text,{1.0,1.0,1.0}},
     {menu_color,{0.75,0.75,0.75,1.0}},
     {dialog_text,{0.0,0.0,0.0}},
     {dialog_disabled,{0.5,0.5,0.5}},
     {dialog_color,{0.75,0.75,0.75,1.0}},
     {title_active_color,{0.41,0.55,0.41,1.0}},
     {title_passive_color,{0.325,0.4,0.325,1.0}},
     {title_text_color,{1.0,1.0,1.0}},
     {no_progress_bar,false},
     %% Console
     {console_width,80},
     {console_height,12},
     {console_save_lines,100},
     {console_color,{0.0,0.0,0.0}},
     {console_text_color,{0.0,1.0,0.0}},
     {console_cursor_color,{1.0,1.0,1.0}},

     %% Undos.
     {num_undo_levels,32},

     %% Languages.
     {lang,en}
    ].

clean(List) ->
    clean(List, []).

clean([{Key,Val}=Pair|T], Acc) ->
    case not_bad(Key, Val) of
	true -> clean(T, [Pair|Acc]);
	false ->
	    io:format("Removed pref: ~p\n", [Pair]),
	    clean(T, Acc)
    end;
clean([{{bindkey,Key},{vector,{pick,[],Res0,Ns}},Type}|T], Acc) ->
    Res = list_to_tuple(reverse(Res0)),
    Mode = last(Ns),
    Bk = {{bindkey,Mode,Key},wings_menu:build_command(Res, Ns),Type},
    clean(T, [Bk|Acc]);
clean([{{bindkey,_}=Bk,{view,{virtual_mirror,Cmd}},user}|T], Acc) ->
    clean(T, [{Bk,{tools,{virtual_mirror,Cmd}},user}|Acc]);
clean([{{bindkey,_},Cmd,user}=Bk|T], Acc) ->
    case bad_command(Cmd) of
	false -> clean(T, [Bk|Acc]);
	true ->
	    io:format("Removed pref: ~p\n", [Bk]),
	    clean(T, Acc)
    end;
clean([{{bindkey,_,_},Cmd,user}=Bk|T], Acc) ->
    case bad_command(Cmd) of
	false -> clean(T, [Bk|Acc]);
	true ->
	    io:format("Removed pref: ~p\n", [Bk]),
	    clean(T, Acc)
    end;
clean([H|T], Acc) ->
    clean(T, [H|Acc]);
clean([], Acc) -> Acc.

%% First, get rid of obsolete stuff.
not_bad(last_point, _) -> false;
not_bad(default_point, _) -> false;
not_bad(smooth_preview, _) -> false;
not_bad(wire_mode, _) -> false;
not_bad(none, _) -> false;
not_bad(use_front_buffer, _) -> false;
not_bad(one_button_mouse, _) -> false;
not_bad(face_color, _) -> false;
not_bad(workmode, _) -> false;
not_bad(orthogonal_view, _) -> false;
not_bad(show_memory_used, _) -> false;
not_bad(show_axes, _) -> false;
not_bad(show_groundplane, _) -> false;
not_bad(current_view, _) -> false;
not_bad(camera_fov, _) -> false;
not_bad(camera_hither, _) -> false;
not_bad(camera_yon, _) -> false;
not_bad(right_click_sel_in_ss, _) -> false;
not_bad(right_click_sel_in_geom, _) -> false;
not_bad(wire_edge_color, _) -> false;
not_bad(show_wire_backfaces, _) -> false;	%Now a window property.

%% Crashes have occurred.
not_bad(last_axis, Val) -> is_wings_vector(Val);
not_bad(default_axis, Val) -> is_wings_vector(Val);
not_bad(magnet_radius, Val) -> is_number(Val);
not_bad(_, _) -> true.

is_wings_vector({{Px,Py,Pz},{Vx,Vy,Vz}})
  when is_number(Px), is_number(Py), is_number(Pz),
       is_number(Vx), is_number(Vy), is_number(Vz) ->
    true;
is_wings_vector(_) -> false.

bad_command({_,{rotate,Atom}}) when is_atom(Atom) -> true;
bad_command({view,virtual_mirror}) -> true;
bad_command({select,edge_loop}) -> true;
bad_command({select,select_region}) -> true;
bad_command({select,edge_ring}) -> true;
bad_command({select,prev_edge_loop}) -> true;
bad_command({select,next_edge_loop}) -> true;
bad_command(_) -> false.
