%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2011. 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_mon_win).
-compile([{nowarn_deprecated_function,{gs,checkbutton,2}},
{nowarn_deprecated_function,{gs,config,2}},
{nowarn_deprecated_function,{gs,destroy,1}},
{nowarn_deprecated_function,{gs,frame,2}},
{nowarn_deprecated_function,{gs,grid,2}},
{nowarn_deprecated_function,{gs,gridline,2}},
{nowarn_deprecated_function,{gs,label,2}},
{nowarn_deprecated_function,{gs,listbox,2}},
{nowarn_deprecated_function,{gs,menu,2}},
{nowarn_deprecated_function,{gs,menubar,2}},
{nowarn_deprecated_function,{gs,menuitem,2}},
{nowarn_deprecated_function,{gs,read,2}},
{nowarn_deprecated_function,{gs,window,2}}]).
%% External exports
-export([init/0]).
-export([create_win/3, get_window/1,
show_option/3,
enable/2, is_enabled/1, select/2,
add_module/3, delete_module/2,
add_process/6, update_process/4, clear_processes/1,
add_break/3, update_break/2, delete_break/2,
clear_breaks/1, clear_breaks/2,
handle_event/2
]).
-define(default_rows,50).
-record(moduleInfo, {module, menubtn}).
-record(procInfo, {pid, row}).
-record(breakInfo, {point, status, break}).
-record(winInfo, {window, % gsobj()
grid, % gsobj()
row, % int() Last row in grid
focus, % int() Selected row in grid
modules=[], % [#moduleInfo{}] Known modules
processes=[], % [#procInfo{}] Known processes
breaks=[], % [#breakInfo{}] Known breakpoints
listbox, % gsobj() Listinng known modules
%% Auto attach buttons
fbutton, % gsobj()
bbutton, % gsobj()
ebutton, % gsobj()
selected=[], % ['First Call'|'On Break'|'On Exit']
slabel, % showing Stack Trace option
blabel % showing Back Trace Size
}).
%%====================================================================
%% External exports
%%====================================================================
init() ->
dbg_ui_win:init().
%%--------------------------------------------------------------------
%% create_win(GS, Title, Menus) -> #winInfo{}
%% GS = gsobj()
%% Title = string()
%% Menus = [menu()] See dbg_ui_win.erl
%%--------------------------------------------------------------------
-define(PAD, 5).
-define(Wf, 150).
-define(Wg, 770).
-define(W, 800).
-define(H, 390).
create_win(GS, Title, Menus) ->
Win = gs:window(GS, [{title, Title},
{width, ?W}, {height, ?H},
{configure,true}, {destroy,true},
{keypress,true}, {motion,true}]),
MenuBar = gs:menubar(Win, []),
dbg_ui_win:create_menus(MenuBar, Menus),
dbg_ui_winman:windows_menu(MenuBar),
Font = dbg_ui_win:font(normal),
Frame = gs:frame(Win, [{x, ?PAD}, {y, 30},
{width, ?Wf}, {height, ?H}]),
Hlb = 200,
Listbox = gs:listbox(Frame, [{x, 0}, {y, 0},
{width, ?Wf}, {height, Hlb},
{data, listbox},
{doubleclick, true},
{items, []}]),
gs:label(Frame, [{x, 0}, {y, Hlb}, {width, ?Wf}, {height, 20},
{align, w},
{label, {text, "Auto Attach:"}}, {font, Font}]),
Fbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+20},
{width, ?Wf}, {height, 20},
{label, {text, 'First Call'}},
{align, w}, {font, Font},
{data, autoattach}]),
Bbtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+40},
{width, ?Wf}, {height, 20},
{label, {text, 'On Break'}},
{align, w}, {font, Font},
{data, autoattach}]),
Ebtn = gs:checkbutton(Frame, [{x, 0}, {y, Hlb+60},
{width, ?Wf}, {height, 20},
{label, {text, 'On Exit'}},
{align, w}, {font, Font},
{data, autoattach}]),
SLabel = gs:label(Frame, [{x, 0}, {y, Hlb+80},
{width, ?Wf}, {height, 40},
{font, Font}, {align, w}]),
BLabel = gs:label(Frame, [{x, 0}, {y, Hlb+120},
{width, ?Wf}, {height, 40},
{font, Font}, {align, w}]),
Grid = gs:grid(Win, [{x, 2*?PAD+?Wf}, {y, 30},
{width, ?W-(2*?PAD+?Wf)}, {height, ?H-30},
{bg, grey}, {fg, black},
{vscroll, right}, {hscroll, bottom},
calc_columnwidths(?Wg),
{rows, {1,?default_rows}}]),
gs:gridline(Grid, [{row, 1}, {bw, 5}, {fg, blue},
{font, Font},
{text, {1,"Pid"}}, {text, {2,"Initial Call"}},
{text, {3,"Name"}}, {text, {4,"Status"}},
{text, {5,"Information"}}]),
gs:config(Win, {map, true}),
#winInfo{window=Win, grid=Grid, row=1, focus=0,
listbox=Listbox,
fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn,
slabel=SLabel, blabel=BLabel}.
%%--------------------------------------------------------------------
%% get_window(WinInfo) -> Window
%% WinInfo = #winInfo{}
%% Window = gsobj()
%%--------------------------------------------------------------------
get_window(WinInfo) ->
WinInfo#winInfo.window.
%%--------------------------------------------------------------------
%% show_option(WinInfo, Option, Value) -> void()
%% WinInfo = #winInfo{}
%% Option = auto_attach | stack_trace | back_trace
%% Value = [Flag] % Option==auto_attach
%% Flag = init | break | exit
%% | true | all | no_tail | false % Option==stack_trace
%% | int() % Option==back_trace
%%--------------------------------------------------------------------
show_option(WinInfo, Option, Value) ->
case Option of
auto_attach ->
lists:foreach(fun (Button) ->
gs:config(Button, {select, false})
end,
option_buttons(WinInfo, [init, break, exit])),
lists:foreach(fun (Button) ->
gs:config(Button, {select, true})
end,
option_buttons(WinInfo, Value));
stack_trace ->
Text = case Value of
all -> "Stack Trace:\n On (with tail)";
true -> "Stack Trace:\n On (with tail)";
no_tail -> "Stack Trace:\n On (no tail)";
false -> "Stack Trace:\n Off"
end,
gs:config(WinInfo#winInfo.slabel, {label, {text, Text}});
back_trace ->
Text = "Back Trace Size:\n " ++ integer_to_list(Value),
gs:config(WinInfo#winInfo.blabel, {label, {text, Text}})
end.
option_buttons(WinInfo, [init|Flags]) ->
[WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)];
option_buttons(WinInfo, [break|Flags]) ->
[WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)];
option_buttons(WinInfo, [exit|Flags]) ->
[WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)];
option_buttons(_WinInfo, []) ->
[].
%%--------------------------------------------------------------------
%% enable([MenuItem], Bool)
%% is_enabled(MenuItem) -> Bool
%% MenuItem = atom()
%% Bool = boolean()
%%--------------------------------------------------------------------
enable(MenuItems, Bool) ->
lists:foreach(fun(MenuItem) ->
gs:config(MenuItem, {enable, Bool})
end,
MenuItems).
is_enabled(MenuItem) ->
gs:read(MenuItem, enable).
%%--------------------------------------------------------------------
%% select(MenuItem, Bool)
%% MenuItem = atom()
%% Bool = boolean()
%%--------------------------------------------------------------------
select(MenuItem, Bool) ->
dbg_ui_win:select(MenuItem, Bool).
%%--------------------------------------------------------------------
%% add_module(WinInfo, Name, Mod) -> WinInfo
%% WinInfo = #winInfo{}
%% Name = atom()
%% Mod = atom()
%%--------------------------------------------------------------------
add_module(WinInfo, Menu, Mod) ->
Modules = WinInfo#winInfo.modules,
case lists:keymember(Mod, #moduleInfo.module, Modules) of
false ->
%% Create a menu for the module
Font = dbg_ui_win:font(normal),
MenuBtn = gs:menuitem(Menu, [{label, {text,Mod}},
{font, Font},
{itemtype, cascade}]),
SubMenu = gs:menu(MenuBtn, []),
gs:menuitem(SubMenu, [{label, {text,"View"}},
{font, Font},
{data, {module,Mod,view}}]),
gs:menuitem(SubMenu, [{label, {text,"Delete"}},
{font, Font},
{data, {module,Mod,delete}}]),
%% Add the module to the listbox
gs:config(WinInfo#winInfo.listbox, {add, Mod}),
ModInfo = #moduleInfo{module=Mod, menubtn=MenuBtn},
WinInfo#winInfo{modules=[ModInfo | Modules]};
true -> WinInfo
end.
%%--------------------------------------------------------------------
%% delete_module(WinInfo, Mod) -> WinInfo
%% WinInfo = #winInfo{}
%% Mod = atom()
%%--------------------------------------------------------------------
delete_module(WinInfo, Mod) ->
{value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module,
WinInfo#winInfo.modules),
gs:destroy(ModInfo#moduleInfo.menubtn),
delete_module(WinInfo#winInfo.listbox, atom_to_list(Mod), 0),
WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module,
WinInfo#winInfo.modules)}.
delete_module(Listbox, ModS, Index) ->
case gs:read(Listbox, {get, Index}) of
ModS ->
gs:config(Listbox, {del, Index});
_OtherModS ->
delete_module(Listbox, ModS, Index+1)
end.
%%--------------------------------------------------------------------
%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo
%% WinInfo = #winInfo{}
%% Pid = pid()
%% Name = undefined | atom()
%% Function = {Mod, Func, Args}
%% Status = idle | running | break | exit
%% Info = {} | term()
%%--------------------------------------------------------------------
add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) ->
Grid = WinInfo#winInfo.grid,
Row = (WinInfo#winInfo.row)+1,
GridLine = case gs:read(Grid, {obj_at_row, Row}) of
undefined ->
if Row>?default_rows ->
gs:config(Grid,[{rows,{1,Row}}]);
true -> ok
end,
gs:gridline(Grid,[{row,Row}, {bw,5}, {fg,black},
{font,dbg_ui_win:font(normal)},
{click, true},
{doubleclick, true}]);
GSObj ->
GSObj
end,
Name2 = case Name of undefined -> ""; _ -> Name end,
FuncS = io_lib:format("~w:~w/~w", [Mod, Func, length(Args)]),
Info2 = case Info of {} -> ""; _ -> Info end,
Options = [{text, {1,Pid}}, {text, {2,FuncS}}, {text, {3,Name2}},
{text, {4,Status}}, {text, {5,Info2}},
{data, {gridline, Pid}}],
gs:config(GridLine, Options),
ProcInfo = #procInfo{pid=Pid, row=Row},
WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes],
row=Row}.
%%--------------------------------------------------------------------
%% update_process(WinInfo, Pid, Status, Info)
%% WinInfo = #winInfo{}
%% Pid = pid()
%% Status = idle | running | break | exit
%% Info = {} | term()
%%--------------------------------------------------------------------
update_process(WinInfo, Pid, Status, Info) ->
{value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid,
WinInfo#winInfo.processes),
Grid = WinInfo#winInfo.grid,
GridLine = gs:read(Grid, {obj_at_row, ProcInfo#procInfo.row}),
Info2 = case Info of {} -> ""; _ -> Info end,
gs:config(GridLine, [{text, {4,Status}}, {text, {5,Info2}}]).
%%--------------------------------------------------------------------
%% clear_processes(WinInfo) -> WinInfo
%% WinInfo = #winInfo{}
%%--------------------------------------------------------------------
clear_processes(WinInfo) ->
Grid = WinInfo#winInfo.grid,
Max = WinInfo#winInfo.row,
clear_processes(Grid, 2, Max),
gs:config(Grid,[{rows,{1,?default_rows}}]),
WinInfo#winInfo{row=1, focus=0, processes=[]}.
clear_processes(Grid, Row, Max) when Row=<Max ->
GridLine = gs:read(Grid, {obj_at_row, Row}),
case gs:read(GridLine,{text,4}) of
"exit" ->
Pid = list_to_pid(gs:read(GridLine,{text,1})),
dbg_ui_winman:clear_process(dbg_ui_trace:title(Pid));
_ ->
ok
end,
Options = [{fg, black},
{{text,1}, ""}, {{text,2},""}, {{text,3},""},
{{text,4}, ""}, {{text,5},""},
{data, []}],
gs:config(GridLine, Options),
clear_processes(Grid, Row+1, Max);
clear_processes(_Grid, Row, Max) when Row>Max ->
done.
%%--------------------------------------------------------------------
%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
%% WinInfo = #winInfo{}
%% Name = atom()
%% 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, {Point, Options}) ->
Break = dbg_ui_win:add_break(Menu, Point),
dbg_ui_win:update_break(Break, Options),
BreakInfo = #breakInfo{point=Point, break=Break},
WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.
%%--------------------------------------------------------------------
%% update_break(WinInfo, {Point, Options})
%% 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, {Point, Options}) ->
{value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
WinInfo#winInfo.breaks),
dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options).
%%--------------------------------------------------------------------
%% delete_break(WinInfo, Point) -> WinInfo
%% WinInfo = #winInfo{}
%% Point = {Mod, Line}
%%--------------------------------------------------------------------
delete_break(WinInfo, Point) ->
{value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
WinInfo#winInfo.breaks),
dbg_ui_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) ->
lists:foreach(fun(BreakInfo) ->
dbg_ui_win:delete_break(BreakInfo#breakInfo.break)
end,
WinInfo#winInfo.breaks),
WinInfo#winInfo{breaks=[]}.
clear_breaks(WinInfo, Mod) ->
Fun =
fun(BreakInfo) ->
case BreakInfo#breakInfo.point of
{Mod, _Line} ->
dbg_ui_win:delete_break(BreakInfo#breakInfo.break),
false;
_ -> true
end
end,
Breaks = lists:filter(Fun, WinInfo#winInfo.breaks),
WinInfo#winInfo{breaks=Breaks}.
%%--------------------------------------------------------------------
%% handle_event(GSEvent, WinInfo) -> Command
%% GSEvent = {gs, Id, Event, Data, Arg}
%% WinInfo = #winInfo{}
%% Command = ignore
%% | stopped
%% | {coords, {X,Y}}
%%
%% | {shortcut, Key}
%% | MenuItem | {Menu, [MenuItem]}
%% MenuItem = Menu = atom()
%% | {break, Point, What}
%% What = delete | {status, Status} | {trigger, Trigger}
%% | {module, Mod, What}
%% What = view | delete
%%
%% | {focus, Pid, WinInfo}
%% | default
%%--------------------------------------------------------------------
%% Window events
handle_event({gs, _Id, configure, _Data, [W, H |_]}, WinInfo) ->
configure(WinInfo, {W, H}),
ignore;
handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
stopped;
handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) ->
{LastX, LastY} = dbg_ui_win:motion(X, Y),
Win = WinInfo#winInfo.window,
{coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}};
%% Menus and keyboard shortcuts
handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) when
Key/='Up', Key/='Down', Key/=p, Key/=n ->
{shortcut, Key};
handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) ->
dbg_ui_winman:raise(Win),
ignore;
handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) ->
Name;
handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) ->
Names = dbg_ui_win:selected(Menu),
{Menu, Names};
handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) ->
{break, Point, What};
handle_event({gs, _Id, click, {module, Mod, What}, _Arg}, _WinInfo) ->
{module, Mod, What};
%% Listbox
handle_event({gs, _Id, doubleclick, listbox, [_Index, ModS|_]}, _WI) ->
{module, list_to_atom(ModS), view};
%% Auto attach buttons
handle_event({gs, _Id, click, autoattach, _Arg}, WinInfo) ->
Names = lists:foldl(fun (Button, NamesAcc) ->
case gs:read(Button, select) of
true ->
{text, Name} =
gs:read(Button, label),
[list_to_atom(Name)|NamesAcc];
false ->
NamesAcc
end
end,
[],
[WinInfo#winInfo.ebutton,
WinInfo#winInfo.bbutton,
WinInfo#winInfo.fbutton]),
{'Auto Attach', Names};
%% Process grid
handle_event({gs, _Id, keypress, _Data, [Key|_]}, WinInfo) when
Key =:= 'Up'; Key =:= 'Down' ->
Dir = if Key =:= 'Up' -> up; Key =:= 'Down' -> down end,
Row = move(WinInfo, Dir),
if Row>1 ->
WinInfo2 = highlight(WinInfo, Row),
#procInfo{pid=Pid} =
lists:keyfind(Row, #procInfo.row, WinInfo#winInfo.processes),
{focus, Pid, WinInfo2};
true ->
ignore
end;
handle_event({gs, _Id, click, {gridline, Pid}, [_Col,Row|_]}, WinInfo) ->
WinInfo2 = highlight(WinInfo, Row),
{focus, Pid, WinInfo2};
handle_event({gs, _Id, doubleclick, _Data, _Arg}, _WinInfo) ->
default;
handle_event(_GSEvent, _WinInfo) ->
ignore.
move(WinInfo, Dir) ->
Row = WinInfo#winInfo.focus,
Last = WinInfo#winInfo.row,
if
Dir =:= up, Row > 1 -> Row-1;
Dir =:= down, Row < Last -> Row+1;
true -> Row
end.
highlight(WinInfo, Row) ->
Grid = WinInfo#winInfo.grid,
case WinInfo#winInfo.focus of
0 -> ignore;
Focus ->
GridLine1 = gs:read(Grid, {obj_at_row, Focus}),
gs:config(GridLine1, {fg, black})
end,
GridLine2 = gs:read(Grid, {obj_at_row, Row}),
gs:config(GridLine2, {fg, white}),
WinInfo#winInfo{focus=Row}.
%%====================================================================
%% Internal functions
%%====================================================================
configure(WinInfo, {W, H}) ->
Grid = WinInfo#winInfo.grid,
NewW = W - (2*?PAD+?Wf),
Dx = NewW - gs:read(Grid, width),
Dy = H-42 - gs:read(Grid, height),
if
(Dx+Dy) =/= 0 ->
gs:config(Grid, [{width, NewW}, {height, H-30}]),
Cols = calc_columnwidths(NewW),
gs:config(Grid, Cols);
true ->
ok
end.
calc_columnwidths(Width) ->
W = if
Width =< ?Wg -> ?Wg;
true -> Width
end,
First = [round(X) || X <- [0.13*W, 0.27*W, 0.18*W, 0.18*W]],
Last = W - lists:sum(First) - 30,
{columnwidths, First++[Last]}.