%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2012. 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= 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]}.