%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-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%
%%
%% ------------------------------------------------------------
%% Purpose: window management and the gs interface
%% ------------------------------------------------------------
-module(pman_win).
-compile([{nowarn_deprecated_function,{gs,button,2}},
{nowarn_deprecated_function,{gs,canvas,2}},
{nowarn_deprecated_function,{gs,config,2}},
{nowarn_deprecated_function,{gs,create,3}},
{nowarn_deprecated_function,{gs,create,4}},
{nowarn_deprecated_function,{gs,destroy,1}},
{nowarn_deprecated_function,{gs,read,2}},
{nowarn_deprecated_function,{gs,start,1}},
{nowarn_deprecated_function,{gs,text,2}},
{nowarn_deprecated_function,{gs,window,2}}]).
%% ---------------------------------------------------------------
%% The user interface exports
%% ---------------------------------------------------------------
-export([pman_window/3, window/1, module_data/1, display/1, format/2,
dialog_window/2, configeditor/2, configwin/3,
update/1, update/3,
msg_win/1, title/1,
remove_menu/1, add_menu/3,
change_colour/3, links_menus/1, calc_columnwidths/1]).
-export([font/0, font/1]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Constants
%%
-include("pman_win.hrl").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% pman_window/3 - Create a GS window and components for the
%% Pman overview window, the main window.
%%
%% Arguments:
%% Size number of processes
%% HiddenModules list of modules
%% Nodes list of supervised nodes
%%
%% Return:
%% {Win, Grid, Frame, Procnum, W, H} where
%% Win The GS top window
%% Grid The GS grid
%% Procnum Number of displayed processes
%%
pman_window(Size, _HiddenModules, Nodes) ->
GS = gs:start([{kernel,true}]),
Font = font(GS),
Win_Options = [{title, lists:concat(["Pman: Overview on ",node()])},
{width, ?WIN_WIDTH}, {height, ?WIN_HEIGHT},
{destroy, true},
{keypress,true}],
Win = gs:create(window, GS, Win_Options),
%% Menu bar
MenuBar = gs:create(menubar, Win, []),
MBFile = gs:create(menubutton, MenuBar, [{label,{text," File "}},
{font,Font},
{underline,1}]),
MBView = gs:create(menubutton, MenuBar, [{label,{text, " View "}},
{font,Font},
{underline,1}]),
MBTrace = gs:create(menubutton, MenuBar, [{label,{text, " Trace "}},
{font,Font},
{underline,1}]),
MBHelp = gs:create(menubutton, MenuBar, [{label, {text, " Help "}},
{font,Font},
{side,right},
{underline,1}]),
%% Addition of a menu for distribution
add_node_menu(MenuBar, Nodes, Font),
%% All menu buttons
MenuFile = gs:create(menu, MBFile, []),
MenuView = gs:create(menu, MBView, []),
MenuTrace = gs:create(menu, MBTrace, []),
MenuHelp = gs:create(menu, MBHelp, []),
%% File menu
gse:named_menuitem('Default Options', MenuFile,
[{label,{text,"Options..."}}, {font,Font},
{underline,0}]),
gse:named_menuitem('Save Options',MenuFile,
[{label,{text,"Save Options"}}, {font,Font}]),
gse:named_menuitem('Exit', MenuFile,
[{label,{text,"Exit"}}, {font,Font},
{underline,0}]),
%% View menu
gse:named_menuitem('Hide All',MenuView,
[{label, {text, "Hide All Processes"}},
{font,Font},
{underline,1}]),
gse:named_menuitem('Hide Modules', MenuView,
[{label, {text, "Hide Modules..."}},
{font,Font},
{underline,8}]),
gse:named_menuitem('Hide Selected Process', MenuView,
[{label, {text, "Hide Selected Process"}},
{font,Font},
{underline,2}]),
gse:named_menuitem('Module',MenuView,
[{label, {text, "Module Info..."}}, {font,Font},
{underline,7}]),
gse:named_menuitem('Refresh', MenuView,
[{label, {text, "Refresh"}}, {font,Font},
{underline,0}]),
gse:named_menuitem('Show All',MenuView,
[{label, {text, "Show All Processes"}},
{font,Font}]),
gse:named_menuitem('Show Selected',MenuView,
[{label, {text, "Show Processes..."}},
{font,Font}]),
%% Trace menu
gs:create(menuitem, 'Kill', MenuTrace, [{label,{text, "Kill"}},
{font,Font},
{underline,0}]),
gs:create(menuitem, 'Trace Process', MenuTrace,
[{label, {text, "Trace Selected Process"}}, {font,Font},
{underline,0}]),
gs:create(menuitem,'Trace Shell', MenuTrace,
[{label, {text,"Shell Process"}}, {font,Font},
{underline,0}]),
%% Help menu
gs:create(menuitem,'Help', MenuHelp, [{label, {text, "Help" }},
{font,Font},
{underline,0}]),
%% Window contents
%% Geometry managing frame
Frame = gse:frame(Win, [{y,?MENU_HEIGHT},
{packer_x,[{stretch, 1}]},
{packer_y,[{stretch,10},
{fixed,?CHECKBAR_HEIGHT}]}]),
%% Grid
Grid_Options = [
{pack_x,1}, {pack_y,1},
{fg,black},
{vscroll,right},{hscroll,bottom},
calc_columnwidths(739),
{rows, {1,Size}}],
Grid = gse:grid(Frame,Grid_Options),
%% Checkbutton bar at the bottom of the window
CheckBar = gse:frame(Frame, [{pack_x,1},
{pack_y,2},
{packer_x,[{stretch, 2, 100,300},
{stretch, 2, 100,300},
{stretch,1},
{stretch, 2,100,300}]},
{packer_y,[{stretch,1}]}]),
gse:named_checkbutton('Hide System',CheckBar,
[{pack_xy,{1,1}},
{justify, left},
{align,w},
{width, 200},
{font, Font},
{label, {text, "Hide System Processes" }}]),
gse:named_checkbutton('Auto Hide New',CheckBar,
[{pack_xy,{2,1}},
{width, 200},
{justify, left},
{align,w},
{font, Font},
{label, {text, "Auto-Hide New" }}]),
gse:named_label('Number Hidden',CheckBar,
[{pack_xy,{4,1}},
{justify, left},
{align,w},
{width, 200},
{font, Font},
{label, {text, ?CPIDHIDDENTEXT }}]),
%% Finalize it!
gse:map(Win),
gse:config(Win,[raise]),
gse:config(Win,[{configure,true}]),
{Win, Grid, Frame, length(processes())+1, ?WIN_WIDTH, ?WIN_HEIGHT}.
%% Calculate columnwidths in respect to the size of the window.
calc_columnwidths(Width) ->
if
Width =< 739 ->
{columnwidths,[75,215,146,90,105,105]};
true ->
S = (Width - 75)/(215+146+90+105+105),
{columnwidths,[75,round(215*S),round(146*S),round(90*S),
round(105*S),round(105*S)]}
end.
%% ---------------------------------------------------------------
%% Create a trace window
%%
%% Process, a process id or an atom
%%
%% Return: A window and a editor
%% ---------------------------------------------------------------
window(Process) ->
GS = gs:start([{kernel,true}]),
Font = font(GS),
Win_Options = [{title,title(Process)}, {width,550}, {keypress,true},
{configure,true},
{destroy,true},{height, 400}],
Win = gs:create(window,GS,Win_Options),
MenuBar = gs:create(menubar, Win, []),
%% File menu
MBFile = gs:create(menubutton,MenuBar,[{label,{text," File "}},
{font,Font},
{underline, 1}]),
MenuFile = gs:create(menu, MBFile, []),
make_menus(pman_process:is_running(Process), MenuBar, MenuFile,
Font),
gse:named_menuitem('Save buffer',MenuFile,
[{label,{text, "Save buffer..."}},
{font,Font},
{underline,0}]),
gse:named_menuitem('Close',MenuFile,
[{label, {text, "Close"}},
{font,Font},
{underline,0}]),
Editor = gs:create(editor,Win,[{x,3}, {y,40},
{width,546}, {height,348},
{font,Font}]),
gs:config(Editor, [{keypress, true},{insert, {'end', display(Process)}}]),
gs:config(Editor, [{enable, false},{vscroll, right}, {hscroll, bottom},
{wrap,none}]),
gs:config(Win, [{map, true}]),
{Win, Editor}.
%% ---------------------------------------------------------------------
%% Menu Help Fuctions
%% ---------------------------------------------------------------------
links_menus(Links) ->
gs:destroy('Links'),
gs:create(menu,'Links','LinksMenu',[]),
Flag = case links_menus(Links,[]) of
[] -> false;
Pids ->
add_menu('Links', Pids, "Trace"),
true
end,
gse:config('LinksMenu',[{enable,Flag}]).
links_menus([],Pids) -> Pids;
links_menus([Pid|Links],Pids) when is_pid(Pid) ->
links_menus(Links,[Pid|Pids]);
links_menus([_Port|Links],Pids) ->
links_menus(Links,Pids).
%% Create the node menu.
add_node_menu(MenuBar, Nodes, Font) ->
MBNode = gs:create(menubutton, MenuBar, [{label,{text, " Nodes "}},
{font,Font},
{underline, 1}]),
gs:create(menu, node, MBNode, []),
add_menu(node, Nodes, "Show", Font),
gse:disable(node()).
%% ---------------------------------------------------------------------
%% Add Menus in the list under Menu menuitem.
add_menu(Menu, Names, Tag) ->
add_menu(Menu, Names, Tag, font()).
add_menu(_Menu, [], _Tag, _Font) -> ok;
add_menu(Menu, [Name|Names], Tag, Font) ->
Title = io_lib:format("~s ~p",[Tag, Name]),
gs:create(menuitem,Name,Menu,[{label,{text,Title}},
{font,Font},
{data,{Menu,Name}}]),
add_menu(Menu, Names, Tag, Font).
%% ---------------------------------------------------------------------
%% Remove a specific menu item, or a whole menu, or a list of both.
%%
remove_menu(List) when is_list(List)->
lists:foreach(fun(X) -> gs:destroy(X) end, List);
remove_menu(Object) ->
gse:destroy(Object).
%% ---------------------------------------------------------------------
%% If the trace window opened is supposed to trace a real pid, let us
%% add the trace menu, and other items specific to tracing. If not,
%% the only menus available are the ones in the default defined in
%% window(Pid).
make_menus(false, _, _, _) -> ok;
make_menus({true,Pid}, MenuBar, MenuFile, Font) ->
MBView = gs:create(menubutton,'ViewMenu',MenuBar,
[{underline,1},
{label,{text," View "}}, {font,Font},
{side,left}]),
MenuView = gs:create(menu, MBView, []),
MBTrace = gs:create(menubutton,'TraceMenu',MenuBar,
[{underline,1},
{label,{text," Trace "}}, {font,Font},
{side,left}]),
MenuTrace = gs:create(menu, MBTrace, []),
MBHelp = gs:create(menubutton,'HelpMenu',MenuBar,
[{underline,1},
{label,{text," Help "}}, {font,Font},
{side,right}]),
MenuHelp = gs:create(menu, MBHelp, []),
%% File menu
gse:named_menuitem('Options', MenuFile,
[{label, {text, "Options..."}}, {font,Font},
{underline,0}]),
%% Trace menu
gse:named_menuitem('All Links', MenuTrace,
[{label, {text, "All Linked Processes"}},
{font,Font},
{underline,0}]),
gse:named_menuitem('LinksMenu', MenuTrace,
[{underline,0},
{label, {text, "Linked Process..."}},
{font,Font},
{itemtype, cascade},
{enable,false}]),
gs:create(menu,'Links','LinksMenu',[]),
case pman_process:pinfo(Pid, links) of
Links when is_list(Links) ->
links_menus(Links);
undefined ->
lists:foreach(fun(X) -> gse:disable(X) end,['LinksMenu'])
end,
gse:named_menuitem('Kill', MenuTrace,
[{label, {text, "Kill"}}, {font,Font},
{underline,0}]),
%% View menu
gse:named_menuitem('Clear', MenuView,
[{label, {text, "Clear buffer"}}, {font,Font},
{underline,0}]),
gse:named_menuitem('Module', MenuView,
[{label, {text, "Module Info"}}, {font,Font},
{underline,0}]),
%% Help menu
gse:named_menuitem('Help', MenuHelp,
[{label, {text, "Help"}}, {font,Font},
{underline,0}]).
%% ---------------------------------------------------------------------
%% Configurate the actual editor
%%
%% Editor, actual editor
%% Options, actual options for the editor
%%
%% Return: A configurated editor with the actual options
%% ---------------------------------------------------------------------
configeditor(Editor, Options) ->
gs:config(Editor, Options).
%% ---------------------------------------------------------------------
%% Configure the actual window after it has been resized.
%% ---------------------------------------------------------------------
configwin(Object, W, H) ->
Dx = abs(W - gs:read(Object,width) - 4),
Dy = abs(H - gs:read(Object,height) - 42),
if
Dx + Dy =/= 0 ->
gs:config(Object,[{width,W - 4}]);
true -> ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% update/1, 3
update(NoOfHidden) ->
Str = lists:flatten(io_lib:format(?CPIDHIDDENTEXT++"~w",
[NoOfHidden])),
gse:config('Number Hidden', [{label, {text,Str}}]).
update(Grid, ShowInfoR, NoOfHidden) ->
%% We reverse the list because we want the processes to appear with
%% the newest (=highest) pid first in the list.
ShowInfo = lists:reverse(ShowInfoR),
%% Set the length of the grid
CGridline = length(ShowInfo) + 1,
gs:config(Grid, [{rows, {1,CGridline}}]),
%% Add the header line
add_gridline(Grid,
1,
{'Pid','Current Function','Name','Msgs','Reds','Size'},
[]),
update(NoOfHidden),
%% Recurse through the ordset of pids
update_r(Grid, ShowInfo, 2).
update_r(Grid, [], Row) ->
delete_gridlines(Grid, Row);
update_r(Grid, [{Pid,Func,Name,Msgs,Reds,Psize}|ShowInfo], Row) ->
{M, F, A} = Func,
FuncText = lists:flatten(io_lib:format("~w:~w/~w", [M, F, A])),
add_gridline(Grid,
Row,
{Pid, FuncText, Name, Msgs, Reds, Psize},
[{data,{pidfunc,Pid,Func}}]),
update_r(Grid, ShowInfo, Row+1).
add_gridline(Grid, Row, Tuple, LIOptSpec) ->
{Pid, FuncText, Name, Msgs, Reds, Psize} = Tuple,
LIOpt = [{click,true},
{doubleclick,true},
{fg, colour(Row)},
{text,{1,Pid}},
{text,{2,FuncText}},
{text,{3,Name}},
{text,{4,Msgs}},
{text,{5,Reds}},
{text,{6,Psize}} |LIOptSpec],
case gs:read(Grid, {obj_at_row, Row}) of
undefined ->
gse:gridline(Grid,[{row, Row}|LIOpt]);
GridLine ->
gs:config(GridLine,LIOpt)
end.
delete_gridlines(Grid, Row) ->
case gs:read(Grid, {obj_at_row, Row}) of
undefined ->
ok;
GridLine ->
gs:destroy(GridLine),
delete_gridlines(Grid, Row+1)
end.
colour(1) ->
?HEADER_COLOUR;
colour(_Row) ->
?UNSELECTED_COLOUR.
%% Interchange colours between two rows
change_colour(Grid, Row, Row) ->
Gitem = gs:read(Grid, {obj_at_row,Row}),
gs:config(Gitem, {fg,?SELECTED_COLOUR});
change_colour(Grid, From, To) ->
Gitem_to = gs:read(Grid, {obj_at_row,To}),
Gitem_fr = gs:read(Grid, {obj_at_row,From}),
gs:config(Gitem_to, {fg,?SELECTED_COLOUR}),
gs:config(Gitem_fr, {fg,colour(From)}).
%% --------------------------------------------------------------
%% Create a title for the window
%% Return: the title
%% --------------------------------------------------------------
title({module, Mod}) ->
lists:flatten([io_lib:format("Pman: Module info ~p", [Mod])]);
title({shell, Sh} ) ->
lists:flatten([io_lib:format("Pman: Shell process ~p on ~p",
[Sh,node(Sh)])]);
title(Sh) ->
lists:flatten([io_lib:format("Pman: Process ~p on ~p",
[Sh, node(Sh)]),name(Sh)]).
name(Pid) ->
case pman_process:pinfo(Pid, registered_name) of
undefined -> "";
Name ->
lists:flatten([io_lib:format("[~p]", [Name])])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% module_data/1 - %% Returns the module information for a
%% module, on a format suitable to insert into a GS editor.
%%
%% Arguments:
%% ModuleName The module
%%
%% Returns:
%% A string with module information.
%%
module_data(ModuleName) ->
vformat("", catch apply(ModuleName, module_info, [])).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% display/1 -
%%
display({_,Pid,_}) -> display(Pid);
display({_,Pid}) -> display(Pid);
display(Pid) when is_pid(Pid) ->
case pman_process:pinfo(Pid) of
undefined ->
format('Process is dead~n',[]);
Other ->
proc_format(Other)
end.
%% --------------------------------------------------------------
%% Format functions for the shell and help window.
%% --------------------------------------------------------------
vformat(Pad, {M,F,A}) when is_atom(F) ->
Pad2 = lists:append(Pad,mkpad(io_lib:format("~w:~w",[M,F]))),
lists:flatten([format("~p:~p", [M,F]),argformat(Pad2, A)]);
vformat(Pad, [H|T]) ->
kvformat(Pad, [H|T],"[");
vformat(_Pad, X) -> format("~p~n", [X]).
format(Format) -> format(Format, []).
format(Format, Args) ->
io_lib:format(Format, Args).
kvformat(S, [Item],Buff) ->
lists:reverse([format("\n~s~p]\n",[S,Item])|Buff]);
kvformat(S,[H|T],Buff) ->
kvformat(S,T,[format("\n~s~p, ",[S,H])|Buff]);
kvformat(_,[],Buff) ->
lists:reverse(["]\n"|Buff]).
argformat(_Pad,A) when is_integer(A) ->
format("/~p\n", [A]);
argformat(_,A) ->
lists:flatten([format("/~p\n", [length(A)]),
format("args: \n"),
argformat2(" ", A)]).
argformat2(Pad, Arglist) ->
Chars = lists:flatten(io_lib:format("~p",[Arglist])),
if
length(Chars) < (70 - length(Pad)) ->
format("~s~s\n", [Pad, Chars]);
true ->
argformat3(Pad, Arglist)
end.
argformat3(_,[]) -> format("\n");
argformat3(Pad, [H|T]) ->
Chars = truncate(65,io_lib:format("~s~p",[Pad, H])),
format("~s,\n", [Chars]),
argformat3(Pad, T).
pformat(false) -> [];
pformat({value,{_, 0}}) -> [];
pformat({value,{_, []}}) -> [];
pformat({value, {Key, Vals}}) ->
Pad = mkpad(io_lib:format("~p ",[Key])),
format(lists:flatten(["~p: " ,vformat(Pad, Vals), "~n"]), [Key]).
truncate(0, _Chars) -> ".....";
truncate(I, [H|T]) -> [H|truncate(I-1, T)];
truncate(_I, []) -> [].
mkpad([_|T]) -> [32|mkpad(T)];
mkpad([]) -> [].
proc_format(Pi) -> %% process_info struct
X1 = pformat(lists:keysearch(initial_call, 1, Pi)),
X2 = pformat(lists:keysearch(current_function, 1,Pi)),
X3 = pformat(lists:keysearch(messages, 1,Pi)),
X4 = pformat(lists:keysearch(dictionary,1, Pi)),
X5 = pformat(lists:keysearch(heap_size, 1,Pi)),
X6 = pformat(lists:keysearch(stack_size, 1,Pi)),
X7 = pformat(lists:keysearch(reductions, 1,Pi)),
X8 = pformat(lists:keysearch(links, 1,Pi)),
X9 = pformat(lists:keysearch(trap_exit, 1,Pi)),
lists:flatten([X1, X2, X3, X4, X5,X6,X7,X8,X9]).
%% Using the tool_utils function for presenting messages.
dialog_window(GSParent, Text) ->
spawn_link(tool_utils, notify, [GSParent, Text]).
%% Create a window with a dismiss button.
msg_win(Text) ->
spawn_link(fun() -> display_msg_win(Text) end).
display_msg_win(Text) ->
GS = gs:start([{kernel,true}]),
Font = font(GS),
Win = gs:window(GS, [{width,200}, {height,75}, {destroy,true},
{title,"Pman Message"}]),
Can = gs:canvas(Win, [{width,200}, {height, 75},{x,0},{y,0}]),
gs:text(Can, [{text,Text}, {coords,[{10,0}]}, {justify,center}]),
Btn = gs:button(Win, [{label,{text,"Dismiss"}}, {font,Font},
{width,100}, {x,50}, {y,40}]),
gs:config(Win, {map,true}),
receive
{gs, Btn, click, _, _} ->
ok
end.
%% Choose default font
font() ->
font(gs:start([{kernel,true}])).
font(GS) ->
case gs:read(GS, {choose_font, {screen,[],12}}) of
Font when element(1, Font)==screen ->
Font;
_ ->
gs:read(GS, {choose_font, {courier,[],12}})
end.