aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer/src
diff options
context:
space:
mode:
authorMagnus Eriksson <[email protected]>2011-09-29 13:20:42 +0200
committerDan Gudmundsson <[email protected]>2011-11-08 08:45:36 +0100
commit5e1fafb077740c9d919bc532b2c392f4f20bbf1b (patch)
tree0697735d4c1669b5d7b1bf3e348a650c0a5a2b00 /lib/observer/src
parent766a0a84f0b9e4b9341fb06364bf5430574588a6 (diff)
downloadotp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.tar.gz
otp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.tar.bz2
otp-5e1fafb077740c9d919bc532b2c392f4f20bbf1b.zip
[observer] Started on a wx gui
Diffstat (limited to 'lib/observer/src')
-rw-r--r--lib/observer/src/Makefile18
-rw-r--r--lib/observer/src/observer_defs.hrl55
-rw-r--r--lib/observer/src/observer_pro_wx.erl944
-rw-r--r--lib/observer/src/observer_procinfo.erl511
-rw-r--r--lib/observer/src/observer_sys.erl131
-rw-r--r--lib/observer/src/observer_sys_wx.erl313
-rw-r--r--lib/observer/src/observer_trace_wx.erl487
-rw-r--r--lib/observer/src/observer_traceoptions_wx.erl1066
-rw-r--r--lib/observer/src/observer_wx.erl506
9 files changed, 4026 insertions, 5 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile
index 3875b62101..514c6e441c 100644
--- a/lib/observer/src/Makefile
+++ b/lib/observer/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
+#
# Copyright Ericsson AB 2002-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%
#
include $(ERL_TOP)/make/target.mk
@@ -41,8 +41,16 @@ MODULES= \
etop_gui \
etop_tr \
etop_txt \
+ observer_wx \
+ observer_pro_wx \
+ observer_procinfo \
+ observer_sys \
+ observer_sys_wx \
+ observer_trace_wx \
+ observer_traceoptions_wx \
ttb \
ttb_et
+
HRL_FILES= \
../include/etop.hrl
INTERNAL_HRL_FILES= \
@@ -109,7 +117,7 @@ docs:
# ----------------------------------------------------
# Release Target
-# ----------------------------------------------------
+# ----------------------------------------------------
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
diff --git a/lib/observer/src/observer_defs.hrl b/lib/observer/src/observer_defs.hrl
new file mode 100644
index 0000000000..7990cc248a
--- /dev/null
+++ b/lib/observer/src/observer_defs.hrl
@@ -0,0 +1,55 @@
+%%
+%% %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%
+
+%% -define(OBS, observer_wx).
+%% -define(OBS_SYS_LOGIC, observer_sys).
+%% -define(OBS_SYS_WX, observer_sys_wx).
+%% -define(OBS_PRO_WX, observer_pro_wx).
+
+
+-record(trace_options, {send = false,
+ treceive = false,
+ functions = false,
+ events = false,
+ on_1st_spawn = false,
+ on_all_spawn = false,
+ on_1st_link = false,
+ on_all_link = false,
+ main_window = true}).
+
+-record(match_spec, {alias,
+ term_ms = [],
+ str_ms = [],
+ fun2ms}).
+
+-record(traced_func, {func_name, %atom
+ arity, %integer
+ match_spec = #match_spec{}}).
+
+-record(on_spawn, {checkbox, all_spawn, first_spawn}).
+
+-record(on_link, {checkbox, all_link, first_link}).
+
+-record(pid, {window, traced}).
+
+-record(create_menu,
+ {id,
+ text,
+ type = append,
+ check = false
+ }).
diff --git a/lib/observer/src/observer_pro_wx.erl b/lib/observer/src/observer_pro_wx.erl
new file mode 100644
index 0000000000..7080da8231
--- /dev/null
+++ b/lib/observer/src/observer_pro_wx.erl
@@ -0,0 +1,944 @@
+%%
+%% %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_pro_wx).
+
+-behaviour(wx_object).
+
+-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_cast/2, to_str/1]).
+-export([get_row/4, get_attr/3]).
+
+-include_lib("wx/include/wx.hrl").
+-include_lib("runtime_tools/include/observer_backend.hrl").
+-include("observer_defs.hrl").
+
+%% Defines
+-define(COL_PID, 0).
+-define(COL_NAME, 1).
+-define(COL_TIME, 2).
+-define(COL_REDS, 3).
+-define(COL_MEM, 4).
+-define(COL_MSG, 5).
+-define(COL_FUN, 6).
+
+-define(ID_KILL, 201).
+-define(ID_PROC, 202).
+-define(ID_REFRESH, 203).
+-define(ID_REFRESH_INTERVAL, 204).
+-define(ID_DUMP_TO_FILE, 205).
+-define(ID_TRACEMENU, 206).
+-define(ID_TRACE_ALL_MENU, 207).
+-define(ID_TRACE_NEW_MENU, 208).
+-define(ID_NO_OF_LINES, 209).
+-define(ID_ACCUMULATE, 210).
+
+-define(START_LINES, 50). %% hardcoded startvalue representing the number of visible lines
+
+%% Records
+-record(attrs, {even, odd, deleted, changed, searched}).
+
+-record(holder, {parent,
+ info,
+ attrs}).
+
+
+-record(pro_wx_state, {parent,
+ etop_monitor,
+ holder_monitor,
+ grid,
+ panel,
+ popup_menu,
+ parent_notebook,
+ trace_options = #trace_options{},
+ match_specs = [],
+ refr_timer = false,
+ tracemenu_opened,
+ procinfo_menu_pids = [],
+ selected_pids = [],
+ last_selected,
+ sort_dir = decr, % decr::atom | incr::incr
+ holder}).
+
+start_link(Notebook, Parent) ->
+ wx_object:start_link(?MODULE, [Notebook, Parent], []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+init([Notebook, Parent]) ->
+ {EtopMonitor, Config} = etop:start(observer),
+ SortDir = decr,
+ Attrs = create_attrs(),
+ Self = self(),
+ change_lines(?START_LINES),
+ Info = etop:update(Config, SortDir),
+ {Holder, HolderMon} = spawn_monitor(fun() ->
+ init_table_holder(Self,
+ Info,
+ Attrs)
+ end),
+ Count = length(Info#etop_info.procinfo),
+ {ProPanel, State} = setup(Notebook, Parent, Holder, Count),
+ refresh_grid(Holder, SortDir),
+ MatchSpecs = generate_matchspecs(),
+ {ProPanel, State#pro_wx_state{etop_monitor = EtopMonitor,
+ holder_monitor = HolderMon,
+ sort_dir = SortDir,
+ match_specs = MatchSpecs}}.
+
+setup(Notebook, Parent, Holder, Count) ->
+ ProPanel = wxPanel:new(Notebook, []),
+
+ Grid = create_list_box(ProPanel, Holder, Count),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL},
+ {proportion, 1},
+ {border,4}]),
+
+ wxWindow:setSizer(ProPanel, Sizer),
+
+ Popup = create_popup_menu(ProPanel),
+
+ State = #pro_wx_state{parent = Parent,
+ grid = Grid,
+ panel = ProPanel,
+ popup_menu = Popup,
+ parent_notebook = Notebook,
+ tracemenu_opened = false,
+ holder = Holder},
+ {ProPanel, State}.
+
+generate_matchspecs() ->
+ try
+ StrMs1 = "[{'_', [], [{return_trace}]}].",
+ StrMs2 = "[{'_', [], [{exception_trace}]}].",
+ StrMs3 = "[{'_', [], [{message, {caller}}]}].",
+ StrMs4 = "[{'_', [], [{message, {process_dump}}]}].",
+
+ {ok, Tokens1, _} = erl_scan:string(StrMs1),
+ {ok, Tokens2, _} = erl_scan:string(StrMs2),
+ {ok, Tokens3, _} = erl_scan:string(StrMs3),
+ {ok, Tokens4, _} = erl_scan:string(StrMs4),
+ {ok, Term1} = erl_parse:parse_term(Tokens1),
+ {ok, Term2} = erl_parse:parse_term(Tokens2),
+ {ok, Term3} = erl_parse:parse_term(Tokens3),
+ {ok, Term4} = erl_parse:parse_term(Tokens4),
+
+ [#match_spec{term_ms = Term1, str_ms = StrMs1},
+ #match_spec{term_ms = Term2, str_ms = StrMs2},
+ #match_spec{term_ms = Term3, str_ms = StrMs3},
+ #match_spec{term_ms = Term4, str_ms = StrMs4}]
+ catch
+ _:_ ->
+ []
+ end.
+
+%% UI-creation
+
+create_pro_menu(Parent) ->
+ MenuEntries = [{"View",
+ [#create_menu{id = ?ID_REFRESH, text = "Refresh"},
+ #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval"}]},
+ {"Options",
+ [#create_menu{id = ?ID_DUMP_TO_FILE, text = "Dump to file"},
+ #create_menu{id = ?ID_ACCUMULATE, text = "Accumulate", type = check},
+ #create_menu{id = ?ID_NO_OF_LINES, text = "Number of lines"}]},
+ {"Trace",
+ [#create_menu{id = ?ID_TRACEMENU, text = "Trace selected processes"},
+ #create_menu{id = ?ID_TRACE_NEW_MENU, text = "Trace new processes"},
+ #create_menu{id = ?ID_TRACE_ALL_MENU, text = "Trace all processes"}]}
+ ],
+ observer_wx:create_menus(Parent, MenuEntries).
+
+create_popup_menu(ParentFrame) ->
+ MiniFrame = wxMiniFrame:new(ParentFrame, ?wxID_ANY, "Options", [{style, ?wxFRAME_FLOAT_ON_PARENT}]),
+ Panel = wxPanel:new(MiniFrame),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ TraceBtn = wxButton:new(Panel, ?ID_TRACEMENU, [{label, "Trace selected"},
+ {style, ?wxNO_BORDER}]),
+ ProcBtn = wxButton:new(Panel, ?ID_PROC, [{label, "Process info"},
+ {style, ?wxNO_BORDER}]),
+ KillBtn = wxButton:new(Panel, ?ID_KILL, [{label, "Kill process"},
+ {style, ?wxNO_BORDER}]),
+
+ wxButton:connect(TraceBtn, command_button_clicked),
+ wxButton:connect(ProcBtn, command_button_clicked),
+ wxButton:connect(KillBtn, command_button_clicked),
+ wxSizer:add(Sizer, TraceBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Sizer, ProcBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Sizer, KillBtn, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:setSizeHints(Sizer, MiniFrame),
+ MiniFrame.
+
+create_list_box(Panel, Holder, Count) ->
+ Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL,
+ ListCtrl = wxListCtrl:new(Panel, [{style, Style},
+ {onGetItemText,
+ fun(_, Item, Col) -> get_row(Holder, Item, Col) end},
+ {onGetItemAttr,
+ fun(_, Item) -> get_attr(Holder, Item) end}
+ ]),
+ Li = wxListItem:new(),
+ AddListEntry = fun({Name, Align, DefSize}, Col) ->
+ wxListItem:setText(Li, Name),
+ wxListItem:setAlign(Li, Align),
+ wxListCtrl:insertColumn(ListCtrl, Col, Li),
+ wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize),
+ Col + 1
+ end,
+ ListItems = [{"Pid", ?wxLIST_FORMAT_CENTRE, 120},
+ {"Name or Initial Func", ?wxLIST_FORMAT_LEFT, 200},
+ {"Time", ?wxLIST_FORMAT_CENTRE, 50},
+ {"Reds", ?wxLIST_FORMAT_CENTRE, 50},
+ {"Memory", ?wxLIST_FORMAT_CENTRE, 50},
+ {"MsgQ", ?wxLIST_FORMAT_LEFT, 50},
+ {"Current Function", ?wxLIST_FORMAT_LEFT, 200}],
+ lists:foldl(AddListEntry, 0, ListItems),
+ wxListItem:destroy(Li),
+
+ wxListCtrl:connect(ListCtrl, size, [{skip, true}]),
+ wxListCtrl:connect(ListCtrl, command_list_item_activated),
+ wxListCtrl:connect(ListCtrl, command_list_item_right_click),
+ wxListCtrl:connect(ListCtrl, command_list_col_click),
+ wxListCtrl:connect(ListCtrl, command_list_item_selected),
+ wxListCtrl:setItemCount(ListCtrl, Count),
+ ListCtrl.
+
+change_node(Node) ->
+ etop_server ! {config, {node, Node}}.
+
+get_node() ->
+ etop_server ! {get_opt, node, self()},
+ receive
+ {node, Node} ->
+ Node
+ end.
+
+change_accum(Bool) ->
+ etop_server ! {config, {accumulate, Bool}}.
+
+change_lines(Int) when is_integer(Int) ->
+ etop_server ! {config, {lines, Int}}.
+
+get_lines() ->
+ etop_server ! {get_opt, lines, self()},
+ receive
+ {lines, Lines} ->
+ Lines
+ end.
+
+change_intv(NewIntv) ->
+ etop_server ! {config, {interval, NewIntv}}.
+
+get_intv() ->
+ etop_server ! {get_opt, intv, self()},
+ receive
+ {intv, Intv} ->
+ Intv
+ end.
+
+get_sort() ->
+ etop_server ! {get_opt, sort, self()},
+ receive {sort, Sort} ->
+ Sort
+ end.
+
+refresh_grid(Holder, Dir) ->
+ etop_server ! {update, Holder, Dir}.
+
+change_sort(Col, Dir) ->
+ case get_sort() =:= map_sort_order(Col) of
+ true when Dir =:= incr->
+ decr;
+ true when Dir =:= decr ->
+ incr;
+ false ->
+ change_sort(Col),
+ Dir
+ end.
+change_sort(?COL_PID) ->
+ etop_server ! {config, {sort, pid}};
+change_sort(?COL_NAME) ->
+ etop_server ! {config, {sort, name}};
+change_sort(?COL_TIME) ->
+ etop_server ! {config, {sort, runtime}};
+change_sort(?COL_REDS) ->
+ etop_server ! {config, {sort, reductions}};
+change_sort(?COL_MEM) ->
+ etop_server ! {config, {sort, memory}};
+change_sort(?COL_MSG) ->
+ etop_server ! {config, {sort, msg_q}};
+change_sort(?COL_FUN) ->
+ etop_server ! {config, {sort, cf}}.
+
+
+map_sort_order(Col) ->
+ case Col of
+ ?COL_PID -> pid;
+ ?COL_NAME -> name;
+ ?COL_TIME -> runtime;
+ ?COL_REDS -> reductions;
+ ?COL_MEM -> memory;
+ ?COL_MSG -> msg_q;
+ ?COL_FUN -> cf
+ end.
+
+to_str(Value) when is_atom(Value) ->
+ atom_to_list(Value);
+to_str({A, B}) ->
+ lists:concat([A, ":", B]);
+to_str({M,F,A}) ->
+ lists:concat([M, ":", F, "/", A]);
+to_str(Value) when is_list(Value) ->
+ case lists:all(fun(X) -> is_integer(X) end, Value) of
+ true -> Value;
+ false ->
+ lists:foldl(fun(X, Acc) ->
+ to_str(X) ++ " " ++ Acc end,
+ "", Value)
+ end;
+to_str(Port) when is_port(Port) ->
+ erlang:port_to_list(Port);
+to_str(Pid) when is_pid(Pid) ->
+ pid_to_list(Pid);
+to_str(No) when is_integer(No) ->
+ integer_to_list(No);
+to_str(ShouldNotGetHere) ->
+ erlang:error({?MODULE, to_str, ShouldNotGetHere}).
+
+clear_all(Grid) ->
+ lists:foreach(fun(I) ->
+ wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_SELECTED),
+ wxListCtrl:setItemState(Grid, I, 0, ?wxLIST_STATE_FOCUSED)
+ end,
+ lists:seq(0, wxListCtrl:getItemCount(Grid))).
+
+set_selected_items(Grid, Holder, Pids) ->
+ Count = wxListCtrl:getItemCount(Grid),
+ set_selected_items(Grid, Holder, 0, Pids, Count, []).
+
+set_selected_items(_, _, Index, Pids, Max, Acc) when Pids =:= []; Index =:= Max ->
+ Acc;
+set_selected_items(Grid, Holder, Index, Pids, Max, Acc) ->
+ {ok, Pid} = get_row(Holder, Index, pid),
+ case lists:member(Pid, Pids) of
+ true ->
+ wxListCtrl:setItemState(Grid, Index,
+ ?wxLIST_STATE_SELECTED,
+ ?wxLIST_STATE_SELECTED),
+ set_selected_items(Grid, Holder, Index+1, lists:delete(Pid, Pids), Max, [Pid | Acc]);
+ false ->
+ set_selected_items(Grid, Holder, Index+1, Pids, Max, Acc)
+ end.
+
+get_selected_items(Grid) ->
+ get_selected_items(Grid, -1, []).
+
+get_selected_items(Grid, Index, ItemAcc) ->
+ Item = wxListCtrl:getNextItem(Grid, Index,
+ [{geometry, ?wxLIST_NEXT_ALL}, {state, ?wxLIST_STATE_SELECTED}]),
+ case Item of
+ -1 ->
+ lists:reverse(ItemAcc);
+ _ ->
+ get_selected_items(Grid, Item, [Item+1 | ItemAcc])
+ end.
+
+interval_dialog(ParentFrame, ParentPid, Enabled, Value, Min, Max) ->
+ Dialog = wxDialog:new(ParentFrame, ?wxID_ANY, "Update Interval",
+ [{style, ?wxDEFAULT_DIALOG_STYLE bor
+ ?wxRESIZE_BORDER}]),
+ Panel = wxPanel:new(Dialog),
+ Check = wxCheckBox:new(Panel, ?wxID_ANY, "Periodical refresh"),
+ wxCheckBox:setValue(Check, Enabled),
+ Style = ?wxSL_HORIZONTAL bor ?wxSL_AUTOTICKS bor ?wxSL_LABELS,
+ Slider = wxSlider:new(Panel, ?wxID_ANY, Value, Min, Max,
+ [{style, Style}, {size, {200, -1}}]),
+ wxWindow:enable(Slider, [{enable, Enabled}]),
+ InnerSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ OKBtn = wxButton:new(Dialog, ?wxID_OK),
+ CancelBtn = wxButton:new(Dialog, ?wxID_CANCEL),
+ Buttons = wxStdDialogButtonSizer:new(),
+ wxStdDialogButtonSizer:addButton(Buttons, OKBtn),
+ wxStdDialogButtonSizer:addButton(Buttons, CancelBtn),
+
+ Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}],
+ wxSizer:add(InnerSizer, Check, Flags),
+ wxSizer:add(InnerSizer, Slider, Flags),
+ wxPanel:setSizer(Panel, InnerSizer),
+ TopSizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(TopSizer, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
+ wxSizer:add(TopSizer, Buttons, [{flag, ?wxEXPAND}]),
+ wxStdDialogButtonSizer:realize(Buttons),
+ wxWindow:setSizerAndFit(Dialog, TopSizer),
+ wxSizer:setSizeHints(TopSizer, Dialog),
+ wxCheckBox:connect(Check, command_checkbox_clicked,
+ [{callback, fun(#wx{event=#wxCommand{type = command_checkbox_clicked,
+ commandInt=Enable0}},_) ->
+ Enable = Enable0 > 0,
+ wxWindow:enable(Slider, [{enable, Enable}])
+ end}]),
+
+ wxButton:connect(OKBtn, command_button_clicked,
+ [{callback,
+ fun(#wx{id = ?wxID_OK,
+ event = #wxCommand{type = command_button_clicked}},_) ->
+ ParentPid ! {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)},
+ wxDialog:destroy(Dialog)
+ end}]),
+ wxButton:connect(CancelBtn, command_button_clicked,
+ [{callback,
+ fun(#wx{id = ?wxID_CANCEL,
+ event = #wxCommand{type = command_button_clicked}},_) ->
+ ParentPid ! cancel,
+ wxDialog:destroy(Dialog)
+ end}]),
+
+ wxDialog:show(Dialog).
+
+
+
+line_dialog(ParentFrame, OldLines, Holder, Dir) ->
+ Dialog = wxDialog:new(ParentFrame, ?wxID_ANY, "Enter number of lines",
+ [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
+ Panel = wxPanel:new(Dialog),
+ TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY, [{value, OldLines}]),
+ InnerSz = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(InnerSz, TxtCtrl, [{flag, ?wxEXPAND bor ?wxALL}]),
+ wxPanel:setSizer(Panel, InnerSz),
+
+ OKBtn = wxButton:new(Dialog, ?wxID_OK),
+ CancelBtn = wxButton:new(Dialog, ?wxID_CANCEL),
+
+ Buttons = wxStdDialogButtonSizer:new(),
+ wxStdDialogButtonSizer:addButton(Buttons, OKBtn),
+ wxStdDialogButtonSizer:addButton(Buttons, CancelBtn),
+
+ TopSz = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(TopSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
+ wxSizer:add(TopSz, Buttons, [{flag, ?wxEXPAND}]),
+ wxStdDialogButtonSizer:realize(Buttons),
+ wxWindow:setSizerAndFit(Dialog, TopSz),
+ wxSizer:setSizeHints(TopSz, Dialog),
+
+ wxButton:connect(OKBtn, command_button_clicked,
+ [{callback,
+ fun(#wx{id = ?wxID_OK,
+ event = #wxCommand{type = command_button_clicked}},_) ->
+ try
+ NewLines = list_to_integer(wxTextCtrl:getValue(TxtCtrl)),
+ case NewLines >= 0 of
+ true ->
+ change_lines(NewLines),
+ refresh_grid(Holder, Dir);
+ false ->
+ observer_wx:create_txt_dialog(Panel,
+ "Invalid input",
+ "Error",
+ ?wxICON_ERROR)
+ end
+ catch error:badarg ->
+ observer_wx:create_txt_dialog(Panel,
+ "Invalid input",
+ "Error",
+ ?wxICON_ERROR)
+ end,
+ wxDialog:destroy(Dialog)
+ end}]),
+ wxButton:connect(CancelBtn, command_button_clicked,
+ [{callback,
+ fun(#wx{id = ?wxID_CANCEL,
+ event = #wxCommand{type = command_button_clicked}},_) ->
+ wxDialog:destroy(Dialog)
+ end}]),
+ wxDialog:show(Dialog).
+
+
+create_attrs() ->
+ Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
+ Text = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT),
+ #attrs{even = wx:typeCast(wx:null(), wxListItemAttr),
+ odd = wxListItemAttr:new(Text, {240,240,255}, Font),
+ searched = wxListItemAttr:new(Text, {235,215,90}, Font)
+ }.
+
+dump_to_file(Parent, FileName, Holder) ->
+ case file:open(FileName, [write]) of
+ {ok, Fd} ->
+ Holder ! {dump, Fd};
+ {error, Reason} ->
+ FailMsg = file:format_error(Reason),
+ MD = wxMessageDialog:new(Parent, FailMsg),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD)
+ end.
+
+start_procinfo(Node, Pid, Frame, Opened) ->
+ case lists:member(Pid, Opened) of
+ true ->
+ Opened;
+ false ->
+ observer_procinfo:start(Node, Pid, Frame, self()),
+ [Pid | Opened]
+ end.
+
+
+
+get_selected_pids(Holder, Indices) ->
+ Ref = erlang:monitor(process, Holder),
+ Holder ! {get_pids, self(), Indices},
+ receive
+ {'DOWN', Ref, _, _, _} -> [];
+ {Holder, Res} ->
+ erlang:demonitor(Ref),
+ Res
+ end.
+
+get_row(Holder, Row, Column) ->
+ Ref = erlang:monitor(process, Holder),
+ Holder ! {get_row, self(), Row, Column},
+ receive
+ {'DOWN', Ref, _, _, _} -> "";
+ {Holder, Res} ->
+ erlang:demonitor(Ref),
+ Res
+ end.
+
+
+get_attr(Holder, Item) ->
+ Ref = erlang:monitor(process, Holder),
+ Holder ! {get_attr, self(), Item},
+ receive
+ {'DOWN', Ref, _, _, _} -> "";
+ {Holder, Res} ->
+ erlang:demonitor(Ref),
+ Res
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info({'DOWN', Ref, _, _, _},
+ #pro_wx_state{etop_monitor = EtopMon} = State) when Ref =:= EtopMon ->
+ io:format("Etop died~n"),
+ {stop, shutdown, State};
+
+handle_info({'DOWN', Ref, _, _, _},
+ #pro_wx_state{holder_monitor = HMonitor} = State) when Ref =:= HMonitor ->
+ io:format("Holder died~n"),
+ {stop, shutdown, State};
+
+handle_info({holder_updated, Count}, #pro_wx_state{grid = Grid,
+ holder = Holder,
+ selected_pids = Pids} = State) ->
+ Pids2 = wx:batch(fun() ->
+ wxListCtrl:setItemCount(Grid, Count),
+ clear_all(Grid),
+ Pids2 = set_selected_items(Grid, Holder, Pids),
+ wxListCtrl:refreshItems(Grid, 0, Count),
+ Pids2
+ end),
+ {noreply, State#pro_wx_state{selected_pids = Pids2}};
+
+handle_info(refresh_interval, #pro_wx_state{sort_dir = Dir,
+ holder = Holder} = State) ->
+ refresh_grid(Holder, Dir),
+ {noreply, State};
+
+handle_info({tracemenu_closed, TraceOpts, MatchSpecs}, State) ->
+ {noreply, State#pro_wx_state{tracemenu_opened = false,
+ trace_options = TraceOpts,
+ match_specs = MatchSpecs}};
+
+handle_info({procinfo_menu_closed, Pid},
+ #pro_wx_state{procinfo_menu_pids = Opened} = State) ->
+ NewPids = lists:delete(Pid, Opened),
+ {noreply, State#pro_wx_state{procinfo_menu_pids = NewPids}};
+
+handle_info({active, Node}, #pro_wx_state{holder = Holder,
+ sort_dir = Dir,
+ refr_timer = Timer0,
+ parent = Parent} = State) ->
+ create_pro_menu(Parent),
+ change_node(Node),
+ refresh_grid(Holder, Dir),
+ Timer = case Timer0 of
+ true ->
+ Intv = get_intv(),
+ {ok, Ref} = timer:send_interval(Intv, refresh_interval),
+ Ref;
+ false ->
+ false
+ end,
+ {noreply, State#pro_wx_state{refr_timer = Timer}};
+
+handle_info(not_active, #pro_wx_state{refr_timer = Timer0} = State) ->
+ Timer = case Timer0 of
+ false -> false;
+ true -> true;
+ Timer0 ->
+ timer:cancel(Timer0),
+ true
+ end,
+ {noreply, State#pro_wx_state{refr_timer=Timer,
+ selected_pids = [],
+ last_selected = undefined}};
+
+handle_info({node, Node}, #pro_wx_state{holder = Holder, sort_dir = Dir} = State) ->
+ change_node(Node),
+ refresh_grid(Holder, Dir),
+ {noreply, State#pro_wx_state{selected_pids = [],
+ last_selected = undefined}};
+
+handle_info(Info, State) ->
+ io:format("~p, ~p, Handled unexpected info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(Reason, #pro_wx_state{holder = Holder}) ->
+ io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]),
+ Holder ! stop,
+ etop:stop(),
+ ok.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+
+handle_cast(Msg, State) ->
+ io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#wx{id = ?ID_DUMP_TO_FILE},
+ #pro_wx_state{panel = Panel,
+ holder = Holder} = State) ->
+ FD = wxFileDialog:new(Panel,
+ [{style,?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
+ case wxFileDialog:showModal(FD) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(FD),
+ wxDialog:destroy(FD),
+ dump_to_file(Panel, Path, Holder);
+ _ ->
+ wxDialog:destroy(FD)
+ end,
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_ACCUMULATE, event = #wxCommand{type = command_menu_selected,
+ commandInt = CmdInt}},
+ #pro_wx_state{holder = Holder,
+ sort_dir = Dir} = State) when CmdInt =:= 1->
+ change_accum(true),
+ refresh_grid(Holder, Dir),
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_ACCUMULATE, event = #wxCommand{type = command_menu_selected,
+ commandInt = CmdInt}},
+ #pro_wx_state{holder = Holder,
+ sort_dir = Dir} = State) when CmdInt =:= 0 ->
+ change_accum(false),
+ refresh_grid(Holder, Dir),
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_NO_OF_LINES, event = #wxCommand{type = command_menu_selected}},
+ #pro_wx_state{panel = Panel,
+ sort_dir = Dir,
+ holder = Holder} = State) ->
+ OldLines = integer_to_list(get_lines()),
+ line_dialog(Panel, OldLines, Holder, Dir),
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}},
+ #pro_wx_state{sort_dir = Dir, holder = Holder} = State) ->
+ refresh_grid(Holder, Dir),
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_REFRESH_INTERVAL},
+ #pro_wx_state{panel = Panel, refr_timer=Timer0} = State) ->
+ Intv0 = get_intv() div 1000,
+ interval_dialog(Panel, self(), Timer0 /= false, Intv0, 1, 5*60),
+ receive
+ cancel ->
+ {noreply, State};
+ {true, Intv} ->
+ case Timer0 of
+ false -> ok;
+ _ -> timer:cancel(Timer0)
+ end,
+ change_intv(Intv),
+ {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval),
+ {noreply, State#pro_wx_state{refr_timer=Timer}};
+ {false, _} ->
+ case Timer0 of
+ false -> ok;
+ _ -> timer:cancel(Timer0)
+ end,
+ {noreply, State#pro_wx_state{refr_timer=false}}
+ end;
+
+handle_event(#wx{id = ?ID_KILL}, #pro_wx_state{popup_menu = Pop,
+ selected_pids = Pids,
+ last_selected = ToKill} = State) ->
+
+ wxWindow:show(Pop, [{show, false}]),
+ exit(ToKill, kill),
+ Pids2 = lists:delete(ToKill, Pids),
+ {noreply, State#pro_wx_state{selected_pids = Pids2, last_selected = undefined}};
+
+
+handle_event(#wx{id = ?ID_PROC},
+ #pro_wx_state{panel = Panel,
+ popup_menu = Pop,
+ last_selected = Pid,
+ procinfo_menu_pids = Opened} = State) ->
+ wxWindow:show(Pop, [{show, false}]),
+ Node = get_node(),
+ Opened2 = start_procinfo(Node, Pid, Panel, Opened),
+ {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}};
+
+handle_event(#wx{id = ?ID_TRACEMENU},
+ #pro_wx_state{popup_menu = Pop,
+ trace_options = Options,
+ match_specs = MatchSpecs,
+ selected_pids = Pids,
+ tracemenu_opened = false,
+ panel = Panel} = State) ->
+ wxWindow:show(Pop, [{show, false}]),
+ case Pids of
+ [] ->
+ observer_wx:create_txt_dialog(Panel, "No selected processes", "Tracer", ?wxICON_EXCLAMATION),
+ {noreply, State};
+ Pids ->
+ Node = get_node(),
+ observer_trace_wx:start(Node,
+ Pids,
+ Options,
+ MatchSpecs,
+ Panel,
+ self()),
+ {noreply, State#pro_wx_state{tracemenu_opened = true}}
+ end;
+
+handle_event(#wx{id = ?ID_TRACE_ALL_MENU, event = #wxCommand{type = command_menu_selected}},
+ #pro_wx_state{trace_options = Options,
+ match_specs = MatchSpecs,
+ tracemenu_opened = false,
+ panel = Panel} = State) ->
+ Node = get_node(),
+ observer_trace_wx:start(Node,
+ all,
+ Options,
+ MatchSpecs,
+ Panel,
+ self()),
+ {noreply, State#pro_wx_state{tracemenu_opened = true}};
+
+
+handle_event(#wx{id = ?ID_TRACE_NEW_MENU, event = #wxCommand{type = command_menu_selected}},
+ #pro_wx_state{trace_options = Options,
+ match_specs = MatchSpecs,
+ tracemenu_opened = false,
+ panel = Panel} = State) ->
+ Node = get_node(),
+ observer_trace_wx:start(Node,
+ new,
+ Options,
+ MatchSpecs,
+ Panel,
+ self()),
+ {noreply, State#pro_wx_state{tracemenu_opened = true}};
+
+handle_event(#wx{event=#wxSize{size={W,_}}},
+ #pro_wx_state{grid=Grid} = State) ->
+ wx:batch(fun() ->
+ Cols = wxListCtrl:getColumnCount(Grid),
+ Last = lists:foldl(fun(I, Last) ->
+ Last - wxListCtrl:getColumnWidth(Grid, I)
+ end, W-2, lists:seq(0, Cols - 2)),
+ Size = max(200, Last),
+ wxListCtrl:setColumnWidth(Grid, Cols-1, Size)
+ end),
+ {noreply, State};
+
+handle_event(#wx{event = #wxList{type = command_list_item_right_click,
+ itemIndex = Row}},
+ #pro_wx_state{popup_menu = Popup,
+ holder = Holder} = State) ->
+
+ case get_row(Holder, Row, pid) of
+ {error, undefined} ->
+ wxWindow:show(Popup, [{show, false}]),
+ undefined;
+ {ok, _} ->
+ wxWindow:move(Popup, wx_misc:getMousePosition()),
+ wxWindow:show(Popup)
+ end,
+ {noreply, State};
+
+handle_event(#wx{event = #wxList{type = command_list_item_selected,
+ itemIndex = Row}},
+ #pro_wx_state{grid = Grid,
+ popup_menu = Pop,
+ holder = Holder} = State) ->
+
+ NewPid = case get_row(Holder, Row, pid) of
+ {error, undefined} ->
+ undefined;
+ {ok, P} when is_pid(P) ->
+ P
+ end,
+ wxWindow:show(Pop, [{show, false}]),
+ Pids = get_selected_pids(Holder, get_selected_items(Grid)),
+ {noreply, State#pro_wx_state{selected_pids = Pids,
+ last_selected = NewPid}};
+
+handle_event(#wx{event = #wxList{type = command_list_col_click, col = Col}},
+ #pro_wx_state{sort_dir = OldDir,
+ holder = Holder} = State) ->
+ NewDir = change_sort(Col, OldDir),
+ refresh_grid(Holder, NewDir),
+ {noreply, State#pro_wx_state{sort_dir = NewDir}};
+
+handle_event(#wx{event = #wxList{type = command_list_item_activated}},
+ #pro_wx_state{panel = Panel,
+ procinfo_menu_pids= Opened,
+ last_selected = Pid} = State) when Pid =/= undefined ->
+ Node = get_node(),
+ Opened2 = start_procinfo(Node, Pid, Panel, Opened),
+ {noreply, State#pro_wx_state{procinfo_menu_pids = Opened2}};
+
+handle_event(Event, State) ->
+ io:format("~p~p, handle event ~p\n", [?MODULE, ?LINE, Event]),
+ {noreply, State}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+init_table_holder(Parent, Info, Attrs) ->
+ table_holder(#holder{parent = Parent,
+ info = Info,
+ attrs = Attrs}).
+
+
+table_holder(#holder{parent = Parent,
+ info = Info,
+ attrs = Attrs} = S0) ->
+ receive
+ {get_row, From, Row, Col} ->
+ get_row(From, Row, Col, Info#etop_info.procinfo),
+ table_holder(S0);
+ {get_attr, From, Row} ->
+ get_attr(From, Row, Attrs),
+ table_holder(S0);
+ {get_pids, From, Indices} ->
+ get_pids(From, Indices, Info#etop_info.procinfo),
+ table_holder(S0);
+ {update, #etop_info{procinfo = ProcInfo} = NewInfo} ->
+ Parent ! {holder_updated, length(ProcInfo)},
+ table_holder(S0#holder{info = NewInfo});
+ {dump, Fd} ->
+ etop_server ! {observer_dump, Fd, Info},
+ table_holder(S0);
+ stop ->
+ ok;
+ What ->
+ io:format("Table holder got ~p~n",[What]),
+ table_holder(S0)
+ end.
+
+
+get_procinfo_data(?COL_PID, #etop_proc_info{pid = Pid}) ->
+ Pid;
+get_procinfo_data(?COL_NAME, #etop_proc_info{name = Name}) ->
+ Name;
+get_procinfo_data(?COL_MEM, #etop_proc_info{mem = Mem}) ->
+ Mem;
+get_procinfo_data(?COL_TIME, #etop_proc_info{runtime = RT}) ->
+ RT;
+get_procinfo_data(?COL_REDS, #etop_proc_info{reds = Reds}) ->
+ Reds;
+get_procinfo_data(?COL_FUN, #etop_proc_info{cf = CF}) ->
+ CF;
+get_procinfo_data(?COL_MSG, #etop_proc_info{mq = MQ}) ->
+ MQ.
+
+get_pids(From, Indices, ProcInfo) ->
+ From ! {self(),
+ [X#etop_proc_info.pid || X <-
+ [lists:nth(I, ProcInfo) || I <- Indices]]}.
+
+get_row(From, Row, pid, Info) ->
+ Pid = case Row =:= -1 of
+ true ->
+ {error, undefined};
+ false ->
+ {ok, get_procinfo_data(?COL_PID, lists:nth(Row+1, Info))}
+ end,
+ From ! {self(), Pid};
+get_row(From, Row, Col, Info) ->
+ Data = case Row+1 > length(Info) of
+ true ->
+ null;
+ false ->
+ ProcInfo = lists:nth(Row+1, Info),
+ get_procinfo_data(Col, ProcInfo)
+ end,
+ From ! {self(), io_lib:format("~p", [Data])}.
+
+get_attr(From, Row, Attrs) ->
+ Attribute = case Row rem 2 =:= 0 of
+ true ->
+ Attrs#attrs.even;
+ false ->
+ Attrs#attrs.odd
+ end,
+ From ! {self(), Attribute}.
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
new file mode 100644
index 0000000000..b01b91c0b2
--- /dev/null
+++ b/lib/observer/src/observer_procinfo.erl
@@ -0,0 +1,511 @@
+%%
+%% %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_procinfo).
+
+-behaviour(wx_object).
+
+-export([start/4]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-define(CLOSE, 601).
+-define(REFRESH, 602).
+-define(SELECT_ALL, 603).
+-define(ID_NOTEBOOK, 604).
+
+-record(procinfo_state, {parent,
+ frame,
+ node,
+ pid,
+ module,
+ procinfo_stc,
+ modinfo_stc,
+ modcode_stc,
+ checklistbox,
+ current_view, % proc_info::atom | module_info::atom | module_code::atom
+ itemlist = [{backtrace, false},
+ {binary, false},
+ {catchlevel, false},
+ {current_function, false},
+ {dictionary, false},
+ {error_handler, true},
+ {garbage_collection, true},
+ {group_leader, true},
+ {heap_size, true},
+ {initial_call, false},
+ {last_calls, false},
+ {links, true},
+ {memory, false},
+ {message_queue_len, true},
+ {messages, false},
+ {monitored_by, false},
+ {monitors, false},
+ {priority, true},
+ {reductions, false},
+ {registered_name, false},
+ {sequential_trace_token, false},
+ {stack_size, false},
+ {status, false},
+ {suspending, false},
+ {total_heap_size, false},
+ {trace, false},
+ {trap_exit,true}]
+ }).
+
+
+
+
+start(Node, Process, ParentFrame, Parent) ->
+ wx_object:start(?MODULE, [Node, Process, ParentFrame, Parent], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([Node, Process, ParentFrame, Parent]) ->
+ try
+ State = #procinfo_state{parent = Parent,
+ node = Node,
+ pid = Process,
+ current_view = proc_info
+ },
+ ItemList = State#procinfo_state.itemlist,
+ Name = case observer_wx:try_rpc(Node, erlang, process_info, [Process, registered_name]) of
+ [] ->
+ undefined;
+ {registered_name, M} ->
+ M
+ end,
+ {initial_call, {Module, _, _}} = observer_wx:try_rpc(Node, erlang, process_info, [Process, initial_call]),
+ {Frame, ProcStc, CheckListBox, ModInfoStc, ModCodeStc} = setup(ParentFrame, Node, Process, ItemList, Module, Name),
+ {Frame, State#procinfo_state{frame = Frame,
+ module = Module,
+ procinfo_stc = ProcStc,
+ modinfo_stc = ModInfoStc,
+ modcode_stc = ModCodeStc,
+ checklistbox = CheckListBox}}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(ParentFrame, Node),
+ {stop, badrpc, #procinfo_state{parent = Parent,
+ pid = Process}}
+ end.
+
+
+setup(ParentFrame, Node, Pid, ItemList, Module, Name) ->
+ Title = case Name of
+ undefined ->
+ atom_to_list(Node) ++ ":" ++ atom_to_list(Module);
+ Name ->
+ atom_to_list(Node) ++ ":" ++ atom_to_list(Name)
+ end,
+ Frame = wxFrame:new(ParentFrame, ?wxID_ANY, Title,
+ [{style, ?wxDEFAULT_FRAME_STYLE},
+ {size, {900,900}}]),
+ Panel = wxPanel:new(Frame, []),
+ MainSz = wxBoxSizer:new(?wxHORIZONTAL),
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+ wxNotebook:connect(Notebook, command_notebook_page_changed),
+ {CodePanel, CheckListBox, CodeStc} = create_procinfo_page(Notebook, Node, Pid, ItemList),
+ {ModInfoPanel, ModInfoStc} = create_page(Notebook, Node, Module, module_info),
+ {ModCodePanel, ModCodeStc} = create_page(Notebook, Node, Module, module_code),
+ wxNotebook:addPage(Notebook, CodePanel, "Process information", []),
+ wxNotebook:addPage(Notebook, ModInfoPanel, "Module information", []),
+ wxNotebook:addPage(Notebook, ModCodePanel, "Module code", []),
+ MenuBar = wxMenuBar:new(),
+ create_menus(MenuBar),
+ wxWindow:setSizer(Panel, MainSz),
+ wxSizer:add(MainSz, Notebook, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxFrame:setMenuBar(Frame, MenuBar),
+ wxFrame:show(Frame),
+ wxFrame:connect(Frame, close_window),
+ wxMenu:connect(Frame, command_menu_selected),
+ {Frame, CodeStc, CheckListBox, ModInfoStc, ModCodeStc}.
+
+
+create_procinfo_page(Notebook, Node, Pid, ItemList) ->
+ Panel = wxPanel:new(Notebook),
+ MainSz = wxBoxSizer:new(?wxHORIZONTAL),
+ CheckSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "View"}]),
+ BtnSz = wxBoxSizer:new(?wxHORIZONTAL),
+
+ Stc = create_styled_txtctrl(Panel, proc_info),
+ Txt = get_formatted_values(Node, Pid, ItemList),
+ set_text(Stc, Txt, text),
+ Choices = [atom_to_list(Tag) || {Tag, _} <- ItemList],
+ CheckListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{choices, Choices},
+ {style, ?wxLB_EXTENDED},
+ {style, ?wxLB_SORT},
+ {style, ?wxLB_NEEDED_SB}]),
+ check_boxes(CheckListBox, ItemList),
+ wxCheckListBox:connect(CheckListBox, command_checklistbox_toggled),
+
+ SelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Select all"}]),
+ DeSelAllBtn = wxButton:new(Panel, ?SELECT_ALL, [{label, "Deselect all"}]),
+
+ wxButton:connect(SelAllBtn, command_button_clicked, [{userData, true}]),
+ wxButton:connect(DeSelAllBtn, command_button_clicked, [{userData, false}]),
+ wxWindow:setSizer(Panel, MainSz),
+ wxSizer:add(MainSz, Stc, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(CheckSz, CheckListBox, [{proportion, 1}]),
+ wxSizer:add(BtnSz, SelAllBtn),
+ wxSizer:add(BtnSz, DeSelAllBtn),
+ wxSizer:add(CheckSz, BtnSz),
+ wxSizer:add(MainSz, CheckSz, [{flag, ?wxEXPAND}]),
+ {Panel, CheckListBox, Stc}.
+
+create_page(Notebook, Node, Module, What) ->
+ Panel = wxPanel:new(Notebook, []),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ Stc = create_styled_txtctrl(Panel, What),
+ {Sort, Txt} = case What of
+ module_info ->
+ {text, get_formatted_modinfo(Node, Module)};
+ module_code ->
+ case get_src_file(Node, Module) of
+ {ok, File} ->
+ {file, File};
+ error->
+ {text, "Error! Could not read sourcefile"}
+ end
+ end,
+ set_text(Stc, Txt, Sort),
+ wxWindow:setSizer(Panel, Sizer),
+ wxSizer:add(Sizer, Stc, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ {Panel, Stc}.
+
+create_menus(MenuBar) ->
+ observer_wx:create_menu(
+ [
+ {"File", [#create_menu{id = ?CLOSE, text = "Close"}]},
+ {"View", [#create_menu{id = ?REFRESH, text = "Refresh"}]}
+ ],
+ MenuBar).
+
+check_boxes(CheckListBox, Bool, all) ->
+ lists:foreach(fun(Index) ->
+ wxCheckListBox:check(CheckListBox, Index, [{check, Bool}])
+ end,
+ lists:seq(0, wxControlWithItems:getCount(CheckListBox))).
+check_boxes(CheckListBox, ItemList) ->
+ lists:foldl(fun({_, Bool}, Index) ->
+ wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]),
+ Index+1
+ end,
+ 0, ItemList).
+
+create_styled_txtctrl(Parent, View) ->
+ FixedFont = wxFont:new(11, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxNORMAL,[]),
+ Stc = wxStyledTextCtrl:new(Parent),
+ wxStyledTextCtrl:styleClearAll(Stc),
+ wxStyledTextCtrl:styleSetFont(Stc, ?wxSTC_STYLE_DEFAULT, FixedFont),
+ wxStyledTextCtrl:setLexer(Stc, ?wxSTC_LEX_ERLANG),
+ wxStyledTextCtrl:setMarginType(Stc, 2, ?wxSTC_MARGIN_NUMBER),
+ W = wxStyledTextCtrl:textWidth(Stc, ?wxSTC_STYLE_LINENUMBER, "9"),
+ wxStyledTextCtrl:setMarginWidth(Stc, 2, W*3),
+
+ wxStyledTextCtrl:setSelectionMode(Stc, ?wxSTC_SEL_LINES),
+ wxStyledTextCtrl:setUseHorizontalScrollBar(Stc, false),
+
+ Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}},
+ {?wxSTC_ERLANG_COMMENT, {160,53,35}},
+ {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
+ {?wxSTC_ERLANG_NUMBER, {5,5,100}},
+ {?wxSTC_ERLANG_KEYWORD, {130,40,172}},
+ {?wxSTC_ERLANG_STRING, {170,45,132}},
+ {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
+ {?wxSTC_ERLANG_ATOM, {0,0,0}},
+ {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
+ {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
+ {?wxSTC_ERLANG_MACRO, {40,144,170}},
+ {?wxSTC_ERLANG_RECORD, {40,100,20}},
+ {?wxSTC_ERLANG_SEPARATOR,{0,0,0}},
+ {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
+ SetStyle = fun({Style, Color}) ->
+ wxStyledTextCtrl:styleSetFont(Stc, Style, FixedFont),
+ wxStyledTextCtrl:styleSetForeground(Stc, Style, Color)
+ end,
+ [SetStyle(Style) || Style <- Styles],
+
+ KeyWords = case View of
+ proc_info ->
+ get_procinfo_keywords();
+ module_info ->
+ get_modinfo_keywords();
+ module_code ->
+ get_erl_keywords()
+ end,
+ wxStyledTextCtrl:setKeyWords(Stc, 0, KeyWords),
+ Stc.
+
+get_erl_keywords() ->
+ L = ["after","begin","case","try","cond","catch","andalso","orelse",
+ "end","fun","if","let","of","query","receive","when","bnot","not",
+ "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
+ lists:flatten([K ++ " "|| K <- L] ++ [0]).
+get_procinfo_keywords() ->
+ L = ["backtrace","binary","catchlevel","current_function","dictionary",
+ "error_handler","garbage_collection","group_leader", "heap_size",
+ "initial_call","last_calls","links","memory","message_queue_len",
+ "messages","monitored_by","monitors", "priority","reductions",
+ "registered_name", "sequential_trace_token","stack_size","status",
+ "suspending", "total_heap_size","trace","trap_exit"],
+ lists:flatten([K ++ " "|| K <- L] ++ [0]).
+get_modinfo_keywords() ->
+ L = ["exports", "imports", "attributes", "compile"],
+ lists:flatten([K ++ " "|| K <- L] ++ [0]).
+
+get_formatted_values(Node, Process, ItemList) ->
+ TagList = [Tag || {Tag, Bool} <- ItemList, Bool =:= true],
+ Values = observer_wx:try_rpc(Node, erlang, process_info, [Process, TagList]),
+ lists:flatten(format_value(Values, [])).
+
+format_value([], Acc) ->
+ lists:reverse(Acc);
+format_value([{backtrace, Bin} | T], Acc) ->
+ format_value(T, [io_lib:format("{backtrace,~s}~n", [binary_to_list(Bin)]) | Acc]);
+format_value([H|T], Acc) ->
+ format_value(T, [io_lib:format("~p~n", [H]) | Acc]).
+
+get_formatted_modinfo(Node, Module) ->
+ Info = observer_wx:try_rpc(Node, Module, module_info, []),
+ lists:flatten([io_lib:format("~p~n", [I]) || I <- Info]).
+get_src_remote(Node, Module) ->
+ case observer_wx:try_rpc(Node, filename, find_src, [Module]) of
+ {error, _} ->
+ error;
+ {SrcFile, _} ->
+ case observer_wx:try_rpc(Node, file, read_file_info, [SrcFile ++ ".erl"]) of
+ {error, _} ->
+ error;
+ {ok, _} ->
+ {ok, SrcFile ++ ".erl"}
+ end
+ end.
+
+get_src_local(Module) ->
+ case filename:find_src(Module) of
+ {error, _} ->
+ error;
+ {SrcFile, _} ->
+ case file:read_file_info(SrcFile ++ ".erl") of
+ {error, _} ->
+ error;
+ {ok, _} ->
+ {ok, SrcFile ++ ".erl"}
+ end
+ end.
+
+get_src_file(Node, Module) ->
+ case get_src_remote(Node, Module) of
+ {ok, SrcFile} ->
+ {ok, SrcFile};
+ error ->
+ get_src_local(Module)
+ end.
+
+
+set_text(Stc, Text, text) ->
+ wxStyledTextCtrl:setReadOnly(Stc, false),
+ wxStyledTextCtrl:setText(Stc, Text),
+ wxStyledTextCtrl:setReadOnly(Stc, true);
+set_text(Stc, File, file) ->
+ wxStyledTextCtrl:setReadOnly(Stc, false),
+ wxStyledTextCtrl:loadFile(Stc, File),
+ wxStyledTextCtrl:setReadOnly(Stc, true).
+
+update_procinfo_page(Stc, Node, Process, ItemList) ->
+ Txt = get_formatted_values(Node, Process, ItemList),
+ set_text(Stc, Txt, text).
+update_modinfo_page(Stc, Node, Module) ->
+ Txt = get_formatted_modinfo(Node, Module),
+ set_text(Stc, Txt, text).
+update_modcode_page(Stc, Node, Module) ->
+ case get_src_file(Node, Module) of
+ {ok, File} ->
+ set_text(Stc, File, file);
+ error ->
+ set_text(Stc, "Error! Could not read sourcefile", text)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Callbacks%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+handle_event(#wx{event = #wxClose{type = close_window}},
+ State) ->
+ {stop, shutdown, State};
+
+handle_event(#wx{id = ?CLOSE,
+ event = #wxCommand{type = command_menu_selected}},
+ State) ->
+ {stop, shutdown, State};
+
+handle_event(#wx{id = ?REFRESH,
+ event = #wxCommand{type = command_menu_selected}},
+ #procinfo_state{current_view = Current,
+ frame = Frame,
+ node = Node,
+ pid = Pid,
+ procinfo_stc = Stc,
+ itemlist = ItemList} = State) when Current =:= proc_info ->
+ try
+ update_procinfo_page(Stc, Node, Pid, ItemList),
+ {noreply, State}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+handle_event(#wx{id = ?REFRESH,
+ event = #wxCommand{type = command_menu_selected}},
+ #procinfo_state{current_view = Current,
+ frame = Frame,
+ node = Node,
+ modinfo_stc = Stc,
+ module = Module} = State) when Current =:= module_info ->
+ try
+ update_modinfo_page(Stc, Node, Module),
+ {noreply, State}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+handle_event(#wx{id = ?REFRESH,
+ event = #wxCommand{type = command_menu_selected}},
+ #procinfo_state{current_view = Current,
+ modcode_stc = Stc,
+ frame = Frame,
+ node = Node,
+ module = Module} = State) when Current =:= module_code ->
+ try
+ update_modcode_page(Stc, Node, Module),
+ {noreply, State}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+
+handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK,
+ event = #wxNotebook{type = command_notebook_page_changed}},
+ #procinfo_state{frame = Frame,
+ module = Module,
+ procinfo_stc = ProcStc,
+ modcode_stc = CodeStc,
+ modinfo_stc = ModInfoStc,
+ node = Node,
+ pid = Pid,
+ itemlist = ItemList} = State) ->
+ try
+ Current = case observer_wx:check_page_title(Notebook) of
+ "Process information" ->
+ update_procinfo_page(ProcStc, Node, Pid, ItemList),
+ proc_info;
+ "Module information" ->
+ update_modinfo_page(ModInfoStc, Node, Module),
+ module_info;
+ "Module code" ->
+ update_modcode_page(CodeStc, Node, Module),
+ module_code
+ end,
+ {noreply, State#procinfo_state{current_view = Current}}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+handle_event(#wx{event = #wxCommand{type = command_checklistbox_toggled,
+ commandInt = Index},
+ obj = CheckListbox},
+ #procinfo_state{frame = Frame,
+ node = Node,
+ pid = Process,
+ procinfo_stc = Stc,
+ itemlist = ItemList} = State) ->
+ try
+ {Tag, _} = lists:nth(Index+1, ItemList),
+ ItemList2 = case wxCheckListBox:isChecked(CheckListbox, Index) of
+ true ->
+ lists:keyreplace(Tag, 1, ItemList, {Tag, true});
+ false ->
+ lists:keyreplace(Tag, 1, ItemList, {Tag, false})
+ end,
+ Txt = get_formatted_values(Node, Process, ItemList2),
+ set_text(Stc, Txt, text),
+ {noreply, State#procinfo_state{itemlist = ItemList2}}
+
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+handle_event(#wx{id = ?SELECT_ALL,
+ event = #wxCommand{type = command_button_clicked},
+ userData = Bool},
+ #procinfo_state{frame = Frame,
+ node = Node,
+ pid = Process,
+ itemlist = ItemList,
+ procinfo_stc = Stc,
+ checklistbox = CheckListBox} = State) ->
+ try
+ check_boxes(CheckListBox, Bool, all),
+ ItemList2 = lists:keymap(fun(_) ->
+ Bool
+ end,
+ 2, ItemList),
+ Txt = get_formatted_values(Node, Process, ItemList2),
+ set_text(Stc, Txt, text),
+ {noreply, State#procinfo_state{itemlist = ItemList2}}
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ {stop, badrpc, State}
+ end;
+
+handle_event(Event, State) ->
+ io:format("~p: ~p, Handle event: ~p~n", [?MODULE, ?LINE, Event]),
+ {noreply, State}.
+
+handle_info(Info, State) ->
+ io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+handle_call(Call, _From, State) ->
+ io:format("~p ~p: Got call ~p~n",[?MODULE, ?LINE, Call]),
+ {reply, ok, State}.
+
+handle_cast(Cast, State) ->
+ io:format("~p ~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]),
+ {noreply, State}.
+
+terminate(Reason, #procinfo_state{parent = Parent,
+ pid = Pid,
+ frame = Frame}) ->
+ io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]),
+ Parent ! {procinfo_menu_closed, Pid},
+ case Frame of
+ undefined ->
+ ok;
+ _ ->
+ wxFrame:destroy(Frame)
+ end,
+ ok.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
diff --git a/lib/observer/src/observer_sys.erl b/lib/observer/src/observer_sys.erl
new file mode 100644
index 0000000000..8db7bb0e46
--- /dev/null
+++ b/lib/observer/src/observer_sys.erl
@@ -0,0 +1,131 @@
+%%
+%% %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_sys).
+
+-export([node_info/0, node_name_str/1, no_procs_str/1, no_cpu_str/1,
+ no_cpu_available_str/1, no_cpu_online_str/1, tot_alloc_str/1,
+ proc_used_str/1, proc_alloc_str/1, atom_used_str/1, atom_alloc_str/1,
+ binary_alloc_str/1, code_alloc_str/1, ets_alloc_str/1]).
+
+-record(node_info, {node_name,
+ no_procs, % number of processes
+ no_cpu, % number of logical cpu's
+ no_cpu_available, %number of logical cpu's available
+ no_cpu_online, % number of logical cpu's online
+ tot_alloc, % total memory allocated
+ proc_used, % memory used by processes
+ proc_alloc, % memory alloc by processes,
+ atom_used, % memory used by atoms
+ atom_alloc, % memory allocated by atoms
+ binary_alloc, % memory allocated for binaries
+ code_alloc, % memory allocated by code
+ ets_alloc}).% memory allocated by ets
+
+
+%%%%%%%%%%%%%%%%%%%%%%% functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+node_info() ->
+ #node_info{node_name = node_name(),
+ no_procs = process_count(),
+ no_cpu = logical_cpus(),
+ no_cpu_available = logical_cpus_available(),
+ no_cpu_online = logical_cpus_online(),
+ tot_alloc = total_alloc(),
+ proc_used = processes_used(),
+ proc_alloc = processes_alloc(),
+ atom_used = atom_used(),
+ atom_alloc = atom_alloc(),
+ binary_alloc = binary_alloc(),
+ code_alloc = code_alloc(),
+ ets_alloc = ets_alloc()
+ }.
+
+node_name() ->
+ node().
+
+process_count() ->
+ erlang:system_info(process_count).
+
+logical_cpus() ->
+ erlang:system_info(logical_processors). % detected number of logical cpus configured on system
+
+logical_cpus_available() -> % detected number of logical cpus available to erlang runtime system
+ erlang:system_info(logical_processors_available).
+
+logical_cpus_online() -> % detected number of logical cpus online on system
+ erlang:system_info(logical_processors_online).
+
+total_alloc() ->
+ erlang:memory(total). % total amount of memory currently allocated
+
+processes_used() -> % amount of memory currently used by the erlang processes
+ erlang:memory(processes_used).
+
+processes_alloc() -> % allocated by erlang processes
+ erlang:memory(processes).
+
+atom_used() -> % amount of memory used for atoms
+ erlang:memory(atom_used).
+
+atom_alloc() -> % amount allocated for atoms
+ erlang:memory(atom).
+
+binary_alloc() -> % amount allocated for binaries
+ erlang:memory(binary).
+
+code_alloc() -> % amount allocated for code
+ erlang:memory(code).
+
+ets_alloc() -> % amount allocated for ets tables
+ erlang:memory(ets).
+
+
+%% formatting functions, from the record-value to string
+node_name_str(#node_info{node_name = ToReturn}) ->
+ erlang:atom_to_list(ToReturn).
+no_procs_str(#node_info{no_procs = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+no_cpu_str(#node_info{no_cpu = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+no_cpu_available_str(#node_info{no_cpu_available = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+no_cpu_online_str(#node_info{no_cpu_online = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+tot_alloc_str(#node_info{tot_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+proc_used_str(#node_info{proc_used = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+proc_alloc_str(#node_info{proc_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+atom_used_str(#node_info{atom_used = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+atom_alloc_str(#node_info{atom_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+binary_alloc_str(#node_info{binary_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+code_alloc_str(#node_info{code_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
+
+ets_alloc_str(#node_info{ets_alloc = ToReturn}) ->
+ erlang:integer_to_list(ToReturn).
diff --git a/lib/observer/src/observer_sys_wx.erl b/lib/observer/src/observer_sys_wx.erl
new file mode 100644
index 0000000000..5116891e91
--- /dev/null
+++ b/lib/observer/src/observer_sys_wx.erl
@@ -0,0 +1,313 @@
+%%
+%% %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_sys_wx).
+
+-behaviour(wx_object).
+
+-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_cast/2]).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-define(ID_REFRESH, 101).
+-define(ID_REFRESH_INTERVAL, 102).
+
+%% Records
+-record(sys_wx_state,
+ {parent,
+ panel,
+ menubar,
+ parent_notebook,
+ no_procs,
+ no_cpu,
+ no_cpu_available,
+ no_cpu_online,
+ tot_alloc,
+ proc_used,
+ proc_alloc,
+ atom_used,
+ atom_alloc,
+ binary_alloc,
+ code_alloc,
+ ets_alloc,
+ node_label,
+ node,
+ refr_timer = false,
+ refr_intv = 30}).
+
+start_link(Notebook, Parent) ->
+ wx_object:start_link(?MODULE, [Notebook, Parent], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init([Notebook, Parent]) ->
+ SysPanel = wxPanel:new(Notebook, []),
+
+ %% Setup sizers
+ SysSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ SysNodeSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Node:"}]),
+
+ SysLoadSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Load:"}]),
+ SysLeftLoadSizer = wxBoxSizer:new(?wxVERTICAL),
+ SysMidLoadSizer = wxBoxSizer:new(?wxHORIZONTAL),
+ SysRightLoadSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ SysMemSizer = wxStaticBoxSizer:new(?wxHORIZONTAL, SysPanel, [{label, "Memory:"}]),
+ SysLeftMemSizer = wxBoxSizer:new(?wxVERTICAL),
+ SysMidMemSizer = wxBoxSizer:new(?wxHORIZONTAL),
+ SysRightMemSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ wxSizer:add(SysSizer, SysNodeSizer, [{flag, ?wxEXPAND}]),
+ wxSizer:add(SysSizer, SysLoadSizer, [{flag, ?wxEXPAND}]),
+ wxSizer:add(SysSizer, SysMemSizer, [{flag, ?wxEXPAND}]),
+ wxSizer:add(SysLoadSizer, SysLeftLoadSizer),
+ wxSizer:add(SysLoadSizer, SysMidLoadSizer),
+ wxSizer:add(SysLoadSizer, SysRightLoadSizer),
+
+ wxSizer:add(SysMemSizer, SysLeftMemSizer),
+ wxSizer:add(SysMemSizer, SysMidMemSizer),
+ wxSizer:add(SysMemSizer, SysRightMemSizer),
+
+ wxSizer:addSpacer(SysMidLoadSizer, 90),
+ wxSizer:addSpacer(SysMidMemSizer, 70),
+
+ %% Create labels
+ NodeInfo = get_syspage_info(node()),
+ NodeLabel = create_info_label(SysPanel, SysNodeSizer, observer_sys:node_name_str(NodeInfo)),
+
+ create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's:"),
+ create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's available:"),
+ create_info_label(SysPanel, SysLeftLoadSizer, "logical CPU's online:"),
+ create_info_label(SysPanel, SysLeftLoadSizer, "existing processes:"),
+ NoCpuTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_str(NodeInfo)),
+ NoCpuAvTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_available_str(NodeInfo)),
+ NoCpuOnTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_cpu_online_str(NodeInfo)),
+ NoProcsTxt = create_info_label(SysPanel, SysRightLoadSizer, observer_sys:no_procs_str(NodeInfo)),
+
+ create_info_label(SysPanel, SysLeftMemSizer, "total allocated:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "used by processes:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "allocated for processes:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "used by atoms:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "allocated for atoms:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "allocated for binaries:"),
+ create_info_label(SysPanel, SysLeftMemSizer, "allocated for code"),
+ create_info_label(SysPanel, SysLeftMemSizer, "allocated for ETS:"),
+ TotAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:tot_alloc_str(NodeInfo)),
+ ProcUsedTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:proc_used_str(NodeInfo)),
+ ProcAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:proc_alloc_str(NodeInfo)),
+ AtomUsedTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:atom_used_str(NodeInfo)),
+ AtomAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:atom_alloc_str(NodeInfo)),
+ BinaryAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:binary_alloc_str(NodeInfo)),
+ CodeAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:code_alloc_str(NodeInfo)),
+ EtsAllocTxt = create_info_label(SysPanel, SysRightMemSizer, observer_sys:ets_alloc_str(NodeInfo)),
+
+ %% Create StateRecord
+ SysPanelState = #sys_wx_state{
+ parent = Parent,
+ panel = SysPanel,
+ parent_notebook = Notebook,
+ node_label = NodeLabel,
+ no_procs = NoProcsTxt,
+ no_cpu = NoCpuTxt,
+ no_cpu_available = NoCpuAvTxt,
+ no_cpu_online= NoCpuOnTxt,
+ tot_alloc = TotAllocTxt,
+ proc_used = ProcUsedTxt,
+ proc_alloc = ProcAllocTxt,
+ atom_used = AtomUsedTxt,
+ atom_alloc = AtomAllocTxt,
+ binary_alloc = BinaryAllocTxt,
+ code_alloc = CodeAllocTxt,
+ ets_alloc = EtsAllocTxt,
+ node = node()},
+
+ wxPanel:setSizer(SysPanel, SysSizer),
+ {SysPanel, SysPanelState}.
+
+get_syspage_info(Node) ->
+ observer_wx:try_rpc(Node, observer_sys, node_info, []).
+
+create_info_label(Panel, Sizer, Msg) ->
+ WxText = wxStaticText:new(Panel, ?wxID_ANY, Msg),
+ wxSizer:add(Sizer, WxText),
+ WxText.
+
+create_sys_menu(Parent) ->
+ View = {"View", [#create_menu{id = ?ID_REFRESH, text = "Refresh"},
+ #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh interval"}]},
+ observer_wx:create_menus(Parent, [View]).
+
+update_syspage(#sys_wx_state{node = Node} = State) ->
+ Info = get_syspage_info(Node),
+ update_info_label(node_label, Info, State#sys_wx_state.node_label),
+ update_info_label(no_procs, Info, State#sys_wx_state.no_procs),
+ update_info_label(no_cpu, Info, State#sys_wx_state.no_cpu),
+ update_info_label(no_cpu_available, Info, State#sys_wx_state.no_cpu_available),
+ update_info_label(no_cpu_online, Info, State#sys_wx_state.no_cpu_online),
+ update_info_label(tot_alloc, Info, State#sys_wx_state.tot_alloc),
+ update_info_label(proc_used, Info, State#sys_wx_state.proc_used),
+ update_info_label(proc_alloc, Info, State#sys_wx_state.proc_alloc),
+ update_info_label(atom_used, Info, State#sys_wx_state.atom_used),
+ update_info_label(atom_alloc, Info, State#sys_wx_state.atom_alloc),
+ update_info_label(binary_alloc, Info, State#sys_wx_state.binary_alloc),
+ update_info_label(code_alloc, Info, State#sys_wx_state.code_alloc),
+ update_info_label(ets_alloc, Info, State#sys_wx_state.ets_alloc).
+
+update_info_label(node_label, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:node_name_str(Info));
+update_info_label(no_procs, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:no_procs_str(Info));
+update_info_label(no_cpu, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_str(Info));
+update_info_label(no_cpu_available, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_available_str(Info));
+update_info_label(no_cpu_online, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:no_cpu_online_str(Info));
+update_info_label(tot_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:tot_alloc_str(Info));
+update_info_label(proc_used, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:proc_used_str(Info));
+update_info_label(proc_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:proc_alloc_str(Info));
+update_info_label(atom_used, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:atom_used_str(Info));
+update_info_label(atom_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:atom_alloc_str(Info));
+update_info_label(binary_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:binary_alloc_str(Info));
+update_info_label(code_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:code_alloc_str(Info));
+update_info_label(ets_alloc, Info, WxTxt) ->
+ wxStaticText:setLabel(WxTxt, observer_sys:ets_alloc_str(Info)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(refresh_interval, #sys_wx_state{panel = Panel,
+ node = Node} = State) ->
+ try
+ update_syspage(State)
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Panel, Node)
+ 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,
+ refr_timer = Timer0,
+ refr_intv = Intv} = State) ->
+ UpdState = State#sys_wx_state{node = Node},
+ create_sys_menu(Parent),
+ try
+ update_syspage(UpdState),
+ Timer = case Timer0 of
+ true ->
+ {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval),
+ Ref;
+ false ->
+ false
+ end,
+ {noreply, UpdState#sys_wx_state{refr_timer = Timer}}
+
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Panel, Node),
+ {noreply, State}
+ end;
+
+
+handle_info(not_active, #sys_wx_state{refr_timer = Timer0} = State) ->
+ Timer = case Timer0 of
+ false -> false;
+ true -> true;
+ Timer0 ->
+ timer:cancel(Timer0),
+ true
+ end,
+ {noreply, State#sys_wx_state{refr_timer = Timer}};
+
+handle_info(Info, State) ->
+ io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(Reason, _State) ->
+ io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]),
+ ok.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p~p: Unhandled cast ~p~n",[?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+handle_event(#wx{id = ?ID_REFRESH, event = #wxCommand{type = command_menu_selected}},
+ #sys_wx_state{node = Node, panel = Panel} = State) ->
+ try
+ update_syspage(State)
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Panel, Node)
+ end,
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_REFRESH_INTERVAL,
+ event = #wxCommand{type = command_menu_selected}},
+ #sys_wx_state{refr_timer = Timer0,
+ refr_intv = Intv0,
+ parent_notebook = Notebook} = State) ->
+ Parent = observer_tv_wx:get_wx_parent(Notebook),
+ case observer_tv_wx:interval_dialog(Parent, Timer0 /= false, Intv0, 1, 5*60) of
+ cancel ->
+ {noreply, State};
+ {true, Intv} ->
+ case Timer0 of
+ false -> ok;
+ _ -> timer:cancel(Timer0)
+ end,
+ {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval),
+ {noreply, State#sys_wx_state{refr_timer=Timer, refr_intv=Intv}};
+ {false, _} ->
+ case Timer0 of
+ false -> ok;
+ _ -> timer:cancel(Timer0)
+ end,
+ {noreply, State#sys_wx_state{refr_timer=false}}
+ end;
+
+handle_event(Event, State) ->
+ io:format("handle event ~p\n", [Event]),
+ {noreply, State}.
diff --git a/lib/observer/src/observer_trace_wx.erl b/lib/observer/src/observer_trace_wx.erl
new file mode 100644
index 0000000000..8e08b57b92
--- /dev/null
+++ b/lib/observer/src/observer_trace_wx.erl
@@ -0,0 +1,487 @@
+%%
+%% %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_trace_wx).
+
+-export([start/6]).
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+ handle_event/2, handle_cast/2]).
+
+-behaviour(wx_object).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-define(OPTIONS, 301).
+-define(SAVE_BUFFER, 302).
+-define(CLOSE, 303).
+-define(CLEAR, 304).
+-define(SAVE_TRACEOPTS, 305).
+-define(LOAD_TRACEOPTS, 306).
+
+
+-record(state, {
+ parent,
+ frame,
+ text_ctrl,
+ trace_options,
+ toggle_button,
+ node,
+ traceoptions_open,
+ traced_procs,
+ traced_funcs = dict:new(), % Key =:= Module::atom, Value =:= [ #traced_func ]
+ match_specs = []}). % [ #match_spec{} ]
+
+
+start(Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid) ->
+ wx_object:start(?MODULE, [Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid], []).
+
+init([Node, TracedProcs, TraceOpts, MatchSpecs, ParentFrame, ParentPid]) ->
+ State =
+ wx:batch(fun() ->
+ create_window(ParentFrame, TraceOpts)
+ end),
+
+ Frame = State#state.frame,
+ TraceOpts2 = State#state.trace_options,
+ TracedFuncs = State#state.traced_funcs,
+
+ wx_object:start(observer_traceoptions_wx,
+ [Frame, self(), Node, TraceOpts2, TracedFuncs, MatchSpecs],
+ []),
+
+ {Frame, State#state{parent = ParentPid,
+ node = Node,
+ traced_procs = TracedProcs,
+ match_specs = MatchSpecs,
+ traceoptions_open = true}}.
+
+
+create_window(ParentFrame, TraceOpts) ->
+ %% Create the window
+ Frame = wxFrame:new(ParentFrame, ?wxID_ANY, "Tracer",
+ [{style, ?wxDEFAULT_FRAME_STYLE},
+ {size, {900, 900}}]),
+ wxFrame:connect(Frame, close_window,[{skip,true}]),
+ Panel = wxPanel:new(Frame, []),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+
+ %% Menues
+ MenuBar = wxMenuBar:new(),
+ create_menues(MenuBar),
+ wxFrame:setMenuBar(Frame, MenuBar),
+ wxMenu:connect(Frame, command_menu_selected, []),
+
+ %% Buttons
+ ToggleButton = wxToggleButton:new(Panel, ?wxID_ANY, "Start Trace", []),
+ wxSizer:add(Sizer, ToggleButton, [{flag, ?wxALL},
+ {border, 5}]),
+ wxMenu:connect(ToggleButton, command_togglebutton_clicked, []),
+
+ TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY,
+ [{style,?wxTE_READONLY bor
+ ?wxTE_MULTILINE},
+ {size, {400, 300}}]),
+
+ wxSizer:add(Sizer, TxtCtrl, [{proportion, 1},
+ {flag, ?wxEXPAND}]),
+
+ %% Display window
+ wxWindow:setSizer(Panel, Sizer),
+ wxFrame:show(Frame),
+ #state{frame = Frame,
+ text_ctrl = TxtCtrl,
+ toggle_button = ToggleButton,
+ trace_options = TraceOpts#trace_options{main_window = false}}.
+
+create_menues(MenuBar) ->
+ observer_wx:create_menu(
+ [
+ {"File", [
+ #create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"},
+ #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"},
+ separator,
+ #create_menu{id = ?SAVE_BUFFER, text = "Save buffer"},
+ separator,
+ #create_menu{id = ?CLOSE, text = "Close"}
+ ]},
+ {"View", [
+ #create_menu{id = ?CLEAR, text = "Clear buffer"}
+ ]},
+ {"Options", [
+ #create_menu{id = ?OPTIONS, text = "Trace options"}
+ ]}
+ ],
+ MenuBar).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %Main window
+
+handle_event(#wx{id = ?CLOSE, event = #wxCommand{type = command_menu_selected}},
+ #state{parent = Parent,
+ trace_options = TraceOpts,
+ match_specs = MatchSpecs} = State) ->
+ Parent ! {tracemenu_closed, TraceOpts, MatchSpecs},
+ {stop, shutdown, State};
+
+handle_event(#wx{id = ?OPTIONS, event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame, trace_options = TraceOpts,
+ traced_funcs = TracedFuncs,
+ node = Node,
+ match_specs = MatchSpecs,
+ traceoptions_open = false} = State) ->
+
+ wx_object:start(observer_traceoptions_wx,
+ [Frame, self(), Node, TraceOpts, TracedFuncs, MatchSpecs],
+ []),
+
+ {noreply, State#state{traceoptions_open = true}};
+
+handle_event(#wx{id = ?CLEAR, event = #wxCommand{type = command_menu_selected}},
+ #state{text_ctrl = TxtCtrl} = State) ->
+ wxTextCtrl:clear(TxtCtrl),
+ {noreply, State};
+
+handle_event(#wx{id = ?SAVE_BUFFER, event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame, text_ctrl = TxtCtrl} = State) ->
+ Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
+ case wxFileDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(Dialog),
+ wxDialog:destroy(Dialog),
+ case filelib:is_file(Path) of
+ true ->
+ observer_wx:create_txt_dialog(Frame, "File already exists: " ++ Path ++ "\n",
+ "Error", ?wxICON_ERROR);
+ false ->
+ wxTextCtrl:saveFile(TxtCtrl, [{file, Path}])
+ end;
+ _ ->
+ wxDialog:destroy(Dialog),
+ ok
+ end,
+ {noreply, State};
+
+handle_event(#wx{id = ?SAVE_TRACEOPTS,
+ event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame,
+ trace_options = TraceOpts,
+ match_specs = MatchSpecs} = State) ->
+ Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
+ case wxFileDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(Dialog),
+ write_file(Frame, Path, TraceOpts, MatchSpecs);
+ _ ->
+ ok
+ end,
+ wxDialog:destroy(Dialog),
+ {noreply, State};
+
+handle_event(#wx{id = ?LOAD_TRACEOPTS,
+ event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame} = State) ->
+ Dialog = wxFileDialog:new(Frame, [{style, ?wxFD_FILE_MUST_EXIST}]),
+ State2 = case wxFileDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Path = wxFileDialog:getPath(Dialog),
+ read_settings(Path, State);
+ _ ->
+ State
+ end,
+ wxDialog:destroy(Dialog),
+ {noreply, State2};
+
+
+handle_event(#wx{event = #wxClose{type = close_window}},
+ #state{parent = Parent,
+ trace_options = TraceOpts,
+ match_specs = MatchSpecs} = State) ->
+ Parent ! {tracemenu_closed, TraceOpts, MatchSpecs},
+ {stop, shutdown, State};
+
+handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}},
+ #state{node = Node,
+ traced_procs = TracedProcs,
+ traced_funcs = TracedDict,
+ trace_options = TraceOpts,
+ text_ctrl = TextCtrl,
+ toggle_button = ToggleBtn} = State) ->
+
+ start_trace(Node, TracedProcs, TracedDict, TraceOpts),
+ wxTextCtrl:appendText(TextCtrl, "Start Trace:\n"),
+ wxToggleButton:setLabel(ToggleBtn, "Stop Trace"),
+ {noreply, State};
+
+handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}}, %%Stop tracing
+ #state{text_ctrl = TxtCtrl,
+ toggle_button = ToggleBtn} = State) ->
+ dbg:stop_clear(),
+ wxTextCtrl:appendText(TxtCtrl, "Stop Trace.\n"),
+ wxToggleButton:setLabel(ToggleBtn, "Start Trace"),
+ {noreply, State};
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#wx{event = What}, State) ->
+ io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info({updated_traceopts,
+ TraceOpts,
+ MatchSpecs,
+ TracedFuncs}, State) ->
+ {noreply, State#state{trace_options = TraceOpts,
+ match_specs = MatchSpecs,
+ traced_funcs = TracedFuncs,
+ traceoptions_open = false}};
+
+handle_info(traceopts_closed, State) ->
+ {noreply, State#state{traceoptions_open = false}};
+
+handle_info(Tuple, #state{text_ctrl = TxtCtrl} = State) when is_tuple(Tuple) ->
+ Text = textformat(Tuple),
+ wxTextCtrl:appendText(TxtCtrl, lists:flatten(Text)),
+ {noreply, State};
+
+handle_info(Any, State) ->
+ io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]),
+ {noreply, State}.
+
+
+terminate(Reason, #state{node = Node,
+ frame = Frame}) ->
+ try
+ case observer_wx:try_rpc(Node, erlang, whereis, [dbg]) of
+ undefined -> fine;
+ Pid -> exit(Pid, kill)
+ end,
+ io:format("~p terminating tracemenu. Reason: ~p~n", [?MODULE, Reason]),
+ wxFrame:destroy(Frame),
+ ok
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(Frame, Node),
+ wxFrame:destroy(Frame)
+ end.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ {noreply, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start_trace(Node, TracedProcs, TracedDict,
+ #trace_options{send = Send, treceive = Receive, functions = Functions,
+ events = Events, on_1st_spawn = On1Spawn,
+ on_all_spawn = AllSpawn, on_1st_link = On1Link,
+ on_all_link = AllLink}) ->
+ dbg:stop_clear(),
+ MyPid = self(),
+ HandlerFun = fun(NewMsg, _) ->
+ MyPid ! NewMsg
+ end,
+ dbg:tracer(process, {HandlerFun, []}),
+
+ case Node =:= node() of
+ true ->
+ ok;
+ false ->
+ dbg:n(Node)
+ end,
+
+ Recs = [{Send, send},
+ {Receive, 'receive'},
+ {Functions, call},
+ {Events, procs},
+ {On1Spawn, set_on_first_spawn},
+ {AllSpawn, set_on_spawn},
+ {On1Link, set_on_first_link},
+ {AllLink, set_on_link}],
+ Flags = [Assoc || {true, Assoc} <- Recs],
+
+ case TracedProcs of
+ all ->
+ dbg:p(all, Flags);
+ new ->
+ dbg:p(new, Flags);
+ _Pids ->
+ lists:foreach(fun(Pid) -> dbg:p(Pid, Flags) end, TracedProcs)
+ end,
+
+ case Functions of
+ true ->
+ trace_functions(TracedDict);
+ false ->
+ ok
+ end.
+
+textformat({died, Pid}) ->
+ io_lib:format("~w Process died.~n",[Pid]);
+textformat({shell_died, Old, New}) ->
+ io_lib:format("~w Shell Process died. Restarted as ~w~n~n",[Old,New]);
+textformat({trace, From, 'receive', Msg}) ->
+ io_lib:format("~w: rec ~s~n", [From,
+ tuple_space(Msg)]);
+textformat({trace, From, send, Msg, To}) ->
+ io_lib:format("~w: ! To: ~w Msg: ~s~n", [From,
+ To,
+ tuple_space(Msg)]);
+textformat({trace, From, call, Func}) ->
+ io_lib:format("~w: call ~s~n",[From, ffunc(Func)]);
+textformat({trace, From, spawn, Data}) ->
+ io_lib:format("~w: spawn ~p~n", [From, Data]);
+textformat({trace, From, link, Data}) ->
+ io_lib:format("~w: link ~p~n", [From, Data]);
+textformat({trace, From, unlink, Data}) ->
+ io_lib:format("~w: U-lnk ~p~n", [From, Data]);
+
+textformat({trace, From, Op, Data}) ->
+ io_lib:format("~w: ~w ~p~n", [From, Op, Data]);
+
+textformat({print, Format, Args}) ->
+ io_lib:format(Format, Args);
+textformat(Other) ->
+ io_lib:format("~p~n",[Other]).
+
+
+tuple_space(X) when is_tuple(X) -> print(size(X), X, "}");
+tuple_space(X) -> io_lib:format("~p",[X]).
+
+
+ffunc({M,F, Argl}) ->
+ io_lib:format("~w:~w(~s)", [M, F, fargs(Argl)]);
+ffunc(X) -> tuple_space(X).
+
+fargs([]) -> [];
+fargs([A]) -> tuple_space(A); %% last arg
+fargs([A|Args]) -> [tuple_space(A),", "|fargs(Args)].
+
+print(0 , _X, Buff) -> ["{"|Buff];
+print(1 , X, Buff) ->
+ Str = tuple_space(element(1, X)),
+ ["{",Str|Buff];
+print(Num, X, Buff) ->
+ Str = tuple_space(element(Num, X)),
+ print(Num-1, X, [", ",Str|Buff]).
+
+trace_functions(TracedDict) ->
+ Trace = fun(KeyAtom, RecordList, acc_in) ->
+
+ lists:foreach(fun(#traced_func{func_name = Function,
+ arity = Arity,
+ match_spec = #match_spec{term_ms = MS}}) ->
+ dbg:tpl({KeyAtom, Function, Arity}, MS)
+ end,
+ RecordList),
+ acc_in
+ end,
+ dict:fold(Trace, acc_in, TracedDict).
+
+
+write_file(Frame, Filename, #trace_options{send = Send,
+ treceive = Receive,
+ functions = Functions,
+ events = Events,
+ on_1st_spawn = On1stSpawn,
+ on_all_spawn = OnAllSpawn,
+ on_1st_link = On1stLink,
+ on_all_link = OnAllLink},
+ MatchSpecs) ->
+
+ FormattedMatchSpecs = lists:flatten(lists:foldl(
+ fun(#match_spec{alias = A, term_ms = T, fun2ms = F}, Acc) ->
+ [io_lib:format("{alias, ~p, term_ms, ~p, fun2ms, ~p}.\n",
+ [A, T, F]) | Acc]
+ end, [], MatchSpecs)),
+
+ Binary =
+ list_to_binary("%%%\n%%% This file is generated by Observer\n"
+ "%%%\n%%% DO NOT EDIT!\n%%%\n"
+ "{send, " ++ atom_to_list(Send) ++ "}.\n"
+ "{treceive, " ++ atom_to_list(Receive) ++ "}.\n"
+ "{functions, " ++ atom_to_list(Functions) ++ "}.\n"
+ "{events, " ++ atom_to_list(Events) ++ "}.\n"
+ "{on_1st_spawn, " ++ atom_to_list(On1stSpawn) ++ "}.\n"
+ "{on_all_spawn, " ++ atom_to_list(OnAllSpawn) ++ "}.\n"
+ "{on_1st_link, " ++ atom_to_list(On1stLink) ++ "}.\n"
+ "{on_all_link, " ++ atom_to_list(OnAllLink) ++ "}.\n"
+ ++ FormattedMatchSpecs),
+
+ case file:write_file(Filename, Binary) of
+ ok ->
+ success;
+ {error, Reason} ->
+ FailMsg = file:format_error(Reason),
+ observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR)
+ end.
+
+
+read_settings(Filename, #state{frame = Frame} = State) ->
+ case file:consult(Filename) of
+ {ok, Terms} ->
+ {TraceOpts, MatchSpecs} = parse_settings(Terms, {#trace_options{}, []}),
+ State#state{trace_options = TraceOpts, match_specs = MatchSpecs};
+ {error, _} ->
+ observer_wx:create_txt_dialog(Frame, "Could not load settings", "Error", ?wxICON_ERROR),
+ State
+ end.
+
+
+parse_settings([], {TraceOpts, MatchSpecs}) ->
+ {TraceOpts, MatchSpecs};
+parse_settings([{send, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{send = Bool}, MS});
+parse_settings([{treceive, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{treceive = Bool}, MS});
+parse_settings([{functions, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{functions = Bool}, MS});
+parse_settings([{events, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{events = Bool}, MS});
+parse_settings([{on_1st_spawn, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{on_1st_spawn = Bool}, MS});
+parse_settings([{on_all_spawn, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{on_all_spawn = Bool}, MS});
+parse_settings([{on_1st_link, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{on_1st_link = Bool}, MS});
+parse_settings([{on_all_link, Bool} | T], {Opts, MS}) ->
+ parse_settings(T, {Opts#trace_options{on_all_link = Bool}, MS});
+parse_settings([{alias, A, term_ms, TermMS, fun2ms, F} | T], {Opts, MatchSpecs}) ->
+ Alias = case A of
+ undefined -> A;
+ _ -> lists:flatten(io_lib:format("~s", [A]))
+ end,
+ Fun2MS = case F of
+ undefined -> F;
+ _ -> lists:flatten(io_lib:format("~s", [F]))
+ end,
+ parse_settings(T, {Opts, [#match_spec{alias = Alias,
+ term_ms = TermMS,
+ str_ms = lists:flatten(io_lib:format("~p", [TermMS])),
+ fun2ms = Fun2MS}
+ | MatchSpecs]}).
diff --git a/lib/observer/src/observer_traceoptions_wx.erl b/lib/observer/src/observer_traceoptions_wx.erl
new file mode 100644
index 0000000000..7244efdc50
--- /dev/null
+++ b/lib/observer/src/observer_traceoptions_wx.erl
@@ -0,0 +1,1066 @@
+%%
+%% %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_traceoptions_wx).
+
+-include_lib("wx/include/wx.hrl").
+-include("observer_defs.hrl").
+
+-export([start/6]).
+-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
+ handle_event/2, handle_cast/2]).
+
+-behaviour(wx_object).
+
+-record(traceopts_state, {
+ parent,
+ frame,
+ tree,
+ boxes,
+ functionpage_listbox,
+ matchpage_styled_txtctrl,
+ matchpage_listbox,
+ popup_open = false,
+ module_popup_dialog,
+ module_popup_checklistbox,
+ matchspec_popup_dialog,
+ matchspec_popup_listbox,
+ matchspec_popup_styled_txtctrl,
+ match_specs, % [ #match_spec{} ]
+ checked_funcs = [],
+ traced_funcs, % Key =:= Module::atom, Value =:= [ #traced_func{} ]
+ trace_options}).
+
+
+-record(boxes, {send, 'receive', functions, events,
+ on_spawn, on_link, all_spawn, all_link}).
+
+-define(TRACEOPTS_FRAME, 501).
+
+-define(MATCHPAGE_ADDFUN, 502).
+-define(MATCHPAGE_ADDMS, 503).
+-define(MATCHPAGE_ADDMS_ALIAS, 504).
+-define(MATCHPAGE_LISTBOX, 505).
+
+-define(MATCH_POPUP_DIALOG, 506).
+
+-define(MODULEPOPUP_SELECT, 507).
+-define(MODULEPOPUP_SELALL, 508).
+-define(MODULEPOPUP_CHECKLISTBOX, 509).
+-define(MODULEPOPUP_TXTCTRL, 510).
+-define(MODULEPOPUP_DIALOG, 511).
+
+-define(FUNCTIONPAGE_LISTBOX, 512).
+-define(FUNCTIONPAGE_TXTCTRL, 513).
+
+
+start(ParentFrame, ParentPid, Node, TraceOpts, TracedFuncs, MatchSpecs) ->
+ wx_object:start(?MODULE, [ParentFrame, ParentPid, Node, TraceOpts,
+ TracedFuncs, MatchSpecs], []).
+
+init([ParentFrame, ParentPid, Node, TraceOpts, TracedFuncs, MatchSpecs]) ->
+ try
+ {Frame, Tree, Boxes, ModuleListBox, MatchTxtCtrl, MatchListBox}
+ = setup(ParentFrame, Node, TraceOpts, TracedFuncs, MatchSpecs),
+
+ {Frame,
+ #traceopts_state{parent = ParentPid,
+ frame = Frame,
+ tree = Tree,
+ functionpage_listbox = ModuleListBox,
+ matchpage_styled_txtctrl = MatchTxtCtrl,
+ matchpage_listbox = MatchListBox,
+ boxes = Boxes,
+ match_specs = MatchSpecs,
+ traced_funcs = TracedFuncs,
+ trace_options = TraceOpts}}
+
+ catch error:{badrpc, _} ->
+ observer_wx:return_to_localnode(ParentFrame, Node),
+ {stop, badrpc, #traceopts_state{}}
+ end.
+
+
+setup(ParentFrame, Node, TraceOpts, TracedFuncs, MatchSpecs) ->
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup main window
+
+ Frame = wxFrame:new(ParentFrame, ?TRACEOPTS_FRAME, "Trace options",
+ [{style, ?wxRESIZE_BORDER bor ?wxCLOSE_BOX},
+ {size, {400, 500}}]),
+ Panel = wxPanel:new(Frame, []),
+ MainSz = wxBoxSizer:new(?wxVERTICAL),
+ Notebook = wxNotebook:new(Panel, ?wxID_ANY),
+ Modules = get_modules(Node),
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup tracing page
+
+ OptPanel = wxPanel:new(Notebook),
+ OptMainSz = wxBoxSizer:new(?wxVERTICAL),
+ TopSz = wxBoxSizer:new(?wxHORIZONTAL),
+ TopLeftSz = wxStaticBoxSizer:new(?wxVERTICAL, OptPanel,
+ [{label, "Tracing options"}]),
+ TopRightSz = wxStaticBoxSizer:new(?wxVERTICAL, OptPanel,
+ [{label, "Inheritance options:"}]),
+
+ SendBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace send", []),
+ check_box(SendBox, TraceOpts#trace_options.send),
+ RecBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace receive", []),
+ check_box(RecBox, TraceOpts#trace_options.treceive),
+ FuncBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace functions", []),
+ check_box(FuncBox, TraceOpts#trace_options.functions),
+ EventBox = wxCheckBox:new(OptPanel, ?wxID_ANY, "Trace events", []),
+ check_box(EventBox, TraceOpts#trace_options.events),
+
+ {SpawnBox, SpwnAllRadio, SpwnFirstRadio} =
+ optionpage_top_right(OptPanel, TopRightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"),
+ {LinkBox, LinkAllRadio, LinkFirstRadio} =
+ optionpage_top_right(OptPanel, TopRightSz, [{flag, ?wxBOTTOM},{border, 5}], "link"),
+ SpawnBool = TraceOpts#trace_options.on_all_spawn or TraceOpts#trace_options.on_1st_spawn,
+ LinkBool = TraceOpts#trace_options.on_all_link or TraceOpts#trace_options.on_1st_link,
+ check_box(SpawnBox, SpawnBool),
+ check_box(LinkBox, LinkBool),
+ enable({SpawnBox, SpwnAllRadio, SpwnFirstRadio}),
+ enable({LinkBox, LinkAllRadio, LinkFirstRadio}),
+ wxRadioButton:setValue(SpwnAllRadio, TraceOpts#trace_options.on_all_spawn),
+ wxRadioButton:setValue(SpwnFirstRadio, TraceOpts#trace_options.on_1st_spawn),
+ wxRadioButton:setValue(LinkAllRadio, TraceOpts#trace_options.on_all_link),
+ wxRadioButton:setValue(LinkFirstRadio, TraceOpts#trace_options.on_1st_link),
+
+ wxSizer:add(TopLeftSz, SendBox, []),
+ wxSizer:add(TopLeftSz, RecBox, []),
+ wxSizer:add(TopLeftSz, FuncBox, []),
+ wxSizer:add(TopLeftSz, EventBox, []),
+ wxSizer:add(TopLeftSz, 150, -1),
+
+ wxSizer:add(TopSz, TopLeftSz, [{flag, ?wxEXPAND}]),
+ wxSizer:add(TopSz, TopRightSz,[{flag, ?wxEXPAND}]),
+ wxSizer:add(OptMainSz, TopSz, []),
+ wxWindow:setSizer(OptPanel, OptMainSz),
+ wxNotebook:addPage(Notebook, OptPanel, "Tracing"),
+
+%%%%%%%%%%%%%%%%%%%%%%%% Setup functions page
+
+ FuncPanel = wxPanel:new(Notebook),
+ FuncMainSz = wxBoxSizer:new(?wxVERTICAL),
+ ModuleSz = wxStaticBoxSizer:new(?wxVERTICAL, FuncPanel, [{label, "Select module"}]),
+ TreeSz = wxStaticBoxSizer:new(?wxVERTICAL, FuncPanel, [{label, "Selected functions"}]),
+
+ AllModules = atomlist_to_stringlist(Modules),
+ ModuleTxtCtrl = wxTextCtrl:new(FuncPanel, ?FUNCTIONPAGE_TXTCTRL),
+ ModuleListBox = wxListBox:new(FuncPanel, ?FUNCTIONPAGE_LISTBOX, [{choices, AllModules}, {style, ?wxLB_SINGLE}]),
+ TreeCtrl = wxTreeCtrl:new(FuncPanel),
+ wxTreeCtrl:addRoot(TreeCtrl, atom_to_list(Node)),
+ update_tree(TreeCtrl, TracedFuncs),
+
+ wxTextCtrl:connect(ModuleTxtCtrl, command_text_updated,
+ [{userData, AllModules}]),
+ wxListBox:connect(ModuleListBox, command_listbox_doubleclicked),
+ wxTreeCtrl:connect(TreeCtrl, command_tree_item_activated),
+
+ wxSizer:add(ModuleSz, ModuleTxtCtrl, [{flag, ?wxEXPAND}]),
+ wxSizer:add(ModuleSz, ModuleListBox, [{flag, ?wxEXPAND}]),
+ wxSizer:add(TreeSz, TreeCtrl, [{flag, ?wxEXPAND},{proportion, 1}]),
+ wxSizer:add(FuncMainSz, ModuleSz, [{flag, ?wxEXPAND}]),
+ wxSizer:add(FuncMainSz, TreeSz, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxWindow:setSizer(FuncPanel, FuncMainSz),
+ wxNotebook:addPage(Notebook, FuncPanel, "Functions"),
+
+
+%%%%%%%%%%%%%%%%%%% Setup match specification page
+
+ {MatchPanel, _, MatchTxtCtrl, MatchListBox} = create_matchspec_page(Notebook, MatchSpecs, matchpage),
+ wxNotebook:addPage(Notebook, MatchPanel, "Match Specs"),
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Setup Dialog
+
+ wxSizer:add(MainSz, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ OKBtn = wxButton:new(Panel, ?wxID_OK, []),
+ CancelBtn = wxButton:new(Panel, ?wxID_CANCEL, []),
+ DialogBtnSz = wxStdDialogButtonSizer:new(),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, OKBtn),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn),
+ wxStdDialogButtonSizer:realize(DialogBtnSz),
+ wxSizer:add(MainSz, DialogBtnSz),
+ wxWindow:setSizer(Panel, MainSz),
+
+ Boxes = #boxes{send = SendBox,
+ 'receive' = RecBox,
+ functions = FuncBox,
+ events = EventBox,
+ on_spawn = #on_spawn{checkbox = SpawnBox,
+ all_spawn = SpwnAllRadio,
+ first_spawn = SpwnFirstRadio},
+ all_spawn = SpwnAllRadio,
+ on_link = #on_link{checkbox = LinkBox,
+ all_link = LinkAllRadio,
+ first_link = LinkFirstRadio},
+ all_link = LinkAllRadio},
+
+
+ wxButton:connect(OKBtn, command_button_clicked, [{userData, trace_options}]),
+ wxButton:connect(CancelBtn, command_button_clicked, [{userData, trace_options}]),
+ wxFrame:connect(Frame, close_window, []),
+ wxFrame:show(Frame),
+ {Frame, TreeCtrl, Boxes, ModuleListBox, MatchTxtCtrl, MatchListBox}.
+
+
+filter_listbox_data(Input, Data, ListBox) ->
+ FilteredData = [X || X <- Data, re:run(X, Input) =/= nomatch],
+ wxListBox:clear(ListBox),
+ wxListBox:appendStrings(ListBox, FilteredData),
+ FilteredData.
+
+get_modules(Node) ->
+ lists:sort([Module || {Module, _} <- observer_wx:try_rpc(Node, code, all_loaded, [])]).
+
+optionpage_top_right(Panel, TopRightSz, Options, Text) ->
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ ChkBox = wxCheckBox:new(Panel, ?wxID_ANY, "Inherit on " ++ Text, []),
+ RadioSz = wxBoxSizer:new(?wxVERTICAL),
+ Radio1 = wxRadioButton:new(Panel, ?wxID_ANY, "All " ++ Text, [{style, ?wxRB_GROUP}]),
+ Radio2 = wxRadioButton:new(Panel, ?wxID_ANY, "First " ++ Text ++ " only", []),
+ wxSizer:add(Sizer, ChkBox, []),
+ wxSizer:add(RadioSz, Radio1, []),
+ wxSizer:add(RadioSz, Radio2, []),
+ wxSizer:add(Sizer, RadioSz, [{flag, ?wxLEFT},{border, 20}]),
+ wxSizer:add(TopRightSz, Sizer, Options),
+ wxCheckBox:connect(ChkBox, command_checkbox_clicked, [{userData, {ChkBox, Radio1, Radio2}}]),
+ {ChkBox, Radio1, Radio2}.
+
+
+read_trace_boxes(ChkBoxes = #boxes{on_spawn = OnSpawn, on_link = OnLink}, Options) ->
+ {On1stSpawn2, OnAllSpawn2} =
+ case wxCheckBox:isChecked(OnSpawn#on_spawn.checkbox) of
+ true ->
+ OnAllSpawn = wxRadioButton:getValue(OnSpawn#on_spawn.all_spawn),
+ On1stSpawn = wxRadioButton:getValue(OnSpawn#on_spawn.first_spawn),
+ {On1stSpawn, OnAllSpawn};
+ false ->
+ {false, false}
+ end,
+ {On1stLink2, OnAllLink2} =
+ case wxCheckBox:isChecked(OnLink#on_link.checkbox) of
+ true ->
+ OnAllLink = wxRadioButton:getValue(OnLink#on_link.all_link),
+ On1stLink = wxRadioButton:getValue(OnLink#on_link.first_link),
+ {On1stLink, OnAllLink};
+ false ->
+ {false, false}
+ end,
+ Options#trace_options{send = wxCheckBox:isChecked(ChkBoxes#boxes.send),
+ treceive = wxCheckBox:isChecked(ChkBoxes#boxes.'receive'),
+ functions = wxCheckBox:isChecked(ChkBoxes#boxes.functions),
+ events = wxCheckBox:isChecked(ChkBoxes#boxes.events),
+ on_all_spawn = OnAllSpawn2,
+ on_1st_spawn = On1stSpawn2,
+ on_all_link = OnAllLink2,
+ on_1st_link = On1stLink2}.
+
+
+create_styled_txtctrl(Parent) ->
+ FixedFont = wxFont:new(12, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxNORMAL,[]),
+ Ed = wxStyledTextCtrl:new(Parent),
+ wxStyledTextCtrl:styleClearAll(Ed),
+ wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
+ wxStyledTextCtrl:setLexer(Ed, ?wxSTC_LEX_ERLANG),
+ wxStyledTextCtrl:setMarginType(Ed, 1, ?wxSTC_MARGIN_NUMBER),
+ wxStyledTextCtrl:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
+ wxStyledTextCtrl:setUseHorizontalScrollBar(Ed, false),
+
+ Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}},
+ {?wxSTC_ERLANG_COMMENT, {160,53,35}},
+ {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
+ {?wxSTC_ERLANG_NUMBER, {5,5,100}},
+ {?wxSTC_ERLANG_KEYWORD, {130,40,172}},
+ {?wxSTC_ERLANG_STRING, {170,45,132}},
+ {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
+ {?wxSTC_ERLANG_ATOM, {0,0,0}},
+ {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
+ {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
+ {?wxSTC_ERLANG_MACRO, {40,144,170}},
+ {?wxSTC_ERLANG_RECORD, {40,100,20}},
+ {?wxSTC_ERLANG_SEPARATOR,{0,0,0}},
+ {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
+ SetStyle = fun({Style, Color}) ->
+ wxStyledTextCtrl:styleSetFont(Ed, Style, FixedFont),
+ wxStyledTextCtrl:styleSetForeground(Ed, Style, Color)
+ end,
+ [SetStyle(Style) || Style <- Styles],
+ wxStyledTextCtrl:setKeyWords(Ed, 0, keyWords()),
+ Ed.
+
+
+keyWords() ->
+ L = ["after","begin","case","try","cond","catch","andalso","orelse",
+ "end","fun","if","let","of","query","receive","when","bnot","not",
+ "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
+ lists:flatten([K ++ " " || K <- L] ++ [0]).
+
+
+enable({CheckBox, AllRadio, FirstRadio}) ->
+ case wxCheckBox:isChecked(CheckBox) of
+ false ->
+ wxWindow:disable(AllRadio),
+ wxWindow:disable(FirstRadio);
+ true ->
+ wxWindow:enable(AllRadio),
+ wxWindow:enable(FirstRadio)
+ end.
+
+
+check_box(ChkBox, Bool) ->
+ case Bool of
+ true ->
+ wxCheckBox:set3StateValue(ChkBox, ?wxCHK_CHECKED);
+ false ->
+ ignore
+ end.
+
+parse_record_function_names(RecordList) ->
+ StrList = [atom_to_list(FName) ++ "/" ++ integer_to_list(Arity)
+ || #traced_func{func_name = FName, arity = Arity} <- RecordList],
+ parse_function_names(StrList, []).
+
+parse_function_names(Choices) ->
+ StrList = [atom_to_list(Name) ++ "/" ++ integer_to_list(Arity) || {Name, Arity} <- Choices],
+ parse_function_names(StrList, []).
+
+parse_function_names([], Acc) ->
+ lists:reverse(Acc);
+parse_function_names([H|T], Acc) ->
+ IsFun = re:run(H, ".*-fun-\\d*?-"),
+ IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-"),
+ IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-"),
+ Parsed =
+ if IsFun =/= nomatch -> "Fun: " ++ H;
+ IsLc =/= nomatch -> "List comprehension: " ++ H;
+ IsLbc =/= nomatch -> "Bit comprehension: " ++ H;
+ true ->
+ H
+ end,
+ parse_function_names(T, [Parsed | Acc]).
+
+show_ms_in_savedlistbox(MatchSpecList) ->
+ MsOrAlias = fun(#match_spec{alias = A, str_ms = M, fun2ms = F}) ->
+ case A of
+ undefined ->
+ if
+ F =:= undefined -> M;
+ true -> F
+ end;
+ _ ->
+ A
+ end
+ end,
+ [MsOrAlias(X) || X <- MatchSpecList].
+
+
+find_and_format_ms(Selection, [ #match_spec{str_ms = Spec, alias = Alias, fun2ms = Fun} | T ]) ->
+ case ((Selection =:= Spec) or (Selection =:= Alias)) or (Selection =:= Fun) of
+ true ->
+ if Selection =:= Alias ->
+ Spec;
+ true ->
+ Selection
+ end;
+ false ->
+ find_and_format_ms(Selection, T)
+ end.
+
+find_ms(_, []) ->
+ {nomatch, #match_spec{}};
+find_ms(Str, [ #match_spec{str_ms = Spec, alias = Alias, fun2ms = Fun} = MS | T ]) ->
+ case ((Str =:= Spec) or (Str =:= Alias)) or (Str =:= Fun) of
+ true ->
+ {match, MS};
+ false ->
+ find_ms(Str, T)
+ end.
+
+apply_matchspec(MatchSpec, TracedDict, root) ->
+ UpdateMS = fun(_Key, RecordList) ->
+ [X#traced_func{match_spec = MatchSpec} || X <- RecordList]
+ end,
+ {ok, dict:map(UpdateMS, TracedDict)};
+apply_matchspec(MatchSpec, TracedDict, {module, Module}) ->
+ RecordList = dict:fetch(Module, TracedDict),
+ RecordList2 = [X#traced_func{match_spec = MatchSpec} || X <- RecordList],
+ {ok, dict:store(Module, RecordList2, TracedDict)};
+apply_matchspec(MatchSpec, TracedDict, {function, Module, TracedFuncRec}) ->
+ RecordList = dict:fetch(Module, TracedDict),
+ NewFunc = TracedFuncRec#traced_func{match_spec = MatchSpec},
+ RecordList2 = [NewFunc | [X || X <- RecordList, X =/= TracedFuncRec]],
+ {NewFunc, dict:store(Module, RecordList2, TracedDict)}.
+
+create_matchspec_page(Parent, MatchSpecs, UserData) ->
+ Panel = wxPanel:new(Parent),
+ MainSz = wxBoxSizer:new(?wxVERTICAL),
+ TxtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Match specification:"}]),
+ BtnSz = wxBoxSizer:new(?wxHORIZONTAL),
+ SavedSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Saved match specifications:"}]),
+
+ TxtCtrl = create_styled_txtctrl(Panel),
+ wxSizer:add(TxtSz, TxtCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ AddMsBtn = wxButton:new(Panel, ?MATCHPAGE_ADDMS, [{label, "Add"}]),
+ AddMsAliasBtn = wxButton:new(Panel, ?MATCHPAGE_ADDMS_ALIAS, [{label, "Add with alias"}]),
+ Fun2MSBtn = wxButton:new(Panel, ?MATCHPAGE_ADDFUN, [{label, "Add fun"}]),
+ wxSizer:add(BtnSz, AddMsBtn),
+ wxSizer:add(BtnSz, AddMsAliasBtn),
+ wxSizer:add(BtnSz, Fun2MSBtn),
+
+ Choices = show_ms_in_savedlistbox(MatchSpecs),
+ SavedMSListBox = wxListBox:new(Panel, ?MATCHPAGE_LISTBOX, [{choices, Choices}]),
+ wxSizer:add(SavedSz, SavedMSListBox, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxButton:connect(AddMsBtn, command_button_clicked, [{userData, UserData}]),
+ wxButton:connect(AddMsAliasBtn, command_button_clicked, [{userData, UserData}] ),
+ wxButton:connect(Fun2MSBtn, command_button_clicked, [{userData, UserData}] ),
+ wxListBox:connect(SavedMSListBox, command_listbox_selected, [{userData, UserData}] ),
+ wxSizer:add(MainSz, TxtSz, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(MainSz, BtnSz),
+ wxSizer:add(MainSz, SavedSz, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxWindow:setSizer(Panel, MainSz),
+ {Panel, MainSz, TxtCtrl, SavedMSListBox}.
+
+
+
+update_tree(Tree, Dict) ->
+ RootId = wxTreeCtrl:getRootItem(Tree),
+ wxTreeCtrl:deleteChildren(Tree, RootId),
+
+ FillTree = fun(KeyAtom, RecordList, acc_in) ->
+ ParsedList = parse_record_function_names(RecordList),
+ Module = wxTreeCtrl:appendItem(Tree, RootId, atom_to_list(KeyAtom)),
+ lists:foldl(fun(TracedFuncRecord, N) ->
+ FNameStr = lists:nth(N, ParsedList),
+ wxTreeCtrl:appendItem(Tree, Module, FNameStr,
+ [{data, TracedFuncRecord}]),
+ N+1
+ end,
+ 1, RecordList),
+ wxTreeCtrl:sortChildren(Tree, Module),
+ acc_in
+ end,
+ dict:fold(FillTree, acc_in, Dict),
+ wxTreeCtrl:sortChildren(Tree, RootId),
+ wxTreeCtrl:expand(Tree, RootId).
+
+
+
+
+create_module_popup(Parent, ModuleName, TracedDict) ->
+ Module = list_to_atom(ModuleName),
+ Value = dict:find(Module, TracedDict),
+ TracedModRecs =
+ case Value of
+ {ok, V} ->
+ V;
+ error ->
+ []
+ end,
+ Functions = Module:module_info(functions),
+ Choices = lists:sort([{Name, Arity} || {Name, Arity} <- Functions, not(erl_internal:guard_bif(Name, Arity))]),
+ ParsedChoices = parse_function_names(Choices),
+
+ Dialog = wxDialog:new(Parent, ?MODULEPOPUP_DIALOG, ModuleName,
+ [{style, ?wxDEFAULT_FRAME_STYLE}]),
+ Panel = wxPanel:new(Dialog),
+ MainSz = wxBoxSizer:new(?wxVERTICAL),
+
+ SelBtnSz = wxBoxSizer:new(?wxHORIZONTAL),
+ TxtCtrl = wxTextCtrl:new(Panel, ?MODULEPOPUP_TXTCTRL),
+ SelBtn = wxButton:new(Panel, ?MODULEPOPUP_SELECT, [{label, "Select"}]),
+ DeSelBtn = wxButton:new(Panel, ?MODULEPOPUP_SELECT, [{label, "Deselect"}]),
+ SelAllBtn = wxButton:new(Panel, ?MODULEPOPUP_SELALL, [{label, "Select all"}]),
+ DeSelAllBtn = wxButton:new(Panel, ?MODULEPOPUP_SELALL, [{label, "Deselect all"}]),
+ CheckListBox = wxCheckListBox:new(Panel, ?MODULEPOPUP_CHECKLISTBOX, [{choices, ParsedChoices}, {style, ?wxLB_EXTENDED}]),
+ Indices = find_index(TracedModRecs, Choices),
+ lists:foreach(fun(X) -> wxCheckListBox:check(CheckListBox, X) end, Indices),
+ Selections = [wxControlWithItems:getString(CheckListBox, I) || I <- Indices],
+
+ OKBtn = wxButton:new(Panel, ?wxID_OK, []),
+ CancelBtn = wxButton:new(Panel, ?wxID_CANCEL, []),
+ DialogBtnSz = wxStdDialogButtonSizer:new(),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, OKBtn),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn),
+ wxStdDialogButtonSizer:realize(DialogBtnSz),
+
+ wxSizer:add(SelBtnSz, SelBtn),
+ wxSizer:add(SelBtnSz, DeSelBtn),
+ wxSizer:add(SelBtnSz, SelAllBtn),
+ wxSizer:add(SelBtnSz, DeSelAllBtn),
+ wxSizer:add(MainSz, TxtCtrl, [{flag, ?wxEXPAND}]),
+ wxSizer:add(MainSz, CheckListBox, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(MainSz, SelBtnSz, [{flag, ?wxEXPAND}]),
+ wxSizer:add(MainSz, DialogBtnSz),
+ wxWindow:setSizer(Panel, MainSz),
+
+ wxButton:connect(SelBtn, command_button_clicked, [{userData, true}]),
+ wxButton:connect(DeSelBtn, command_button_clicked, [{userData, false}]),
+ wxButton:connect(SelAllBtn, command_button_clicked, [{userData, true}]),
+ wxButton:connect(DeSelAllBtn, command_button_clicked, [{userData, false}]),
+ wxButton:connect(OKBtn, command_button_clicked, [{userData, {module_popup, Module, ParsedChoices, Choices}}]),
+ wxButton:connect(CancelBtn, command_button_clicked, [{userData, module_popup}]),
+ wxTextCtrl:connect(TxtCtrl, command_text_updated, [{userData, ParsedChoices}]),
+ wxCheckListBox:connect(CheckListBox, command_checklistbox_toggled),
+ wxDialog:connect(Dialog, close_window),
+ wxDialog:show(Dialog),
+ {Dialog, CheckListBox, Selections}.
+
+get_selections(Selections, FunctionList) ->
+ get_selections(Selections, FunctionList, []).
+get_selections([], _, Acc) ->
+ Acc;
+get_selections([Int|T], FuncList, Acc) ->
+ get_selections(T, FuncList, [lists:nth(Int, FuncList) | Acc]).
+
+find_index(Selections, FunctionList) ->
+ find_index(Selections, FunctionList, 1, []).
+find_index(Selections, FunctionList, N, Acc) when N > length(FunctionList); Selections =:= [] ->
+ Acc;
+
+find_index([#traced_func{func_name = Name, arity = Arity} |STail] = Selections,
+ FunctionList, N, Acc) ->
+ {Fname, A} = lists:nth(N, FunctionList),
+ case (Fname =:= Name) and (A =:= Arity) of
+ true ->
+ find_index(STail, FunctionList, 1, [N-1|Acc]);
+ false ->
+ find_index(Selections, FunctionList, N+1, Acc)
+ end;
+
+find_index([Sel|STail] = Selections, FunctionList, N, Acc) when is_list(Sel) ->
+ case lists:nth(N, FunctionList) =:= Sel of
+ true ->
+ find_index(STail, FunctionList, 1, [N-1|Acc]);
+ false ->
+ find_index(Selections, FunctionList, N+1, Acc)
+ end.
+
+atomlist_to_stringlist(Modules) ->
+ [atom_to_list(X) || X <- Modules].
+
+ensure_last_is_dot([]) ->
+ ".";
+ensure_last_is_dot(String) ->
+ case lists:last(String) =:= $. of
+ true ->
+ String;
+ false ->
+ String ++ "."
+ end.
+
+check_correct_MS(String) ->
+ Tokens = try_scan(String),
+ case try_parse(Tokens) of
+ {ok, Term} ->
+ case erlang:match_spec_test([], Term, trace) of
+ {ok, _, _, _} ->
+ {true, Term};
+ {error, List} ->
+ Reason = unparse_error_msg(List, []),
+ {false, Reason}
+ end;
+ error ->
+ {false, "Invalid term"}
+ end.
+
+unparse_error_msg([], Acc) ->
+ lists:reverse(Acc);
+unparse_error_msg([{_, Reason} | T], Acc) ->
+ unparse_error_msg(T, [Reason ++ "\n" | Acc]).
+
+try_scan(String) ->
+ try
+ erl_scan:string(String) of
+ {ok, T, _} ->
+ T;
+ _ ->
+ error
+ catch
+ _:_ -> error
+ end.
+
+try_parse(Tokens) ->
+ try
+ erl_parse:parse_term(Tokens) of
+ {ok, Term} ->
+ {ok, Term};
+ _ ->
+ error
+ catch
+ _:_ ->
+ error
+ end.
+
+update_matchspec_listbox(Str, {PopupBox, PageBox}, From) ->
+ case From of
+ matchpopup ->
+ wxControlWithItems:append(PageBox, Str),
+ wxControlWithItems:append(PopupBox, Str);
+ matchpage ->
+ wxControlWithItems:append(PageBox, Str)
+ end.
+
+
+dbg_from_string(Str0) ->
+ Str = unicode:characters_to_list(Str0),
+ case erl_scan:string(Str) of
+ {ok, Tokens,_} ->
+ case erl_parse:parse_exprs(Tokens) of
+ {ok,[{'fun',_,{clauses, Cl}}]} ->
+ case ms_transform:
+ transform_from_shell(dbg,Cl,orddict:new()) of
+ {error, [{_,[{Line,ms_transform,Info}]}],_} ->
+ {error,{Line,ms_transform,Info}};
+ {error, _} = ET1 ->
+ ET1;
+ Else ->
+ {ok, Else, "[" ++ lists:flatten(io_lib:format("~p", Else)) ++ "]"}
+ end;
+ {ok,_} ->
+ {error, {1,ms_transform,1}};
+ {error,Reason} ->
+ {error,Reason}
+ end;
+ {error,Reason2,_} ->
+ {error,Reason2}
+ end.
+
+get_correct_matchspec_components(From, State) ->
+ case From of
+ matchpage ->
+ {State#traceopts_state.matchpage_styled_txtctrl,
+ State#traceopts_state.frame};
+ matchpopup ->
+ {State#traceopts_state.matchspec_popup_styled_txtctrl,
+ State#traceopts_state.matchspec_popup_dialog}
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %Trace option window
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% All pages
+
+handle_event(#wx{id = ?wxID_OK,
+ event = #wxCommand{type = command_button_clicked},
+ userData = trace_options},
+ #traceopts_state{boxes = Boxes,
+ trace_options = TraceOpts,
+ match_specs = MatchSpecs,
+ traced_funcs = TracedFuncs,
+ parent = Parent} = State) ->
+ UpdTraceOpts = wx:batch(fun() ->
+ read_trace_boxes(Boxes, TraceOpts)
+ end),
+ Parent ! {updated_traceopts,
+ UpdTraceOpts,
+ MatchSpecs,
+ TracedFuncs},
+ {stop, shutdown, State};
+
+handle_event(#wx{id = ?wxID_CANCEL,
+ event = #wxCommand{type = command_button_clicked},
+ userData = trace_options},
+ #traceopts_state{parent = Parent} = State) ->
+ Parent ! traceopts_closed,
+ {stop, shutdown, State};
+
+handle_event(#wx{id = ?TRACEOPTS_FRAME,
+ event = #wxClose{type = close_window}},
+ #traceopts_state{parent = Parent} = State) ->
+ Parent ! traceopts_closed,
+ {stop, shutdown, State};
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Tracing
+
+handle_event(#wx{event = #wxCommand{type = command_checkbox_clicked}, userData = Boxgroup},
+ State) ->
+ enable(Boxgroup),
+ {noreply, State};
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Functions
+
+handle_event(#wx{id = ?FUNCTIONPAGE_LISTBOX,
+ event = #wxCommand{type = command_listbox_doubleclicked,
+ cmdString = ChosenModule}},
+ #traceopts_state{frame = Frame,
+ traced_funcs = TracedDict,
+ popup_open = false} = State) ->
+ {Dialog, CheckListBox, CheckedFuncs} = create_module_popup(Frame, ChosenModule, TracedDict),
+ {noreply, State#traceopts_state{popup_open = true,
+ module_popup_dialog = Dialog,
+ module_popup_checklistbox = CheckListBox,
+ checked_funcs = CheckedFuncs}};
+
+handle_event(#wx{id = ?FUNCTIONPAGE_TXTCTRL,
+ event = #wxCommand{type = command_text_updated,
+ cmdString = Input},
+ userData = Data},
+ #traceopts_state{functionpage_listbox = ListBox} = State) ->
+ filter_listbox_data(Input, Data, ListBox),
+ {noreply, State};
+
+handle_event(#wx{event = #wxTree{type = command_tree_item_activated,
+ item = Item}},
+ #traceopts_state{frame = Frame,
+ match_specs = MatchSpecs,
+ popup_open = false} = State) ->
+
+ Dialog = wxDialog:new(Frame, ?MATCH_POPUP_DIALOG, "Match specification",
+ [{style, ?wxDEFAULT_FRAME_STYLE}]),
+ {MatchPanel, MatchSz, StyledTxtCtrl, ListBox} = create_matchspec_page(Dialog, MatchSpecs, matchpopup),
+ ApplyBtn = wxButton:new(MatchPanel, ?wxID_APPLY),
+ CancelBtn = wxButton:new(MatchPanel, ?wxID_CANCEL, []),
+ wxButton:connect(ApplyBtn, command_button_clicked, [{userData, Item}]),
+ wxButton:connect(CancelBtn, command_button_clicked, [{userData, matchspec_popup}]),
+ DialogBtnSz = wxStdDialogButtonSizer:new(),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, ApplyBtn),
+ wxStdDialogButtonSizer:addButton(DialogBtnSz, CancelBtn),
+ wxStdDialogButtonSizer:realize(DialogBtnSz),
+ wxSizer:add(MatchSz, DialogBtnSz),
+
+ wxDialog:connect(Dialog, close_window),
+ wxDialog:show(Dialog),
+ {noreply, State#traceopts_state{matchspec_popup_dialog = Dialog,
+ matchspec_popup_listbox = ListBox,
+ matchspec_popup_styled_txtctrl = StyledTxtCtrl,
+ popup_open = true}};
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Page - Match specs
+
+handle_event(#wx{event = #wxCommand{type = command_listbox_selected,
+ cmdString = Txt}},
+ State) when Txt =:= [] ->
+ {noreply, State};
+
+handle_event(#wx{id = ?MATCHPAGE_LISTBOX,
+ event = #wxCommand{type = command_listbox_selected,
+ cmdString = SavedTxt},
+ userData = From},
+ #traceopts_state{match_specs = MatchSpecs} = State) ->
+ {StyledTxtCtrl, _} = get_correct_matchspec_components(From, State),
+ MsOrFun = find_and_format_ms(SavedTxt, MatchSpecs),
+ wxStyledTextCtrl:setText(StyledTxtCtrl, MsOrFun),
+ {noreply, State};
+
+handle_event(#wx{id = ?MATCHPAGE_ADDFUN,
+ event = #wxCommand{type = command_button_clicked},
+ userData = From},
+ #traceopts_state{match_specs = MatchSpecs,
+ matchpage_listbox = PageListBox,
+ matchspec_popup_listbox = PopupListBox} = State) ->
+
+ {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State),
+ StrFun = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)),
+
+ MatchSpecs2 = case dbg_from_string(StrFun) of
+ {ok, TermMS, StrMS} ->
+ FunMS = #match_spec{str_ms = StrMS, term_ms = TermMS, fun2ms = StrFun},
+ case lists:member(FunMS, MatchSpecs) of
+ true ->
+ observer_wx:create_txt_dialog(Frame, StrFun ++ "\nalready exists",
+ "Error", ?wxICON_ERROR),
+ MatchSpecs;
+ false ->
+ wxStyledTextCtrl:setText(StyledTxtCtrl, StrMS),
+ update_matchspec_listbox(StrFun, {PopupListBox, PageListBox}, From),
+ lists:reverse([FunMS | MatchSpecs])
+ end;
+ {error, {_, Module, What}} ->
+ FailMsg = Module:format_error(What),
+ observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR),
+ MatchSpecs
+ end,
+ {noreply, State#traceopts_state{match_specs = MatchSpecs2}};
+
+handle_event(#wx{id = ?MATCHPAGE_ADDMS,
+ event = #wxCommand{type = command_button_clicked},
+ userData = From},
+ #traceopts_state{match_specs = MatchSpecs,
+ matchpage_listbox = PageListBox,
+ matchspec_popup_listbox = PopupListBox} = State) ->
+
+ {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State),
+ StrMS = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)),
+ MatchSpecs2 = case check_correct_MS(StrMS) of
+ {true, TermMS} ->
+ MS = #match_spec{str_ms = StrMS, term_ms = TermMS},
+ case lists:member(MS, MatchSpecs) of
+ true ->
+ observer_wx:create_txt_dialog(Frame, StrMS ++ "\nalready exists",
+ "Error", ?wxICON_ERROR),
+ MatchSpecs;
+ false ->
+ update_matchspec_listbox(StrMS, {PopupListBox, PageListBox}, From),
+ lists:reverse([MS | MatchSpecs])
+ end;
+ {false, Reason} ->
+ observer_wx:create_txt_dialog(Frame, Reason, "Error", ?wxICON_ERROR),
+ MatchSpecs
+ end,
+ {noreply, State#traceopts_state{match_specs = MatchSpecs2}};
+
+
+handle_event(#wx{id = ?MATCHPAGE_ADDMS_ALIAS,
+ event = #wxCommand{type = command_button_clicked},
+ userData = From},
+ #traceopts_state{match_specs = MatchSpecs,
+ matchpage_listbox = PageListBox,
+ matchspec_popup_listbox = PopupListBox} = State) ->
+
+ {StyledTxtCtrl, Frame} = get_correct_matchspec_components(From, State),
+ StrMS = ensure_last_is_dot(wxStyledTextCtrl:getText(StyledTxtCtrl)),
+
+ MatchSpecs2 = case check_correct_MS(StrMS) of
+ {true, TermMS} ->
+ Dialog = wxTextEntryDialog:new(Frame, "Enter ms alias: "),
+ Alias = case wxDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ wxTextEntryDialog:getValue(Dialog);
+ ?wxID_CANCEL ->
+ ""
+ end,
+ wxDialog:destroy(Dialog),
+
+ case Alias of
+ "" ->
+ observer_wx:create_txt_dialog(Frame, "Bad alias", "Syntax error",
+ ?wxICON_ERROR),
+ MatchSpecs;
+
+ _ ->
+ MS = #match_spec{alias = Alias, str_ms = StrMS,
+ term_ms = TermMS},
+ {OccupiedAlias, _} = find_ms(Alias, MatchSpecs),
+
+ if
+ OccupiedAlias =:= match ->
+ observer_wx:create_txt_dialog(Frame, "Alias " ++ Alias ++ " already exists",
+ "Error", ?wxICON_ERROR),
+ MatchSpecs;
+ true ->
+ update_matchspec_listbox(Alias, {PopupListBox, PageListBox}, From),
+ lists:reverse([MS | MatchSpecs])
+ end
+ end;
+ {false, Reason} ->
+ observer_wx:create_txt_dialog(Frame, Reason, "Error", ?wxICON_ERROR),
+ MatchSpecs
+ end,
+ {noreply, State#traceopts_state{match_specs = MatchSpecs2}};
+
+
+handle_event(#wx{id = ?wxID_APPLY,
+ event = #wxCommand{type = command_button_clicked},
+ userData = Item},
+ #traceopts_state{matchspec_popup_dialog = Dialog,
+ matchspec_popup_listbox = ListBox,
+ tree = Tree,
+ match_specs = MatchSpecs,
+ traced_funcs = TracedDict} = State) ->
+ IntSelection = wxListBox:getSelection(ListBox),
+ StrSelection =
+ case IntSelection >= 0 of
+ true ->
+ wxControlWithItems:getString(ListBox, IntSelection);
+ false ->
+ []
+ end,
+ {_, MS} = find_ms(StrSelection, MatchSpecs),
+ RootId = wxTreeCtrl:getRootItem(Tree),
+ ItemParent = wxTreeCtrl:getItemParent(Tree, Item),
+
+ TracedDict2 =
+ if (Item =:= RootId) ->
+ {ok, NewDict} = apply_matchspec(MS, TracedDict, root),
+ NewDict;
+ (ItemParent =:= RootId) ->
+ Module = list_to_atom(wxTreeCtrl:getItemText(Tree, Item)),
+ {ok, NewDict} = apply_matchspec(MS, TracedDict, {module, Module}),
+ NewDict;
+ true ->
+ TracedFuncRec = wxTreeCtrl:getItemData(Tree, Item),
+ Module = list_to_atom(wxTreeCtrl:getItemText(Tree, ItemParent)),
+ {NewTracedFuncRecord, NewDict} =
+ apply_matchspec(MS,
+ TracedDict,
+ {function,
+ Module,
+ TracedFuncRec}),
+ wxTreeCtrl:setItemData(Tree, Item, NewTracedFuncRecord),
+ NewDict
+ end,
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{traced_funcs = TracedDict2,
+ popup_open = false}};
+
+handle_event(#wx{id = ?wxID_CANCEL,
+ event = #wxCommand{type = command_button_clicked},
+ userData = matchspec_popup},
+ #traceopts_state{matchspec_popup_dialog = Dialog} = State) ->
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{popup_open = false}};
+
+handle_event(#wx{id = ?MATCH_POPUP_DIALOG,
+ event = #wxClose{type = close_window}},
+ #traceopts_state{matchspec_popup_dialog = Dialog} = State) ->
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{popup_open = false}};
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %Module Popup
+
+handle_event(#wx{id = ?wxID_OK,
+ event = #wxCommand{type = command_button_clicked},
+ userData = {module_popup, Module,
+ ParsedChoices, Choices}},
+ #traceopts_state{
+ module_popup_dialog = Dialog,
+ traced_funcs = TracedDict,
+ tree = Tree,
+ checked_funcs = CheckedFuncs} = State) ->
+
+ Indices = [I+1 || I <- find_index(CheckedFuncs, ParsedChoices)],
+ Selections = get_selections(Indices, Choices),
+ TracedDict2 = case Selections of
+ [] ->
+ dict:erase(Module, TracedDict);
+ _ ->
+ Traced = [#traced_func{arity = Arity,
+ func_name = Function}
+ || {Function, Arity} <- Selections],
+ dict:store(Module, Traced, TracedDict)
+ end,
+
+ update_tree(Tree, TracedDict2),
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{traced_funcs = TracedDict2,
+ checked_funcs = [],
+ popup_open = false}};
+
+handle_event(#wx{id = ?wxID_CANCEL,
+ event = #wxCommand{type = command_button_clicked},
+ userData = module_popup},
+ #traceopts_state{module_popup_dialog = Dialog} = State) ->
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{popup_open = false,
+ checked_funcs = []}};
+
+handle_event(#wx{id = ?MODULEPOPUP_SELECT,
+ event = #wxCommand{type = command_button_clicked},
+ userData = Bool},
+ #traceopts_state{module_popup_checklistbox = CheckListBox,
+ checked_funcs = CheckedFuncs} = State) ->
+ {_, Selections} = wxListBox:getSelections(CheckListBox),
+ lists:foreach(fun(Index) -> wxCheckListBox:check(CheckListBox, Index, [{check, Bool}]) end, Selections),
+ StrSelections = [wxControlWithItems:getString(CheckListBox, N) || N <- Selections],
+ CheckedFuncs2 = case Bool of
+ true ->
+ [X || X <- StrSelections,
+ not(lists:member(X, CheckedFuncs))] ++ CheckedFuncs;
+ false ->
+ CheckedFuncs -- StrSelections
+ end,
+ {noreply, State#traceopts_state{checked_funcs = CheckedFuncs2}};
+
+handle_event(#wx{id = ?MODULEPOPUP_SELALL,
+ event = #wxCommand{type = command_button_clicked},
+ userData = Bool},
+ #traceopts_state{module_popup_checklistbox = CheckListBox} = State) ->
+ lists:foreach(fun(Index) ->
+ wxCheckListBox:check(CheckListBox, Index, [{check, Bool}])
+ end,
+ lists:seq(0, wxControlWithItems:getCount(CheckListBox))),
+ CheckedFuncs = case Bool of
+ true ->
+ [wxControlWithItems:getString(CheckListBox, N)
+ || N <- lists:seq(0, wxControlWithItems:getCount(CheckListBox))];
+ false ->
+ []
+ end,
+ {noreply, State#traceopts_state{checked_funcs = CheckedFuncs}};
+
+handle_event(#wx{id = ?MODULEPOPUP_CHECKLISTBOX,
+ obj = CheckListBox,
+ event = #wxCommand{type = command_checklistbox_toggled,
+ commandInt = Index}},
+ #traceopts_state{checked_funcs = CheckedFuncs} = State) ->
+
+ UpdCheckedFuncs = case
+ wxCheckListBox:isChecked(CheckListBox, Index) of
+ true ->
+ [wxControlWithItems:getString(CheckListBox, Index) | CheckedFuncs];
+ false ->
+ lists:delete(wxControlWithItems:getString(CheckListBox, Index), CheckedFuncs)
+ end,
+ {noreply, State#traceopts_state{checked_funcs = UpdCheckedFuncs}};
+
+handle_event(#wx{id = ?MODULEPOPUP_TXTCTRL,
+ event = #wxCommand{type = command_text_updated,
+ cmdString = Input},
+ userData = Data},
+ #traceopts_state{module_popup_checklistbox = CListBox,
+ checked_funcs = CheckedFuncs} = State) ->
+ FilteredData = filter_listbox_data(Input, Data, CListBox),
+ lists:foreach(fun(Index) ->
+ wxCheckListBox:check(CListBox, Index, [{check, true}])
+ end,
+ [wxControlWithItems:findString(CListBox, X) || X <- CheckedFuncs, lists:member(X, FilteredData)]),
+ {noreply, State};
+
+handle_event(#wx{id = ?MODULEPOPUP_DIALOG,
+ event = #wxClose{type = close_window}},
+ #traceopts_state{module_popup_dialog = Dialog} = State) ->
+ wxDialog:destroy(Dialog),
+ {noreply, State#traceopts_state{popup_open = false,
+ checked_funcs = []}};
+
+handle_event(#wx{event = What}, State) ->
+ io:format("~p~p: Unhandled event: ~p ~n", [?MODULE, self(), What]),
+ {noreply, State}.
+
+
+
+terminate(Reason, #traceopts_state{frame = Frame}) ->
+ io:format("~p terminating traceopts. Reason: ~p~n", [?MODULE, Reason]),
+ wxFrame:destroy(Frame),
+ ok.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+handle_info(Any, State) ->
+ io:format("~p~p: received unexpected message: ~p\n", [?MODULE, self(), Any]),
+ {noreply, State}.
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_cast(Msg, State) ->
+ io:format("~p ~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]),
+ {noreply, State}.
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
new file mode 100644
index 0000000000..b4a85cd43b
--- /dev/null
+++ b/lib/observer/src/observer_wx.erl
@@ -0,0 +1,506 @@
+%%
+%% %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_wx).
+
+-behaviour(wx_object).
+
+-export([start/0]).
+-export([create_menus/2, create_menu/2, create_txt_dialog/4, try_rpc/4,
+ return_to_localnode/2]).
+
+-export([init/1, handle_event/2, handle_cast/2, terminate/2, code_change/3,
+ handle_call/3, handle_info/2, check_page_title/1]).
+
+%% Includes
+-include_lib("wx/include/wx.hrl").
+
+-include("observer_defs.hrl").
+
+%% Defines
+
+-define(ID_PING, 1).
+-define(ID_CONNECT, 2).
+-define(ID_NOTEBOOK, 3).
+
+-define(FIRST_NODES_MENU_ID, 1000).
+-define(LAST_NODES_MENU_ID, 2000).
+
+%% Records
+-record(state,
+ {frame,
+ menubar,
+ status_bar,
+ notebook,
+ main_panel,
+ pro_panel,
+ tv_panel,
+ sys_panel,
+ active_tab,
+ node,
+ nodes
+ }).
+
+start() ->
+ wx_object:start(?MODULE, [], []).
+
+create_menus(Object, Menus) when is_list(Menus) ->
+ wx_object:call(Object, {create_menus, Menus}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init(_Args) ->
+ wx:new(),
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Observer", [{size, {1000, 500}},
+ {style, ?wxDEFAULT_FRAME_STYLE}]),
+ IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"),
+ Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
+ wxFrame:setIcon(Frame, Icon),
+ wxIcon:destroy(Icon),
+
+ State = #state{frame = Frame},
+ UpdState = setup(State),
+ wxFrame:show(Frame),
+ net_kernel:monitor_nodes(true),
+ {Frame, UpdState}.
+
+setup(#state{frame = Frame} = State) ->
+ %% Setup Menubar & Menus
+ MenuBar = wxMenuBar:new(),
+
+ {Nodes, NodeMenus} = get_nodes(),
+ DefMenus = default_menus(NodeMenus),
+ create_menu(DefMenus, MenuBar),
+
+ wxFrame:setMenuBar(Frame, MenuBar),
+ StatusBar = wxFrame:createStatusBar(Frame, []),
+ wxFrame:setTitle(Frame, atom_to_list(node())),
+ wxStatusBar:setStatusText(StatusBar, atom_to_list(node())),
+
+ %% Setup panels
+ Panel = wxPanel:new(Frame, []),
+ Notebook = wxNotebook:new(Panel, ?ID_NOTEBOOK, [{style, ?wxBK_DEFAULT}]),
+
+ %% Setup sizer
+ MainSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ %% System Panel
+ SysPanel = observer_sys_wx:start_link(Notebook, self()),
+ wxNotebook:addPage(Notebook, SysPanel, "System", []),
+
+ %% Process Panel
+ ProPanel = observer_pro_wx:start_link(Notebook, self()),
+ wxNotebook:addPage(Notebook, ProPanel, "Processes", []),
+
+ %% Table Viewer Panel
+ TVPanel = observer_tv_wx:start_link(Notebook, self()),
+ wxNotebook:addPage(Notebook, TVPanel, "Table Viewer", []),
+
+ wxSizer:add(MainSizer, Notebook, [{proportion, 1}, {flag, ?wxEXPAND}]),
+ wxPanel:setSizer(Panel, MainSizer),
+
+ wxNotebook:connect(Notebook, command_notebook_page_changed),
+ wxFrame:connect(Frame, close_window, [{skip, true}]),
+ wxMenu:connect(Frame, command_menu_selected, [{skip, true}]),
+
+ SysPid = wx_object:get_pid(SysPanel),
+ SysPid ! {active, node()},
+ UpdState = State#state{main_panel = Panel,
+ notebook = Notebook,
+ menubar = MenuBar,
+ status_bar = StatusBar,
+ sys_panel = SysPanel,
+ pro_panel = ProPanel,
+ tv_panel = TVPanel,
+ active_tab = SysPid,
+ node = node(),
+ nodes = Nodes
+ },
+ UpdState.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%Callbacks
+handle_event(#wx{obj = Notebook, id = ?ID_NOTEBOOK,
+ event = #wxNotebook{type = command_notebook_page_changed}},
+ #state{active_tab=Previous, node=Node, notebook = Notebook} = State) ->
+ io:format("Command notebook changed ~n"),
+ Pid = get_active_pid(State),
+ Previous ! not_active,
+ Pid ! {active, Node},
+ {noreply, State#state{active_tab=Pid}};
+
+handle_event(#wx{event = #wxClose{}}, State) ->
+ {stop, normal, State};
+
+handle_event(#wx{id = ?wxID_EXIT, event = #wxCommand{type = command_menu_selected}}, State) ->
+ io:format("~p ~p, you clicked close", [?MODULE, ?LINE]),
+ {stop, normal, State};
+
+handle_event(#wx{id = ?wxID_HELP, event = #wxCommand{type = command_menu_selected}}, State) ->
+ io:format("~p ~p, you clicked help", [?MODULE, ?LINE]),
+ {noreply, State};
+
+handle_event(#wx{id = ?ID_CONNECT, event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame} = State) ->
+ UpdState = case create_connect_dialog(connect, State) of
+ cancel ->
+ State;
+ {value, [], _, _} ->
+ create_txt_dialog(Frame, "Node must have a name",
+ "Error", ?wxICON_ERROR),
+ State;
+ {value, NodeName, LongOrShort, Cookie} -> %Shortname,
+ try
+ case connect(list_to_atom(NodeName), LongOrShort, list_to_atom(Cookie)) of
+ {ok, set_cookie} ->
+ change_node_view(node(), State);
+ {error, set_cookie} ->
+ create_txt_dialog(Frame, "Could not set cookie",
+ "Error", ?wxICON_ERROR),
+ State;
+ {error, net_kernel, _Reason} ->
+ create_txt_dialog(Frame, "Could not enable node",
+ "Error", ?wxICON_ERROR),
+ State
+ end
+ catch _:_ ->
+ create_txt_dialog(Frame, "Could not enable node",
+ "Error", ?wxICON_ERROR),
+ State
+ end
+ end,
+ {noreply, UpdState};
+
+handle_event(#wx{id = ?ID_PING, event = #wxCommand{type = command_menu_selected}},
+ #state{frame = Frame} = State) ->
+ UpdState = case create_connect_dialog(ping, State) of
+ cancel ->
+ State;
+ {value, Value} when is_list(Value) ->
+ try
+
+ Node = list_to_atom(Value),
+ case net_adm:ping(Node) of
+ pang ->
+ create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION),
+ State;
+ pong ->
+ change_node_view(Node, State)
+ end
+
+ catch _:_ ->
+ create_txt_dialog(Frame, "Connect failed", "Pang", ?wxICON_EXCLAMATION),
+ State
+ end
+ end,
+ {noreply, UpdState};
+
+handle_event(#wx{id = Id, event = #wxCommand{type = command_menu_selected}}, State)
+ when Id > ?FIRST_NODES_MENU_ID, Id < ?LAST_NODES_MENU_ID ->
+
+ Node = lists:nth(Id - ?FIRST_NODES_MENU_ID, State#state.nodes),
+ UpdState = change_node_view(Node, State),
+ {noreply, UpdState};
+
+handle_event(Event, State) ->
+ Pid = get_active_pid(State),
+ Pid ! Event,
+ {noreply, State}.
+
+handle_cast(Cast, State) ->
+ io:format("~p:~p: Got cast ~p~n", [?MODULE, ?LINE, Cast]),
+ {noreply, State}.
+
+handle_call({create_menus, TabMenus}, _From, State = #state{menubar=MenuBar}) ->
+ wx:batch(fun() ->
+ {_Nodes, NodeMenus} = get_nodes(),
+ DefMenus = default_menus(NodeMenus),
+ Menus = merge_menus(DefMenus, TabMenus),
+ clean_menus(MenuBar),
+ create_menu(Menus, MenuBar)
+ end),
+ {reply, ok, State};
+
+handle_call(Msg, _From, State) ->
+ io:format("~p~p: Got Call ~p~n",[?MODULE, ?LINE, Msg]),
+ {reply, ok, State}.
+
+handle_info({nodeup, _Node}, State) ->
+ State2 = update_node_list(State),
+ {noreply, State2};
+
+handle_info({nodedown, Node},
+ #state{frame = Frame} = State) ->
+ State2 = case Node =:= State#state.node of
+ true ->
+ change_node_view(node(), State);
+ false ->
+ State
+ end,
+ State3 = update_node_list(State2),
+ Msg = ["Node down: " | atom_to_list(Node)],
+ create_txt_dialog(Frame, Msg, "Node down", ?wxICON_EXCLAMATION),
+ {noreply, State3};
+
+handle_info(Info, State) ->
+ io:format("~p, ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
+ {noreply, State}.
+
+terminate(Reason, #state{frame = Frame}) ->
+ wxFrame:destroy(Frame),
+ io:format("~p terminating. Reason: ~p~n", [?MODULE, Reason]),
+ ok.
+
+code_change(_, _, State) ->
+ {stop, not_yet_implemented, State}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+try_rpc(Node, Mod, Func, Args) ->
+ case
+ rpc:call(Node, Mod, Func, Args) of
+ {badrpc, Reason} ->
+ error_logger:error_report([{node, Node},
+ {call, {Mod, Func, Args}},
+ {reason, {badrpc, Reason}}]),
+ error({badrpc, Reason});
+ Res ->
+ Res
+ end.
+
+return_to_localnode(Frame, Node) ->
+ case node() =/= Node of
+ true ->
+ create_txt_dialog(Frame, "Error occured on remote node",
+ "Error", ?wxICON_ERROR),
+ disconnect_node(Node);
+ false ->
+ ok
+ end.
+
+create_txt_dialog(Frame, Msg, Title, Style) ->
+ MD = wxMessageDialog:new(Frame, Msg, [{style, Style}]),
+ wxMessageDialog:setTitle(MD, Title),
+ wxDialog:showModal(MD),
+ wxDialog:destroy(MD).
+
+connect(NodeName, 0, Cookie) ->
+ connect2(NodeName, shortnames, Cookie);
+connect(NodeName, 1, Cookie) ->
+ connect2(NodeName, longnames, Cookie).
+
+connect2(NodeName, Opts, Cookie) ->
+ case net_kernel:start([NodeName, Opts]) of
+ {ok, _} ->
+ case is_alive() of
+ true ->
+ erlang:set_cookie(node(), Cookie),
+ {ok, set_cookie};
+ false ->
+ {error, set_cookie}
+ end;
+ {error, Reason} ->
+ {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]),
+ StatusText = ["Observer - " | atom_to_list(Node)],
+ wxFrame:setTitle(State#state.frame, StatusText),
+ wxStatusBar:setStatusText(State#state.status_bar, StatusText),
+ State#state{node = Node}.
+
+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}) ->
+ Panel = case check_page_title(Notebook) of
+ "Processes" -> Pro;
+ "System" -> Sys;
+ "Table Viewer" -> Tv
+ end,
+ wx_object:get_pid(Panel).
+
+create_connect_dialog(ping, #state{frame = Frame}) ->
+ Dialog = wxTextEntryDialog:new(Frame, "Connect to node"),
+ case wxDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ Value = wxTextEntryDialog:getValue(Dialog),
+ wxDialog:destroy(Dialog),
+ {value, Value};
+ ?wxID_CANCEL ->
+ wxDialog:destroy(Dialog),
+ cancel
+ end;
+create_connect_dialog(connect, #state{frame = Frame}) ->
+ Dialog = wxDialog:new(Frame, ?wxID_ANY, "Distribute node "),
+
+ VSizer = wxBoxSizer:new(?wxVERTICAL),
+ RadioBoxSizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ Choices = ["Short name", "Long name"],
+ RadioBox = wxRadioBox:new(Dialog, 1, "",
+ ?wxDefaultPosition,
+ ?wxDefaultSize,
+ Choices,
+ [{majorDim, 2},
+ {style, ?wxHORIZONTAL}]),
+
+ NameText = wxStaticText:new(Dialog, ?wxID_ANY, "Node name: "),
+ NameCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]),
+ wxTextCtrl:setValue(NameCtrl, "observer"),
+ CookieText = wxStaticText:new(Dialog, ?wxID_ANY, "Secret cookie: "),
+ CookieCtrl = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {200, 25}}, {style, ?wxDEFAULT}]),
+
+ BtnSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxID_DEFAULT),
+ Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}],
+ wxSizer:add(RadioBoxSizer, RadioBox, Flags),
+
+ wxSizer:add(VSizer, RadioBoxSizer, Flags),
+ wxSizer:addSpacer(VSizer, 10),
+ wxSizer:add(VSizer, NameText),
+ wxSizer:add(VSizer, NameCtrl, Flags),
+ wxSizer:addSpacer(VSizer, 10),
+ wxSizer:add(VSizer, CookieText),
+ wxSizer:add(VSizer, CookieCtrl, Flags),
+ wxSizer:addSpacer(VSizer, 10),
+ wxSizer:add(VSizer, BtnSizer, [{flag, ?wxALIGN_LEFT}]),
+
+ wxWindow:setSizer(Dialog, VSizer),
+ CookiePath = filename:join(os:getenv("HOME"), ".erlang.cookie"),
+ DefaultCookie = case filelib:is_file(CookiePath) of
+ true ->
+ {ok, IoDevice} = file:open(CookiePath, read),
+ case file:read_line(IoDevice) of
+ {ok, Cookie} ->
+ Cookie;
+ _ ->
+ ""
+ end;
+ false ->
+ ""
+ end,
+ wxTextCtrl:setValue(CookieCtrl, DefaultCookie),
+ case wxDialog:showModal(Dialog) of
+ ?wxID_OK ->
+ NameValue = wxTextCtrl:getValue(NameCtrl),
+ NameLngthValue = wxRadioBox:getSelection(RadioBox),
+ CookieValue = wxTextCtrl:getValue(CookieCtrl),
+ wxDialog:destroy(Dialog),
+ {value, NameValue, NameLngthValue, CookieValue};
+ ?wxID_CANCEL ->
+ wxDialog:destroy(Dialog),
+ cancel
+ end.
+
+default_menus(NodesMenuItems) ->
+ FileMenu = {"File", [#create_menu{id = ?wxID_EXIT, text = "Quit"}]},
+ HelpMenu = {"Help", [#create_menu{id = ?wxID_HELP, text = "Help"}]},
+ NodeMenu = case erlang:is_alive() of
+ true ->
+ {"Nodes", NodesMenuItems ++
+ [#create_menu{id = ?ID_PING, text = "Connect Node"}]};
+ false ->
+ {"Nodes", NodesMenuItems ++
+ [#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}]}
+ end,
+ [FileMenu, NodeMenu, HelpMenu].
+
+clean_menus(MenuBar) ->
+ Count = wxMenuBar:getMenuCount(MenuBar),
+ remove_menu_item(MenuBar, Count).
+
+remove_menu_item(MenuBar, Item) when Item > -1 ->
+ Menu = wxMenuBar:getMenu(MenuBar, Item),
+ wxMenuBar:remove(MenuBar, Item),
+ wxMenu:destroy(Menu),
+ remove_menu_item(MenuBar, Item-1);
+remove_menu_item(_, _) ->
+ ok.
+
+merge_menus([{Label, Items}|Default], [{Label, TabItems}|TabMenus]) ->
+ [{Label, TabItems ++ Items} | merge_menus(Default, TabMenus)];
+merge_menus([Menu = {"File", _}|Default], TabMenus) ->
+ [Menu | merge_menus(Default, TabMenus)];
+merge_menus(Default = [{"Nodes", _}|_], TabMenus) ->
+ TabMenus ++ Default.
+
+create_menu(Menus, MenuBar) ->
+ Add = fun({Name, MenuItems}) ->
+ Menu = wxMenu:new(),
+ lists:foreach(fun(Record) ->
+ create_menu_item(Record, Menu)
+ end,
+ MenuItems),
+ wxMenuBar:append(MenuBar, Menu, Name)
+ end,
+ wx:foreach(Add, Menus),
+ ok.
+
+create_menu_item(#create_menu{id = Id, text = Text, type = Type, check = Check}, Menu) ->
+ case Type of
+ append ->
+ wxMenu:append(Menu, Id, Text);
+ check ->
+ wxMenu:appendCheckItem(Menu, Id, Text),
+ wxMenu:check(Menu, Id, Check);
+ radio ->
+ wxMenu:appendRadioItem(Menu, Id, Text),
+ wxMenu:check(Menu, Id, Check);
+ separator ->
+ wxMenu:appendSeparator(Menu)
+ end;
+create_menu_item(separator, Menu) ->
+ wxMenu:appendSeparator(Menu).
+
+
+get_nodes() ->
+ Nodes = [node()| nodes()],
+ {_, Menues} =
+ lists:foldl(fun(Node, {Id, Acc}) when Id < ?LAST_NODES_MENU_ID ->
+ {Id + 1, [#create_menu{id = Id + ?FIRST_NODES_MENU_ID,
+ text = atom_to_list(Node)} | Acc]}
+ end,
+ {1, []},
+ Nodes),
+ {Nodes, lists:reverse(Menues)}.
+
+update_node_list(State = #state{menubar=MenuBar}) ->
+ {Nodes, NodesMenuItems} = get_nodes(),
+ NodeMenuId = wxMenuBar:findMenu(MenuBar, "Nodes"),
+ NodeMenu = wxMenuBar:getMenu(MenuBar, NodeMenuId),
+ wx:foreach(fun(Item) ->
+ wxMenu:'Destroy'(NodeMenu, Item)
+ end,
+ wxMenu:getMenuItems(NodeMenu)),
+
+ wx:foreach(fun(Record) ->
+ create_menu_item(Record, NodeMenu)
+ end, NodesMenuItems),
+
+ case erlang:is_alive() of
+ true ->
+ create_menu_item(#create_menu{id = ?ID_PING, text = "Connect node"}, NodeMenu);
+ false ->
+ create_menu_item(#create_menu{id = ?ID_CONNECT, text = "Enable distribution"}, NodeMenu)
+ end,
+ State#state{nodes = Nodes}.