%% ``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 via the world wide web 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.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
-module(appmon_a).

%%----------------------------------------------------------------------
%%
%% Monitors an application, i.e its supervision tree.
%%
%%----------------------------------------------------------------------
%%
%%
%%		INTRODUCTION
%%		------------
%%
%%		This file contains a description of the files involved
%%		and the communication between the appmon_a display
%%		manager and the appmon_a2 information gatherer. Further
%%		information on the placement algorithm can be found in
%%		the place.erl file.
%%
%%
%%		FILES
%%		-----
%%
%%		The supervision tree graphical software consists of
%%		the following files:
%%
%% appmon_a	Gen server driving the process display window.
%%		Responsible for assigning gs identifiers to all 
%%		processes and process link
%% appmon_a2	The process information gathering routines. 
%%		Works by following the process links from application 
%%		master once every second
%% dg		The process database is implemented as a shared 
%%		digraph (see manual pages for digraph) and this is 
%%		the routines handling this digraph. Since the digraph 
%%		is shared appmon_a2 will put some info into it that the 
%%		appmon_a later will modify. The structures used are 
%%		described in dg.hrl
%% place	Places a tree, decides the x and y coordinates (not 
%%		necessarily corresponding to window coordinates) of 
%%		processes (or vertices to be specific). Note that 
%%		special routines are used to transform the possibly 
%%		cyclic digraph into a strict tree before trying to 
%%		place it.
%%
%%
%%
%%		IMPLEMENTATION DETAIL
%%		---------------------
%%
%%		The appmon_a module will follow links between processes,
%%		starting with the application master. A unique
%%		reference is used to prevent infinite recursion. Note
%%		that this process and link gathering is done in the
%%		live digraph so that already known processes are
%%		updated with the reference and new ones are added to
%%		the digraph. After all processes and links have been
%%		added or updated a search is made for those processes
%%		and links that have an old reference. These are those
%%		processes and links that are not present in the
%%		application any more. Those are extracted from the
%%		digraph and then deleted and the extracts are then
%%		used (by appmon_a) to delete the appropriate gs
%%		objects. The responsibilities of appmon_a is thus 1) add
%%		all new processes and links to the digraph and 2) make
%%		a list of all those objects from the digraph that have
%%		been deleted.
%%
%%		When appmon_a2 has gathered all necessary information it
%%		notifies the appmon_a display manager. Note that this is
%%		implemented as a call (as opposed to a cast) to
%%		prevent appmon_a2 from changing the digraph while appmon_a
%%		uses it. appmon_a places all processes using the place
%%		module. place will place the processes in the x y
%%		planes, hopefully in a nice way, re-forming the
%%		digraph during the process into a strict tree using
%%		some simple heuristics, some links that makes the
%%		graph cyclic will be considered secondary and later
%%		coloured red. Note that the process links are not
%%		placed since their coordinates are those of the
%%		processes that they are links between. The place
%%		module is only concerned at a fairly high level of
%%		abstraction. Currently its x coordinates are used as
%%		real coordinates while the y coordinates must be
%%		scaled to correct values, thus the x plane is
%%		continous and the y plane is disctrete.
%%
%%		Having placed processes the new ones are drawn on the
%%		display along with all new process links, then all
%%		processes and process links are moved to their
%%		possibly new positions. The place module is not
%%		sensitive to changes in position and therefore has no
%%		concept of which nodes will have to be moved. hence
%%		all nodes are moved (but most of them probably to the
%%		same position as before)
%%
%%
%%
%%
%%----------------------------------------------------------------------



-export([start/2, start/3, stop/0]).


-record(astate, {app, name, client, digraph}).

-import(lists, [foreach/2]).

%% gen server stuff
-behaviour(gen_server).
-export([init/1, handle_cast/2, handle_info/2, terminate/2]).
-export([handle_call/3, code_change/3]).


-define(APPSPACE, 10).				% The space between apps
-define(NODEAREA_H, 90).			% The height of a node
-define(BUTTAREA_H, 80).			% The button area height
-define(APPBUTT_H, 20).				% Height of appl button
-define(EDITORW, 260).

-define(MAXWIDTH, 800).
-define(MINWIDTH, 382).
-define(MAXHEIGHT, 450).
-define(MINHEIGHT, 325).

-define(SUPVIEWTXT, "Sup. view").
-define(PROCVIEWTXT, "Proc. view").
-define(CLOSETXT, "Close").
-define(REFRESHTXT, "Refresh").
-define(SAVEOPTSTXT, "Save options").
-define(HELPTXT, "Help").

-define(CHARWIDTH, 7).				%Should use GS primitives

-define( darkkhaki, {189, 183, 107}).
-define( palegoldenrod, {238, 232, 170}).
-define( peachpuff4, {139, 119, 101}).
-define( red, red).
-define( darkgrey, {169, 169, 169}).
-define( lightgrey, {211, 211, 211}).
-define( royalblue, {65, 105, 225}).
-define( aquamarine4, {69, 139, 116}).
-define( palegreen4, {84, 139,  84}).
-define( darkseagreen, {105, 139, 105}).
-define( f_line_col, {150, 150, 255}).


-include("appmon_dg.hrl").


%%------------------------------------------------------------
%%------------------------------------------------------------


start(NodeName, AppName) ->
    gen_server:start_link(?MODULE, {NodeName, AppName, AppName}, []).

start(NodeName, AppName, AppId) ->
    gen_server:start_link(?MODULE, {NodeName, AppName, AppId}, []).


stop() ->
    ok.



%%------------------------------------------------------------
%% Public interface


%%------------------------------------------------------------
%% Administration

%% AppName is the name of the application, usually an atom like sasl
%% or kernel, AppId is the application pid or the application name,
%% either goes.
init({NodeName, AppName, AppId}) ->
    process_flag(trap_exit, true),
    {ok, Client} = appmon_info:start_link(NodeName, self(), []),
    init_ref(),
    init_foreign_places(),
    DG = digraph:new([cyclic, private]),
    State = #astate{app=AppId, name=AppName, client=Client, digraph=DG},
    refresh(State),
    setup_base_win(NodeName, AppName),
    {ok, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

handle_call(norequest, _From, State) ->
    {reply, null, State}.

%%------------------------------------------------------------
%% handle casts

handle_cast({ping, _Node, _From}, State) ->
    {noreply, State};
handle_cast(_Other, State) ->
    {noreply, State}.



%%------------------------------------------------------------
%% handle info

handle_info({gs, _, click, _, [?CLOSETXT|_]}, State) ->
    {stop, normal, State};
handle_info({gs, _, destroy, _, _}, State) ->
    {stop, normal, State};
handle_info({gs, _, click, _, [?REFRESHTXT|_]}, State) ->
    refresh(State),
    {noreply, State};
handle_info({gs, _, click, _, [?HELPTXT|_]}, State) ->
    HelpFile = filename:join([code:lib_dir(appmon),
			      "doc", "html", "part_frame.html"]),
    tool_utils:open_help(win(), HelpFile),
    {noreply, State};
handle_info({gs, Id, click, {mode, Mode}, _}, State) ->
    %%io:format("handle_info: Setting mode: ~p~n", [Mode]),
    set_mode(Id, Mode),
    {noreply, State};
handle_info({gs, _, click, _, [?SUPVIEWTXT|_]}, State) ->
    refresh(State, [{info_type, sup}]),
    {noreply, State};
handle_info({gs, _, click, _, [?PROCVIEWTXT|_]}, State) ->
    refresh(State, [{info_type, link}]),
    {noreply, State};
handle_info({gs, Id, buttonpress, _,[1, X, Y|_]}, State) ->
    %%io:format("Id clicked: ~p~n", [gs:read(Id, {find, {X, Y}})]),
    catch find_pid(State, Id, X, Y),
    set_default_mode(),
    {noreply, State};
handle_info({gs, Win, configure, _Data, [W, H|_]}, State) ->
    case win() of Win -> user_driven_resize(W, H);
	_-> ok
    end,
    {noreply, State};

handle_info({delivery, _S, pinfo, _N, Res}, State) ->
    appmon_txt:print(Res),
    {noreply, State};
handle_info({delivery, S, app, N, Res}, State) ->
    {delivery, _Serv, app, _Name, {Root, Vs, Ls, SecLs}} = 
	flush({delivery, S, app, N, Res}),
    update2(Vs, Root, Ls, SecLs, State),
    {noreply, State};

handle_info({kill}, State) ->
    {stop, normal, State};
handle_info({state}, State) ->
    {noreply, State};
handle_info({'EXIT', _Pid, _Reason}, State) ->
    {noreply, State};
handle_info(_Other, State) ->
    {noreply, State}.


%% Refresh sets new options for the request and forces an update of
%% the screen ant status.
refresh(State) ->
    refresh(State, []).
refresh(State, Opts) ->
    appmon_info:app(State#astate.client,
		    State#astate.name, true, Opts).



%% find_pid finds the pid of the clicked object. The scenario is that
%% the user clicks on an item in his window, that ObjId is searched
%% for among all nodes (vertices) and if found action is taken
%% depending on the current mode (see handle_info)
find_pid(State, Id, X, Y) ->
    %% Try to manage both versions of GS, remove first case later.
    ObjList = case gs:read(Id, {find, {X, Y}}) of
		  {error, _} ->
		      gs:read(Id, {hit, {X, Y}}); % Try new format
		  Num when is_integer(Num) -> [Num];
		  _Other -> []
	      end,
    DG = State#astate.digraph,
    All = appmon_dg:get(all, DG),
    find_pid2(ObjList, All, DG, State).

find_pid2([Id | Ids], All, DG, State) ->
    case search_for_pid(All, DG, Id) of
	{ok, _KeyStr, Pid} ->
	    handle_proc_press(mode(), Pid, State);
	_ -> find_pid2(Ids, All, DG, State)
    end;
find_pid2([], _All, _DG, _State) -> ok.

search_for_pid([V|Vs], DG, ObjId) ->
    VD = appmon_dg:get(data, DG, V),
    if  ObjId==VD#vdata.txt_obj ->
	    {ok, V, VD#vdata.type};
	true -> search_for_pid(Vs, DG, ObjId)
    end;
search_for_pid([], _DG, _ObjId) -> false.


%%
%% called when a process has been clicked on.
%%
handle_proc_press(info, Pid, State) -> 
    appmon_info:pinfo(State#astate.client, Pid, true, 
		      [{timeout, at_most_once}]);
handle_proc_press(send, Pid, _State) -> 
    {P, RawStr} = two_entries(winroot(), 250, 70,
			      "Send", "To: ", "Msg: ", 
			      pid_to_list(Pid), "", bg()),
    Str = case lists:last(RawStr) of
	       46 -> RawStr;
	       _ -> RawStr++"."
	   end,
    case erl_scan:string(Str) of
	{ok, Tokens, _} ->
	    case erl_parse:parse_term(Tokens) of
		{ok, Term} ->
		    case catch list_to_pid(P) of
			To when is_pid(To) -> To ! Term;
			_ -> error
		    end;
		_Error -> error
	    end;
	_Error -> error
    end;
handle_proc_press(trace, Pid, _State) -> 
    case trace_state(Pid) of
	true ->
	    io:format("Removing trace on ~p~n", [Pid]),
	    sys:trace(Pid, false),
	    set_trace_state(Pid, false);
	_Other ->
	    io:format("Putting trace on ~p~n", [Pid]),
	    sys:trace(Pid, true, 1000),
	    set_trace_state(Pid, true)
    end;
handle_proc_press(kill, Pid, _State) -> 
    exit(Pid, kill).


trace_state(Pid) -> get({trace_state, Pid}).
set_trace_state(Pid, State) -> put({trace_state, Pid}, State).

set_default_mode() ->
    {Id, Mode} = get(default_mode),
    case mode() of
	Mode -> true;
	_Other -> set_mode(Id, Mode)
    end.
set_default_mode(Id, Mode) ->
    put(default_mode, {Id, Mode}),
    select(Id),					%Dirty workaround
    set_default_mode().

set_mode(Id, Mode) ->
    %%io:format("mode=~p~n", [Mode]),
    set_mode(Mode),
    deselect(),
    select(Id).

set_mode(Mode)	-> put(mode, Mode).
mode()		-> get(mode).

flush({delivery, S, A, N, R}) ->
    receive
	{delivery, S, A, N, R2} ->
	    flush({delivery, S, A, N, R2})
    after 0 ->
	    {delivery, S, A, N, R}
    end.


%%------------------------------------------------------------
%% Real stuff
%% 
update2(Vs, Root, Ls, SecLs, State) ->
    DG = State#astate.digraph,
    Ref = mk_ref(),
    Added = add_procs(Vs, DG, Ref), 
    AddedLs = add_links(Ls, DG, Ref, primary), 
    AddedLs2 = add_links(SecLs, DG, Ref, secondary),
    DelLs = del_links(appmon_dg:eget(all, DG), DG, Ref),
    Dels = del_procs(appmon_dg:get(all, DG), DG, Ref),

    LastX = appmon_place:place(DG, Root),
    Width = lists:max(LastX),
    Height = length(LastX),

    %% Delete things from screen
    del(Dels), del(DelLs),
    
    %% Add vertices to screen
    foreach(fun(V) ->  draw_node(DG, V) end, Added),
    
    %% Add edges to screen
    foreach(fun(E) ->  draw_edge(DG, E) end, AddedLs),
    foreach(fun(E) ->  draw_edge(DG, E) end, AddedLs2),
    
    %% Move vertices on screen
    foreach(fun(V) ->  move_vertex(DG, V) end, appmon_dg:get(all, DG)),

    tree_driven_resize(Width, Height),

    gs:config(win(), {map, true}),		%Make win visible
    ok.

%% Make an integer reference, could have used make_ref BIF but didn't
mk_ref() -> put(reference, get(reference)+1).
init_ref() -> put(reference, 0).


%% Add processes (vertices) to digraph, use the string repr of pid as
%% key in digraph.
add_procs([{Pid, Str}|Vs], DG, Ref) ->
    case appmon_dg:add(DG, Str, mk_vdata(Str, Pid, Ref), Ref) of
	known	-> add_procs(Vs, DG, Ref);
	updated -> add_procs(Vs, DG, Ref);
	_	-> 
	    case lists:member(32, Str) of
		true -> 
		    appmon_dg:set(x, DG, Str, foreign), % UNHOLY!
		    add_procs(Vs, DG, Ref);	% Don't add foreign
		_ -> [Str | add_procs(Vs, DG, Ref)]
	    end
    end;
add_procs([], _DG, _Ref) -> [].


%% Add links to digraph. NOTE that foreign links get a special weight
%% and that no link is added if it goes to a process not in the set of
%% vertices.
%%
%% OTP-1970: Check that linked-to processes really exist.
%%
add_links([{V1, V2}|Ls], DG, Ref, Weight) ->
    L = case lists:member(32, V2) of
	    true -> {V1, V2, foreign};
	    _ ->    {V1, V2, Weight}
	end,
    case appmon_dg:get(data, DG, V2) of
	false -> add_links(Ls, DG, Ref, Weight);
	VD ->
	    if  VD#vdata.ref == Ref ->		% OTP-1970
		    case appmon_dg:eadd(DG, L, mk_edata(L, Ref), Ref) of
			known -> add_links(Ls, DG, Ref, Weight);
			updated -> add_links(Ls, DG, Ref, Weight);
			_Other -> [L | add_links(Ls, DG, Ref, Weight)]
		    end;
		true ->
		    add_links(Ls, DG, Ref, Weight)
	    end
    end;
add_links([], _DG, _Ref, _Weight) -> [].

%% Make an edge data structure
mk_edata(_L, Ref) ->
    #edata{ref=Ref}.

%% Make a vertex data structure, note that pid can be either a pid or
%% a port, we're not picky here.
mk_vdata(P, Pid, Ref) ->
    #vdata{ref=Ref, type=Pid, txt=P, width=width(P)}.
width(Txt) -> ?CHARWIDTH*length(Txt)+10.	% Should use GS stuff instead


%% Delete those processes that have the wrong reference from the
%% digraph. Returns a list of deleted procs and their data (to be able
%% to erase things on screen).
del_procs([V|Vs], DG, Ref) ->
    VD = appmon_dg:get(data, DG, V),
    if  VD#vdata.ref /= Ref -> appmon_dg:del(DG, V),
			       [{V, VD} | del_procs(Vs, DG, Ref)];
	true -> del_procs(Vs, DG, Ref)
    end;
del_procs([], _DG, _Ref) -> [].


%% Deletes links that have the wrong reference from the digraph, note
%% that the weight of the edge is not considered here. Returns a list
%% of deleted links and their data (to be able to erase things on
%% screen).
del_links([L | Ls], DG, Ref) ->
    ED = appmon_dg:eget(data, DG, L),
    if  ED#edata.ref /= Ref -> appmon_dg:edel(DG, L),
			       [{L, ED} | del_links(Ls, DG, Ref)];
	true -> del_links(Ls, DG, Ref)
    end;
del_links([], _DG, _Ref) -> [].

%% Del deletes the GS objects of the list of should-be-deleted
%% items. Returns nothing particular.
del(L) ->
    lists:foreach(fun({{V1, V2, Weight}, D}) when is_record(D, edata) ->
			  if Weight== foreign ->
				  dealloc_foreign({V1, V2, Weight});
			     true -> ok end,
			  destroy(D#edata.line);
		     ({_I, D}) when is_record(D, vdata) ->
			  destroy(D#vdata.sym_obj),
			  destroy(D#vdata.txt_obj)
		  end, L).


move_vertex(DG, V) ->
    VData = appmon_dg:get(data, DG, V),
%%    io:format("Vertex ~p data: x:~p, oldx:~p, y:~p, oldy:~p offs:~p~n",
%%	      [V, VData#vdata.x, VData#vdata.origx, 
%%	       VData#vdata.y, VData#vdata.origy, offsetx()]),
    if  VData#vdata.x == foreign -> ok;
	true ->
	    X = VData#vdata.x,
	    Y = scaley(VData#vdata.y),
	    OldX = VData#vdata.origx,
	    OldY = scaley(VData#vdata.origy),
	    if  X==OldX, Y==OldY -> true;
		true ->
		    %%io:format("Moving vertex: ~p~n", [V]),
		    
		    W = VData#vdata.width,
		    {BoxC, TxtC} = calc_box_coords(X, Y, W),
		    
		    %% move the symbol and text
		    gs:config(VData#vdata.sym_obj, [{coords, BoxC}]),
		    gs:config(VData#vdata.txt_obj, [{coords, TxtC}]),
		    foreach(fun(E) -> move_edge(DG, E) end, 
			    appmon_dg:get(edges, DG, V))
	    end
    end.


move_edge(DG, E) ->
    {{V1, V2, Weight}, V1, V2, ED} = appmon_dg:eget(edge, DG, E),
    VD1 = appmon_dg:get(data, DG, V1),
    VD2 = appmon_dg:get(data, DG, V2),
    Line = ED#edata.line,
    move_line(DG, VD1, VD2, Line, Weight).
move_line(DG, VD1, VD2, Line, Weight) when is_list(Line) ->
    move_line(DG, VD1, VD2, hd(Line), Weight);
move_line(_DG, VD1, VD2, Line, Weight) ->
    Coords = calc_coords(VD1, VD2, Weight),
    gs:config(Line, [{coords, Coords} | line_opts(Weight)]).

%% Draw the vertex on the canvas
draw_node(DG, V) ->
    %%io:format("Drawing~n",[]),
    Data = appmon_dg:get(data, DG, V),

    X      = Data#vdata.x,
    Y      = scaley(Data#vdata.y),

    {Sym, Txt} = draw(rectangle, sup_col(), Data#vdata.txt, X, Y,
		      Data#vdata.width),
    
    appmon_dg:av(DG, V, Data#vdata{sym_obj=Sym, txt_obj=Txt}),
    true.

%% Draws a symbol (rectangle for instance) on the canvas.
draw(Symbol, Col, Txt, X, Y, W) ->
    {BoxC, TxtC} = calc_box_coords(X, Y, W),
    Box = gs:create(Symbol, canvas(), [{coords, BoxC}, {fill, Col}]),
    
    TxtObj = gs:create(text, canvas(), [{coords, TxtC},
					{anchor, c},
					%%{buttonpress, true},
					{text, Txt}]),
    {Box, TxtObj}.

%% Returns {BoxCoords, TextCoords}
calc_box_coords(X, Y, W) ->
    {[{X, Y-radius()}, {X+W, Y+radius()}],   [{X+trunc(W/2), Y}]}.


%% Draw a line on the canvas
draw_edge(DG, E) ->
    {V1, V2, Weight} = E,
    Line = draw_line(DG, V1, V2, Weight),
%%    io:format("Line: ~p~n", [Line]),
    appmon_dg:eset(line, DG, E, Line).


%% From is parent, To is child. If To is not a record then we are
%% dealing with a link to a process on another node. Find a suitable
%% place at the left margin and write the process name there and draw
%% a line to it.
%%
draw_line(DG, From, To, foreign) ->
    VD1 = appmon_dg:get(data, DG, From),
    Y = find_foreign_place(VD1#vdata.y+0.5, foreign_places()),
    add_foreign_place({From, To, foreign}, Y),
%%    io:format("New Y: ~p~n", [Y]),
    appmon_dg:set(x, DG, To, 0),
    appmon_dg:set(y, DG, To, Y),
    VD2 = appmon_dg:get(data, DG, To),
    Coords = calc_coords(VD1, VD2, foreign),
%%    io:format("Node coords: ~p~n", [Coords]),
    L = gs:create(line, canvas(), [{coords, Coords} | line_opts(foreign)]),
    T = gs:create(text, canvas(), [{coords, [{0, 5+scaley(Y)}]},
				   {anchor, nw}, {fg, f_line_col()},
%%				   {font, {screen, 10}}, 
				   {text, To}]),
    [L, T];

draw_line(DG, From, To, Weight) ->
    VD1 = appmon_dg:get(data, DG, From),
    VD2 = appmon_dg:get(data, DG, To),
    Coords = calc_coords(VD1, VD2, Weight),
    gs:create(line, canvas(), [{coords, Coords} | line_opts(Weight)]).

%%----------------------------------------------------------------------
%%
%% Line coordinate calculation
%%
%% Calculate coordinates for edges (links, lines). Primaries have a
%% nice knee and secondaries are oriented differently. If weight is
%% foreign then we will calculate a nice line to the left margin.
%%
calc_coords(From, To, foreign) ->
    Y = scaley(To#vdata.y),
    X1 = From#vdata.x+trunc(From#vdata.width/2),
    Y1 = scaley(From#vdata.y)+radius(),
    [{0, Y}, {X1-40, Y}, {X1, Y1}];
calc_coords(From, To, primary) ->
    X1 = From#vdata.x+trunc(From#vdata.width/2),
    Y1 = scaley(From#vdata.y)+radius(),
    
    X2 = To#vdata.x+trunc(To#vdata.width/2),
    Y2 = scaley(To#vdata.y)-radius(),
    
    Y3 = trunc((Y1+Y2)/2),
    [{X1, Y1}, {X1, Y3}, {X2, Y3}, {X2, Y2}];

calc_coords(V1, V2, _Weight) ->
    Y1  = scaley(V1#vdata.y),
    X1  = V1#vdata.x,
    X1w = X1+V1#vdata.width,
    Y2  = scaley(V2#vdata.y),
    X2  = V2#vdata.x,
    X2w = X2+V2#vdata.width,

    if  Y1 == Y2 -> calc_u(X1, X1w, Y1, X2, X2w);
	X1w < X2 -> calc_s(X1w, Y1, X2, Y2);
	X2w < X1 -> calc_s(X1, Y1, X2w, Y2);
	true ->
	    D1 = abs(X1-X2), D2 = abs(X1w-X2w),
	    if  D1 > D2 -> calc_rbrack(X1w, Y1, X2w, Y2);
		true -> calc_lbrack(X1, Y1, X2, Y2)
	    end
    end.

%% Calculates line coordinates that will go from bottom of one node to
%% bottom of another on the same level. The line will form a nice "U".
calc_u(X1, X1w, Y, X2, X2w) ->
    X3 = trunc((X1+X1w)/2),
    X4 = trunc((X2+X2w)/2),
    Y2 = Y+radius(),
    Y3 = Y2+20,
    [{X3, Y2}, {X3, Y3}, {X4, Y3}, {X4, Y2}].

%% Calculates line coordinates that will go from right (or left) side
%% to right (or left) side, thus forming a "[" or a "]" (bracket).
calc_rbrack(X1, Y1, X2, Y2) ->
    X3 = 20 + if  X1 > X2 -> X1;
		  true -> X2
	      end,
    [{X1, Y1}, {X3, Y1}, {X3, Y2}, {X2, Y2}].
calc_lbrack(X1, Y1, X2, Y2) ->
    X3 = -20 + if  X1 < X2 -> X1;
		   true -> X2
	      end,
    [{X1, Y1}, {X3, Y1}, {X3, Y2}, {X2, Y2}].

%% Calculates line coordinates that will form a nice "S"
calc_s(X1, Y1, X2, Y2) ->
    X3 = trunc((X1+X2)/2),
    [{X1, Y1}, {X3, Y1}, {X3, Y2}, {X2, Y2}].


%% Options for lines (edges, links)
line_opts(foreign)	-> [{width, 2}, {smooth, true},  {fg, f_line_col()}];
line_opts(primary)	-> [{width, 2}, {smooth, false}, {fg, line_col()}];
line_opts(_)		-> [{width, 2}, {smooth, true}, {fg, sec_line_col()}].



%%----------------------------------------------------------------------
%%
%% Handling of links to foreign processes
%%
%%----------------------------------------------------------------------
dealloc_foreign(L) ->
%%    io:format("deallocing foreign: ~p~n", [L]),
    put(foreign_places, lists:keydelete(L, 1, foreign_places())).
add_foreign_place(V, Y) ->
%%    io:format("Adding foreign: ~p~n", [V]),
    put(foreign_places, [{V, Y} | foreign_places()]).
foreign_places() ->
    get(foreign_places).
init_foreign_places() ->
    put(foreign_places, []).

%% Find a good place for the foreign node
find_foreign_place(StartY, L) ->
    case lists:keysearch(StartY, 2, L) of
	{value, _} -> find_foreign_place(StartY + 1, L);
	_ -> StartY
    end.
    

%%------------------------------------------------------------
%%
%% Graphical stuff
%%

setup_base_win(NodeName, AppName) ->
    set_winroot(gs:start([{kernel,true}])),

    W = ?MINWIDTH, H = ?MINHEIGHT,
    
    Name = "APPMON: " ++ atom_to_list(AppName) ++ " on " ++
	atom_to_list(NodeName),

    set_win(gs:create(window, winroot(), [{title, Name}, %%{bg, red},
					  {x, 250}, {y, 100},
					  {width, W}, {bg, win_col()},
					  {height, H+?BUTTAREA_H}])),
    %% standard buttons
    mk_std_butts(win(), W),
    set_canvas(gs:create(canvas, win(),[{x,0}, {y,?BUTTAREA_H}, 
					{width, W}, {height, H}, 
					{bg, bg()}, 
					{buttonpress, true}])),
    
    set_old_win_size(width, gs:read(win(), width)),
    set_old_win_size(height, gs:read(win(), height)),

%%    gs:config(win(), {map, true}),		%Make win visible
    ok.


nice_line_coords(W, H) ->
    [{0,H-10}, {W,H-10}].

%%------------------------------
%% Button stuff

mk_butt_area(Win, W) ->
    H = ?BUTTAREA_H,
    F = gs:create(frame, Win,[{x,0}, {y,0}, %%{bg, frame_col()},
			      {width,W}, {height,H}]),
    C = gs:create(canvas,F,[{x,0}, {y,0}, {width, W}, {height, H-9}, 
			    {bg, bg()}]),
    L = gs:create(line,C,[{coords,nice_line_coords(W, H)}]),

    MB = gs:create(menubar, Win, []),

    FMB = gs:create(menubutton, MB, [{label, {text, "File"}}]),
    FM = gs:create(menu, FMB, []),
    gs:create(menuitem, FM, [{label, {text, ?CLOSETXT}}]),
    
    OMB = gs:create(menubutton, MB, [{label, {text, "Options"}}]),
    OM = gs:create(menu, OMB, []),
    gs:create(menuitem, OM, [{label, {text, ?REFRESHTXT}}]),
    Group = now(),
    gs:create(menuitem, OM, [{itemtype, separator}]),
    gs:create(menuitem, OM, [{label, {text, ?SUPVIEWTXT}}, {itemtype, radio},
			     {group, Group}]),
    gs:create(menuitem, OM, [{label, {text, ?PROCVIEWTXT}}, {select, true}, 
			     {group, Group}, {itemtype, radio}]),

    HMB = gs:create(menubutton, MB, [{label, {text, "Help"}}, {side, right}]),
    HM = gs:create(menu, HMB, []),
    gs:create(menuitem, HM, [{label, {text, ?HELPTXT}}]),
  
    {F, C, L}.

mk_std_butts(Win, W) ->
    {F, C, L} = mk_butt_area(Win, W),
    set_bframe(F), set_bcanvas(C), set_bline(L),

    IButt = mk_mode_butt({text, "Info"}, {mode, info}, 10),
    mk_mode_butt({text, "Send"}, {mode, send}, 90),
    mk_mode_butt({text, "Trace"}, {mode, trace}, 170),
    mk_mode_butt({text, "Kill"}, {mode, kill}, 250),

    set_default_mode(IButt, info),
    
    true.

select(Id) ->
    gs:config(Id, {bg, sel_col()}),
    set_selected(Id).

deselect() ->
    gs:config(selected(), {bg, de_sel_col()}).

mk_mode_butt(Label, Data, X) ->
    gs:create(button, bframe(), [{label, Label}, {x, X}, {y, 35},
				 {data, Data}, {width, 70}, {height, 25}]).

%%------------------------------------------------------------
%% Graphical utilities

mk_frame(P, X, Y, W, H, BG) ->
    gs:create(frame, P, [{x, X}, {y, Y}, {width, W}, {height, H}, {bg, BG}]).
    
mk_butt(P, X, Y, W, H, Txt) ->
    gs:create(button, P, [{x, X}, {y, Y}, {height, H}, {width, W}, 
			  {label, {text, Txt}}]).

mk_butt(P, X, Y, Txt) ->
    mk_butt(P, X, Y, 70, 20, Txt).

mk_label(P, X, Y, W, H, Txt, BG) ->
    gs:create(label, P, [{x, X}, {y, Y}, {height, H}, {width, W}, 
			 {label, {text, Txt}}, {bg, BG}]).

mk_entry(P, X, Y, W, H, Txt, BG) ->
    gs:create(entry, P, [{x, X}, {y, Y}, {height, H}, {width, W}, {text, Txt},
			 {bg, BG}, {keypress, true}]).


two_entries(Root, W, H, Name, LTxt1, LTxt2, StartTxt1, StartTxt2, BG) ->
    Win = gs:create(window, Root, [{title, Name}, %%{bg, red},
				   %%{x, X}, {y, Y},
				   {width, W}, {bg, BG},
				   {height, H}]),
    F = mk_frame(Win, 0, 0, W, H, BG),

    mk_label(F, 10, 10, 30, 20, LTxt1, BG),
    mk_label(F, 10, 40, 30, 20, LTxt2, BG),

    E1 = mk_entry(F, 40, 10, 120, 20, StartTxt1, BG),
    E2 = mk_entry(F, 40, 40, 120, 20, StartTxt2, BG),

    Ok = mk_butt(F, 170, 10, "Ok"),
    Cn = mk_butt(F, 170, 40, "Cancel"),
    gs:config(Win, {map, true}),

    Ret = case catch two_entries_loop(E1, E2, Ok, Cn) of
	      {P2, Msg} -> {P2, Msg};
	      _Other ->
		  false
	  end,
    gs:destroy(Win),
    Ret.


two_entries_loop(E1, E2, Ok, Cn) ->
    receive
	{gs, Ok, click, _, _} ->
	    {gs:read(E1, text),
	     gs:read(E2, text)};
	{gs, E1, keypress, _, ['Return'|_]} ->
	    {gs:read(E1, text),
	     gs:read(E2, text)};
	{gs, E2, keypress, _, ['Return'|_]} ->
	    {gs:read(E1, text),
	     gs:read(E2, text)};
	{gs, _, keypress, _, _} ->
	    two_entries_loop(E1, E2, Ok, Cn);
	{gs, Cn, click, _, _} ->
	    true
    end.

%%--------------------------------------------------------------------
%%
%% Resizing routines.
%%
%%	Resizing deals with a number of different interdependent
%%	sizes. Top size is the window size. From window size all other
%%	sizes are calculated, we call this the "leader" size. The
%%	canvas is usually the same size as the window, except for the
%%	row of buttons at the top of the windoww. The canvas is also
%%	displaced when the tree is smaller than the minimum window
%%	size.
%%
%%
%%	Window size - the size of the outer window. Note that
%%	provisions must be made for the button area at the top of the
%%	window, this is called WinAdj. this is the only item taht
%%	changes when the user manually resizes the window.
%%
%%	Canvas size - The size of the canvas, should be equal to
%%	window size less the button area. Must be adjusted when the
%%	window has been manually resized. The canvas also has a
%%	scrollregion which must be maintained. Note that we could have
%%	used the canvas size as "leading" size, but this did not work
%%	since the canvas doesn't fill the complete window when the
%%	tree is smaller than the window.
%%
%%	Tree size - The size of the tree. This may change whenever a
%%	new tree is delivered from the info routine.
%%
%%	Dim - All these size adjustments are done in some dimension
%%	(width or height).
%%
%%	Max, Min - The outmost window may not become larger than Max
%%	size or smaller than Min size when resized by the tree
%%	size. The user resizing is not restricted to these sizes.
%%
%% Scrollbars:
%%
%%	Scrollbars are used whenever necessary, whenever the tree size
%%	is bigger than canvas size (in any dimension).
%%
%% Invariants:
%%
%%	The three sizes are not varied at the same time. When the
%%	window is resized because of a new tree, then window and
%%	canvas must be updated. When the user has resized, then only
%%	the canvas must be changed (to fit in the window)
%%
%% Tree driven resize
%%
%%	This occurs when the tree has been updated. The window may
%%	grow and shrink to fit the tree, but may not be smaller than
%%	Min and not bigger than Max (scrollbars will be used instead)
%%

tree_driven_resize(TWidth, THeight) ->
    gs:config(win(), {configure, false}),
    Width = TWidth+20,
    Height = scaley(THeight+1),
    put({width, tree}, Width),
    put({height, tree}, Height),
    adjust_win(width, Width),
    adjust_win(height, Height),
    fit_tree_to_win(width, Width),
    fit_tree_to_win(height, Height),
    check_scroll_region(Width, Height, gs:read(canvas(), scrollregion)),
    gs:config(win(), {configure, true}),
    ok.


%% Will adjust the window size to the tree size (given the max and min
%% restrictions.
adjust_win(Dim, TreeSize) ->
    case get({Dim, user_resize}) of
	true -> ok;
	_ ->
	    WinSize = gs:read(win(), Dim),%%get_dim(Dim, win()),
	    case get_wanted_winsize(Dim, TreeSize) + winadj(Dim) of
		WinSize -> ok;
		NewSize -> 
		    %%set(Dim, win(), NewSize+winadj(Dim))
		    set_old_win_size(Dim, NewSize),
		    gs:config(win(), {Dim, NewSize})
	    end
    end.

get_wanted_winsize(Dim, Size) ->
    Max = maxsize(Dim), Min = minsize(Dim),
    if  Size > Max	-> Max;
	Size < Min	-> Min;
	true		-> Size
    end.

set_old_win_size(Dim, Size) -> put({Dim, winsize}, Size).
old_win_size(Dim) -> get({Dim, winsize}).


%%--------------------------------------------------------------------
%%
%% user_driven_resize
%%
%%	This is when the user drags the window to some size. This is
%%	basically the same as a tree resize, only this time the window
%%	itself must not be fiddled with. When the window has been
%%	resized this way then normal tree driven resize is not allow
%%	to alter the size in that dimension. User overrides.
%%
user_driven_resize(W, H) ->
    gs:config(win(), {configure, false}),
    check_user_resize(width, W),
    check_user_resize(height, H),
    check_scroll_region(get({width, tree}), get({height, tree}),
			gs:read(canvas(), scrollregion)),
    gs:config(win(), {configure, true}).

check_user_resize(Dim, Size) ->
    case old_win_size(Dim) of
	Size -> false;
	_ ->
	    put({Dim, user_resize}, true),
	    set_old_win_size(Dim, Size),
	    fit_tree_to_win(Dim, get({Dim, tree}))
    end.



%%--------------------------------------------------------------------
%%
%% General resizing routines
%%
%% fit_tree_to_win - Will fit the canvas into a pre-sized window in
%% one dimension.
%%
fit_tree_to_win(Dim, TreeSize) ->
    Size = gs:read(win(), Dim) - winadj(Dim),
    set_canvas_offset(Dim, Size, TreeSize),
    set_button_width(Dim, Size),
    if  TreeSize > Size ->
	    gs:config(canvas(), {trans_dim2vh(Dim), trans_dim2enable(Dim)});
	TreeSize < Size ->
	    gs:config(canvas(), {trans_dim2vh(Dim), false});
	true ->
	    gs:config(canvas(), {trans_dim2vh(Dim), false})
    end.


%%------------------------------
%% Set the canvas width and displacement in x.
set_canvas_offset(height, Size, _) -> 
    gs:config(canvas(), {height, Size});
set_canvas_offset(width, Size, Size) -> 
    gs:config(canvas(), [{x, 0}, {width, Size}]);
set_canvas_offset(width, Size, TreeSize) when Size<TreeSize -> 
    gs:config(canvas(), [{x, 0}, {width, Size}]);
set_canvas_offset(width, Size, TreeSize) when Size>TreeSize-> 
    Val = trunc((Size-TreeSize)/2),
    gs:config(canvas(), [{x, Val}, {width, Size-Val}]).

%%------------------------------
%% Set the button area width
set_button_width(height,_) -> ok;
set_button_width(width, W) ->
    gs:config(bcanvas(), [{width, W}]),
    gs:config(bframe(), [{width, W}]),
    gs:config(bline(), [{coords, nice_line_coords(W, ?BUTTAREA_H)}]).
    

%%------------------------------
%% Update the scrollregion size if needed.
check_scroll_region(W, H, {_, _, W, H}) -> ok;
check_scroll_region(W, H, {_, _, _, _}) -> 
    gs:config(canvas(), {scrollregion, {0, 0, W, H}}).


%% Window sizing primitives
winadj(width)	-> 0;
winadj(height)	-> ?BUTTAREA_H.
maxsize(width)	-> ?MAXWIDTH;
maxsize(height)	-> ?MAXHEIGHT.
minsize(width)	-> ?MINWIDTH;
minsize(height)	-> ?MINHEIGHT.



trans_dim2vh(width) -> hscroll;
trans_dim2vh(height) -> vscroll.
trans_dim2enable(width) -> bottom;
trans_dim2enable(height) -> right.





%%------------------------------------------------------------
%% Global Window info

winroot() ->		get(winroot).
win() ->		get(win).
canvas() ->		get(canvas).
bframe() ->		get(bframe).
bcanvas() ->		get(bcanvas).
bline() ->              get(bline).
set_winroot(X) ->	put(winroot, X).
set_win(X) ->		put(win, X).
set_canvas(X) ->	put(canvas, X).
set_bframe(X) ->	put(bframe, X).
set_bcanvas(X) ->	put(bcanvas, X).
set_bline(X) ->		put(bline, X).

sup_col()	-> ?darkkhaki.
%%work_col()	-> ?orange.
bg()		-> ?palegoldenrod.
line_col()	-> ?peachpuff4. %% saddlebrown.darkgoldenrod
f_line_col()	-> ?royalblue. %% saddlebrown.darkgoldenrod
sec_line_col()	-> ?red.
win_col()	-> bg(). %%darkolivegreen.

sel_col()	-> ?darkgrey.
de_sel_col()	-> ?lightgrey.
set_selected(Id)-> put(selected, Id).
selected()	-> get(selected).

scaley(Y)	-> 55*Y.
radius()	-> 10.

destroy(undefined)	-> true;
destroy(L) when is_list(L) -> lists:foreach(fun(X) -> destroy(X) end , L);	
destroy(Win)		-> gs:destroy(Win).

