%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1998-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_ui_trace).

%% External exports
-export([start/1, start/3]).
-export([title/1]).

-define(TRACEWIN, ['Button Area', 'Evaluator Area', 'Bindings Area']).
-define(BACKTRACE, 100).

-record(state, {gs,            % term() Graphics system id
		win,           % term() Attach process window data
		coords,        % {X,Y} Mouse point position

		pid,           % pid() Debugged process
		meta,          % pid() Meta process
		status,        % {Status,Mod,Line} ¦ {exit,Where,Reason}
		               %   Status = init ¦ idle | break
		               %      | wait_break ¦ wait_running
		               %      ¦ running
                               % Where={Mod,Line} | null

		cm,            % atom() | undefined Current module
		cm_obsolete=false, % boolean() Curr mod needs reloading

		stack,         % {Cur,Max}

		trace,         % boolean()
		stack_trace,   % all | no_tail | false
		backtrace      % integer() #call frames to fetch
	       }).

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

%%--------------------------------------------------------------------
%% start(Pid)
%% start(Pid, TraceWin, BackTrace)
%%   Pid = pid()
%%   TraceWin = [WinArea]
%%     WinArea = 'Button|Evaluator|Bindings|Trace Area'
%%   Backtrace = integer()
%%--------------------------------------------------------------------
start(Pid) -> % Used by debugger:quick/3 (no monitor)
    start(Pid, ?TRACEWIN, ?BACKTRACE).
start(Pid, TraceWin, BackTrace) ->
    case {whereis(dbg_wx_mon), whereis(dbg_ui_mon)} of
	{undefined, undefined} ->
	    case which_gui() of
		gs ->
		    start2(Pid, TraceWin, BackTrace);
		wx ->
		    dbg_wx_trace:start(Pid, TraceWin, BackTrace)
	    end;
	{undefined, Monitor} when is_pid(Monitor) ->
	    start2(Pid, TraceWin, BackTrace);
	{Monitor, _} when is_pid(Monitor) ->
	    dbg_wx_trace:start(Pid, TraceWin, BackTrace)
    end.

start2(Pid, TraceWin, BackTrace) ->
    %% Inform int about my existence and get the meta pid back
    case int:attached(Pid) of
	{ok, Meta} ->
	    init(Pid, Meta, TraceWin, BackTrace);
	error ->
	    ignore
    end.

which_gui() ->
    try
	wx:new(),
	wx:destroy(),
	wx
    catch _:_ ->
	    gs
    end.

%%--------------------------------------------------------------------
%% title(Pid) -> string()
%% By exporting this function, dbg_ui_mon may check with dbg_ui_winman
%% if there already is an attach window for a given pid and thus avoid
%% spawning processes unnecessarily.
%%--------------------------------------------------------------------
title(Pid) ->
    "Attach Process " ++ pid_to_list(Pid).


%%====================================================================
%% Main loop and message handling
%%====================================================================

init(Pid, Meta, TraceWin, BackTrace) ->

    %% Start necessary stuff
    GS = dbg_ui_trace_win:init(),               % Graphics system

    %% Create attach process window
    Title = title(Pid),
    Win = dbg_ui_trace_win:create_win(GS, Title, TraceWin, menus()),
    Window = dbg_ui_trace_win:get_window(Win),
    dbg_ui_winman:insert(Title, Window),

    %% Initial process state
    State1 = #state{gs=GS, win=Win, coords={0,0}, pid=Pid, meta=Meta,
		    status={idle,null,null},
		    stack={1,1}},

    State2 = init_options(TraceWin,
			  int:stack_trace(),    % Stack Trace
			  BackTrace,            % Back trace size
			  State1),

    State3 = init_contents(int:all_breaks(),    % Breakpoints
			   State2),

    int:meta(Meta, trace, State3#state.trace),

    gui_enable_updown(stack_trace, {1,1}),
    gui_enable_btrace(false, false),
    dbg_ui_trace_win:display(idle),

    loop(State3).

init_options(TraceWin, StackTrace, BackTrace, State) ->
    lists:foreach(fun(Area) -> dbg_ui_trace_win:select(Area, true) end,
		  TraceWin),

    Trace = lists:member('Trace Area', TraceWin),

    dbg_ui_trace_win:select(map(StackTrace), true),

    %% Backtrace size is (currently) not shown in window

    State#state{trace=Trace,stack_trace=StackTrace,backtrace=BackTrace}.

init_contents(Breaks, State) ->
    Win =
	lists:foldl(fun(Break, Win) ->
			    dbg_ui_trace_win:add_break(Win,
						       'Break',Break)
		    end,
		    State#state.win,
		    Breaks),

    State#state{win=Win}.

loop(#state{meta=Meta} = State) ->
    receive

	%% From the GUI main window
	GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
	    Cmd =
		dbg_ui_trace_win:handle_event(GuiEvent,State#state.win),
	    State2 = gui_cmd(Cmd, State),
	    loop(State2);

	%% From the GUI help windows
	{gui, Cmd} ->
	    State2 = gui_cmd(Cmd, State),
	    loop(State2);

	%% From the interpreter
	{int, Cmd} ->
	    State2 = int_cmd(Cmd, State),
	    loop(State2);

	%% From the meta process
	{Meta, Cmd} ->
	    State2 = meta_cmd(Cmd, State),
	    loop(State2);
	{NewMeta, {exit_at, Where, Reason, Cur}} ->
	    State2 = meta_cmd({exit_at, Where, Reason, Cur},
			      State#state{meta=NewMeta}),
	    loop(State2);

	%% From the dbg_ui_edit process
	{dbg_ui_edit, 'Backtrace:', BackTrace}  ->
	    loop(State#state{backtrace=BackTrace});
	{dbg_ui_edit, Var, Val} ->
	    Cmd = atom_to_list(Var)++"="++io_lib:format("~p", [Val]),
	    State2 = gui_cmd({user_command, lists:flatten(Cmd)}, State),
	    loop(State2);

	%% From the dbg_ui_winman process (Debugger window manager)
	{dbg_ui_winman, update_windows_menu, Data} ->
	    dbg_ui_winman:update_windows_menu(Data),
	    loop(State);
	{dbg_ui_winman, destroy} ->
	    exit(stop)
    end.

%%--Commands from the GUI---------------------------------------------

gui_cmd(ignore, State) ->
    State;
gui_cmd({win, Win}, State) ->
    State#state{win=Win};
gui_cmd(stopped, _State) ->
    exit(stop);
gui_cmd({coords, Coords}, State) ->
    State#state{coords=Coords};

gui_cmd({shortcut, Key}, State) ->
    case shortcut(Key) of
	{always, Cmd} -> gui_cmd(Cmd, State);
	{if_enabled, Cmd} ->
	    case dbg_ui_trace_win:is_enabled(Cmd) of
		true -> gui_cmd(Cmd, State);
		false -> State
	    end;
	false -> State
    end;

%% File menu
gui_cmd('Close', State) ->
    gui_cmd(stopped, State);

%% Edit menu
gui_cmd('Go To Line...', State) ->
    %% Will result in message handled below: {gui, {gotoline, Line}}
    dbg_ui_trace_win:helpwin(gotoline, State#state.win,
			      State#state.gs, State#state.coords),
    State;
gui_cmd({gotoline, Line}, State) ->
    Win = dbg_ui_trace_win:select_line(State#state.win, Line),
    State#state{win=Win};
gui_cmd('Search...', State) ->
    dbg_ui_trace_win:helpwin(search, State#state.win,
			     State#state.gs, State#state.coords),
    State;

%% Process menu
gui_cmd('Step', State) ->
    int:meta(State#state.meta, step),
    State;
gui_cmd('Next', State) ->
    int:meta(State#state.meta, next),
    State;
gui_cmd('Continue', State) ->
    int:meta(State#state.meta, continue),
    {Status, Mod, Line} = State#state.status,
    if
	Status==wait_break ->
	    Win = dbg_ui_trace_win:unmark_line(State#state.win),
	    gui_enable_functions(wait_running),
	    State#state{win=Win, status={wait_running,Mod,Line}};
	true ->
	    dbg_ui_trace_win:enable(['Stop'], true),
	    dbg_ui_trace_win:enable(['Continue'], false),
	    State
    end;
gui_cmd('Finish', State) ->
    int:meta(State#state.meta, finish),
    State;
gui_cmd('Skip', State) ->
    int:meta(State#state.meta, skip),
    State;
gui_cmd('Time Out', State) ->
    int:meta(State#state.meta, timeout),
    State;
gui_cmd('Stop', State) ->
    int:meta(State#state.meta, stop),
    {Status, Mod, Line} = State#state.status,
    if
	Status==wait_running ->
	    Win = dbg_ui_trace_win:mark_line(State#state.win, Line,
					     break),
	    gui_enable_functions(wait_break),
	    gui_enable_updown(State#state.stack_trace,
			      State#state.stack),
	    gui_enable_btrace(State#state.trace,
			      State#state.stack_trace),
	    dbg_ui_trace_win:display({wait, Mod, Line}),
	    State#state{win=Win, status={wait_break,Mod,Line}};
	true ->
	    dbg_ui_trace_win:enable(['Stop'], false),
	    dbg_ui_trace_win:enable(['Continue'], true),
	    State
    end;
gui_cmd('Where', State) ->
    {_Cur, Max} = State#state.stack,
    Stack = {Max, Max},
    {_Status, Mod, Line} = State#state.status,
    Win = gui_show_module(State#state.win, Mod, Line,
			  State#state.cm, State#state.pid, break),
    gui_update_bindings(State#state.meta),
    gui_enable_updown(State#state.stack_trace, Stack),
    dbg_ui_trace_win:display(State#state.status),
    State#state{win=Win, cm=Mod, stack=Stack};

gui_cmd('Kill', State) ->
    exit(State#state.pid, kill),
    State;
gui_cmd('Messages', State) ->
    case int:meta(State#state.meta, messages) of
	[] ->
	    dbg_ui_trace_win:eval_output("< No Messages!\n", bold);
	Messages ->
	    dbg_ui_trace_win:eval_output("< --- Current Messages ---\n",
					 bold),
	    lists:foldl(
	      fun(Msg, N) ->
		      Str1 = io_lib:format(" ~w:", [N]),
		      dbg_ui_trace_win:eval_output(Str1, bold),
		      Str2 = io_lib:format(" ~s~n",[io_lib:print(Msg)]),
		      dbg_ui_trace_win:eval_output(Str2, normal),
		      N+1
	      end,
	      1,
	      Messages)
    end,
    State;
gui_cmd('Back Trace', State) ->
    dbg_ui_trace_win:trace_output("\nBACK TRACE\n----------\n"),
    lists:foreach(
      fun({Le, {Mod,Func,Args}}) ->
	      Str = io_lib:format("~p > ~p:~p~p~n",
				  [Le, Mod, Func, Args]),
	      dbg_ui_trace_win:trace_output(Str);
	 ({Le, {Fun,Args}}) ->
	      Str = io_lib:format("~p > ~p~p~n", [Le, Fun, Args]),
	      dbg_ui_trace_win:trace_output(Str);
	 (_) -> ignore
      end,
      int:meta(State#state.meta, backtrace, State#state.backtrace)),
    dbg_ui_trace_win:trace_output("\n"),
    State;
gui_cmd('Up', State) ->
    {Cur, Max} = State#state.stack,
    case int:meta(State#state.meta, stack_frame, {up, Cur}) of
	{New, {undefined,-1}, _Bs} -> % call from non-interpreted code
	    Stack = {New, Max},
	    Win = dbg_ui_trace_win:show_no_code(State#state.win),
	    dbg_ui_trace_win:update_bindings([]),
	    gui_enable_updown(State#state.stack_trace, Stack),
	    dbg_ui_trace_win:display({New,null,null}),
	    State#state{win=Win, cm=null, stack=Stack};

	{New, {Mod,Line}, Bs} ->
	    Stack = {New, Max},
	    Win = gui_show_module(State#state.win, Mod, Line,
				  State#state.cm, State#state.pid,
				  where),
	    dbg_ui_trace_win:update_bindings(Bs),
	    gui_enable_updown(State#state.stack_trace, Stack),
	    dbg_ui_trace_win:display({New,Mod,Line}),
	    State#state{win=Win, cm=Mod, stack=Stack};
	top ->
	    dbg_ui_trace_win:enable(['Up'], false),
	    State
    end;
gui_cmd('Down', State) ->
    {Cur, Max} = State#state.stack,
    case int:meta(State#state.meta, stack_frame, {down, Cur}) of
	{New, {undefined,-1}, _Bs} -> % call from non-interpreted code
	    Stack = {New, Max},
	    Win = dbg_ui_trace_win:show_no_code(State#state.win),
	    dbg_ui_trace_win:update_bindings([]),
	    gui_enable_updown(State#state.stack_trace, Stack),
	    dbg_ui_trace_win:display({New,null,null}),
	    State#state{win=Win, cm=null, stack=Stack};

	{New, {Mod,Line}, Bs} ->
	    Stack = {New, Max},
	    Win = gui_show_module(State#state.win, Mod, Line,
				  State#state.cm, State#state.pid,
				  where),
	    dbg_ui_trace_win:update_bindings(Bs),
	    gui_enable_updown(State#state.stack_trace, Stack),
	    dbg_ui_trace_win:display({New,Mod,Line}),
	    State#state{win=Win, cm=Mod, stack=Stack};

	bottom ->
	    gui_cmd('Where', State)
    end;

%% Break menu
gui_cmd('Line Break...', State) ->
    add_break(State#state.gs, State#state.coords, line,
	      State#state.cm,
	      dbg_ui_trace_win:selected_line(State#state.win)),
    State;
gui_cmd('Conditional Break...', State) ->
    add_break(State#state.gs, State#state.coords, conditional,
	      State#state.cm,
	      dbg_ui_trace_win:selected_line(State#state.win)),
    State;
gui_cmd('Function Break...', State) ->
    add_break(State#state.gs, State#state.coords, function,
	      State#state.cm, undefined),
    State;
gui_cmd('Enable All', State) ->
    Breaks = int:all_breaks(),
    ThisMod = State#state.cm,
    lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
			  int:enable_break(Mod, Line);
		      (_Break) ->
			  ignore
		  end,
		  Breaks),
    State;
gui_cmd('Disable All', State) ->
    Breaks = int:all_breaks(),
    ThisMod = State#state.cm,
    lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
			  int:disable_break(Mod, Line);
		      (_Break) ->
			  ignore
		  end,
		  Breaks),
    State;
gui_cmd('Delete All', State) ->
    int:no_break(State#state.cm),
    State;
gui_cmd({break, {Mod, Line}, What}, State) ->
    case What of
	add -> int:break(Mod, Line);
	delete -> int:delete_break(Mod, Line);
	{status, inactive} -> int:disable_break(Mod, Line);
	{status, active} -> int:enable_break(Mod, Line);
	{trigger, Action} -> int:action_at_break(Mod, Line, Action)
    end,
    State;

%% Options menu
gui_cmd({'Trace Window', TraceWin}, State) ->
    Trace = lists:member('Trace Area', TraceWin),
    int:meta(State#state.meta, trace, Trace),
    Win = dbg_ui_trace_win:configure(State#state.win, TraceWin),
    {Status,_,_} = State#state.status,
    if
	Status==break; Status==wait_break ->
	    gui_enable_btrace(Trace, State#state.stack_trace);
	true -> ignore
    end,
    State#state{win=Win, trace=Trace};
gui_cmd({'Stack Trace', [Name]}, State) ->
    int:meta(State#state.meta, stack_trace, map(Name)),
    {Status,_,_} = State#state.status,
    if
	Status==break; Status==wait_break ->
	    gui_enable_btrace(State#state.trace, map(Name));
	true -> ignore
    end,
    State;
gui_cmd('Back Trace Size...', State) ->
    dbg_ui_edit:start(State#state.gs, State#state.coords, "Backtrace",
		      'Backtrace:', {integer, State#state.backtrace}),
    State;

%% Help menu
gui_cmd('Debugger', State) ->
    Window = dbg_ui_trace_win:get_window(State#state.win),
    HelpFile = filename:join([code:lib_dir(debugger),
			      "doc", "html", "part_frame.html"]),
    tool_utils:open_help(Window, HelpFile),
    State;

gui_cmd({user_command, Cmd}, State) ->
    {Status, _Mod, _Line} = State#state.status,
    if
	Status==break;
	Status==wait_break;
	Status==wait_running ->
	    Cm = State#state.cm,
	    Arg = case State#state.stack of
		      {Cur, Max} when Cur<Max -> {Cm, Cmd, Cur};
		      _Stack -> {Cm, Cmd}
		  end,

	    %% Reply will be received as {Meta, {eval_rsp, Res}}
	    int:meta(State#state.meta, eval, Arg);
	true ->
	    Str = "Commands not allowed",
	    dbg_ui_trace_win:eval_output([$<,Str,10], normal)
    end,
    State;

gui_cmd({edit, {Var, Val}}, State) ->
    dbg_ui_edit:start(State#state.gs, State#state.coords,
		      "Edit variable", Var, {term, Val}),
    State.

add_break(GS, Coords, Type, undefined, _Line) ->
    dbg_ui_break:start(GS, Coords, Type);
add_break(GS, Coords, Type, Mod, undefined) ->
    dbg_ui_break:start(GS, Coords, Type, Mod);
add_break(GS, Coords, Type, Mod, Line) ->
    dbg_ui_break:start(GS, Coords, Type, Mod, Line).

%%--Commands from the interpreter-------------------------------------

int_cmd({interpret, Mod}, State) ->
    if
	Mod==State#state.cm ->
	    State#state{cm_obsolete=true};
	true ->
	    State
    end;
int_cmd({no_interpret, Mod}, State) ->
    if
	Mod==State#state.cm ->
	    State#state{cm_obsolete=true};
	true ->
	    Win = dbg_ui_trace_win:remove_code(State#state.win, Mod),
	    State#state{win=Win}
    end;

int_cmd({new_break, Break}, State) ->
    Win = dbg_ui_trace_win:add_break(State#state.win, 'Break', Break),
    State#state{win=Win};
int_cmd({delete_break, Point}, State) ->
    Win = dbg_ui_trace_win:delete_break(State#state.win, Point),
    State#state{win=Win};
int_cmd({break_options, Break}, State) ->
    Win = dbg_ui_trace_win:update_break(State#state.win, Break),
    State#state{win=Win};
int_cmd(no_break, State) ->
    Win = dbg_ui_trace_win:clear_breaks(State#state.win),
    State#state{win=Win};
int_cmd({no_break, Mod}, State) ->
    Win = dbg_ui_trace_win:clear_breaks(State#state.win, Mod),
    State#state{win=Win}.

%%--Commands from the meta process------------------------------------

%% Message received when first attached to a living process
%% '_Trace' is a boolean indicating if the process is traced or not --
%% ignore this as we already have ordered tracing or not depending on if
%% the Trace Area is shown or not.
meta_cmd({attached, Mod, Line, _Trace}, State) ->
    Win = if
	      Mod/=undefined ->
		  gui_enable_functions(init),
		  gui_show_module(State#state.win, Mod, Line,
				  State#state.cm, State#state.pid,
				  break);
	      true -> State#state.win
	  end,
    State#state{win=Win, status={init,Mod,Line}, cm=Mod};

%% Message received when returning to interpreted code
meta_cmd({re_entry, dbg_ieval, eval_fun}, State) ->
    State;
meta_cmd({re_entry, Mod, _Func}, State) ->
    Obs = State#state.cm_obsolete,
    case State#state.cm of
	Mod when Obs==true ->
	    Win = gui_load_module(State#state.win, Mod,State#state.pid),
	    State#state{win=Win, cm_obsolete=false};
	Mod -> State;
	Cm ->
	    Win = gui_show_module(State#state.win, Mod, 0,
				  Cm, State#state.pid, break),
	    State#state{win=Win, cm=Mod}
    end;

%% Message received when attached to a terminated process
meta_cmd({exit_at, null, Reason, Cur}, State) ->
    Stack = {Cur, Cur},
    gui_enable_functions(exit),
    gui_enable_updown(false, Stack),
    dbg_ui_trace_win:display({exit, null, Reason}),
    State#state{status={exit,null,Reason}, stack=Stack};
meta_cmd({exit_at, {Mod,Line}, Reason, Cur}, State) ->
    Stack = {Cur+1, Cur+1},
    Win = gui_show_module(State#state.win, Mod, Line,
			  State#state.cm, State#state.pid, break),
    gui_enable_functions(exit),
    gui_enable_updown(State#state.stack_trace, Stack),
    gui_enable_btrace(State#state.trace, State#state.stack_trace),
    gui_update_bindings(State#state.meta),
    dbg_ui_trace_win:display({exit, {Mod,Line}, Reason}),
    State#state{win=Win, cm=Mod,status={exit,{Mod,Line},Reason},
		stack=Stack};

meta_cmd({break_at, Mod, Line, Cur}, State) ->
    Stack = {Cur,Cur},
    Win = gui_show_module(State#state.win, Mod, Line,
			  State#state.cm, State#state.pid, break),
    gui_enable_functions(break),
    gui_enable_updown(State#state.stack_trace, Stack),
    gui_enable_btrace(State#state.trace, State#state.stack_trace),
    gui_update_bindings(State#state.meta),
    dbg_ui_trace_win:display({break, Mod, Line}),
    State#state{win=Win, cm=Mod, status={break,Mod,Line}, stack=Stack};
meta_cmd({func_at, Mod, Line, Cur}, State) ->
    Stack = {Cur,Cur},
    Win = gui_show_module(State#state.win, Mod, Line,
			  State#state.cm, State#state.pid, where),
    gui_enable_functions(idle),
    dbg_ui_trace_win:display(idle),
    State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack};
meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State)
  when Status/=init, Status/=break ->
    Stack = {Cur,Cur},
    gui_enable_functions(wait_running),
    dbg_ui_trace_win:display({wait,Mod,Line}),
    State#state{status={wait_running,Mod,Line}, stack=Stack};
meta_cmd({wait_at, Mod, Line, Cur}, State) ->
    Stack = {Cur,Cur},
    Win = gui_show_module(State#state.win, Mod, Line,
			  State#state.cm, State#state.pid, break),
    gui_enable_functions(wait_break),
    gui_enable_updown(State#state.stack_trace, Stack),
    gui_enable_btrace(State#state.trace, State#state.stack_trace),
    gui_update_bindings(State#state.meta),
    dbg_ui_trace_win:display({wait, Mod, Line}),
    State#state{win=Win, cm=Mod, status={wait_break,Mod,Line},
		stack=Stack};
meta_cmd({wait_after_at, Mod, Line, Sp}, State) ->
    meta_cmd({wait_at, Mod, Line, Sp}, State);
meta_cmd(running, State) ->
    Win = dbg_ui_trace_win:unmark_line(State#state.win),
    gui_enable_functions(running),
    dbg_ui_trace_win:update_bindings([]),
    dbg_ui_trace_win:display({running, State#state.cm}),
    State#state{win=Win, status={running,null,null}};

meta_cmd(idle, State) ->
    Win = dbg_ui_trace_win:show_no_code(State#state.win),
    gui_enable_functions(idle),
    dbg_ui_trace_win:update_bindings([]),
    dbg_ui_trace_win:display(idle),
    State#state{win=Win, status={idle,null,null}, cm=undefined};

%% Message about changed trace option can be ignored, the change must
%% have been ordered by this process. (In theory, the change could have
%% been ordered by another attached process. The Debugger, though,
%% allows max one attached process per debugged process).
meta_cmd({trace, _Bool}, State) ->
    State;

meta_cmd({stack_trace, Flag}, State) ->
    dbg_ui_trace_win:select(map(Flag), true),
    gui_enable_updown(Flag, State#state.stack),
    {Status,_,_} = State#state.status,
    if
	Status==break; Status==wait_break ->
	    gui_enable_btrace(State#state.trace, Flag);
	true -> ignore
    end,
    State#state{stack_trace=Flag};

meta_cmd({trace_output, Str}, State) ->
    dbg_ui_trace_win:trace_output(Str),
    State;

%% Reply on a user command
meta_cmd({eval_rsp, Res}, State) ->
    Str = io_lib:print(Res),
    dbg_ui_trace_win:eval_output([$<,Str,10], normal),
    State.


%%====================================================================
%% GUI auxiliary functions
%%====================================================================

menus() ->
    [{'File', [{'Close', no}]},
     {'Edit', [{'Go To Line...', no},
	       {'Search...', no}]},
     {'Process', [{'Step', 0},
		  {'Next', 0},
		  {'Continue', 0},
		  {'Finish', 0},
		  {'Skip', no},
		  {'Time Out', no},
		  {'Stop', no},
		  separator,
		  {'Kill', no},
		  separator,
		  {'Messages', 0},
		  {'Back Trace', no},
		  separator,
		  {'Where', 0},
		  {'Up', no},
		  {'Down', no}]},
     {'Break', [{'Line Break...', 5},
		{'Conditional Break...', no},
		{'Function Break...', no},
		separator,
		{'Enable All', no},
		{'Disable All', no},
		{'Delete All', 0},
		separator]},
     {'Options', [{'Trace Window', no, cascade,
		   [{'Button Area', no, check},
		    {'Evaluator Area', no, check},
		    {'Bindings Area', no, check},
		    {'Trace Area', no, check}]},
		  {'Stack Trace', no, cascade,
		   [{'Stack On, Tail', no, radio},
		    {'Stack On, No Tail', no, radio},
		    {'Stack Off', no, radio}]},
		  {'Back Trace Size...', no}]},
     {'Help', [{'Debugger', no}]}].

%% enable(Status) -> [MenuItem]
%%   Status = init  % when first message from Meta has arrived
%%          | idle | break | exit | wait_break ¦ wait_running | running
enable(init) -> [];
enable(idle) -> ['Stop','Kill'];
enable(break) -> ['Step','Next','Continue','Finish','Skip',
		  'Kill','Messages'];
enable(exit) -> [];
enable(wait_break) -> ['Continue','Time Out','Kill'];
enable(wait_running) -> ['Stop','Kill'];
enable(running) -> ['Stop','Kill'].

all_buttons() ->
    ['Step','Next','Continue','Finish','Skip','Time Out','Stop',
     'Kill','Messages','Back Trace','Where','Up','Down'].

shortcut(s) -> {if_enabled, 'Step'};
shortcut(n) -> {if_enabled, 'Next'};
shortcut(c) -> {if_enabled, 'Continue'};
shortcut(f) -> {if_enabled, 'Finish'};
shortcut(m) -> {if_enabled, 'Messages'};
shortcut(w) -> {if_enabled, 'Where'};

shortcut(b) -> {always, 'Line Break...'};
shortcut(d) -> {always, 'Delete All'};

shortcut(_) -> false.

map('Stack On, Tail')    -> all;               % Stack trace
map('Stack On, No Tail') -> no_tail;
map('Stack Off')         -> false;
map(all)                 -> 'Stack On, Tail';
map(true)                -> 'Stack On, Tail';
map(no_tail)             -> 'Stack On, No Tail';
map(false)               -> 'Stack Off'.


%% gui_show_module(Win, Mod, Line, Cm, Pid, How) -> Win
%% gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) -> Win
%%   How = where | break
%% Show contents of a module in code area
gui_show_module(Win, {Mod,Line}, _Reason, Cm, Pid, How) ->
    gui_show_module(Win, Mod, Line, Cm, Pid, How);
gui_show_module(Win, Mod, Line, Mod, _Pid, How) ->
    dbg_ui_trace_win:mark_line(Win, Line, How);
gui_show_module(Win, Mod, Line, _Cm, Pid, How) ->
    Win2 = case dbg_ui_trace_win:is_shown(Win, Mod) of
	       {true, Win3} -> Win3;
	       false -> gui_load_module(Win, Mod, Pid)
	   end,
    dbg_ui_trace_win:mark_line(Win2, Line, How).

gui_load_module(Win, Mod, Pid) ->
    dbg_ui_trace_win:display({text, "Loading module..."}),
    Contents = int:contents(Mod, Pid),
    Win2 = dbg_ui_trace_win:show_code(Win, Mod, Contents),
    dbg_ui_trace_win:display({text, ""}),
    Win2.

gui_update_bindings(Meta) ->
    Bs = int:meta(Meta, bindings, nostack),
    dbg_ui_trace_win:update_bindings(Bs).

gui_enable_functions(Status) ->
    Enable = enable(Status),
    Disable = all_buttons() -- Enable,
    dbg_ui_trace_win:enable(Disable, false),
    dbg_ui_trace_win:enable(Enable, true).

gui_enable_updown(Flag, Stack) ->
    {Enable, Disable} =
	if
	    Flag==false -> {[], ['Up', 'Down']};
	    true ->
		case Stack of
		    {1,1} -> {[], ['Up', 'Down']};
		    {2,2} -> {[], ['Up', 'Down']};
		    {Max,Max} -> {['Up'], ['Down']};
		    {2,_Max} -> {['Down'], ['Up']};
		    {_Cur,_Max} -> {['Up', 'Down'], []}
		end
	end,
    dbg_ui_trace_win:enable(Enable, true),
    dbg_ui_trace_win:enable(Disable, false),
    if
	Enable==[] -> dbg_ui_trace_win:enable(['Where'], false);
	true -> dbg_ui_trace_win:enable(['Where'], true)
    end.

gui_enable_btrace(Trace, StackTrace) ->
    Bool = if
	       Trace==false -> false;
	       StackTrace==false -> false;
	       true -> true
	   end,
    dbg_ui_trace_win:enable(['Back Trace'], Bool).
