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