aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pman/src/pman_win.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pman/src/pman_win.erl')
-rw-r--r--lib/pman/src/pman_win.erl667
1 files changed, 667 insertions, 0 deletions
diff --git a/lib/pman/src/pman_win.erl b/lib/pman/src/pman_win.erl
new file mode 100644
index 0000000000..52d5a237cf
--- /dev/null
+++ b/lib/pman/src/pman_win.erl
@@ -0,0 +1,667 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-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%
+%%
+%% ------------------------------------------------------------
+%% Purpose: window management and the gs interface
+%% ------------------------------------------------------------
+
+-module(pman_win).
+
+%% ---------------------------------------------------------------
+%% 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.