%%
%% %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}.