1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2017. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19-module(observer_app_wx).
20
21-export([start_link/3]).
22
23%% wx_object callbacks
24-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
25	 handle_event/2, handle_sync_event/3, handle_cast/2]).
26
27-behaviour(wx_object).
28-include_lib("wx/include/wx.hrl").
29-include("observer_defs.hrl").
30
31%% Import drawing wrappers
32-import(observer_perf_wx, [haveGC/0, make_gc/2, destroy_gc/1,
33			   setPen/2, setFont/3, setBrush/2,
34			   strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
35			   drawText/4, getTextExtent/2]).
36
37-record(state,
38	{
39	  parent,
40	  panel,
41	  apps_w,
42	  app_w,
43	  paint,
44	  current,
45	  app,
46	  sel,
47	  appmon,
48	  usegc = false
49	}).
50
51-record(paint, {font, fg, pen, brush, sel, links}).
52
53-record(app, {ptree, n2p, links, dim}).
54-record(box, {x,y, w,h, s1}).
55-record(str, {x,y,text,pid}).
56
57-define(BX_E, 10). %% Empty width between text and box
58-define(BX_HE, (?BX_E div 2)).
59-define(BY_E, 10). %% Empty height between text and box
60-define(BY_HE, (?BY_E div 2)).
61
62-define(BB_X, 16). %% Empty width between boxes
63-define(BB_Y, 12). %% Empty height between boxes
64
65-define(DRAWAREA, 5).
66-define(ID_PROC_INFO, 101).
67-define(ID_PROC_MSG,  102).
68-define(ID_PROC_KILL, 103).
69-define(ID_TRACE_PID, 104).
70-define(ID_TRACE_NAME, 105).
71-define(ID_TRACE_TREE_PIDS, 106).
72-define(ID_TRACE_TREE_NAMES, 107).
73
74-define(wxGC, wxGraphicsContext).
75
76start_link(Notebook, Parent, Config) ->
77    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []).
78
79init([Notebook, Parent, _Config]) ->
80    Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)},
81				   {winid, 1}
82				  ]),
83    Main = wxBoxSizer:new(?wxHORIZONTAL),
84    Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)},
85					    {style, ?SASH_STYLE},
86					    {id, 2}
87					   ]),
88    Apps = wxListBox:new(Splitter, 3, []),
89    %% Need extra panel and sizer to get correct size updates
90    %% in draw area for some reason
91    P2 = wxPanel:new(Splitter, [{winid, 4}]),
92    Extra = wxBoxSizer:new(?wxVERTICAL),
93    DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA},
94					    {style,?wxFULL_REPAINT_ON_RESIZE}]),
95    BG = wxWindow:getBackgroundColour(Apps),
96    wxWindow:setBackgroundStyle(DrawingArea, ?wxBG_STYLE_PAINT),
97    wxWindow:setVirtualSize(DrawingArea, 800, 800),
98    wxSplitterWindow:setMinimumPaneSize(Splitter,50),
99    wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]),
100    wxWindow:setSizer(P2, Extra),
101    wxSplitterWindow:splitVertically(Splitter, Apps, P2, [{sashPosition, 150}]),
102    wxWindow:setSizer(Panel, Main),
103
104    wxSizer:add(Main, Splitter, [{flag, ?wxEXPAND bor ?wxALL},
105				 {proportion, 1}, {border, 5}]),
106    wxWindow:setSizer(Panel, Main),
107    wxListBox:connect(Apps, command_listbox_selected),
108    wxPanel:connect(DrawingArea, paint, [callback]),
109    wxPanel:connect(DrawingArea, size, [{skip, true}]),
110    wxPanel:connect(DrawingArea, left_up),
111    wxPanel:connect(DrawingArea, left_dclick),
112    wxPanel:connect(DrawingArea, right_down),
113    case os:type() of
114	{win32, _} -> %% Ignore erase on windows
115	    wxPanel:connect(DrawingArea, erase_background, [{callback, fun(_,_) -> ok end}]);
116	_ -> ok
117    end,
118
119    UseGC = haveGC(),
120    Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8,
121    Scale = observer_wx:get_scale(),
122    Font = case os:type() of
123	       {unix,_} when UseGC, Version28 ->
124		   wxFont:new(Scale * 12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_NORMAL);
125	       _ ->
126		   Font0 = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
127           wxFont:setPointSize(Font0, Scale * wxFont:getPointSize(Font0)),
128           Font0
129	   end,
130    SelCol   = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT),
131    {Fg,BGBrush,Pen} =
132        case observer_lib:is_darkmode(BG) of
133            false ->
134                {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT),
135                 wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)),
136                 wxPen:new({80,80,80}, [{width, Scale * 2}])};
137            true ->
138                {wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT),
139                 wxBrush:new(wxSystemSettings:getColour(?wxSYS_COLOUR_BTNSHADOW)),
140                 wxPen:new({0,0,0}, [{width, Scale * 2}])}
141        end,
142    SelBrush = wxBrush:new(SelCol),
143    LinkPen  = wxPen:new(SelCol, [{width, Scale * 2}]),
144    process_flag(trap_exit, true),
145    {Panel, #state{parent=Parent,
146		   panel =Panel,
147		   apps_w=Apps,
148		   app_w =DrawingArea,
149		   usegc = UseGC,
150		   paint=#paint{font = Font,
151                                fg   = Fg,
152				pen  = Pen,
153				brush= BGBrush,
154				sel  = SelBrush,
155				links= LinkPen
156			       }
157		  }}.
158
159setup_scrollbar(AppWin, App) ->
160    setup_scrollbar(wxWindow:getClientSize(AppWin), AppWin, App).
161
162setup_scrollbar({CW, CH}, AppWin, #app{dim={W0,H0}}) ->
163    W = max(W0,CW),
164    H = max(H0,CH),
165    PPC = 20,
166    if W0 =< CW, H0 =< CH ->
167	    wxScrolledWindow:setScrollbars(AppWin, W, H, 0, 0);
168       H0 =< CH ->
169	    wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 0);
170       W0 =< CW ->
171	    wxScrolledWindow:setScrollbars(AppWin, W, PPC, 0, H div PPC+1);
172       true ->
173	    wxScrolledWindow:setScrollbars(AppWin, PPC, PPC, W div PPC+1, H div PPC+1)
174    end;
175setup_scrollbar(_, _, undefined) -> ok.
176
177%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
178
179handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=AppStr}},
180	     State = #state{appmon=AppMon, current=Prev}) ->
181    case AppStr of
182	[] ->
183	    {noreply, State};
184	_ ->
185	    App = list_to_atom(AppStr),
186	    (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []),
187	    appmon_info:app(AppMon, App, true, []),
188	    {noreply, State#state{current=App}}
189    end;
190
191handle_event(#wx{id=Id, event=_Sz=#wxSize{size=Size}},
192	     State=#state{app=App, app_w=AppWin}) ->
193    Id =:= ?DRAWAREA andalso setup_scrollbar(Size,AppWin,App),
194    {noreply, State};
195
196handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}},
197	     S0=#state{app=App, app_w=AppWin}) ->
198    case App of
199	#app{ptree=Tree} ->
200	    {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0),
201	    Hit   = locate_node(X,Y, [Tree]),
202	    State = handle_mouse_click(Hit, Type, S0),
203	    {noreply, State};
204	_ ->
205	    {noreply, S0}
206    end;
207
208handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
209	     State = #state{panel=Panel,sel=undefined}) ->
210    observer_lib:display_info_dialog(Panel,"Select process first"),
211    {noreply, State};
212
213handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}},
214	     State = #state{sel={#box{s1=#str{pid=Pid}},_}}) ->
215    observer ! {open_link, Pid},
216    {noreply, State};
217
218handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}},
219	     State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) ->
220    case observer_lib:user_term(Panel, "Enter message", "") of
221	cancel ->         ok;
222	{ok, Term} ->     Pid ! Term;
223	{error, Error} -> observer_lib:display_info_dialog(Panel,Error)
224    end,
225    {noreply, State};
226
227handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}},
228	     State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) ->
229    case observer_lib:user_term(Panel, "Enter Exit Reason", "kill") of
230	cancel ->         ok;
231	{ok, Term} ->     exit(Pid, Term);
232	{error, Error} -> observer_lib:display_info_dialog(Panel,Error)
233    end,
234    {noreply, State};
235
236%%% Trace api
237handle_event(#wx{id=?ID_TRACE_PID, event=#wxCommand{type=command_menu_selected}},
238	     State = #state{sel={Box,_}}) ->
239    observer_trace_wx:add_processes([box_to_pid(Box)]),
240    {noreply, State};
241handle_event(#wx{id=?ID_TRACE_NAME, event=#wxCommand{type=command_menu_selected}},
242	     State = #state{sel={Box,_}}) ->
243    observer_trace_wx:add_processes([box_to_reg(Box)]),
244    {noreply, State};
245handle_event(#wx{id=?ID_TRACE_TREE_PIDS, event=#wxCommand{type=command_menu_selected}},
246	     State = #state{sel=Sel}) ->
247    Get = fun(Box) -> box_to_pid(Box) end,
248    observer_trace_wx:add_processes(tree_map(Sel, Get)),
249    {noreply, State};
250handle_event(#wx{id=?ID_TRACE_TREE_NAMES, event=#wxCommand{type=command_menu_selected}},
251	     State = #state{sel=Sel}) ->
252    Get = fun(Box) -> box_to_reg(Box) end,
253    observer_trace_wx:add_processes(tree_map(Sel, Get)),
254    {noreply, State};
255
256handle_event(Event, _State) ->
257    error({unhandled_event, Event}).
258
259%%%%%%%%%%
260handle_sync_event(#wx{event = #wxPaint{}},_,
261		  #state{app_w=DA, app=App, sel=Sel, paint=Paint, usegc=UseGC}) ->
262    GC = {GC0, DC} = make_gc(DA, UseGC),
263    case UseGC of
264	false ->
265	    wxScrolledWindow:doPrepareDC(DA,DC);
266	true ->
267	    %% Argh must handle scrolling when using ?wxGC
268	    {Sx,Sy} = wxScrolledWindow:calcScrolledPosition(DA, {0,0}),
269	    ?wxGC:translate(GC0, Sx,Sy)
270    end,
271    %% Nothing is drawn until wxPaintDC is destroyed.
272    draw(GC, App, Sel, Paint),
273    destroy_gc(GC),
274    ok.
275%%%%%%%%%%
276handle_call(get_config, _, State) ->
277    {reply, #{}, State};
278handle_call(Event, From, _State) ->
279    error({unhandled_call, Event, From}).
280
281handle_cast(Event, _State) ->
282    error({unhandled_cast, Event}).
283%%%%%%%%%%
284handle_info({active, Node}, State = #state{parent=Parent, current=Curr, appmon=Appmon}) ->
285    create_menus(Parent, []),
286    Pid = try
287	      Node = node(Appmon),
288	      Appmon
289	  catch _:_ ->
290		  {ok, P} = appmon_info:start_link(Node, self(), []),
291		  P
292	  end,
293    appmon_info:app_ctrl(Pid, Node, true, []),
294    (Curr =/= undefined) andalso appmon_info:app(Pid, Curr, true, []),
295    {noreply, State#state{appmon=Pid}};
296handle_info(not_active, State = #state{appmon=AppMon}) ->
297    appmon_info:app_ctrl(AppMon, node(AppMon), false, []),
298    lists:member(node(AppMon), nodes()) andalso exit(AppMon, normal),
299    observer_wx:set_status(""),
300    {noreply, State#state{appmon=undefined}};
301handle_info({delivery, Pid, app_ctrl, _, Apps0},
302	    State = #state{appmon=Pid, apps_w=LBox, current=Curr0}) ->
303    Apps = [atom_to_list(App) || {_, App, {_, _, _}} <- Apps0],
304    wxListBox:clear(LBox),
305    wxListBox:appendStrings(LBox, [App || App <- lists:sort(Apps)]),
306    case Apps of
307	[App|_] when Curr0 =:= undefined ->
308	    Curr = list_to_atom(App),
309	    appmon_info:app(Pid, Curr, true, []),
310	    {noreply, State#state{current=Curr}};
311	_ ->
312	    {noreply, State}
313    end;
314handle_info({delivery, _Pid, app, _Curr, {[], [], [], []}},
315	    State = #state{panel=Panel}) ->
316    wxWindow:refresh(Panel),
317    {noreply, State#state{app=undefined, sel=undefined}};
318
319handle_info({delivery, Pid, app, Curr, AppData},
320	    State = #state{panel=Panel, appmon=Pid, current=Curr, usegc=UseGC,
321			   app_w=AppWin, paint=#paint{fg=Fg, font=Font}}) ->
322    GC = if UseGC -> {?wxGC:create(AppWin), false};
323	    true ->  {false, wxWindowDC:new(AppWin)}
324	 end,
325    setFont(GC, Font, Fg),
326    App = build_tree(AppData, GC),
327    destroy_gc(GC),
328    setup_scrollbar(AppWin, App),
329    wxWindow:refresh(Panel),
330    wxWindow:layout(Panel),
331    {noreply, State#state{app=App, sel=undefined}};
332
333handle_info({'EXIT', _, noconnection}, State) ->
334    {noreply, State};
335handle_info({'EXIT', _, normal}, State) ->
336    {noreply, State};
337handle_info(_Event, State) ->
338    %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),
339    {noreply, State}.
340
341%%%%%%%%%%
342terminate(_Event, _State) ->
343    ok.
344code_change(_, _, State) ->
345    State.
346
347handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type,
348		   State=#state{app_w=AppWin,panel=Panel}) ->
349    case Type of
350	left_dclick -> observer ! {open_link, Pid};
351	right_down  -> popup_menu(Panel);
352	_ ->           ok
353    end,
354    observer_wx:set_status(io_lib:format("Pid: ~p", [Pid])),
355    wxWindow:refresh(AppWin),
356    State#state{sel=Node};
357handle_mouse_click(_, _, State = #state{sel=undefined}) ->
358    State;
359handle_mouse_click(_, right_down, State=#state{panel=Panel}) ->
360    popup_menu(Panel),
361    State;
362handle_mouse_click(_, _, State=#state{app_w=AppWin}) ->
363    observer_wx:set_status(""),
364    wxWindow:refresh(AppWin),
365    State#state{sel=undefined}.
366
367%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
368
369create_menus(Parent, _) ->
370    MenuEntries =
371	[{"File",
372	  [#create_menu{id=?ID_PROC_INFO, text="Process info"},
373	   #create_menu{id=?ID_PROC_MSG,  text="Send Msg"},
374	   #create_menu{id=?ID_PROC_KILL, text="Kill process"}
375	  ]},
376	 {"Trace",
377	  [#create_menu{id=?ID_TRACE_PID,  text="Trace process"},
378	   #create_menu{id=?ID_TRACE_NAME, text="Trace named process"},
379	   #create_menu{id=?ID_TRACE_TREE_PIDS,  text="Trace process tree"},
380	   #create_menu{id=?ID_TRACE_TREE_NAMES,  text="Trace named process tree"}
381	  ]}],
382    observer_wx:create_menus(Parent, MenuEntries).
383
384popup_menu(Panel) ->
385    Menu = wxMenu:new(),
386    wxMenu:append(Menu, ?ID_PROC_INFO,  "Process info"),
387    wxMenu:append(Menu, ?ID_TRACE_PID,  "Trace process"),
388    wxMenu:append(Menu, ?ID_TRACE_NAME, "Trace named process"),
389    wxMenu:append(Menu, ?ID_TRACE_TREE_PIDS, "Trace process tree"),
390    wxMenu:append(Menu, ?ID_TRACE_TREE_NAMES, "Trace named process tree"),
391    wxMenu:append(Menu, ?ID_PROC_MSG,   "Send Msg"),
392    wxMenu:append(Menu, ?ID_PROC_KILL,  "Kill process"),
393    wxWindow:popupMenu(Panel, Menu),
394    wxMenu:destroy(Menu).
395
396%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
397locate_node(X, _Y, [{Box=#box{x=BX}, _Chs}|_Rest])
398  when X < BX ->
399    {left, Box};
400locate_node(X,Y, [Node={Box=#box{x=BX,y=BY,w=BW,h=BH}, _Chs}|Rest])
401  when X =< (BX+BW)->
402    if
403	Y < BY -> {above, Box}; %% Above
404	Y =< (BY+BH) -> Node;
405	true -> locate_node(X,Y,Rest)
406    end;
407locate_node(X,Y, [{_, Chs}|Rest]) ->
408    case locate_node(X,Y,Chs) of
409	Node = {#box{},_} -> Node;
410	_Miss ->
411	    locate_node(X,Y,Rest)
412    end;
413locate_node(_, _, []) -> false.
414
415locate_box(From, [{Box=#box{s1=#str{pid=From}},_}|_]) -> Box;
416locate_box(From, [{_,Chs}|Rest]) ->
417    case locate_box(From, Chs) of
418	Box = #box{} -> Box;
419	_ -> locate_box(From, Rest)
420    end;
421locate_box(From, []) -> {false, From}.
422
423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424
425build_tree({Root, P2Name, Links, XLinks0}, FontW) ->
426    Fam = sofs:relation_to_family(sofs:relation(Links)),
427    Name2P = gb_trees:from_orddict(lists:sort([{Name,Pid} || {Pid,Name} <- P2Name])),
428    Lookup = gb_trees:from_orddict(sofs:to_external(Fam)),
429    {_, Tree0} = build_tree2(Root, Lookup, Name2P, FontW),
430    {Tree, Dim} = calc_tree_size(Tree0),
431    Fetch = fun({From, To}, Acc) ->
432		    try {value, ToPid} = gb_trees:lookup(To, Name2P),
433			 FromPid = gb_trees:get(From, Name2P),
434			 [{locate_box(FromPid, [Tree]),locate_box(ToPid, [Tree])}|Acc]
435		    catch _:_ ->
436			    Acc
437		    end
438	    end,
439    XLinks = lists:foldl(Fetch, [], XLinks0),
440    #app{ptree=Tree, dim=Dim, links=XLinks}.
441
442build_tree2(Root, Tree0, N2P, FontW) ->
443    case gb_trees:lookup(Root, Tree0) of
444	none -> {Tree0, {box(Root, N2P, FontW), []}};
445	{value, Children} ->
446	    Tree1 = gb_trees:delete(Root, Tree0),
447	    {Tree, CHs} = lists:foldr(fun("port " ++_, Acc) ->
448					      Acc; %% Skip ports
449					 (Child,{T0, Acc}) ->
450					      {T, C} = build_tree2(Child, T0, N2P, FontW),
451					      {T, [C|Acc]}
452				      end, {Tree1, []}, Children),
453	    {Tree, {box(Root, N2P, FontW), CHs}}
454    end.
455
456calc_tree_size(Tree) ->
457    Cols = calc_col_start(Tree, [0]),
458    {Boxes,{W,Hs}} = calc_tree_size(Tree, Cols, ?BB_X, [?BB_Y]),
459    {Boxes, {W,lists:max(Hs)}}.
460
461calc_col_start({#box{w=W}, Chs}, [Max|Acc0]) ->
462    Acc = if Acc0 == [] -> [0]; true -> Acc0 end,
463    Depth = lists:foldl(fun(Child, MDepth) -> calc_col_start(Child, MDepth) end,
464			Acc, Chs),
465    [max(W,Max)|Depth].
466
467calc_tree_size({Box=#box{w=W,h=H}, []}, _, X, [Y|Ys]) ->
468    {{Box#box{x=X,y=Y}, []}, {X+W+?BB_X,[Y+H+?BB_Y|Ys]}};
469calc_tree_size({Box, Children}, [Col|Cols], X, [H0|Hs0]) ->
470    Hs1 = calc_row_start(Children, H0, Hs0),
471    StartX = X+Col+?BB_X,
472    {Boxes, {W,Hs}} = calc_tree_sizes(Children, Cols, StartX, StartX, Hs1, []),
473    Y = middle(Boxes, H0),
474    H = Y+Box#box.h+?BB_Y,
475    {{Box#box{x=X,y=Y}, Boxes}, {W,[H|Hs]}}.
476
477calc_tree_sizes([Child|Chs], Cols, X0, W0, Hs0, Acc) ->
478    {Tree, {W,Hs}} = calc_tree_size(Child, Cols, X0, Hs0),
479    calc_tree_sizes(Chs, Cols, X0, max(W,W0), Hs, [Tree|Acc]);
480calc_tree_sizes([], _, _, W,Hs, Acc) ->
481    {lists:reverse(Acc), {W,Hs}}.
482
483calc_row_start(Chs = [{#box{h=H},_}|_], Start, Hs0) ->
484    NChs = length(Chs),
485    Wanted = (H*NChs + ?BB_Y*(NChs-1)) div 2 - H div 2,
486    case Hs0 of
487	[] -> [max(?BB_Y, Start - Wanted)];
488	[Next|Hs] ->
489	    [max(Next, Start - Wanted)|Hs]
490    end.
491
492middle([], Y) -> Y;
493middle([{#box{y=Y}, _}], _) -> Y;
494middle([{#box{y=Y0},_}|List], _) ->
495    {#box{y=Y1},_} = lists:last(List),
496    (Y0+Y1) div 2.
497
498box(Str0, N2P, FontW) ->
499    Pid = gb_trees:get(Str0, N2P),
500    Str = if hd(Str0) =:= $< -> lists:append(io_lib:format("~w", [Pid]));
501	     true -> Str0
502	  end,
503    {TW,TH} = getTextExtent(FontW, Str),
504    Data = #str{text=Str, x=?BX_HE, y=?BY_HE, pid=Pid},
505    %% Add pid
506    #box{w=round(TW)+?BX_E, h=round(TH)+?BY_E, s1=Data}.
507
508box_to_pid(#box{s1=#str{pid=Pid}}) -> Pid.
509box_to_reg(#box{s1=#str{text=[$<|_], pid=Pid}}) -> Pid;
510box_to_reg(#box{s1=#str{text=Name}}) -> list_to_atom(Name).
511
512tree_map({Box, Chs}, Fun) ->
513    tree_map(Chs, Fun, [Fun(Box)]).
514tree_map([{Box, Chs}|Rest], Fun, Acc0) ->
515    Acc = tree_map(Chs, Fun, [Fun(Box)|Acc0]),
516    tree_map(Rest, Fun, Acc);
517tree_map([], _ , Acc) -> Acc.
518
519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
520draw(_DC, undefined, _, _) ->
521    ok;
522draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel,
523     #paint{font=Font, fg=Fg, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) ->
524    setPen(DC, LPen),
525    [draw_xlink(Link, DC) || Link <- Links],
526    setPen(DC, Pen),
527    %% ?wxGC:drawRectangle(DC, 2,2, _W-2,_H-2), %% DEBUG
528    setBrush(DC, Brush),
529    setFont(DC, Font, Fg),
530    draw_tree(Tree, root, DC),
531    case Sel of
532	undefined -> ok;
533	{#box{x=X,y=Y,w=W,h=H,s1=Str1}, _} ->
534	    setBrush(DC, SelBrush),
535	    drawRoundedRectangle(DC, X-1,Y-1, W+2,H+2, 8.0),
536	    draw_str(DC, Str1, X, Y)
537    end.
538
539draw_tree({Box=#box{x=X,y=Y,w=W,h=H,s1=Str1}, Chs}, Parent, DC) ->
540    drawRoundedRectangle(DC, X,Y, W,H, 8.0),
541    draw_str(DC, Str1, X, Y),
542    Dot = case Chs of
543	      [] -> ok;
544	      [{#box{x=CX0},_}|_] ->
545		  CY = Y+(H div 2),
546		  CX = CX0-(?BB_X div 2),
547		  strokeLine(DC, X+W, CY, CX, CY),
548		  {CX, CY}
549	  end,
550    draw_link(Parent, Box, DC),
551    [draw_tree(Child, Dot, DC) || Child <- Chs].
552
553draw_link({CX,CY}, #box{x=X,y=Y0,h=H}, DC) ->
554    Y = Y0+(H div 2),
555    case Y =:= CY of
556	true ->
557	    strokeLine(DC, CX, CY, X, CY);
558	false ->
559	    strokeLines(DC, [{CX, CY}, {CX, Y}, {X,Y}])
560    end;
561draw_link(_, _, _) -> ok.
562
563draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, y=Y1}}, DC)
564  when X0 =:= X1 ->
565    draw_xlink(X0,Y0,X1,Y1,BH,DC);
566draw_xlink({#box{x=X0, y=Y0, h=BH, w=BW}, #box{x=X1, y=Y1}}, DC)
567  when X0 < X1 ->
568    draw_xlink(X0+BW,Y0,X1,Y1,BH,DC);
569draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, w=BW, y=Y1}}, DC)
570  when X0 > X1 ->
571    draw_xlink(X1+BW,Y1,X0,Y0,BH,DC);
572draw_xlink({_From, _To}, _DC) ->
573    ignore.
574draw_xlink(X0, Y00, X1, Y11, BH, DC) ->
575    {Y0,Y1} = if Y00 < Y11 -> {Y00+BH-6, Y11+6};
576		 true -> {Y00+6, Y11+BH-6}
577	      end,
578    strokeLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]).
579
580draw_str(DC, #str{x=Sx,y=Sy, text=Text}, X, Y) ->
581    drawText(DC, Text, X+Sx,Y+Sy);
582draw_str(_, _, _, _) -> ok.
583