diff options
Diffstat (limited to 'lib/observer/src/observer_traceoptions_wx.erl')
-rw-r--r-- | lib/observer/src/observer_traceoptions_wx.erl | 1066 |
1 files changed, 1066 insertions, 0 deletions
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}. |