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