1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19
20-module(observer_traceoptions_wx).
21
22-include_lib("wx/include/wx.hrl").
23-include("observer_defs.hrl").
24
25-export([process_trace/2, port_trace/2, trace_pattern/4, select_nodes/2,
26	 output/2, select_matchspec/4]).
27
28process_trace(Parent, Default) ->
29    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options",
30			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
31    Panel = wxPanel:new(Dialog),
32    MainSz = wxBoxSizer:new(?wxVERTICAL),
33    PanelSz = wxBoxSizer:new(?wxHORIZONTAL),
34    LeftSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel,  [{label, "Tracing options"}]),
35    RightSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Inheritance options:"}]),
36
37    FuncBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace function call", []),
38    check_box(FuncBox, lists:member(functions, Default)),
39    ArityBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace arity instead of arguments", []),
40    check_box(ArityBox, lists:member(functions, Default)),
41    SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []),
42    check_box(SendBox, lists:member(send, Default)),
43    RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []),
44    check_box(RecBox, lists:member('receive', Default)),
45    EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace process events", []),
46    check_box(EventBox, lists:member(events, Default)),
47    SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of processes", []),
48    check_box(SchedBox, lists:member(running_procs, Default)),
49    ExitBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of exiting processes", []),
50    check_box(ExitBox, lists:member(exiting, Default)),
51    GCBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace garbage collections", []),
52    check_box(GCBox, lists:member(garbage_collection, Default)),
53
54    {SpawnBox, SpwnAllRadio, SpwnFirstRadio} =
55	optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"),
56    {LinkBox, LinkAllRadio, LinkFirstRadio} =
57	optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "link"),
58    SpawnBool = lists:member(on_spawn, Default) orelse lists:member(on_first_spawn, Default),
59    LinkBool = lists:member(on_link, Default) orelse lists:member(on_first_link, Default),
60    check_box(SpawnBox, SpawnBool),
61    check_box(LinkBox,  LinkBool),
62    enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]),
63    enable(LinkBox, [LinkAllRadio, LinkFirstRadio]),
64    [wxRadioButton:setValue(Radio, lists:member(Opt, Default)) ||
65	{Radio, Opt} <- [{SpwnAllRadio, on_spawn}, {SpwnFirstRadio, on_first_spawn},
66			 {LinkAllRadio, on_link},  {LinkFirstRadio, on_first_link}]],
67
68    [wxSizer:add(LeftSz, CheckBox, []) || CheckBox <- [FuncBox,ArityBox,SendBox,RecBox,EventBox,SchedBox,ExitBox,GCBox]],
69    wxSizer:add(LeftSz, 150, -1),
70
71    wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]),
72    wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}, {proportion,1}]),
73    wxPanel:setSizer(Panel, PanelSz),
74    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]),
75    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
76    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
77    wxWindow:setSizerAndFit(Dialog, MainSz),
78    wxSizer:setSizeHints(MainSz, Dialog),
79    wxCheckBox:connect(SpawnBox, command_checkbox_clicked,
80		       [{callback, fun(#wx{event=#wxCommand{}},_) ->
81					   enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio])
82				   end}]),
83    wxCheckBox:connect(LinkBox, command_checkbox_clicked,
84		       [{callback, fun(#wx{event=#wxCommand{}},_) ->
85					   enable(LinkBox, [LinkAllRadio, LinkFirstRadio])
86				   end}]),
87
88    case wxDialog:showModal(Dialog) of
89	?wxID_OK ->
90	    All = [{SendBox, send}, {RecBox, 'receive'},
91		   {FuncBox, functions}, {ArityBox, arity},
92		   {EventBox, events}, {SchedBox, running_procs},
93		   {ExitBox, exiting}, {GCBox, garbage_collection},
94		   {{SpawnBox, SpwnAllRadio}, on_spawn},
95		   {{SpawnBox,SpwnFirstRadio}, on_first_spawn},
96		   {{LinkBox, LinkAllRadio}, on_link},
97		   {{LinkBox, LinkFirstRadio}, on_first_link}],
98	    Check = fun({Box, Radio}) ->
99			    wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio);
100		       (Box) ->
101			    wxCheckBox:getValue(Box)
102		    end,
103	    Opts = [Id || {Tick, Id} <- All, Check(Tick)],
104	    wxDialog:destroy(Dialog),
105	    lists:reverse(Opts);
106	?wxID_CANCEL ->
107	    wxDialog:destroy(Dialog),
108	    throw(cancel)
109    end.
110
111port_trace(Parent, Default) ->
112    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Port Options",
113			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
114    Panel = wxPanel:new(Dialog),
115    MainSz = wxBoxSizer:new(?wxVERTICAL),
116    OptsSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel,  [{label, "Tracing options"}]),
117
118    SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []),
119    check_box(SendBox, lists:member(send, Default)),
120    RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []),
121    check_box(RecBox, lists:member('receive', Default)),
122    EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace port events", []),
123    check_box(EventBox, lists:member(events, Default)),
124    SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of ports", []),
125    check_box(SchedBox, lists:member(running_ports, Default)),
126
127    [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox,SchedBox]],
128    wxSizer:add(OptsSz, 150, -1),
129
130    wxPanel:setSizer(Panel, OptsSz),
131    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]),
132    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
133    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
134    wxWindow:setSizerAndFit(Dialog, MainSz),
135    wxSizer:setSizeHints(MainSz, Dialog),
136
137    case wxDialog:showModal(Dialog) of
138	?wxID_OK ->
139	    All = [{SendBox, send}, {RecBox, 'receive'},
140		   {EventBox, events}, {SchedBox, running_ports}],
141	    Opts = [Id || {Tick, Id} <- All, wxCheckBox:getValue(Tick)],
142	    wxDialog:destroy(Dialog),
143	    lists:reverse(Opts);
144	?wxID_CANCEL ->
145	    wxDialog:destroy(Dialog),
146	    throw(cancel)
147    end.
148
149trace_pattern(ParentPid, Parent, Node, MatchSpecs) ->
150    try
151	{Module,MFAs,MatchSpec} =
152	    case module_selector(Parent, Node) of
153		{'$trace_event',Event} ->
154		    MS = select_matchspec(ParentPid, Parent, MatchSpecs, Event),
155		    {'Events',[{'Events',Event}],MS};
156		Mod ->
157		    MFAs0 = function_selector(Parent, Node, Mod),
158		    MS = select_matchspec(ParentPid, Parent, MatchSpecs, funcs),
159		    {Mod,MFAs0,MS}
160	    end,
161	{Module, [#tpattern{m=M,fa=FA,ms=MatchSpec} || {M,FA} <- MFAs]}
162    catch cancel -> cancel
163    end.
164
165select_nodes(Parent, Nodes) ->
166    Choices = [{atom_to_list(X), X} || X <- Nodes],
167    check_selector(Parent, Choices).
168
169module_selector(Parent, Node) ->
170    Scale = observer_wx:get_scale(),
171    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module or Event",
172			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
173			   {size, {400*Scale, 400*Scale}}]),
174    Panel = wxPanel:new(Dialog),
175    PanelSz = wxBoxSizer:new(?wxVERTICAL),
176    MainSz  = wxBoxSizer:new(?wxVERTICAL),
177
178    TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY),
179    ListBox = wxListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_SINGLE}]),
180    wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]),
181    wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]),
182    wxPanel:setSizer(Panel, PanelSz),
183    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL},
184				{border, 5}, {proportion, 1}]),
185    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
186    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL},
187				  {border, 5}, {proportion, 0}]),
188    wxWindow:setSizer(Dialog, MainSz),
189    OkId = wxDialog:getAffirmativeId(Dialog),
190    OkButt = wxWindow:findWindowById(OkId),
191    wxWindow:disable(OkButt),
192    wxWindow:setFocus(TxtCtrl),
193    %% init data
194    Modules = get_modules(Node),
195    Events = [{"Messages sent",{'$trace_event',send}},
196	      {"Messages received",{'$trace_event','receive'}}],
197    AllModules = Events ++ [{atom_to_list(X), X} || X <- Modules],
198    filter_listbox_data("", AllModules, ListBox),
199    wxTextCtrl:connect(TxtCtrl, command_text_updated,
200		       [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) ->
201					   filter_listbox_data(Input, AllModules, ListBox)
202				   end}]),
203    wxListBox:connect(ListBox, command_listbox_doubleclicked,
204		      [{callback, fun(_, _) -> wxDialog:endModal(Dialog, ?wxID_OK) end}]),
205    wxListBox:connect(ListBox, command_listbox_selected,
206		      [{callback, fun(#wx{event=#wxCommand{commandInt=Id}}, _) ->
207					  Id >= 0 andalso wxWindow:enable(OkButt)
208				  end}]),
209
210    case wxDialog:showModal(Dialog) of
211	?wxID_OK ->
212	    SelId  = wxListBox:getSelection(ListBox),
213	    case SelId >= 0 of
214		true ->
215		    Module = wxListBox:getClientData(ListBox, SelId),
216		    wxDialog:destroy(Dialog),
217		    Module;
218		false ->
219		    wxDialog:destroy(Dialog),
220		    throw(cancel)
221	    end;
222	?wxID_CANCEL ->
223	    wxDialog:destroy(Dialog),
224	    throw(cancel)
225    end.
226
227function_selector(Parent, Node, Module) ->
228    Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]),
229    Externals = observer_wx:try_rpc(Node, Module, module_info, [exports]),
230
231    Choices = lists:usort([{Name, Arity} || {Name, Arity} <- Externals ++ Functions,
232					    not(erl_internal:guard_bif(Name, Arity))]),
233    ParsedChoices = parse_function_names(Choices),
234    case check_selector(Parent, ParsedChoices) of
235	[] -> [{Module, {'_', '_'}}];
236	FAs ->
237	    [{Module, {F, A}} || {F,A} <- FAs]
238    end.
239
240check_selector(Parent, ParsedChoices) ->
241    Scale = observer_wx:get_scale(),
242    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions",
243			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
244			   {size, {400*Scale, 400*Scale}}]),
245
246    Panel = wxPanel:new(Dialog),
247    PanelSz = wxBoxSizer:new(?wxVERTICAL),
248    MainSz  = wxBoxSizer:new(?wxVERTICAL),
249
250    TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY),
251    ListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_EXTENDED}]),
252    wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]),
253    wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]),
254    SelAllBtn   = wxButton:new(Panel, ?wxID_ANY,   [{label, "Check Visible"}]),
255    DeSelAllBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Uncheck Visible"}]),
256    ButtonSz = wxBoxSizer:new(?wxHORIZONTAL),
257    [wxSizer:add(ButtonSz, Button, []) || Button <- [SelAllBtn, DeSelAllBtn]],
258    wxSizer:add(PanelSz, ButtonSz, [{flag, ?wxEXPAND bor ?wxALL},
259				    {border, 5}, {proportion, 0}]),
260    wxPanel:setSizer(Panel, PanelSz),
261    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL},
262				{border, 5}, {proportion, 1}]),
263
264    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
265    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL},
266				  {border, 5}, {proportion, 0}]),
267    wxWindow:setSizer(Dialog, MainSz),
268    wxWindow:setFocus(TxtCtrl),
269    %% Init
270    filter_listbox_data("", ParsedChoices, ListBox, false),
271    %% Setup Event handling
272    wxTextCtrl:connect(TxtCtrl, command_text_updated,
273		       [{callback,
274			 fun(#wx{event=#wxCommand{cmdString=Input}}, _) ->
275				 filter_listbox_data(Input, ParsedChoices, ListBox, false)
276			 end}]),
277    Self = self(),
278
279    %% Sigh clientdata in checklistbox crashes on windows, wx-bug I presume.
280    %% Don't have time to investigate now, workaround file bug report later
281    GetClientData = fun(LB, N) ->
282			    String = wxListBox:getString(LB, N),
283			    {_, Data} = lists:keyfind(String, 1, ParsedChoices),
284			    Data
285		    end,
286    wxCheckListBox:connect(ListBox, command_checklistbox_toggled,
287			   [{callback,
288			     fun(#wx{event=#wxCommand{commandInt=N}}, _) ->
289				     Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N),
290					     GetClientData(ListBox, N)}
291			     end}]),
292    Check = fun(Id, Bool) ->
293    		    wxCheckListBox:check(ListBox, Id, [{check, Bool}]),
294    		    Self ! {ListBox, Bool, GetClientData(ListBox, Id)}
295    	    end,
296    wxButton:connect(SelAllBtn, command_button_clicked,
297    		     [{callback, fun(#wx{}, _) ->
298    					 Count = wxListBox:getCount(ListBox),
299    					 [Check(SelId, true) ||
300    					     SelId <- lists:seq(0, Count-1),
301    					     not wxCheckListBox:isChecked(ListBox, SelId)]
302    				 end}]),
303    wxButton:connect(DeSelAllBtn, command_button_clicked,
304    		     [{callback, fun(#wx{}, _) ->
305    					 Count = wxListBox:getCount(ListBox),
306    					 [Check(SelId, false) ||
307    					     SelId <- lists:seq(0, Count-1),
308    					     wxCheckListBox:isChecked(ListBox, SelId)]
309    				 end}]),
310    case wxDialog:showModal(Dialog) of
311	?wxID_OK ->
312	    wxDialog:destroy(Dialog),
313	    get_checked(ListBox, []);
314	?wxID_CANCEL ->
315	    wxDialog:destroy(Dialog),
316	    get_checked(ListBox, []),
317	    throw(cancel)
318    end.
319
320get_checked(ListBox, Acc) ->
321    receive
322	{ListBox, true, FA} ->
323	    get_checked(ListBox, [FA|lists:delete(FA,Acc)]);
324	{ListBox, false, FA} ->
325	    get_checked(ListBox, lists:delete(FA,Acc))
326    after 0 ->
327	    lists:reverse(Acc)
328    end.
329
330select_matchspec(Pid, Parent, AllMatchSpecs, Key) ->
331    {MatchSpecs,RestMS} =
332	case lists:keytake(Key,1,AllMatchSpecs) of
333	    {value,{Key,MSs0},Rest} -> {MSs0,Rest};
334	    false -> {[],AllMatchSpecs}
335	end,
336    Scale = observer_wx:get_scale(),
337    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications",
338			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER},
339			   {size, {400*Scale, 400*Scale}}]),
340
341    Panel = wxPanel:new(Dialog),
342    PanelSz = wxBoxSizer:new(?wxVERTICAL),
343    MainSz = wxBoxSizer:new(?wxVERTICAL),
344    TxtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Match specification:"}]),
345    BtnSz = wxBoxSizer:new(?wxHORIZONTAL),
346    SavedSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Saved match specifications:"}]),
347
348    TextCtrl = create_styled_txtctrl(Panel),
349    wxSizer:add(TxtSz, TextCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]),
350
351    AddMsBtn  = wxButton:new(Panel, ?wxID_ANY, [{label, "New"}]),
352    EditMsBtn  = wxButton:new(Panel, ?wxID_ANY, [{label, "Edit"}]),
353    DelMsBtn  = wxButton:new(Panel, ?wxID_ANY, [{label, "Delete"}]),
354    wxSizer:add(BtnSz, AddMsBtn),
355    wxSizer:add(BtnSz, EditMsBtn),
356    wxSizer:add(BtnSz, DelMsBtn),
357
358    ListBox = wxListBox:new(Panel, ?wxID_ANY, []),
359    wxSizer:add(SavedSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]),
360    wxSizer:add(PanelSz, TxtSz, [{flag, ?wxEXPAND}, {proportion, 1}]),
361    wxSizer:add(PanelSz, BtnSz),
362    wxSizer:add(PanelSz, SavedSz, [{flag, ?wxEXPAND}, {proportion, 1}]),
363
364    wxWindow:setSizer(Panel, PanelSz),
365    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL},
366				{border, 5}, {proportion, 1}]),
367    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
368    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL},
369				  {border, 5}, {proportion, 0}]),
370    wxWindow:setSizer(Dialog, MainSz),
371    OkId = wxDialog:getAffirmativeId(Dialog),
372    OkButt = wxWindow:findWindowById(OkId),
373    wxWindow:disable(OkButt),
374    wxWindow:disable(EditMsBtn),
375    wxWindow:disable(DelMsBtn),
376
377    Choices = ms_names(MatchSpecs),
378    filter_listbox_data("", Choices, ListBox),
379
380    Add = fun(_,_) ->
381		  case edit_ms(TextCtrl, new, Dialog) of
382		      Ms = #match_spec{} ->
383			  add_and_select(-1, Ms, ListBox),
384			  wxWindow:enable(OkButt),
385			  wxWindow:enable(EditMsBtn),
386			  wxWindow:enable(DelMsBtn);
387		      Else -> Else
388		  end
389	  end,
390    Edit = fun(_,_) ->
391		   SelId = wxListBox:getSelection(ListBox),
392		   case SelId >= 0 of
393		       true ->
394			   #match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId),
395			   case edit_ms(TextCtrl, Name, Dialog) of
396			       Ms = #match_spec{} ->
397				   add_and_select(SelId, Ms, ListBox),
398				   wxWindow:enable(OkButt),
399				   wxWindow:enable(EditMsBtn),
400				   wxWindow:enable(DelMsBtn);
401			       Else -> Else
402			   end;
403		       false ->
404			   ok
405		   end
406	   end,
407    Del = fun(_,_) ->
408		  SelId = wxListBox:getSelection(ListBox),
409		  case SelId >= 0 of
410		      true ->
411			  wxListBox:delete(ListBox, SelId);
412		      false ->
413			  ok
414		  end
415	  end,
416    Sel = fun(#wx{event=#wxCommand{commandInt=Id}}, _) ->
417		  case Id >= 0 of
418		      true ->
419			  wxWindow:enable(OkButt),
420			  wxWindow:enable(EditMsBtn),
421			  wxWindow:enable(DelMsBtn),
422			  #match_spec{func=Str} = wxListBox:getClientData(ListBox,Id),
423			  wxStyledTextCtrl:setText(TextCtrl, Str);
424		      false ->
425			  try
426			      wxWindow:disable(OkButt),
427			      wxWindow:disable(EditMsBtn),
428			      wxWindow:disable(DelMsBtn)
429			  catch _:_ -> ok
430			  end
431		  end
432	  end,
433    wxButton:connect(AddMsBtn,  command_button_clicked,  [{callback,Add}]),
434    wxButton:connect(EditMsBtn,  command_button_clicked, [{callback,Edit}]),
435    wxButton:connect(DelMsBtn,  command_button_clicked,  [{callback,Del}]),
436    wxListBox:connect(ListBox, command_listbox_selected, [{callback, Sel}]),
437    case wxDialog:showModal(Dialog) of
438	?wxID_OK ->
439	    SelId  = wxListBox:getSelection(ListBox),
440	    Count = wxListBox:getCount(ListBox),
441	    MSs = [wxListBox:getClientData(ListBox, Id) ||
442		      Id <- lists:seq(0, Count-1)],
443	    Pid ! {update_ms, [{Key,MSs}|RestMS]},
444	    MS = lists:nth(SelId+1, MSs),
445	    wxDialog:destroy(Dialog),
446	    MS;
447	?wxID_CANCEL ->
448	    wxDialog:destroy(Dialog),
449	    throw(cancel)
450    end.
451
452output(Parent, Default) ->
453    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options",
454			  [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]),
455    Panel = wxPanel:new(Dialog),
456    MainSz = wxBoxSizer:new(?wxVERTICAL),
457    PanelSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel,  [{label, "Output"}]),
458
459    %% Output select
460    WinB = wxCheckBox:new(Panel, ?wxID_ANY, "Window", []),
461    check_box(WinB, proplists:get_value(window, Default, true)),
462    ShellB = wxCheckBox:new(Panel, ?wxID_ANY, "Shell", []),
463    check_box(ShellB, proplists:get_value(shell, Default, false)),
464    [wxSizer:add(PanelSz, CheckBox, []) || CheckBox <- [WinB, ShellB]],
465    GetFileOpts = ttb_file_options(Panel, PanelSz, Default),
466    %% Set sizer and show dialog
467    wxPanel:setSizer(Panel, PanelSz),
468    wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 3}]),
469    Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
470    wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]),
471    wxWindow:setSizerAndFit(Dialog, MainSz),
472    wxSizer:setSizeHints(MainSz, Dialog),
473    case wxDialog:showModal(Dialog) of
474	?wxID_OK ->
475	    Res = [{window, wxCheckBox:getValue(WinB)},
476		   {shell, wxCheckBox:getValue(ShellB)} | GetFileOpts()],
477	    wxDialog:destroy(Dialog),
478	    Res;
479	?wxID_CANCEL ->
480	    wxDialog:destroy(Dialog),
481	    throw(cancel)
482    end.
483
484edit_ms(TextCtrl, Label0, Parent) ->
485    Str = ensure_last_is_dot(wxStyledTextCtrl:getText(TextCtrl)),
486    try
487	MatchSpec = ms_from_string(Str),
488	Label = case Label0 == new of
489		    true -> get_label(Parent);
490		    _ -> Label0
491		end,
492	#match_spec{name=Label, term=MatchSpec,
493		    str=io_lib:format("~tw",[MatchSpec]),
494		    func=Str}
495    catch
496	throw:cancel ->
497	    ok;
498	throw:Error ->
499	    observer_wx:create_txt_dialog(Parent, Error, "Error", ?wxICON_ERROR),
500	    ok
501    end.
502
503get_label(Frame) ->
504    Dialog = wxTextEntryDialog:new(Frame, "Enter alias: "),
505    case wxDialog:showModal(Dialog) of
506	?wxID_OK ->
507	    wxTextEntryDialog:getValue(Dialog);
508	?wxID_CANCEL ->
509	    throw(cancel)
510    end.
511
512ms_from_string(Str) ->
513    try
514	Tokens = case erl_scan:string(Str) of
515		     {ok, Ts, _} -> Ts;
516		     {error, {SLine, SMod, SError}, _} ->
517			 throw(io_lib:format("~w: ~ts", [SLine,SMod:format_error(SError)]))
518		 end,
519	Exprs  = case erl_parse:parse_exprs(Tokens) of
520		     {ok, T} -> T;
521		     {error, {PLine, PMod, PError}} ->
522			 throw(io_lib:format("~w: ~ts", [PLine,PMod:format_error(PError)]))
523		 end,
524	Term = case Exprs of
525		   [{'fun', _, {clauses, Clauses}}|_] ->
526		       case ms_transform:transform_from_shell(dbg,Clauses,orddict:new()) of
527			   {error, [{_,[{MSLine,Mod,MSInfo}]}],_} ->
528			       throw(io_lib:format("~w: ~tp", [MSLine,Mod:format_error(MSInfo)]));
529			   {error, _} ->
530			       throw("Could not convert fun() to match spec");
531			   Ms ->
532			       Ms
533		       end;
534		   [Expr|_] ->
535		       erl_parse:normalise(Expr)
536	       end,
537	case erlang:match_spec_test([], Term, trace) of
538	    {ok, _, _, _} -> Term;
539	    {error, List} -> throw([[Error, $\n] || {_, Error} <- List])
540	end
541    catch error:_Reason ->
542	    %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, Stacktrace]),
543	    throw("Invalid term")
544    end.
545
546add_and_select(Id, MS0, ListBox) ->
547    [{Str,User}] = ms_names([MS0]),
548    Sel = case Id >= 0 of
549	     true ->
550		  wxListBox:setString(ListBox, Id, Str),
551		  wxListBox:setClientData(ListBox, Id, User),
552		  Id;
553	      false ->
554		  wxListBox:append(ListBox, Str, User)
555	  end,
556    wxListBox:setSelection(ListBox, Sel).
557
558filter_listbox_data(Input, Data, ListBox) ->
559    filter_listbox_data(Input, Data, ListBox, true).
560
561filter_listbox_data(Input, Data, ListBox, AddClientData) ->
562    FilteredData = [X || X = {Str, _} <- Data,
563                         re:run(Str, Input, [unicode]) =/= nomatch],
564    wxListBox:clear(ListBox),
565    wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]),
566    AddClientData andalso
567	wx:foldl(fun({_, Term}, N) ->
568			 wxListBox:setClientData(ListBox, N, Term),
569			 N+1
570		 end, 0, FilteredData),
571    FilteredData.
572
573get_modules(Node) ->
574    lists:sort([Module || {Module, _} <- observer_wx:try_rpc(Node, code, all_loaded, [])]).
575
576optionpage_top_right(Panel, TopRightSz, Options, Text) ->
577    Sizer = wxBoxSizer:new(?wxVERTICAL),
578    ChkBox = wxCheckBox:new(Panel, ?wxID_ANY, "Inherit on " ++ Text, []),
579    RadioSz = wxBoxSizer:new(?wxVERTICAL),
580    Radio1 = wxRadioButton:new(Panel, ?wxID_ANY, "All " ++ Text, [{style, ?wxRB_GROUP}]),
581    Radio2 = wxRadioButton:new(Panel, ?wxID_ANY, "First " ++ Text ++ " only", []),
582    wxSizer:add(Sizer, ChkBox, []),
583    wxSizer:add(RadioSz, Radio1, []),
584    wxSizer:add(RadioSz, Radio2, []),
585    wxSizer:add(Sizer, RadioSz, [{flag, ?wxLEFT},{border, 20}]),
586    wxSizer:add(TopRightSz, Sizer, Options),
587    {ChkBox, Radio1, Radio2}.
588
589
590create_styled_txtctrl(Parent) ->
591    FixedFont = observer_wx:get_attrib({font, fixed}),
592    Ed = wxStyledTextCtrl:new(Parent),
593    wxStyledTextCtrl:styleClearAll(Ed),
594    wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
595    wxStyledTextCtrl:setLexer(Ed, ?wxSTC_LEX_ERLANG),
596    wxStyledTextCtrl:setMarginType(Ed, 1, ?wxSTC_MARGIN_NUMBER),
597    wxStyledTextCtrl:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
598    wxStyledTextCtrl:setUseHorizontalScrollBar(Ed, false),
599
600    Styles =  [{?wxSTC_ERLANG_DEFAULT,  {0,0,0}},
601	       {?wxSTC_ERLANG_COMMENT,  {160,53,35}},
602	       {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
603	       {?wxSTC_ERLANG_NUMBER,   {5,5,100}},
604	       {?wxSTC_ERLANG_KEYWORD,  {130,40,172}},
605	       {?wxSTC_ERLANG_STRING,   {170,45,132}},
606	       {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
607	       {?wxSTC_ERLANG_ATOM,     {0,0,0}},
608	       {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
609	       {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
610	       {?wxSTC_ERLANG_MACRO,    {40,144,170}},
611	       {?wxSTC_ERLANG_RECORD,   {40,100,20}},
612	       {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
613    SetStyle = fun({Style, Color}) ->
614		       wxStyledTextCtrl:styleSetFont(Ed, Style, FixedFont),
615		       wxStyledTextCtrl:styleSetForeground(Ed, Style, Color)
616	       end,
617    [SetStyle(Style) || Style <- Styles],
618    wxStyledTextCtrl:setKeyWords(Ed, 0, keyWords()),
619    Ed.
620
621
622keyWords() ->
623    L = ["after","begin","case","try","cond","catch","andalso","orelse",
624	 "end","fun","if","let","of","receive","when","bnot","not",
625	 "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
626    lists:flatten([K ++ " " || K <- L] ++ [0]).
627
628
629enable(CheckBox, Radio) ->
630    case wxCheckBox:isChecked(CheckBox) of
631	false ->
632	    [wxWindow:disable(R) || R <- Radio];
633	true ->
634	    [wxWindow:enable(R) || R <- Radio]
635    end.
636
637
638check_box(ChkBox, Bool) ->
639    case Bool of
640	true ->
641	    wxCheckBox:set3StateValue(ChkBox, ?wxCHK_CHECKED);
642	false ->
643	    ignore
644    end.
645
646parse_function_names(Choices) ->
647    StrList = [{atom_to_list(Name) ++ "/" ++ integer_to_list(Arity), Term}
648	       || Term = {Name, Arity} <- Choices],
649    parse_function_names(StrList, []).
650
651parse_function_names([], Acc) ->
652    lists:reverse(Acc);
653parse_function_names([{H, Term}|T], Acc) ->
654    IsFun = re:run(H, ".*-fun-\\d*?-", [unicode,ucp]),
655    IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]),
656    IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]),
657    Parsed =
658	if IsFun =/= nomatch -> "Fun: " ++ H;
659	   IsLc =/= nomatch -> "List comprehension: " ++ H;
660	   IsLbc =/= nomatch -> "Bit comprehension: " ++ H;
661	   true ->
662		H
663	end,
664    parse_function_names(T, [{Parsed, Term} | Acc]).
665
666ms_names(MatchSpecList) ->
667    MsOrAlias = fun(#match_spec{name = A, str = M}) ->
668			case A of
669			    "" -> M;
670			    _ -> A ++ "    " ++ M
671			end
672		end,
673    [{MsOrAlias(X), X} || X <- MatchSpecList].
674
675ensure_last_is_dot([]) ->
676    ".";
677ensure_last_is_dot(String) ->
678    case lists:last(String) =:= $. of
679	true ->
680	    String;
681	false ->
682	    String ++ "."
683    end.
684
685ttb_file_options(Panel, Sizer, Default) ->
686    Top   = wxBoxSizer:new(?wxVERTICAL),
687    NameS = wxBoxSizer:new(?wxHORIZONTAL),
688    FileBox = wxCheckBox:new(Panel, ?wxID_ANY, "File (Using ttb file tracer)", []),
689    check_box(FileBox, proplists:get_value(file, Default, false)),
690    wxSizer:add(Sizer, FileBox),
691    Desc  = wxStaticText:new(Panel, ?wxID_ANY, "File"),
692    FileName = proplists:get_value(filename, Default, "ttb"),
693    FileT = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {150,-1}}, {value, FileName}]),
694    FileB = wxButton:new(Panel, ?wxID_ANY, [{label, "Browse"}]),
695    wxSizer:add(NameS, Desc,  [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]),
696    wxSizer:add(NameS, FileT, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}]),
697    wxSizer:add(NameS, FileB, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]),
698
699    WrapB = wxCheckBox:new(Panel, ?wxID_ANY, "Wrap logs"),
700    WrapSz = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_sz, Default, 128),
701			  64, 10*1024, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]),
702    WrapC = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_c, Default, 8),
703			 2, 100, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]),
704
705    wxSizer:add(Top, NameS, [{flag, ?wxEXPAND}]),
706    wxSizer:add(Top, WrapB, []),
707    wxSizer:add(Top, WrapSz,[{flag, ?wxEXPAND}]),
708    wxSizer:add(Top, WrapC, [{flag, ?wxEXPAND}]),
709    wxSizer:add(Sizer, Top, [{flag, ?wxEXPAND bor ?wxLEFT},{border, 20}]),
710
711    Enable = fun(UseFile, UseWrap0) ->
712		     UseWrap = UseFile andalso UseWrap0,
713		     [wxWindow:enable(W, [{enable, UseFile}]) || W <- [Desc,FileT,FileB,WrapB]],
714		     [wxWindow:enable(W, [{enable, UseWrap}]) || W <- [WrapSz, WrapC]],
715		     check_box(WrapB, UseWrap0)
716	     end,
717    Enable(proplists:get_value(file, Default, false), proplists:get_value(wrap, Default, false)),
718    wxPanel:connect(FileBox, command_checkbox_clicked,
719		    [{callback, fun(_,_) ->
720					Enable(wxCheckBox:getValue(FileBox),
721					       wxCheckBox:getValue(WrapB))
722				end}]),
723    wxPanel:connect(WrapB, command_checkbox_clicked,
724		    [{callback, fun(_,_) ->
725					Enable(true, wxCheckBox:getValue(WrapB))
726				end}]),
727    wxPanel:connect(FileB, command_button_clicked,
728		    [{callback, fun(_,_) ->  get_file(FileT) end}]),
729    fun() ->
730	    [{file, wxCheckBox:getValue(FileBox)},
731	     {filename, wxTextCtrl:getValue(FileT)},
732	     {wrap, wxCheckBox:getValue(WrapB)},
733	     {wrap_sz, wxSlider:getValue(WrapSz)},
734	     {wrap_c, wxSlider:getValue(WrapC)}]
735    end.
736
737get_file(Text) ->
738    Str = wxTextCtrl:getValue(Text),
739    Dialog = wxFileDialog:new(Text,
740			      [{message, "Select a file"},
741			       {defaultFile, Str}]),
742    case wxDialog:showModal(Dialog) of
743	?wxID_OK ->
744	    Dir = wxFileDialog:getDirectory(Dialog),
745	    File = wxFileDialog:getFilename(Dialog),
746	    wxTextCtrl:setValue(Text, filename:join(Dir, File));
747	_ -> ok
748    end,
749    wxFileDialog:destroy(Dialog).
750