aboutsummaryrefslogblamecommitdiffstats
path: root/lib/debugger/src/dbg_wx_trace_win.erl
blob: 48935c66a5a1c478708882c073db3d841279d4a3 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                   
  
                                                        
  



                                                                      
  


                                                                         
  



















                                                                      
                          



































































































































































                                                                                       
                                       
















































































































































































































                                                                                  
                                                       

                                                                     
                                                                   













                                                                      
                                            














































                                                                              

                                                          





                                                               
                                                               











































                                                                                 
                     
                                             
                      

























                                                                               



                                                                                            



                          


                              





















































                                                                                
                                                                                               



























































                                                                                 
                                                                                               



































































                                                                                       
                                                                            





































































                                                                                             
                                                  

















                                                                                      


                                                                    
















































                                                                                            
                                             













































































































                                                                                       
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2013. 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_trace_win).

%% External exports
-export([init/0, stop/1]).
-export([create_win/4, 
	 get_window/1,
	 configure/2,
	 enable/2, is_enabled/1, select/2,
	 add_break/3, update_break/2, delete_break/2,
	 clear_breaks/1, clear_breaks/2,
	 display/2,                                   % Help messages
	 is_shown/2,                                  % Code area
	 show_code/3, show_no_code/1, remove_code/2,      
	 mark_line/3, unmark_line/1,
	 select_line/2, selected_line/1,
	 eval_output/3,                               % Evaluator area
	 update_bindings/2,                           % Bindings area
         update_strings/1,
	 trace_output/2,                              % Trace area
	 handle_event/2
	]).
-export([helpwin/2]).

-include_lib("wx/include/wx.hrl").

-record(breakInfo, {point, status, break}).
-record(break, {mb, smi, emi, dimi, demi}).  %% BUGBUG defined in dbg_wx_win
-record(winInfo, {window,          % wxobj()
		  size,            % {W, H}
		  find,            % #find{}
		  m_szr,           % {Panel, Sizer},
		  e_szr,           % {bool Shown, Sizer},

		  code,            % code editor #sub{}
		  sb,              % status bar
		  sg,              % Search/Goto #sub{}
		  bs,              % Buttons #sub{} subwindow info
		  eval,            % Eval #sub{} subwindow info
		  bind,            % Bindings #sub{} subwindow info
		  trace,           % Trace #sub{} subwindow info
		  
		  marked_line=0,   % integer() Current line
		  selected_line=0, % integer() Selected line

		  breaks=[],       % [#breakInfo{}] Known breakpoints

		  editor,          % {Mod, Editor}  Visible code editor
		  editors=[]       % [{Mod,Editor}] Code editors
		 }).

-record(sub, {enable=true,         %  Subwindow is enabled
	      win,                 %  Sash Sub window obj
	      in,                  %  undefined or input window obj
	      out,                 %  undefined or output window obj
	      name                 %  name
	     }).               

-record(sa, {search,               %  Search input ctrl
	     goto,                 %  Goto  input ctrl
	     radio}).              %  Radio buttons

-record(find, {start,              % start pos
	       strlen,             % search string len
	       found               % status
	      }).


-define(StepButton,     401).
-define(NextButton,     402).
-define(ContinueButton, 403).
-define(FinishButton,   404).
-define(WhereButton,    405).
-define(UpButton,       406).
-define(DownButton,     407).

-define(EVAL_ENTRY,     410).
-define(EVAL_LOG,       411).
-define(BIND_PANEL,     412).
-define(SEARCH_ENTRY,   413).
-define(GOTO_ENTRY,     414).


-define(SASH_CODE,      425).
-define(SASH_EVAL,      426).
-define(SASH_TRACE,     427).

-define(WIN_W,          700).
-define(WIN_H,          650).

-define(CODE_H,         400).
-define(BUTT_H,          50).  % Maximum
-define(EVAL_H,         200).
-define(BIND_H,         200).
-define(TRACE_H,        100).

%%====================================================================
%% External exports
%%====================================================================

%%--------------------------------------------------------------------
%% init() -> GS
%%   GS = term()
%%--------------------------------------------------------------------
init() ->
    dbg_wx_win:init().

stop(#winInfo{window=Win}) ->
    (catch wxFrame:destroy(Win)),
    ok.

%%--------------------------------------------------------------------
%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{}
%%  GS = gsobj()
%%  Title = string()
%%  TraceWin = [WinArea]
%%    WinArea = 'Button|Evaluator|Bindings|Trace Area'
%%  Menus = [menu()]  See dbg_wx_win.erl
%%--------------------------------------------------------------------
create_win(Parent, Title, Windows, Menus) ->
    Do = 
	fun() ->
		Win = wxFrame:new(Parent, ?wxID_ANY, dbg_wx_win:to_string(Title), 
				  [{size, {?WIN_W,?WIN_H}}]),
		Panel = wxPanel:new(Win, [{size, {?WIN_W,?WIN_H}}]),
		MenuBar = wxMenuBar:new(),
		dbg_wx_win:create_menus(MenuBar, Menus, Win, 1),
		wxFrame:setMenuBar(Win, MenuBar),
		
		Sizer = wxBoxSizer:new(?wxVERTICAL),
		Code = code_area(Panel),
		wxSizer:add(Sizer, Code#sub.win, 
			    [{proportion,1}, {border, 2}, 
			     {flag, ?wxEXPAND bor ?wxDOWN}]),
		wxSizer:setVirtualSizeHints(Sizer, Code#sub.win),

		ExpandWithBorder = [{border, 3},{flag,?wxEXPAND bor ?wxALL}],
		Search = search_area(Panel),
		wxSizer:add(Sizer, Search#sub.win, ExpandWithBorder),
		Bs     = button_area(Panel),
		wxSizer:add(Sizer, Bs#sub.win, ExpandWithBorder),

		InfoArea = wxBoxSizer:new(?wxHORIZONTAL),
		wxSizer:setMinSize(InfoArea, {100, ?EVAL_H}),
		Eval  = eval_area(Panel),
		wxSizer:add(InfoArea, Eval#sub.win, [{proportion,1},{flag,?wxEXPAND}]),
		Bind  = bind_area(Panel),		
		wxSizer:add(InfoArea, Bind#sub.win, 
			    [{proportion,1},{border, 2}, 
			     {flag,?wxEXPAND bor ?wxLEFT}]),
		wxSizer:add(Sizer, InfoArea, ExpandWithBorder),
		
		Trace = trace_area(Panel),
		wxSizer:add(Sizer, Trace#sub.win, ExpandWithBorder),
		SB    = wxFrame:createStatusBar(Win,[]),

		%% Note id and lastId to get the event when it dragged is complete
		wxFrame:connect(Win, sash_dragged, [{id,?SASH_CODE}, 
						    {lastId,?SASH_TRACE}]),
		wxFrame:connect(Win, close_window, [{skip, true}]),
		wxFrame:connect(Win, size, [{skip, true}]),
		wxWindow:connect(Win, key_up, [{skip,true}]),
		wxWindow:setFocus(Code#sub.out),
		
		Wi0 = #winInfo{window=Win, 
			       m_szr={Panel, Sizer}, 
			       e_szr={true, InfoArea},
			       code=Code, sb=SB, sg=Search, bs=Bs, 
			       eval=Eval, trace=Trace, bind=Bind,
			       editor={'$top', Code#sub.out}, 
			       editors=[{'$top', Code#sub.out}]},
		
		Wi = show_windows(enable_windows(Wi0,Windows)),
		wxWindow:setSizer(Panel, Sizer),
		wxSizer:fit(Sizer, Win),
		wxSizer:setSizeHints(Sizer,Win),

		IconFile = dbg_wx_win:find_icon("erlang_bug.png"),
		Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
		wxFrame:setIcon(Win, Icon),
		wxIcon:destroy(Icon),
		
		wxFrame:show(Win),
		put(window, Win),
                put(strings, [str_on]),
		Wi
	end,

    try wx:batch(Do) 
    catch E:R ->
	    io:format("Crashed ~p ~p",[E,R]),
	    erlang:error(E)
    end.


%%--------------------------------------------------------------------
%% get_window(WinInfo) -> Window
%%   WinInfo = #winInfo{}
%%   Window = gsobj()
%%--------------------------------------------------------------------
get_window(WinInfo) ->
    WinInfo#winInfo.window.

%%--------------------------------------------------------------------
%% configure(WinInfo, Windows) -> WinInfo
%%   WinInfo = #winInfo{}
%%  Windows = [WinArea]
%%    WinArea = 'Button|Evaluator|Bindings|Trace Area'
%% Window areas should be opened or closed.
%%--------------------------------------------------------------------
configure(Wi=#winInfo{window=Win,m_szr={Panel,Sizer}}) ->
    wx:batch(fun() ->
		     show_windows(Wi),
		     wxSizer:layout(Sizer),
		     %%wxWindow:setSizerAndFit(Panel,Sizer),
		     wxWindow:setSizer(Panel, Sizer),
		     wxSizer:fit(Sizer, Win),
		     wxSizer:setSizeHints(Sizer,Win),		
		     Wi
	     end).
    
configure(Wi0=#winInfo{window=Win,m_szr={Panel,Sizer}}, Windows) ->
    wx:batch(fun() -> 
		     Wi = enable_windows(Wi0, Windows),
		     show_windows(Wi),
		     wxSizer:layout(Sizer),
		     wxWindow:setSizer(Panel, Sizer),
		     wxSizer:fit(Sizer, Win),
		     wxSizer:setSizeHints(Sizer,Win),		
		     Wi
	     end).

enable_windows(Wi=#winInfo{e_szr={_,InfoArea},bs=Bs0,sg=SG0,
			   eval=Eval0,trace=Trace0,bind=Bind0},Windows) ->
    Subs  = [Window#sub{enable=lists:member(Window#sub.name,Windows)} 
	     || Window <- [SG0,Bs0,Eval0,Trace0,Bind0]],
    [SG, Bs,Eval,Trace,Bind] = Subs,
    ESzr = Eval#sub.enable orelse Bind#sub.enable,
    Wi#winInfo{e_szr={ESzr, InfoArea},sg=SG,bs=Bs,
	       eval=Eval,trace=Trace,bind=Bind}.
    

show_windows(Wi=#winInfo{m_szr={_,Sizer}, e_szr={_,InfoArea},bs=Bs,sg=SG,
		      eval=Eval,trace=Trace,bind=Bind}) -> 
    case SG#sub.enable of
	false -> wxSizer:hide(Sizer, SG#sub.win);
	_ ->     wxSizer:show(Sizer, SG#sub.win)
    end,
    case Bs#sub.enable of
	false -> wxSizer:hide(Sizer, Bs#sub.win);
	_ ->     wxSizer:show(Sizer, Bs#sub.win)
    end,
    if (not Eval#sub.enable) andalso (not Bind#sub.enable) ->
	    wxSizer:hide(Sizer, InfoArea);
       not Eval#sub.enable  ->
	    wxSizer:show(Sizer, InfoArea),
	    wxSizer:hide(InfoArea, Eval#sub.win),
	    wxSizer:show(InfoArea, Bind#sub.win);
       not Bind#sub.enable ->
	    [EvalSI|_] = wxSizer:getChildren(InfoArea),
	    wxSizerItem:setProportion(EvalSI, 1),
	    wxSizer:show(Sizer, InfoArea),
	    wxSizer:hide(InfoArea, Bind#sub.win),
	    wxSizer:show(InfoArea, Eval#sub.win),
	    true;
       true ->
	    wxSizer:show(Sizer, InfoArea),
	    wxSizer:show(InfoArea, Eval#sub.win),
	    wxSizer:show(InfoArea, Bind#sub.win)
    end,
    case Trace#sub.enable of
	false -> wxSizer:hide(Sizer, Trace#sub.win);
	_ ->     wxSizer:show(Sizer, Trace#sub.win)
    end,
    Wi.

%%--------------------------------------------------------------------
%% enable([MenuItem], Bool)
%% is_enabled(MenuItem) -> Bool
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
enable(MenuItems, Bool) ->
    wx:foreach(fun(MenuItem) ->
		       MI = get(MenuItem),
		       wxMenuItem:enable(MI, [{enable, Bool}]),
		       case is_button(MenuItem) of
			   {true, ButtonId} ->
			       Parent = get(window),
			       Butt = wxWindow:findWindowById(ButtonId, 
							      [{parent, Parent}]),
			       case wx:is_null(Butt) of
				   true -> ignore;
				   false ->
				       wxButton:enable(Butt, [{enable, Bool}])
			       end;
			   _ -> 
			       ignore
		       end
	       end,
	       MenuItems).

is_enabled(MenuItem) ->
    MI = get(MenuItem),
    wxMenuItem:isEnabled(MI).

%%--------------------------------------------------------------------
%% select(MenuItem, Bool)
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
select(MenuItem, Bool) ->
    MI = get(MenuItem),
    wxMenuItem:check(MI, [{check, Bool}]).

%%--------------------------------------------------------------------
%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Name = atom() Menu name
%%   Point = {Mod, Line}
%%   Options = [Status, Action, Mods, Cond]
%%     Status = active | inactive
%%     Action = enable | disable | delete
%%     Mods = null (not used)
%%     Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) ->
    case WinInfo#winInfo.editor of
	{Mod, Editor} ->
	    dbg_wx_code:add_break_to_code(Editor, Line, Status);
	_ -> ignore
    end,
    add_break_to_menu(WinInfo, Menu, Break).

add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) ->
    Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point),
    dbg_wx_win:update_break(Break, Options),
    BreakInfo = #breakInfo{point=Point, status=Status, break=Break},
    WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.

%%--------------------------------------------------------------------
%% update_break(WinInfo, {Point, Options}) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Point = {Mod, Line}
%%   Options = [Status, Action, Mods, Cond]
%%     Status = active | inactive
%%     Action = enable | disable | delete
%%     Mods = null (not used)
%%     Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) ->
    case WinInfo#winInfo.editor of
	{Mod, Editor} ->
	    dbg_wx_code:add_break_to_code(Editor, Line, Status);
	_ -> ignore
    end,
    update_break_in_menu(WinInfo, Break).

update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options),
    BreakInfo2 = BreakInfo#breakInfo{status=Status},
    WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point,
					    WinInfo#winInfo.breaks,
					    BreakInfo2)}.

%%--------------------------------------------------------------------
%% delete_break(WinInfo, Point) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Point = {Mod, Line}
%%--------------------------------------------------------------------
delete_break(WinInfo, {Mod,Line}=Point) ->
    case WinInfo#winInfo.editor of
	{Mod, Editor} -> dbg_wx_code:del_break_from_code(Editor, Line);
	_ -> ignore
    end,
    delete_break_from_menu(WinInfo, Point).

delete_break_from_menu(WinInfo, Point) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
    WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
					   WinInfo#winInfo.breaks)}.

%%--------------------------------------------------------------------
%% clear_breaks(WinInfo) -> WinInfo
%% clear_breaks(WinInfo, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%--------------------------------------------------------------------
clear_breaks(WinInfo) ->
    clear_breaks(WinInfo, all).
clear_breaks(WinInfo, Mod) ->
    Remove = if
		 Mod =:= all -> WinInfo#winInfo.breaks;
		 true ->
		     lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
					  if
					      Mod2 =:= Mod -> true;
					      true -> false
					  end
				  end,
				  WinInfo#winInfo.breaks)
	     end,
    lists:foreach(fun(#breakInfo{point=Point}) ->
			  delete_break(WinInfo, Point)
		  end,
		  Remove),
    Remain = WinInfo#winInfo.breaks -- Remove,
    WinInfo#winInfo{breaks=Remain}.

%%--------------------------------------------------------------------
%% display(Arg)
%%   Arg = idle | {Status,Mod,Line} | {running,Mod}
%%       | {exit,Where,Reason} | {text,Text}
%%     Status = break | wait | Level
%%       Level = int()
%%     Mod = atom()
%%     Line = integer()
%%     Where = {Mod,Line} | null
%%     Reason = term()
%%     Text = string()
%%--------------------------------------------------------------------
display(#winInfo{window=Win, sb=Sb},Arg) ->
    Str = case Arg of
	      idle -> "State: uninterpreted";
	      {exit, {Mod,Line}, Reason} ->
		  wxWindow:raise(Win),
		  dbg_wx_win:to_string("State: EXITED [~w.erl/~w], Reason:~w",
				       [Mod, Line, Reason]);
	      {exit, null, Reason} ->
		  wxWindow:raise(Win),
		  dbg_wx_win:to_string("State: EXITED [uninterpreted], "
				       "Reason:~w", [Reason]);
	      {Level, null, _Line} when is_integer(Level) ->
		  dbg_wx_win:to_string("*** Call level #~w "
				       "(in non-interpreted code)",
				       [Level]);
	      {Level, Mod, Line} when is_integer(Level) ->
		  dbg_wx_win:to_string("*** Call level #~w [~w.erl/~w]",
				       [Level, Mod, Line]);
	      {Status, Mod, Line} ->
		  What = case Status of
			     wait -> 'receive';
			     _ -> Status
			 end,
		  dbg_wx_win:to_string("State: ~w [~w.erl/~w]",
				       [What, Mod, Line]);
	      {running, Mod} ->
		  dbg_wx_win:to_string("State: running [~w.erl]", [Mod]);
	      {text, Text}  -> dbg_wx_win:to_string(Text)
	  end,
    wxStatusBar:setStatusText(Sb, Str).

%%--------------------------------------------------------------------
%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false
%% show_code(WinInfo, Mod, Contents) -> WinInfo
%% show_no_code(WinInfo) -> WinInfo
%% remove_code(WinInfo, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Mod = atom()
%%   Contents = string()
%% Note: remove_code/2 should not be used for currently shown module.
%%--------------------------------------------------------------------
is_shown(_WinInfo, _Mod) ->
    %% Previously cached modules here, nyi so return false
    false.

show_code(WinInfo = #winInfo{editor={_, Ed}}, Mod, Contents) ->
    %% Insert code and update breakpoints, if any
    dbg_wx_code:load_code(Ed, Contents), 
    
    lists:foreach(fun(BreakInfo) ->
			  case BreakInfo#breakInfo.point of
			      {Mod2, Line} when Mod2 =:= Mod ->
				  Status = BreakInfo#breakInfo.status,
				  dbg_wx_code:add_break_to_code(Ed, Line,Status);
			      _Point -> ignore
			  end
		  end,
		  WinInfo#winInfo.breaks),
    
    WinInfo#winInfo{editor={Mod,Ed},find=undefined}.

show_no_code(WinInfo = #winInfo{editor={_, Ed}}) ->
    dbg_wx_code:unload_code(Ed),
    WinInfo#winInfo{editor={'$top', Ed}}.

remove_code(WinInfo, _Mod) ->
    WinInfo.

%%--------------------------------------------------------------------
%% mark_line(WinInfo, Line, How) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Line = integer()
%%   How = break | where
%% Mark the code line where the process is executing.
%%--------------------------------------------------------------------
mark_line(WinInfo = #winInfo{editor={_,Ed}}, Line, _How) ->
    dbg_wx_code:mark_line(Ed, WinInfo#winInfo.marked_line, Line), 
    WinInfo#winInfo{marked_line=Line}.

unmark_line(WinInfo) ->    
    mark_line(WinInfo, 0, false).


%%--------------------------------------------------------------------
%% select_line(WinInfo, Line) -> WinInfo
%% selected_line(WinInfo) -> undefined | Line
%%   WinInfo = #winInfo{}
%%   Line = integer()
%% Select/unselect a line (unselect if Line=0).
%%--------------------------------------------------------------------
select_line(WinInfo, Line) ->
    {_Mod, Ed} = WinInfo#winInfo.editor,

    %% Since 'Line' may be specified by the user in the 'Go To Line'
    %% help window, it must be checked that it is correct
    Size = dbg_wx_code:get_no_lines(Ed),
    if
	Line =:= 0 ->
	    dbg_wx_code:goto_line(Ed,1),
	    WinInfo#winInfo{selected_line=0};
	Line < Size ->
	    dbg_wx_code:goto_line(Ed,Line),
	    WinInfo#winInfo{selected_line=Line};
	true ->
	    WinInfo
    end.

selected_line(#winInfo{editor={_,Ed}}) ->
    wxStyledTextCtrl:getCurrentLine(Ed)+1.

%%--------------------------------------------------------------------
%% eval_output(winInfo{}, Str, Face)
%%   Str = string()
%%   Face = normal | bold
%%--------------------------------------------------------------------
eval_output(#winInfo{eval=#sub{out=Log}}, Text, _Face) -> 
    wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)),
    ok.
    
%%--------------------------------------------------------------------
%% update_bindings(Bs)
%%   Bs = [{Var,Val}]
%%--------------------------------------------------------------------
update_bindings(#winInfo{bind=#sub{out=BA}}, Bs) ->
    wxListCtrl:deleteAllItems(BA),
    wx:foldl(fun({Var,Val},Row) ->
		     wxListCtrl:insertItem(BA, Row, ""), 
		     wxListCtrl:setItem(BA, Row, 0, dbg_wx_win:to_string(Var)),
                     Format = case get(strings) of
                                  []        -> "~999999lP";
                                  [str_on]  -> "~999999tP"
                              end,
		     wxListCtrl:setItem(BA, Row, 1, dbg_wx_win:to_string(Format,[Val, 20])),
		     Row+1
	     end, 0, Bs),
    put(bindings,Bs),
    ok.

update_strings(Strings) ->
    _ = put(strings, Strings),
    ok.

%%--------------------------------------------------------------------
%% trace_output(Str)
%%   Str = string()
%%--------------------------------------------------------------------
trace_output(#winInfo{trace=#sub{out=Log}}, Text) -> 
    wxTextCtrl:appendText(Log, dbg_wx_win:to_string(Text)),
    ok.

%%--------------------------------------------------------------------
%% handle_event(GSEvent, WinInfo) -> Command
%%   GSEvent = {gs, Id, Event, Data, Arg}
%%   WinInfo = #winInfo{}
%%   Command = ignore
%%           | {win, WinInfo}
%%           | stopped
%%           | {coords, {X,Y}}
%%
%%           | {shortcut, Key}
%%           | MenuItem | {Menu, [MenuItem]}
%%               MenuItem = Menu = atom()
%%           | {break, Point, What}
%%               What = add | delete | {status,Status} |{trigger,Trigger}
%%           | {module, Mod, view}
%%
%%           | {user_command, Cmd}
%%
%%           | {edit, {Var, Val}}
%%--------------------------------------------------------------------
%% Window events
handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) ->
    stopped;

handle_event(#wx{event=#wxSize{size=Size}}, Wi0) ->
    Wi = Wi0#winInfo{size=Size},
    resize(Wi),
    {win, Wi};

handle_event(#wx{event=#wxSash{dragStatus=?wxSASH_STATUS_OUT_OF_RANGE}},_Wi) ->
    ignore;
handle_event(#wx{id=?SASH_CODE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
    #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi,

    case Enable orelse Trace#sub.enable of
	false -> 
	    ignore;
	true ->
	    {_, CMH} = wxWindow:getMinSize(Code#sub.win),
	    case CMH > H of
		true ->  wxSashWindow:setMinSize(Code#sub.win, {500, H});
		_ -> ignore 
	    end,
	    {_, CH} = wxWindow:getSize(Code#sub.win),
	    Change = CH - H, 
	    ChangeH = fun(Item) ->
			      {ItemW, ItemH} = wxSizerItem:getMinSize(Item),
			      wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1))
		      end,
	    if Enable ->
		    {IW, IH} = wxSizer:getMinSize(InfoSzr),
		    [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)],
		    wxSizer:setMinSize(InfoSzr, {IW, IH+Change}),
		    ok;
	       Trace#sub.enable -> 
		    {TW, TH} = wxWindow:getMinSize(Trace#sub.win),
		    wxWindow:setMinSize(Trace#sub.win, {TW, TH+Change}),
		    ok
	    end,
	    wxSizer:layout(Sizer),
	    ignore
    end;

handle_event(#wx{id=?SASH_EVAL, event=#wxSash{dragRect={_X,_Y,W,_H}}}, Wi) ->
    #winInfo{m_szr={_,Sizer},e_szr={Enable,InfoSzr},
	     eval=#sub{enable=Enable, win=EvalSzr}} = Wi,
    case Enable of
	false ->
	    ignore;
	true ->
	    [Eval,Bind] = wxSizer:getChildren(InfoSzr),
	    {Tot,_} = wxSizer:getSize(InfoSzr),
	    EvalWidth = Tot-W,
	    
	    Change = fun(Szr, Width) ->
			     {_EW,EH} = wxSizerItem:getMinSize(Szr),
			     wxSizerItem:setInitSize(Szr, Width, EH)
		     end,

	    Change(Eval, EvalWidth),
	    [Change(Kid, EvalWidth) || Kid <- wxSizer:getChildren(EvalSzr)],
	    Change(Bind, W),
	    
	    wxSizerItem:setProportion(Eval, 0),
	    wxSizer:layout(InfoSzr),
	    wxSizer:layout(Sizer),

	    resize(Wi),
	    ignore
    end;

handle_event(#wx{id=?SASH_TRACE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
    #winInfo{code=Code,m_szr={_,Sizer},e_szr={Enable,InfoSzr},trace=Trace} = Wi,
    {TW, TH} = wxWindow:getSize(Trace#sub.win),
    Change = TH - H, 
    case Enable of
	false ->  %% Eval Area or Bindings
	    {_, CH}  = wxWindow:getSize(Code#sub.win),
	    {_, CMH} = wxWindow:getMinSize(Code#sub.win),
	    case CMH > CH+Change of
		true ->  wxSashWindow:setMinSize(Code#sub.win, {500, CH+Change});
		_ -> ignore 
	    end,
	    wxWindow:setMinSize(Trace#sub.win, {TW, H}),
	    wxSizer:layout(Sizer),
	    ignore;
	true ->  %% Change the Eval and Bindings area
	    ChangeH = fun(Item) ->
			      {ItemW, ItemH} = wxSizerItem:getMinSize(Item),
			      wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1))
		      end,
	    {IW, IH} = wxSizer:getMinSize(InfoSzr),
	    [ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)],
	    Wanted = IH+Change,
	    wxSizer:setMinSize(InfoSzr, {IW, Wanted}),
	    {_,RH} = wxSizer:getMinSize(InfoSzr),
	    case RH > Wanted of
		true ->  %% Couldn't get the size we wanted try adjusting the code area
		    {_, CH}  = wxWindow:getSize(Code#sub.win),
		    {_, CMH} = wxWindow:getMinSize(Code#sub.win),
		    CC = CH - (RH-Wanted),
		    case CMH > CC of
			true when CC > 50 ->  
			    wxWindow:setMinSize(Trace#sub.win, {TW, H}),
			    wxSashWindow:setMinSize(Code#sub.win, {500, CC});
			_ when CC < 50 -> 			    
			    ignore;
			_ ->
			    wxWindow:setMinSize(Trace#sub.win, {TW, H})
		    end,
		    ok;
		false ->
		    wxWindow:setMinSize(Trace#sub.win, {TW, H})
	    end,
	    wxSizer:layout(Sizer),
	    ignore
    end;

%% Menus, buttons and keyboard shortcuts
handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) ->
    %% io:format("Key ~p ~n",[_Ev]),
    if
	Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN -> 
	    try  
		{shortcut, list_to_atom([Key+($a-$A)])}
	    catch _:_ -> ignore
	    end;
	true -> 
	    ignore
    end;
handle_event(#wx{userData={dbg_ui_winman, Win}, 
		 event=#wxCommand{type=command_menu_selected}}, _WinInfo) ->
    dbg_wx_winman:raise(Win),
    ignore;

handle_event(#wx{userData={break, Point, status}, 
		 event=#wxCommand{type=command_menu_selected}},
	     WinInfo) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    %% This is a temporary hack !!
    #breakInfo{break=#break{smi=Smi}} = BreakInfo,

    case wxMenuItem:getText(Smi) of
	"Enable" -> {break, Point, {status, active}};
	"Disable" -> {break, Point, {status, inactive}}
    end;

handle_event(#wx{userData=Data, 
		 event=_Cmd=#wxCommand{type=command_menu_selected}},
	     _WinInfo) ->
    %%io:format("Command  ~p ~p~n",[Data,_Cmd]),
    Data;

%% Code area
handle_event(#wx{event=#wxStyledText{type=stc_doubleclick}}, 
	     WinInfo = #winInfo{editor={Mod,Ed}}) ->
    Line = wxStyledTextCtrl:getCurrentLine(Ed),
    Point = {Mod, Line+1},
    case lists:keymember(Point, #breakInfo.point, WinInfo#winInfo.breaks) of
	true -> {break, Point, delete};
	false -> {break, Point, add}
    end;

%% Search Area
handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, WinInfo) ->
    try 
	Line = list_to_integer(Str),
	{gotoline, Line}
    catch 
	_:_ ->
	    display(WinInfo, {text,"Not a line number"}),
	    ignore
    end;
handle_event(#wx{id=?SEARCH_ENTRY, event=#wxFocus{}}, Wi) ->
    {win, Wi#winInfo{find=undefined}};
handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdString=Str}}, 
	     Wi = #winInfo{code=Code,find=Find, sg=#sub{in=#sa{radio={NextO,_,CaseO}}}}) 
  when Find =/= undefined ->
    Dir  = wxRadioButton:getValue(NextO) xor wx_misc:getKeyState(?WXK_SHIFT),
    Case = wxCheckBox:getValue(CaseO),
    Pos = if Find#find.found, Dir ->  %% Forward Continuation
		  wxStyledTextCtrl:getAnchor(Code#sub.out);
	     Find#find.found ->  %% Backward Continuation 
		  wxStyledTextCtrl:getCurrentPos(Code#sub.out);
	     Dir ->   %% Forward wrap
		  0;
	     true ->  %% Backward wrap
		  wxStyledTextCtrl:getLength(Code#sub.out)
	  end,
    dbg_wx_code:goto_pos(Code#sub.out,Pos),
    case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of
	true -> 	    
	    display(Wi, {text,""}),
	    {win, Wi#winInfo{find=Find#find{found=true}}};
	false ->
	    display(Wi, {text,"Not found (Hit Enter to wrap search)"}),
	    {win, Wi#winInfo{find=Find#find{found=false}}}
    end;
handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=""}}, 
	     Wi=#winInfo{code=Code}) ->
    %% Reset search (and selection pos)
    Pos = dbg_wx_code:current_pos(Code#sub.out),
    dbg_wx_code:goto_pos(Code#sub.out,Pos),
    {win, Wi#winInfo{find=undefined}};
handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}}, 
	     Wi = #winInfo{code=Code,find=Find, 
			   sg=#sub{in=#sa{radio={NextO,_,CaseO}}}}) ->
    Dir  = wxRadioButton:getValue(NextO),
    Case = wxCheckBox:getValue(CaseO),   
    
    Cont = case Find of
	       undefined ->
		   Pos = dbg_wx_code:current_pos(Code#sub.out),
		   #find{start=Pos, strlen=length(Str)};
	       #find{strlen=Old} when Old < length(Str) ->
		   Find#find{strlen=length(Str)};
	       _ ->
		   dbg_wx_code:goto_pos(Code#sub.out,Find#find.start),
		   Find#find{strlen=length(Str)}
	   end,
    case dbg_wx_code:find(Code#sub.out, Str, Case, Dir) of
	true -> 
	    display(Wi, {text,""}),
	    {win, Wi#winInfo{find=Cont#find{found=true}}};
	false ->
	    display(Wi, {text,"Not found (Hit Enter to wrap search)"}),
	    {win, Wi#winInfo{find=Cont#find{found=false}}}
    end;

%% Button area
handle_event(#wx{id=ID, event=#wxCommand{type=command_button_clicked}},_Wi) ->
    {Button, _} = lists:keyfind(ID, 2, buttons()),
    Button;

%% Evaluator area
handle_event(#wx{id=?EVAL_ENTRY, event=#wxCommand{type=command_text_enter}},
	     Wi = #winInfo{eval=#sub{in=TC}}) ->
    case wxTextCtrl:getValue(TC) of
	[10] ->
	    eval_output(Wi, "\n", normal),
	    ignore;
	Cmd ->
	    eval_output(Wi, [$>, Cmd, 10], normal),
	    wxTextCtrl:setValue(TC,""),
	    {user_command, Cmd}
    end;

%% Bindings area
handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Row}},Wi) ->
    Bs = get(bindings),
    {Var,Val} = lists:nth(Row+1, Bs),
    Str = case get(strings) of
              []       -> io_lib:format("< ~s = ~lp~n", [Var, Val]);
              [str_on] -> io_lib:format("< ~s = ~tp~n", [Var, Val])
          end,
    eval_output(Wi, Str, bold),
    ignore;
handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Row}},_Wi) ->    
    Bs = get(bindings),
    Binding = lists:nth(Row+1, Bs),
    {edit, Binding};
    
handle_event(_GSEvent, _WinInfo) ->
    %%io:format("~p: unhandled ~p~n",[?MODULE, _GSEvent]),
    ignore.


%%====================================================================
%% resize(WinInfo) -> WinInfo

resize(#winInfo{bind=Bind}) ->
    %% Tweak the Binding settings text size
    if 
	Bind#sub.enable =:= false ->  
	    ok;
	Bind#sub.enable -> 
	    {EW, _} = wxWindow:getClientSize(Bind#sub.out),
	    B0W = wxListCtrl:getColumnWidth(Bind#sub.out, 0),
	    wxListCtrl:setColumnWidth(Bind#sub.out, 1, EW - B0W),
	    ok
    end.

%%====================================================================
%% Internal functions
%%====================================================================

%%--Code Area-------------------------------------------------------
code_area(Win) ->
    CodeWin = wxSashWindow:new(Win, [{id,?SASH_CODE}, 
				     {size, {?WIN_W,?CODE_H}}, 
				     {style, ?wxSW_3D}]),
    Code  = dbg_wx_code:code_area(CodeWin),
    wxSashWindow:setSashVisible(CodeWin, ?wxSASH_BOTTOM, true),
    wxWindow:setMinSize(CodeWin, {600, ?CODE_H}),
    #sub{name='Code Area',enable=true, win=CodeWin, out=Code}.


%%--Button Area-------------------------------------------------------

buttons() ->
    [{'Step',?StepButton}, {'Next',?NextButton},
     {'Continue',?ContinueButton}, {'Finish',?FinishButton},
     {'Where',?WhereButton}, {'Up',?UpButton}, {'Down',?DownButton}].

is_button(Name) ->
    case lists:keyfind(Name, 1, buttons()) of
	{Name, Button} -> {true, Button};
	false -> false
    end.

button_area(Parent) -> 
    Sz = wxBoxSizer:new(?wxHORIZONTAL),
    wx:foreach(fun({Name, Button}) ->
		       B=wxButton:new(Parent, Button, 
				      [{label,dbg_wx_win:to_string(Name)}]),
		       Id = wxWindow:getId(B),
		       wxSizer:add(Sz,B, []),
		       wxButton:connect(B, command_button_clicked, [{id,Id}])
	       end, buttons()),
    #sub{name='Button Area', win=Sz}.
    
%%--Search/Goto Area-------------------------------------------------

search_area(Parent) ->
    HSz = wxBoxSizer:new(?wxHORIZONTAL),
    wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Find:"), 
		[{flag,?wxALIGN_CENTER_VERTICAL}]),
    TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), 
    wxSizer:add(HSz, TC1,  [{proportion,3}, {flag, ?wxEXPAND}]),
    Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"),
    wxRadioButton:setValue(Nbtn, true),
    wxSizer:add(HSz,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
    Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"),
    wxSizer:add(HSz,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
    Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"),
    wxSizer:add(HSz,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
    wxSizer:add(HSz, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]),
    wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Goto Line:"), 
		[{flag,?wxALIGN_CENTER_VERTICAL}]),
    TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), 
    wxSizer:add(HSz, TC2,  [{proportion,0}, {flag, ?wxEXPAND}]),
    wxTextCtrl:connect(TC1, command_text_updated),
    wxTextCtrl:connect(TC1, command_text_enter),
    wxTextCtrl:connect(TC1, kill_focus),
    wxTextCtrl:connect(TC2, command_text_enter),
    wxWindow:connect(Parent, command_button_clicked),

    #sub{name='Search Area', win=HSz, 
	 in=#sa{search=TC1,goto=TC2,radio={Nbtn,Pbtn,Cbtn}}}.

%%--Evaluator Area----------------------------------------------------

eval_area(Parent) -> 
    VSz = wxBoxSizer:new(?wxVERTICAL),
    HSz = wxBoxSizer:new(?wxHORIZONTAL),
    
    wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Evaluator:"), 
		[{flag,?wxALIGN_CENTER_VERTICAL}]),
    TC = wxTextCtrl:new(Parent, ?EVAL_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), 
    wxSizer:add(HSz, TC,  [{proportion,1}, {flag, ?wxEXPAND}]),
    wxSizer:add(VSz, HSz, [{flag, ?wxEXPAND}]),
    TL = wxTextCtrl:new(Parent, ?EVAL_LOG, [{style, ?wxTE_DONTWRAP bor 
					  ?wxTE_MULTILINE bor ?wxTE_READONLY}]), 
    wxSizer:add(VSz, TL, [{proportion,5}, {flag, ?wxEXPAND}]),
    
    wxTextCtrl:connect(TC, command_text_enter),
    #sub{name='Evaluator Area', win=VSz, in=TC, out=TL}.

%%--Bindings Area-----------------------------------------------------

bind_area(Parent) ->
    Style = {style, ?wxSW_3D bor  ?wxCLIP_CHILDREN},
    Win = wxSashWindow:new(Parent, [{id, ?SASH_EVAL},Style]),
    wxSashWindow:setSashVisible(Win, ?wxSASH_LEFT, true),

    BA = wxListCtrl:new(Win, [{style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL}]),
    LI = wxListItem:new(),
    
    wxListItem:setText(LI, "Name"), 
    wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
    wxListCtrl:insertColumn(BA, 0, LI),
    
    wxListItem:setText(LI, "Value"), 
    wxListCtrl:insertColumn(BA, 1, LI),
    wxListItem:destroy(LI),

    wxListCtrl:setColumnWidth(BA, 0, 100),
    wxListCtrl:setColumnWidth(BA, 1, 150),
    wxListCtrl:connect(BA, command_list_item_selected), 
    wxListCtrl:connect(BA, command_list_item_activated), 
    
    #sub{name='Bindings Area', win=Win, out=BA}.

%%--Trace Area--------------------------------------------------------

trace_area(Parent) -> 
    Style = {style, ?wxSW_3D bor  ?wxCLIP_CHILDREN},
    Win = wxSashWindow:new(Parent, [{id, ?SASH_TRACE},
				    {size, {?WIN_W,?TRACE_H}}, Style]),
    wxSashWindow:setSashVisible(Win, ?wxSASH_TOP, true),
    wxWindow:setMinSize(Win, {500, ?TRACE_H}),
    TC = wxTextCtrl:new(Win, ?wxID_ANY, [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}]),
    #sub{name='Trace Area', win=Win, out=TC}.
    
%%====================================================================
%% 'Go To Line' and 'Search' help windows
%%====================================================================

helpwin(Type, WinInfo = #winInfo{sg=Sg =#sub{in=Sa}}) ->
    Wi = case Sg#sub.enable of
	     false -> configure(WinInfo#winInfo{sg=Sg#sub{enable=true}});
	     true ->  WinInfo
	 end,
    case Type of
	gotoline ->  wxWindow:setFocus(Sa#sa.goto);
	search   ->  wxWindow:setFocus(Sa#sa.search)
    end,
    Wi.