diff options
Diffstat (limited to 'lib/debugger/src/dbg_wx_filedialog_win.erl')
-rw-r--r-- | lib/debugger/src/dbg_wx_filedialog_win.erl | 572 |
1 files changed, 572 insertions, 0 deletions
diff --git a/lib/debugger/src/dbg_wx_filedialog_win.erl b/lib/debugger/src/dbg_wx_filedialog_win.erl new file mode 100644 index 0000000000..d883438639 --- /dev/null +++ b/lib/debugger/src/dbg_wx_filedialog_win.erl @@ -0,0 +1,572 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(dbg_wx_filedialog_win). + +-behaviour(wx_object). + +%% API +-export([new/3, getFilename/1, getFilenames/1, getDirectory/1, destroy/1]). + +%% Internal +-export([init/1, handle_call/3, handle_event/2, + handle_info/2, code_change/3, terminate/2]). + +-include_lib("wx/include/wx.hrl"). +-include("dbg_wx_filedialog_win.hrl"). + +-define(ID_PATH, 200). +-define(COMPLETION_WIN, 201). +-record(state, {win, + back, forward, up, %% Buttons + text, %% Text (Path) + ptext, + icons=[], + completion, + list, + path, + files, + rstack = [], + fstack = [], + filter, + sort, + cancel, + ok}). + +-record(file, + {name = "", %% File or dir name + type = "file", %% Type descr + date = "", %% Modification date + icon = 0, %% Icon + color = {0,0,0} + }). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Client API + + +%% Options are: +%% {message, Title} +%% {defaultDir, Path} +%% {filter, fun(Dir,File) -> skip | {DescriptionStr, icon_type}} +%% {icons, [{icon_type,wxBitmap}]} + +new(Parent, Id, Options0) -> + wx_object:start_link(?MODULE, [Parent, Id, Options0], []). + +getFilename(FD) -> + wx_object:call(FD, getFilename). +getFilenames(FD) -> + wx_object:call(FD, getFilenames). +getDirectory(FD) -> + wx_object:call(FD, getDirectory). +destroy(FD) -> + wx_object:call(FD, destroy). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Object Callbacks +init([Parent, Id, Options0]) -> + Name = proplists:get_value(message, Options0, "Open"), + Size = proplists:get_value(size, Options0, ?wxDefaultSize), + Pos = proplists:get_value(pos, Options0, ?wxDefaultPosition), + {ok, DefPath} = file:get_cwd(), + Path = proplists:get_value(defaultDir, Options0, DefPath), + ExtraIcons = proplists:get_value(icons, Options0, []), + Filter = proplists:get_value(filter, Options0, fun file_type_and_icon/2), + SortCol = sort_col(proplists:get_value(sort, Options0, name)), + Dlg = wxDialog:new(Parent, Id, Name, [{size,Size}, {pos,Pos}, + {style, ?wxDEFAULT_DIALOG_STYLE + bor ?wxRESIZE_BORDER}]), + + %% Top + Back = wxButton:new(Dlg, ?wxID_BACKWARD), + wxButton:disable(Back), + Forw = wxButton:new(Dlg, ?wxID_FORWARD), + wxButton:disable(Forw), + Up = wxButton:new(Dlg, ?wxID_UP), + Dir = wxTextCtrl:new(Dlg, ?ID_PATH, [{style, ?wxTE_PROCESS_ENTER}]), + update_dir(Path, Dir), + wxTextCtrl:connect(Dir, command_text_updated), + wxTextCtrl:connect(Dir, command_text_enter), + Self = self(), + IsTab = fun(Ev = #wx{event=#wxKey{keyCode=KC, + controlDown=false,shiftDown=false, altDown=false}}, + _Object) when KC =:= ?WXK_TAB ; KC =:= ?WXK_ESCAPE -> + Self ! Ev; + (_Ev, Object) -> + %% Let the default handler handle anything else + wxEvent:skip(Object) + end, + + wxTextCtrl:connect(Dir, char, [{callback, IsTab}]), + + Top = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(Top, Back, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Top, Forw, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Top, Up, [{border, 2},{flag,?wxALL bor ?wxEXPAND}]), + + %% List Ctrl + {Art, IconMap} = create_icons(ExtraIcons), + + LC = wxListCtrl:new(Dlg, [{style, ?wxLC_REPORT bor ?wxVSCROLL}, {size, {400,200}}]), + wxListCtrl:assignImageList(LC, Art, ?wxIMAGE_LIST_SMALL), + + LI = wxListItem:new(), + Add = fun(MenuName, Row) -> + wxListItem:setText(LI, MenuName), + wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(LC, Row, LI), + Row + 1 + end, + lists:foldl(Add, 0, ["Name", "Type", "Modified"]), + wxListItem:destroy(LI), + + Files = list_files(Path, {SortCol,false}, Filter), + update_files(Files,LC,IconMap), + + wxListCtrl:setColumnWidth(LC, 0, ?wxLIST_AUTOSIZE), + wxListCtrl:setColumnWidth(LC, 1, ?wxLIST_AUTOSIZE), + wxListCtrl:setColumnWidth(LC, 2, ?wxLIST_AUTOSIZE), + wxListCtrl:connect(LC, command_list_item_activated), + wxListCtrl:connect(LC, command_list_col_click), + wxListCtrl:connect(LC, size, [{skip, true}]), + + %% Bottom buttons + Bott = wxDialog:createButtonSizer(Dlg, ?wxCANCEL bor ?wxOK), + wxDialog:connect(Dlg, command_button_clicked), + + %% Ok done + Box = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Box, Top, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Box, Dir, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + wxSizer:add(Box, LC, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}, {proportion, 1}]), + wxSizer:add(Box, Bott, [{border, 2}, {flag,?wxALL bor ?wxEXPAND}]), + + wxWindow:setSizer(Dlg, Box), + wxSizer:fit(Box, Dlg), + wxSizer:setSizeHints(Box,Dlg), + State = #state{win=Dlg, + back=Back, forward=Forw, up=Up, + text=Dir, + list=LC, + icons=IconMap, + sort ={SortCol,false}, + filter = Filter, + path=Path, + files=Files + }, + {Dlg, State}. + +%% calls +handle_call(getFilename, _From, State = #state{list=LC, files=Fs}) -> + case wxListCtrl:getNextItem(LC, -1, [{state,?wxLIST_STATE_SELECTED}]) of + -1 -> + {reply, "", State}; + Item -> + {reply, (lists:nth(Item+1,Fs))#file.name, State} + end; + +handle_call(getFilenames, _From, State = #state{list=LC, files=Fs}) -> + Items = get_selection(LC,-1, []), + Files = [(lists:nth(Item+1,Fs))#file.name || Item <- Items], + {reply, Files, State}; + +handle_call(getDirectory, _From, State = #state{path=Dir}) -> + {reply, Dir, State}; + +handle_call(destroy, _From, State) -> + {stop, normal, ok, State}. + +%% events +handle_event(#wx{id=?wxID_UP}, State0) -> + State = update_window(change_dir(0, State0)), + {noreply, State}; + +handle_event(#wx{id=?wxID_BACKWARD}, State0 = #state{rstack=[Prev|Stack]}) -> + State = update_window(change_dir(Prev, State0#state{rstack=Stack}, forward)), + {noreply, State}; + +handle_event(#wx{id=?wxID_FORWARD}, State0 = #state{fstack=[Prev|Stack]}) -> + State = update_window(change_dir(Prev, State0#state{fstack=Stack}, reverse)), + {noreply, State}; + +handle_event(#wx{id=Id=?wxID_CANCEL}, State = #state{win=Win}) -> + wxDialog:endModal(Win,Id), + {noreply, State}; + +handle_event(#wx{id=Id=?wxID_OK}, State = #state{win=Win}) -> + wxDialog:endModal(Win,Id), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Column0}}, + State0 = #state{files=Fs, sort=Sort0}) -> + case Column0 >= 0 of + true -> + Column = sort_col(Column0+1), + Sort = case Sort0 of + {Column,Bool} -> {Column, not Bool}; + {_,_} -> {Column, false} + end, + {noreply, update_window(State0#state{files=sort_files(Fs,Sort),sort=Sort})}; + false -> + {noreply, State0} + end; + +handle_event(#wx{event=#wxList{itemIndex=Index}}, + State0 = #state{files=Fs,win=Win}) -> + case lists:nth(Index+1, Fs) of + #file{type="directory"} -> + State = update_window(change_dir(Index, State0)), + {noreply, State}; + _Dbg = #file{} -> + wxDialog:endModal(Win,?wxID_OK), + {noreply, State0} + end; + +handle_event(#wx{event=#wxCommand{type=command_text_updated, cmdString=Wanted}}, + State = #state{ptext=Previous, completion=Comp}) -> + case Previous =:= undefined orelse lists:prefix(Wanted, Previous) of + true -> + case Comp of + {Temp,_} -> wxWindow:destroy(Temp); + undefined -> ok + end, + {noreply, State#state{ptext=Wanted,completion=undefined}}; + false -> + {noreply, show_completion(Wanted, State)} + end; + +handle_event(#wx{event=#wxCommand{type=command_text_enter, cmdString=Wanted}}, + State) -> + case filelib:is_dir(Wanted, erl_prim_loader) of + true -> + {Path0, Dir} = split_dir(Wanted), + Path = filename:join(Path0,Dir), + {noreply, update_window(change_dir(Path, State))}; + false -> + {Path, _} = split_dir(Wanted), + {noreply, update_window(change_dir(Path, State))} + end; + +handle_event(#wx{event=#wxKey{keyCode=?WXK_TAB}}, + State = #state{text=TC, ptext=Wanted, completion=Comp}) -> + case wxTextCtrl:getSelection(TC) of + {Pos,Pos} -> + {noreply, show_completion(Wanted, State)}; + _ -> + wxTextCtrl:setInsertionPointEnd(TC), + destroy_completion(Comp), + {noreply, State#state{completion=undefined}} + end; + +handle_event(#wx{id=?COMPLETION_WIN, event=#wxCommand{cmdString=[]}}, State) -> + {noreply, State}; +handle_event(#wx{id=?COMPLETION_WIN, obj=LB, + event=_Ev=#wxCommand{cmdString=Dir,commandInt=N}}, + State = #state{ptext=Wanted0, text=TC}) -> + case wxListBox:isSelected(LB, N) of + true -> + Wanted = case Wanted0 of + undefined -> wxTextCtrl:getValue(TC); + _ -> Wanted0 + end, + Path1 = case filelib:is_dir(Wanted, erl_prim_loader) of + true -> Wanted; + false -> + {ThePath, _} = split_dir(Wanted), + ThePath + end, + Path = filename:join(Path1, Dir), + {noreply, update_window(change_dir(Path, State))}; + false -> + {noreply, State} + end; + +handle_event(#wx{event=#wxSize{size={Width,_}}}, State = #state{list=LC}) -> + wx:batch(fun() -> + Tot = wx:foldl(fun(C,Sum) -> + Sum + wxListCtrl:getColumnWidth(LC, C) + end, 0, [1,2]), + wxListCtrl:setColumnWidth(LC, 0, Width-Tot-30) + end), + {noreply, State}; + +handle_event(Event,State) -> + io:format("~p Got ~p ~n",[self(), Event]), + {noreply, State}. + +handle_info(_Msg, State) -> + {noreply,State}. + +terminate(_Reason,State) -> + wxDialog:destroy(State#state.win), + ok. + +code_change(_,_,State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%% Internal %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +update_window(State = #state{files=Fs, list=LC, + path=Path, text=TC, + icons=Icons, + completion = Comp}) -> + update_files(Fs, LC, Icons), + update_dir(Path, TC), + if State#state.rstack == [] -> wxButton:disable(State#state.back); + true -> wxButton:enable(State#state.back) + end, + if State#state.fstack == [] -> wxButton:disable(State#state.forward); + true -> wxButton:enable(State#state.forward) + end, + if Path == "/" -> wxButton:disable(State#state.up); + true -> wxButton:enable(State#state.up) + end, + destroy_completion(Comp), + State#state{completion=undefined, ptext=undefined}. + +update_dir(Path, TC) -> + case Path of + "/" -> + wxTextCtrl:setValue(TC, Path); + _ -> + wxTextCtrl:setValue(TC, Path ++ "/") + end, + wxTextCtrl:setInsertionPointEnd(TC). + +update_files(Files,LC, Icons) -> + wxListCtrl:deleteAllItems(LC), + wx:foldl(fun(F=#file{name=Name,type=TypeStr,date=Date, color=Color},Row) -> + wxListCtrl:insertItem(LC, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(LC, Row, {240,240,255}); + true -> ignore + end, + wxListCtrl:setItemTextColour(LC, Row, Color), + wxListCtrl:setItem(LC, Row, 0, Name, [{imageId,get_icon(F,Icons)}]), + wxListCtrl:setItem(LC, Row, 2, format_date(Date)), + wxListCtrl:setItem(LC, Row, 1, TypeStr), + Row+1 + end, 0, Files). + +show_completion(undefined, State = #state{text=TC}) -> + show_completion(wxTextCtrl:getValue(TC), State); +show_completion(Wanted, State = #state{text=TC, win=Win, list=LC, completion=Comp}) -> + Paths0 = filelib:wildcard(Wanted ++ "*", erl_prim_loader), + Paths = [File || File <- Paths0, filelib:is_dir(File, erl_prim_loader)], + case Paths of + [Path] -> + Start = length(Wanted), + wxTextCtrl:setValue(TC, Path++"/"), + wxTextCtrl:setInsertionPoint(TC, Start), + wxTextCtrl:setSelection(TC, Start, -1), + destroy_completion(Comp), + wxWindow:setFocus(TC), + State#state{ptext=Path, completion=undefined}; + Paths when Comp =:= undefined -> + {PosX,PosY} = wxListCtrl:getPosition(LC), + {SzX, SzY} = wxListCtrl:getSize(LC), + Pos0 = {PosX+5,PosY}, + Size = {SzX-50,SzY-50}, + Files = [filename:basename(File) || File <- Paths], + Temp = case os:type() of + {win32,nt} -> + Pos = wxWindow:clientToScreen(Win,Pos0), + wxFrame:new(Win, -1, "", + [{pos, Pos}, {size, Size}, + {style, ?wxFRAME_FLOAT_ON_PARENT}]); + _ -> + wxWindow:new(Win, -1, + [{pos, Pos0}, {size, Size}, + {style, ?wxFRAME_FLOAT_ON_PARENT}]) + end, + LB = wxListBox:new(Temp, ?COMPLETION_WIN, + [{style, ?wxLB_SINGLE}, {choices, Files}, {size, Size}]), + + wxListBox:connect(LB, command_listbox_doubleclicked), + wxListBox:connect(LB, command_listbox_selected), + wxWindow:show(Temp), + wxWindow:setFocus(TC), + State#state{completion = {Temp, LB}, ptext=Wanted}; + Paths -> + {_Temp, LB} = Comp, + wxListBox:clear(LB), + Files = [filename:basename(File) || File <- Paths], + wxListBox:insertItems(LB,Files,0), + wxWindow:setFocus(TC), + State#state{ptext=Wanted} + end. + +destroy_completion(undefined) -> ok; +destroy_completion({Window, _}) -> + Parent = wxWindow:getParent(Window), + wxWindow:destroy(Window), + wxWindow:refresh(Parent). + +split_dir(Path0) -> + Split1 = filename:split(Path0), + case lists:reverse(Split1) of + [File| Split2] when Split2 =/= [] -> + Split3 = lists:reverse(Split2), + Path = filename:join(Split3), + {Path, File}; + _ -> + {"/", ""} + end. + +change_dir(What,State) -> + change_dir(What,State,new). + +change_dir(Num, State = #state{files=Fs0, path=Path0},Stack) + when is_integer(Num) -> + case lists:nth(Num+1, Fs0) of + #file{name=".."} -> + {Path,_} = split_dir(Path0); + #file{name=Dir} -> + Path = filename:join(Path0, Dir) + end, + change_dir(Path, State, Stack); +change_dir(Path, State0 = #state{path=Path0, sort=Sort, filter=Filter},StackDir) -> + Files = list_files(Path, Sort, Filter), + add_to_stack(StackDir, Path0, State0#state{files=Files, path=Path}). + +add_to_stack(new, Path, State = #state{rstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{rstack=Stack, fstack=[]}; +add_to_stack(reverse, Path, State = #state{rstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{rstack=Stack}; +add_to_stack(forward, Path, State = #state{fstack=Stack0}) -> + Stack = [Path|Stack0], + State#state{fstack=Stack}. + +list_files(Dir, Sort, Filter) -> + Contents0 = filelib:wildcard(Dir ++ "/*", erl_prim_loader), + Contents = case Dir of + "/" -> Contents0; + _ -> [".."|Contents0] + end, + {Ds0,Fs0} = get_file_info(Contents, Dir, Filter, [], []), + sort_files(lists:reverse(Ds0), Fs0, Sort). + +sort_files(Mixed, Sort) -> + {Ds,Fs} = + lists:foldr(fun(Dir = #file{type="directory"}, {Ds,Fs}) -> + {[Dir|Ds],Fs}; + (File, {Ds,Fs}) -> + {Ds,[File|Fs]} + end, {[],[]}, Mixed), + sort_files(Ds,Fs,Sort). + +sort_files(Ds0, Fs0, {SortElement, Rev}) -> + {Top,Ds1} = case Ds0 of + [Up=#file{name=".."}|Rest] -> {Up,Rest}; + _ -> {undefined, Ds0} + end, + Ds = lists:keysort(SortElement, Ds1), + Fs = case Rev of + true -> lists:reverse(lists:keysort(SortElement,Fs0)); + false -> lists:keysort(SortElement,Fs0) + end, + case Top of + undefined -> Ds ++ Fs; + _ -> [Top|Ds++Fs] + end. + +get_file_info([AbsName|Rest],Dir,Filter, Files,Dirs) -> + Name = filename:basename(AbsName), + Mod = filelib:last_modified(AbsName, erl_prim_loader), + IsDir = filelib:is_dir(AbsName, erl_prim_loader), + Entry0 = #file{name=Name, date=Mod}, + case IsDir of + true when Name =:= ".." -> + Entry = Entry0#file{type="directory",icon=prev_dir}, + get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]); + true -> + Entry = Entry0#file{type="directory",icon=dir}, + get_file_info(Rest, Dir, Filter, Files, [Entry|Dirs]); + false -> + case Filter(Dir,Name) of + {Type,Icon,Color} -> + Entry = Entry0#file{type=Type,icon=Icon,color=Color}, + get_file_info(Rest, Dir, Filter, [Entry|Files], Dirs); + skip -> + get_file_info(Rest, Dir, Filter, Files, Dir) + end + end; +get_file_info([], _, _, Fs,Ds) -> + {Ds,Fs}. + +format_date({{Y,M,D},{H,Mi,S}}) -> + lists:flatten(io_lib:format("~w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w",[Y,M,D,H,Mi,S])); +format_date(_) -> + "unknown". + +get_selection(LC, Prev, Acc) -> + case wxListCtrl:getNextItem(LC, Prev, [{state,?wxLIST_STATE_SELECTED}]) of + -1 -> + lists:reverse(Acc); + Item -> + get_selection(LC, Item, [Item|Acc]) + end. + +file_type_and_icon(_Dir, Name) -> + case filename:extension(Name) of + ".erl" -> + {"erl src", erl_src, {0,90,0}}; + ".hrl" -> + {"erl hrl", erl_hrl, {0,90,0}}; + ".beam" -> + {"erl bin", erl_bin, {0,0,0}}; + _ -> + {"file", file, {0,0,0}} + end. + +create_icons(Extra) -> + Art = wxImageList:new(16,16), + BuiltIn0 = [{file, "wxART_NORMAL_FILE"}, + {dir, "wxART_FOLDER"}, + {prev_dir, "wxART_GO_DIR_UP"}], + BuiltIn = [{Type, wxArtProvider:getBitmap(ArtID, [{size, {16,16}}])} || + {Type,ArtID} <- BuiltIn0], + + Test = [{Type, wxBitmap:new(wxImage:new(16,16,Bin))} + || {Type,Bin} <- + [{erl_src, ?ERL_SRC}, + {erl_hrl, ?ERL_HRL}, + {erl_bin, ?ERL_BIN}]], + + Icons = BuiltIn ++ Test ++ Extra, + [wxImageList:add(Art, Image) || {_, Image} <- Icons], + {Art, ids(Icons, 0)}. + +get_icon(#file{icon=Icon}, Icons) -> + proplists:get_value(Icon,Icons, 0). + +ids([{Type,_}|Rest], Id) -> + [{Type,Id}|ids(Rest,Id+1)]; +ids([],_) -> []. + +sort_col(1) -> #file.name; +sort_col(2) -> #file.type; +sort_col(3) -> #file.date; +sort_col(name) -> #file.name; +sort_col(type) -> #file.type; +sort_col(date) -> #file.date. + |