diff options
Diffstat (limited to 'lib/observer')
-rw-r--r-- | lib/observer/src/Makefile | 1 | ||||
-rw-r--r-- | lib/observer/src/observer_app_wx.erl | 520 | ||||
-rw-r--r-- | lib/observer/src/observer_lib.erl | 25 | ||||
-rw-r--r-- | lib/observer/src/observer_pro_wx.erl | 4 | ||||
-rw-r--r-- | lib/observer/src/observer_sys_wx.erl | 11 | ||||
-rw-r--r-- | lib/observer/src/observer_trace_wx.erl | 2 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_table.erl | 31 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_wx.erl | 6 | ||||
-rw-r--r-- | lib/observer/src/observer_wx.erl | 28 |
9 files changed, 569 insertions, 59 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 19702d6d89..ca26afc11d 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -42,6 +42,7 @@ MODULES= \ etop_tr \ etop_txt \ observer \ + observer_app_wx \ observer_lib \ observer_wx \ observer_pro_wx \ diff --git a/lib/observer/src/observer_app_wx.erl b/lib/observer/src/observer_app_wx.erl new file mode 100644 index 0000000000..4f35a93004 --- /dev/null +++ b/lib/observer/src/observer_app_wx.erl @@ -0,0 +1,520 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(observer_app_wx). + +-export([start_link/2]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +-record(state, + { + parent, + panel, + apps_w, + app_w, + paint, + current, + app, + sel, + appmon + }). + +-record(paint, {font, pen, brush, sel, links}). + +-record(app, {ptree, n2p, links, dim}). +-record(box, {x,y, w,h, s1}). +-record(str, {x,y,text,pid}). + +-define(BX_E, 10). %% Empty width between text and box +-define(BX_HE, (?BX_E div 2)). +-define(BY_E, 10). %% Empty height between text and box +-define(BY_HE, (?BY_E div 2)). + +-define(BB_X, 16). %% Empty width between boxes +-define(BB_Y, 12). %% Empty height between boxes + +-define(DRAWAREA, 5). +-define(ID_PROC_INFO, 101). +-define(ID_PROC_MSG, 102). +-define(ID_PROC_KILL, 103). +-define(ID_TRACE_PID, 104). +-define(ID_TRACE_NAME, 105). +-define(ID_TRACE_TREE_PIDS, 106). +-define(ID_TRACE_TREE_NAMES, 107). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}, + {winid, 1} + ]), + Main = wxBoxSizer:new(?wxHORIZONTAL), + Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)}, + {style, ?wxSP_LIVE_UPDATE}, + {id, 2} + ]), + Apps = wxListBox:new(Splitter, 3, []), + %% Need extra panel and sizer to get correct size updates + %% in draw area for some reason + P2 = wxPanel:new(Splitter, [{winid, 4}]), + Extra = wxBoxSizer:new(?wxVERTICAL), + DrawingArea = wxScrolledWindow:new(P2, [{winid, ?DRAWAREA}, + {style,?wxFULL_REPAINT_ON_RESIZE}]), + wxWindow:setBackgroundColour(DrawingArea, ?wxWHITE), + wxWindow:setVirtualSize(DrawingArea, 800, 800), + wxSplitterWindow:setMinimumPaneSize(Splitter,50), + wxSizer:add(Extra, DrawingArea, [{flag, ?wxEXPAND},{proportion, 1}]), + wxWindow:setSizer(P2, Extra), + wxSplitterWindow:splitVertically(Splitter, Apps, P2, [{sashPosition, 150}]), + wxWindow:setSizer(Panel, Main), + + wxSizer:add(Main, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Main), + wxListBox:connect(Apps, command_listbox_selected), + wxPanel:connect(DrawingArea, paint, [callback]), + wxPanel:connect(DrawingArea, size, [{skip, true}]), + wxPanel:connect(DrawingArea, left_up), + wxPanel:connect(DrawingArea, left_dclick), + wxPanel:connect(DrawingArea, right_down), + + DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + SelCol = wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), + SelBrush = wxBrush:new(SelCol), + LinkPen = wxPen:new(SelCol, [{width, 2}]), + %% GC = wxGraphicsContext:create(DrawingArea), + %% _Font = wxGraphicsContext:createFont(GC, DefFont), + {Panel, #state{parent=Parent, + panel =Panel, + apps_w=Apps, + app_w =DrawingArea, + paint=#paint{font= DefFont, + pen= ?wxBLACK_PEN, + brush=?wxLIGHT_GREY_BRUSH, + sel= SelBrush, + links=LinkPen + } + }}. + +setup_scrollbar(AppWin, App) -> + setup_scrollbar(wxWindow:getClientSize(AppWin), AppWin, App). + +setup_scrollbar({CW, CH}, AppWin, #app{dim={W0,H0}}) -> + W = max(W0,CW), + H = max(H0,CH), + PPC = 20, + if W0 =< CW, H0 =< CH -> + wxScrolledWindow:setScrollbars(AppWin, W, H, 1, 1); + H0 =< CH -> + wxScrolledWindow:setScrollbars(AppWin, PPC, H, W div PPC+1, 1); + W0 =< CW -> + wxScrolledWindow:setScrollbars(AppWin, W, PPC, 1, H div PPC+1); + true -> + wxScrolledWindow:setScrollbars(AppWin, PPC, PPC, W div PPC+1, H div PPC+1) + end; +setup_scrollbar(_, _, undefined) -> ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{event=#wxCommand{type=command_listbox_selected, cmdString=AppStr}}, + State = #state{appmon=AppMon, current=Prev}) -> + case AppStr of + [] -> + {noreply, State}; + _ -> + App = list_to_atom(AppStr), + (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []), + appmon_info:app(AppMon, App, true, []), + {noreply, State#state{current=App}} + end; + +handle_event(#wx{id=Id, event=_Sz=#wxSize{size=Size}}, + State=#state{app=App, app_w=AppWin}) -> + Id =:= ?DRAWAREA andalso setup_scrollbar(Size,AppWin,App), + {noreply, State}; + +handle_event(#wx{event=#wxMouse{type=Type, x=X0, y=Y0}}, + S0=#state{app=#app{ptree=Tree}, app_w=AppWin}) -> + {X,Y} = wxScrolledWindow:calcUnscrolledPosition(AppWin, X0, Y0), + Hit = locate_node(X,Y, [Tree]), + State = handle_mouse_click(Hit, Type, S0), + {noreply, State}; + +handle_event(#wx{event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=undefined}) -> + observer_lib:display_info_dialog("Select process first"), + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_INFO, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + observer_procinfo:start(Pid, Panel, self()), + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_MSG, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + case observer_lib:user_term(Panel, "Enter message", "") of + cancel -> ok; + {ok, Term} -> Pid ! Term; + {error, Error} -> observer_lib:display_info_dialog(Error) + end, + {noreply, State}; + +handle_event(#wx{id=?ID_PROC_KILL, event=#wxCommand{type=command_menu_selected}}, + State = #state{panel=Panel, sel={#box{s1=#str{pid=Pid}},_}}) -> + case observer_lib:user_term(Panel, "Enter Exit Reason", "") of + cancel -> ok; + {ok, Term} -> exit(Pid, Term); + {error, Error} -> observer_lib:display_info_dialog(Error) + end, + {noreply, State}; + +%%% Trace api +handle_event(#wx{id=?ID_TRACE_PID, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel={Box,_}}) -> + observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_pid(Box)]), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_NAME, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel={Box,_}}) -> + observer_trace_wx:add_processes(observer_wx:get_tracer(), [box_to_reg(Box)]), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_TREE_PIDS, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=Sel}) -> + Get = fun(Box) -> box_to_pid(Box) end, + observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + {noreply, State}; +handle_event(#wx{id=?ID_TRACE_TREE_NAMES, event=#wxCommand{type=command_menu_selected}}, + State = #state{sel=Sel}) -> + Get = fun(Box) -> box_to_reg(Box) end, + observer_trace_wx:add_processes(observer_wx:get_tracer(), tree_map(Sel, Get)), + {noreply, State}; + +handle_event(Event, _State) -> + error({unhandled_event, Event}). + +%%%%%%%%%% +handle_sync_event(#wx{event = #wxPaint{}},_, + #state{app_w=DA, app=App, sel=Sel, paint=Paint}) -> + %% PaintDC must be created in a callback to work on windows. + DC = wxPaintDC:new(DA), + wxScrolledWindow:doPrepareDC(DA,DC), + %% Nothing is drawn until wxPaintDC is destroyed. + draw(DC, App, Sel, Paint), + wxPaintDC:destroy(DC), + ok. +%%%%%%%%%% +handle_call(Event, From, _State) -> + error({unhandled_call, Event, From}). + +handle_cast(Event, _State) -> + error({unhandled_cast, Event}). +%%%%%%%%%% +handle_info({active, Node}, State = #state{parent=Parent, current=Curr, appmon=Appmon}) -> + create_menus(Parent, []), + {ok, Pid} = appmon_info:start_link(Node, self(), []), + case Appmon of + undefined -> ok; + Pid -> ok; + _ -> %% Deregister me as client (and stop appmon if last) + exit(Appmon, normal) + end, + appmon_info:app_ctrl(Pid, Node, true, []), + (Curr =/= undefined) andalso appmon_info:app(Pid, Curr, true, []), + {noreply, State#state{appmon=Pid}}; + +handle_info(not_active, State = #state{appmon=AppMon, current=Prev}) -> + appmon_info:app_ctrl(AppMon, node(AppMon), false, []), + (Prev =/= undefined) andalso appmon_info:app(AppMon, Prev, false, []), + {noreply, State}; + +handle_info({delivery, Pid, app_ctrl, _, Apps0}, + State = #state{appmon=Pid, apps_w=LBox}) -> + Apps = [atom_to_list(App) || {_, App, {_, _, _}} <- Apps0], + wxListBox:clear(LBox), + wxListBox:appendStrings(LBox, [App || App <- lists:sort(Apps)]), + {noreply, State}; + +handle_info({delivery, Pid, app, Curr, AppData}, + State = #state{panel=Panel, appmon=Pid, current=Curr, + app_w=AppWin, paint=#paint{font=Font}}) -> + App = build_tree(AppData, {AppWin,Font}), + setup_scrollbar(AppWin, App), + wxWindow:refresh(Panel), + wxWindow:layout(Panel), + {noreply, State#state{app=App, sel=undefined}}; + +handle_info(_Event, State) -> + %% io:format("~p:~p: ~p~n",[?MODULE,?LINE,_Event]), + {noreply, State}. + +%%%%%%%%%% +terminate(_Event, _State) -> + ok. +code_change(_, _, State) -> + State. + +handle_mouse_click(Node = {#box{s1=#str{pid=Pid}},_}, Type, + State=#state{app_w=AppWin,panel=Panel}) -> + case Type of + left_dclick -> observer_procinfo:start(Pid, Panel, self()); + right_down -> popup_menu(Panel); + _ -> ok + end, + wxWindow:refresh(AppWin), + State#state{sel=Node}; +handle_mouse_click(_, _, State = #state{sel=undefined}) -> + State; +handle_mouse_click(_, right_down, State=#state{panel=Panel}) -> + popup_menu(Panel), + State; +handle_mouse_click(_, _, State=#state{app_w=AppWin}) -> + wxWindow:refresh(AppWin), + State#state{sel=undefined}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, _) -> + MenuEntries = + [{"File", + [#create_menu{id=?ID_PROC_INFO, text="Process info"}, + #create_menu{id=?ID_PROC_MSG, text="Send Msg"}, + #create_menu{id=?ID_PROC_KILL, text="Kill process"} + ]}, + {"Trace", + [#create_menu{id=?ID_TRACE_PID, text="Trace process"}, + #create_menu{id=?ID_TRACE_NAME, text="Trace named process"}, + #create_menu{id=?ID_TRACE_TREE_PIDS, text="Trace process tree"}, + #create_menu{id=?ID_TRACE_TREE_NAMES, text="Trace named process tree"} + ]}], + observer_wx:create_menus(Parent, MenuEntries). + +popup_menu(Panel) -> + Menu = wxMenu:new(), + wxMenu:append(Menu, ?ID_PROC_INFO, "Process info"), + wxMenu:append(Menu, ?ID_TRACE_PID, "Trace process"), + wxMenu:append(Menu, ?ID_TRACE_NAME, "Trace named process"), + wxMenu:append(Menu, ?ID_TRACE_TREE_PIDS, "Trace process tree"), + wxMenu:append(Menu, ?ID_TRACE_TREE_NAMES, "Trace named process tree"), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +locate_node(X, _Y, [{Box=#box{x=BX}, _Chs}|_Rest]) + when X < BX -> + {left, Box}; +locate_node(X,Y, [Node={Box=#box{x=BX,y=BY,w=BW,h=BH}, _Chs}|Rest]) + when X =< (BX+BW)-> + if + Y < BY -> {above, Box}; %% Above + Y =< (BY+BH) -> Node; + true -> locate_node(X,Y,Rest) + end; +locate_node(X,Y, [{_, Chs}|Rest]) -> + case locate_node(X,Y,Chs) of + Node = {#box{},_} -> Node; + _Miss -> + locate_node(X,Y,Rest) + end; +locate_node(_, _, []) -> false. + +locate_box(From, [{Box=#box{s1=#str{pid=From}},_}|_]) -> Box; +locate_box(From, [{_,Chs}|Rest]) -> + case locate_box(From, Chs) of + Box = #box{} -> Box; + _ -> locate_box(From, Rest) + end; +locate_box(From, []) -> {false, From}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +build_tree({Root, P2Name, Links, XLinks0}, Font) -> + Fam = sofs:relation_to_family(sofs:relation(Links)), + Name2P = gb_trees:from_orddict(lists:sort([{Name,Pid} || {Pid,Name} <- P2Name])), + Lookup = gb_trees:from_orddict(sofs:to_external(Fam)), + {_, Tree0} = build_tree2(Root, Lookup, Name2P, Font), + {Tree, Dim} = calc_tree_size(Tree0), + Fetch = fun({From, To}, Acc) -> + case gb_trees:lookup(To, Name2P) of + {value, ToPid} -> + FromPid = gb_trees:get(From, Name2P), + [{locate_box(FromPid, [Tree]),locate_box(ToPid, [Tree])}|Acc]; + none -> + Acc + end + end, + XLinks = lists:foldl(Fetch, [], XLinks0), + #app{ptree=Tree, dim=Dim, links=XLinks}. + +build_tree2(Root, Tree0, N2P, Font) -> + case gb_trees:lookup(Root, Tree0) of + none -> {Tree0, {box(Root, N2P, Font), []}}; + {value, Children} -> + Tree1 = gb_trees:delete(Root, Tree0), + {Tree, CHs} = lists:foldr(fun("port " ++_, Acc) -> + Acc; %% Skip ports + (Child,{T0, Acc}) -> + {T, C} = build_tree2(Child, T0, N2P, Font), + {T, [C|Acc]} + end, {Tree1, []}, Children), + {Tree, {box(Root, N2P, Font), CHs}} + end. + +calc_tree_size(Tree) -> + Cols = calc_col_start(Tree, [0]), + {Boxes,{W,Hs}} = calc_tree_size(Tree, Cols, ?BB_X, [?BB_Y]), + {Boxes, {W,lists:max(Hs)}}. + +calc_col_start({#box{w=W}, Chs}, [Max|Acc0]) -> + Acc = if Acc0 == [] -> [0]; true -> Acc0 end, + Depth = lists:foldl(fun(Child, MDepth) -> calc_col_start(Child, MDepth) end, + Acc, Chs), + [max(W,Max)|Depth]. + +calc_tree_size({Box=#box{w=W,h=H}, []}, _, X, [Y|Ys]) -> + {{Box#box{x=X,y=Y}, []}, {X+W+?BB_X,[Y+H+?BB_Y|Ys]}}; +calc_tree_size({Box, Children}, [Col|Cols], X, [H0|Hs0]) -> + Hs1 = calc_row_start(Children, H0, Hs0), + StartX = X+Col+?BB_X, + {Boxes, {W,Hs}} = calc_tree_sizes(Children, Cols, StartX, StartX, Hs1, []), + Y = middle(Boxes, H0), + H = Y+Box#box.h+?BB_Y, + {{Box#box{x=X,y=Y}, Boxes}, {W,[H|Hs]}}. + +calc_tree_sizes([Child|Chs], Cols, X0, W0, Hs0, Acc) -> + {Tree, {W,Hs}} = calc_tree_size(Child, Cols, X0, Hs0), + calc_tree_sizes(Chs, Cols, X0, max(W,W0), Hs, [Tree|Acc]); +calc_tree_sizes([], _, _, W,Hs, Acc) -> + {lists:reverse(Acc), {W,Hs}}. + +calc_row_start(Chs = [{#box{h=H},_}|_], Start, Hs0) -> + NChs = length(Chs), + Wanted = (H*NChs + ?BB_Y*(NChs-1)) div 2 - H div 2, + case Hs0 of + [] -> [max(?BB_Y, Start - Wanted)]; + [Next|Hs] -> + [max(Next, Start - Wanted)|Hs] + end. + +middle([], Y) -> Y; +middle([{#box{y=Y}, _}], _) -> Y; +middle([{#box{y=Y0},_}|List], _) -> + {#box{y=Y1},_} = lists:last(List), + (Y0+Y1) div 2. + +box(Str0, N2P, {Win,Font}) -> + Pid = gb_trees:get(Str0, N2P), + Str = if hd(Str0) =:= $< -> lists:append(io_lib:format("~w", [Pid])); + true -> Str0 + end, + {TW,TH, _, _} = wxWindow:getTextExtent(Win, Str, [{theFont, Font}]), + Data = #str{text=Str, x=?BX_HE, y=?BY_HE, pid=Pid}, + %% Add pid + #box{w=TW+?BX_E, h=TH+?BY_E, s1=Data}. + +box_to_pid(#box{s1=#str{pid=Pid}}) -> Pid. +box_to_reg(#box{s1=#str{text=[$<|_], pid=Pid}}) -> Pid; +box_to_reg(#box{s1=#str{text=Name}}) -> list_to_atom(Name). + +tree_map({Box, Chs}, Fun) -> + tree_map(Chs, Fun, [Fun(Box)]). +tree_map([{Box, Chs}|Rest], Fun, Acc0) -> + Acc = tree_map(Chs, Fun, [Fun(Box)|Acc0]), + tree_map(Rest, Fun, Acc); +tree_map([], _ , Acc) -> Acc. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +draw(_DC, undefined, _, _) -> + ok; +draw(DC, #app{dim={_W,_H}, ptree=Tree, links=Links}, Sel, + #paint{font=Font, pen=Pen, brush=Brush, links=LPen, sel=SelBrush}) -> + %% Canvas = wxGraphicsContext:create(DC), + %% Pen = wxGraphicsContext:createPen(Canvas, ?wxBLACK_PEN), + %% wxGraphicsContext:setPen(Canvas, Pen), + %% Brush = wxGraphicsContext:createBrush(Canvas, ?wxLIGHT_GREY_BRUSH), + %% wxGraphicsContext:setBrush(Canvas, Brush), + %% Font = wxGraphicsContext:createFont(Canvas, wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT)), + %% wxGraphicsContext:setFont(Canvas, Font), + %% draw_tree(Tree, Canvas). + wxDC:setPen(DC, LPen), + [draw_xlink(Link, DC) || Link <- Links], + wxDC:setPen(DC, Pen), + %% wxDC:drawRectangle(DC, {2,2}, {W-2,H-2}), %% DEBUG + wxDC:setBrush(DC, Brush), + wxDC:setFont(DC, Font), + draw_tree(Tree, root, DC), + case Sel of + undefined -> ok; + {#box{x=X,y=Y,w=W,h=H,s1=Str1}, _} -> + wxDC:setBrush(DC, SelBrush), + wxDC:drawRoundedRectangle(DC, {X-1,Y-1}, {W+2,H+2}, 8.0), + draw_str(DC, Str1, X, Y) + end. + +draw_tree({Box=#box{x=X,y=Y,w=W,h=H,s1=Str1}, Chs}, Parent, DC) -> + %%wxGraphicsContext:drawRoundedRectangle(DC, float(X), float(Y), float(W), float(H), 8.0), + wxDC:drawRoundedRectangle(DC, {X,Y}, {W,H}, 8.0), + draw_str(DC, Str1, X, Y), + Dot = case Chs of + [] -> ok; + [{#box{x=CX0},_}|_] -> + CY = Y+(H div 2), + CX = CX0-(?BB_X div 2), + wxDC:drawLine(DC, {X+W, CY}, {CX, CY}), + {CX, CY} + end, + draw_link(Parent, Box, DC), + [draw_tree(Child, Dot, DC) || Child <- Chs]. + +draw_link({CX,CY}, #box{x=X,y=Y0,h=H}, DC) -> + Y = Y0+(H div 2), + case Y =:= CY of + true -> + wxDC:drawLine(DC, {CX, CY}, {X, CY}); + false -> + wxDC:drawLines(DC, [{CX, CY}, {CX, Y}, {X,Y}]) + end; +draw_link(_, _, _) -> ok. + +draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, y=Y1}}, DC) + when X0 =:= X1 -> + draw_xlink(X0,Y0,X1,Y1,BH,DC); +draw_xlink({#box{x=X0, y=Y0, h=BH, w=BW}, #box{x=X1, y=Y1}}, DC) + when X0 < X1 -> + draw_xlink(X0+BW,Y0,X1,Y1,BH,DC); +draw_xlink({#box{x=X0, y=Y0, h=BH}, #box{x=X1, w=BW, y=Y1}}, DC) + when X0 > X1 -> + draw_xlink(X1+BW,Y1,X0,Y0,BH,DC); +draw_xlink({_From, _To}, _DC) -> + ignore. +draw_xlink(X0, Y00, X1, Y11, BH, DC) -> + {Y0,Y1} = if Y00 < Y11 -> {Y00+BH-6, Y11+6}; + true -> {Y00+6, Y11+BH-6} + end, + wxDC:drawLines(DC, [{X0,Y0}, {X0+5,Y0}, {X1-5,Y1}, {X1,Y1}]). + +draw_str(DC, #str{x=Sx,y=Sy, text=Text}, X, Y) -> + %%wxGraphicsContext:drawText(DC, Text, float(Sx+X), float(Sy+Y)); + wxDC:drawText(DC, Text, {X+Sx,Y+Sy}); +draw_str(_, _, _, _) -> ok. diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl index 7763e8c60a..967baa5c7a 100644 --- a/lib/observer/src/observer_lib.erl +++ b/lib/observer/src/observer_lib.erl @@ -19,7 +19,7 @@ -module(observer_lib). -export([get_wx_parent/1, - display_info_dialog/1, + display_info_dialog/1, user_term/3, interval_dialog/4, start_timer/1, stop_timer/1, display_info/2, fill_info/2, update_info/2, to_str/1, create_menus/3, create_menu_item/3, @@ -196,6 +196,7 @@ to_str(No) when is_integer(No) -> to_str(Term) -> io_lib:format("~w", [Term]). +create_menus([], _MenuBar, _Type) -> ok; create_menus(Menus, MenuBar, Type) -> Add = fun({Tag, Ms}, Index) -> create_menu(Tag, Ms, Index, MenuBar, Type) @@ -330,3 +331,25 @@ scroll_size(LCtrl) -> false -> 0 end end. + + +user_term(Parent, Title, Default) -> + Dialog = wxTextEntryDialog:new(Parent, Title, [{value, Default}]), + case wxTextEntryDialog:showModal(Dialog) of + ?wxID_OK -> + Str = wxTextEntryDialog:getValue(Dialog), + wxTextEntryDialog:destroy(Dialog), + parse_string(Str); + ?wxID_CANCEL -> + wxTextEntryDialog:destroy(Dialog) + end. + +parse_string(Str) -> + try + {ok, Tokens, _} = erl_scan:string(Str), + erl_parse:parse_term(Tokens) + catch _:{badmatch, {error, {_, _, Err}}} -> + {error, ["Parse error: ", Err]}; + _Err -> + {error, ["Syntax error in: ", Str]} + end. diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl index 85f84aa640..7578215ff9 100644 --- a/lib/observer/src/observer_pro_wx.erl +++ b/lib/observer/src/observer_pro_wx.erl @@ -241,10 +241,6 @@ handle_info(not_active, #state{timer=Timer0}=State) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({node, Node}, #state{holder=Holder}=State) -> - Holder ! {change_node, Node}, - {noreply, State}; - handle_info(Info, State) -> io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), {noreply, State}. diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl index 789728bbeb..2535b50876 100644 --- a/lib/observer/src/observer_sys_wx.erl +++ b/lib/observer/src/observer_sys_wx.erl @@ -126,16 +126,6 @@ handle_info(refresh_interval, #sys_wx_state{panel = Panel, end, {noreply, State}; -handle_info({node, Node}, #sys_wx_state{panel = Panel} = State) -> - UpdState = State#sys_wx_state{node = Node}, - try - update_syspage(UpdState), - {noreply, UpdState} - catch error:{badrpc, _} -> - observer_wx:return_to_localnode(Panel, Node), - {noreply, State} - end; - handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel, timer = Timer} = State) -> UpdState = State#sys_wx_state{node = Node}, @@ -148,7 +138,6 @@ handle_info({active, Node}, #sys_wx_state{parent = Parent, panel = Panel, {noreply, State} end; - handle_info(not_active, #sys_wx_state{timer = Timer} = State) -> {noreply, State#sys_wx_state{timer = observer_lib:stop_timer(Timer)}}; diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl index ef46030cc7..d0b6a1e063 100644 --- a/lib/observer/src/observer_trace_wx.erl +++ b/lib/observer/src/observer_trace_wx.erl @@ -610,7 +610,7 @@ ttb_output_args(Parent, Opts) -> ToWindow = proplists:get_value(window, Opts, true), ToShell = proplists:get_value(shell, Opts, false), ToFile = proplists:get_value(file, Opts, false), - ToWindow orelse ToShell orelse ToShell orelse + ToWindow orelse ToShell orelse ToFile orelse throw({error, "No output of trace"}), {LogWin,Text} = create_logwindow(Parent, ToWindow), Write = output_fun(Text, ToShell), diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl index dea3526a8b..dd11ba5470 100644 --- a/lib/observer/src/observer_tv_table.erl +++ b/lib/observer/src/observer_tv_table.erl @@ -221,33 +221,10 @@ search_area(Parent) -> edit(Index, #state{pid=Pid, frame=Frame}) -> Str = get_row(Pid, Index, all), - Dialog = wxTextEntryDialog:new(Frame, "Edit object:", [{value, Str}]), - case wxTextEntryDialog:showModal(Dialog) of - ?wxID_OK -> - New = wxTextEntryDialog:getValue(Dialog), - wxTextEntryDialog:destroy(Dialog), - case Str =:= New of - true -> ok; - false -> - complete_edit(Index, New, Pid) - end; - ?wxID_CANCEL -> - wxTextEntryDialog:destroy(Dialog) - end. - -complete_edit(Row, New0, Pid) -> - New = case lists:reverse(New0) of - [$.|_] -> New0; - _ -> New0 ++ "." - end, - try - {ok, Tokens, _} = erl_scan:string(New), - {ok, Term} = erl_parse:parse_term(Tokens), - Pid ! {edit, Row, Term} - catch _:{badmatch, {error, {_, _, Err}}} -> - self() ! {error, ["Parse error: ", Err]}; - _Err -> - self() ! {error, ["Syntax error in: ", New]} + case observer_lib:user_term(Frame, "Edit object:", Str) of + cancel -> ok; + {ok, Term} -> Pid ! {edit, Index, Term}; + Err = {error, _} -> self() ! Err end. handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) -> diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl index c25b395fda..bf66b7178c 100644 --- a/lib/observer/src/observer_tv_wx.erl +++ b/lib/observer/src/observer_tv_wx.erl @@ -197,12 +197,6 @@ handle_info(not_active, State = #state{timer = Timer0}) -> Timer = observer_lib:stop_timer(Timer0), {noreply, State#state{timer=Timer}}; -handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) -> - Tables = get_tables(Node, Opt), - Tabs = update_grid(Grid, Opt, Tables), - wxWindow:setFocus(Grid), - {noreply, State#state{node=Node, tabs=Tabs}}; - handle_info({error, Error}, State) -> handle_error(Error), {noreply, State}; diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl index 5586cfcb9a..89f4afdba0 100644 --- a/lib/observer/src/observer_wx.erl +++ b/lib/observer/src/observer_wx.erl @@ -54,6 +54,7 @@ tv_panel, sys_panel, trace_panel, + app_panel, active_tab, node, nodes @@ -137,6 +138,10 @@ setup(#state{frame = Frame} = State) -> TracePanel = observer_trace_wx:start_link(Notebook, self()), wxNotebook:addPage(Notebook, TracePanel, ?TRACE_STR, []), + %% App Viewer Panel + AppPanel = observer_app_wx:start_link(Notebook, self()), + wxNotebook:addPage(Notebook, AppPanel, "Applications", []), + %% Force redraw (window needs it) wxWindow:refresh(Panel), @@ -150,6 +155,7 @@ setup(#state{frame = Frame} = State) -> pro_panel = ProPanel, tv_panel = TVPanel, trace_panel = TracePanel, + app_panel = AppPanel, active_tab = SysPid, node = node(), nodes = Nodes @@ -277,10 +283,13 @@ handle_cast(_Cast, State) -> handle_call({create_menus, TabMenus}, _From, State = #state{menubar=MenuBar, menus=PrevTabMenus}) -> - wx:batch(fun() -> - clean_menus(PrevTabMenus, MenuBar), - observer_lib:create_menus(TabMenus, MenuBar, plugin) - end), + if TabMenus == PrevTabMenus -> ignore; + true -> + wx:batch(fun() -> + clean_menus(PrevTabMenus, MenuBar), + observer_lib:create_menus(TabMenus, MenuBar, plugin) + end) + end, {reply, ok, State#state{menus=TabMenus}}; handle_call({get_attrib, Attrib}, _From, State) -> @@ -379,9 +388,8 @@ connect2(NodeName, Opts, Cookie) -> {error, net_kernel, Reason} end. -change_node_view(Node, State = #state{pro_panel=Pro, sys_panel=Sys, tv_panel=Tv}) -> - lists:foreach(fun(Pid) -> wx_object:get_pid(Pid) ! {node, Node} end, - [Pro, Sys, Tv]), +change_node_view(Node, State) -> + get_active_pid(State) ! {active, Node}, StatusText = ["Observer - " | atom_to_list(Node)], wxFrame:setTitle(State#state.frame, StatusText), wxStatusBar:setStatusText(State#state.status_bar, StatusText), @@ -391,12 +399,14 @@ check_page_title(Notebook) -> Selection = wxNotebook:getSelection(Notebook), wxNotebook:getPageText(Notebook, Selection). -get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, tv_panel=Tv, trace_panel=Trace}) -> +get_active_pid(#state{notebook=Notebook, pro_panel=Pro, sys_panel=Sys, + tv_panel=Tv, trace_panel=Trace, app_panel=App}) -> Panel = case check_page_title(Notebook) of "Processes" -> Pro; "System" -> Sys; "Table Viewer" -> Tv; - ?TRACE_STR -> Trace + ?TRACE_STR -> Trace; + "Applications" -> App end, wx_object:get_pid(Panel). |