1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2011-2017. 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_trace_wx).
21
22-export([start_link/3, add_processes/1, add_ports/1]).
23-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
24	 handle_event/2, handle_cast/2]).
25
26-behaviour(wx_object).
27
28-include_lib("wx/include/wx.hrl").
29-include("observer_defs.hrl").
30
31-define(SAVE_TRACEOPTS, 305).
32-define(LOAD_TRACEOPTS, 306).
33-define(TOGGLE_TRACE, 307).
34-define(ADD_ALL, 308).
35-define(ADD_NEW_PROCS, 309).
36-define(ADD_NEW_PORTS, 310).
37-define(ADD_TP, 311).
38-define(TRACE_OUTPUT, 312).
39-define(DEF_MS_FUNCS,  313).
40-define(DEF_MS_SEND,  314).
41-define(DEF_MS_RECV,  315).
42-define(DEF_PROC_OPTS,  316).
43-define(DEF_PORT_OPTS,  317).
44
45-define(NODES_WIN, 330).
46-define(ADD_NODES, 331).
47-define(REMOVE_NODES, 332).
48
49-define(PROC_WIN, 340).
50-define(EDIT_PROCS, 341).
51-define(REMOVE_PROCS, 342).
52
53-define(PORT_WIN, 350).
54-define(EDIT_PORTS, 351).
55-define(REMOVE_PORTS, 352).
56
57-define(MODULES_WIN, 360).
58-define(REMOVE_MOD_MS, 361).
59
60-define(FUNCS_WIN, 370).
61-define(EDIT_FUNCS_MS, 371).
62-define(REMOVE_FUNCS_MS, 372).
63
64-define(LOG_WIN, 380).
65-define(LOG_SAVE, 381).
66-define(LOG_CLEAR, 382).
67
68-define(NO_NODES_HELP,"Right click to add nodes").
69-define(NODES_HELP,"Select nodes to see traced processes and ports").
70-define(NO_P_HELP,"Add items from Processes/Ports tab").
71-define(P_HELP,"Select nodes to see traced processes and ports").
72-define(NO_TP_HELP,"Add trace pattern with button below").
73-define(TP_HELP,"Select module to see trace patterns").
74
75-record(state,
76	{parent,
77	 panel,
78	 n_view, proc_view, port_view, m_view, f_view,  %% The listCtrl's
79	 logwin, %% The latest log window
80	 nodes = [],
81	 toggle_button,
82	 tpids = [],  % #titem
83	 tports = [], % #titem
84	 def_proc_flags = [],
85	 def_port_flags = [],
86	 output = [],
87	 tpatterns = dict:new(), % Key =:= Module::atom, Value =:= {M, F, A, MatchSpec}
88	 match_specs = []}). % [ #match_spec{} ]
89
90-record(titem, {id, opts}).
91
92start_link(Notebook, ParentPid, Config) ->
93    wx_object:start_link(?MODULE, [Notebook, ParentPid, Config], []).
94
95add_processes(Pids) when is_list(Pids) ->
96    wx_object:cast(observer_wx:get_tracer(), {add_processes, Pids}).
97
98add_ports(Ports) when is_list(Ports) ->
99    wx_object:cast(observer_wx:get_tracer(), {add_ports, Ports}).
100
101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102
103init([Notebook, ParentPid, Config]) ->
104    wx:batch(fun() -> create_window(Notebook, ParentPid, Config) end).
105
106create_window(Notebook, ParentPid, Config) ->
107    %% Create the window
108    Panel = wxPanel:new(Notebook, [{size, wxWindow:getClientSize(Notebook)}]),
109    Sizer = wxBoxSizer:new(?wxVERTICAL),
110    Splitter = wxSplitterWindow:new(Panel, [{size, wxWindow:getClientSize(Panel)},
111					    {style, ?SASH_STYLE}]),
112    {NodeProcView, NodeView, ProcessView, PortView} =
113	create_proc_port_view(Splitter),
114    {MatchSpecView,ModView,FuncView} = create_matchspec_view(Splitter),
115    wxSplitterWindow:setSashGravity(Splitter, 0.5),
116    wxSplitterWindow:setMinimumPaneSize(Splitter,50),
117    wxSplitterWindow:splitHorizontally(Splitter, NodeProcView, MatchSpecView,
118				       [{sashPosition,368}]),
119    wxSizer:add(Sizer, Splitter, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}, {proportion, 1}]),
120    %% Buttons
121    Buttons = wxBoxSizer:new(?wxHORIZONTAL),
122    ToggleButton = wxToggleButton:new(Panel, ?TOGGLE_TRACE, "Start Trace", []),
123    wxSizer:add(Buttons, ToggleButton, [{flag, ?wxALIGN_CENTER_VERTICAL}]),
124    wxSizer:addSpacer(Buttons, 15),
125    wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NODES, [{label, "Add Nodes"}])),
126    AllButton = wxButton:new(Panel, ?ADD_ALL, [{label, "Add All"}]),
127    wxControl:setToolTip(AllButton, "Trace all processes and ports"),
128    wxSizer:add(Buttons, AllButton),
129    wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PROCS, [{label, "Add 'new' Processes"}])),
130    wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_NEW_PORTS, [{label, "Add 'new' Ports"}])),
131    wxSizer:add(Buttons, wxButton:new(Panel, ?ADD_TP, [{label, "Add Trace Pattern"}])),
132    wxMenu:connect(Panel, command_togglebutton_clicked, [{skip, true}]),
133    wxMenu:connect(Panel, command_button_clicked, [{skip, true}]),
134    wxSizer:add(Sizer, Buttons, [{flag, ?wxLEFT bor ?wxRIGHT bor ?wxDOWN},
135				 {border, 5}, {proportion,0}]),
136    wxWindow:setSizer(Panel, Sizer),
137    MS = parse_ms(maps:get(match_specs, Config, []), default_matchspecs()),
138    {Panel, #state{parent=ParentPid, panel=Panel,
139		   n_view=NodeView, proc_view=ProcessView, port_view=PortView,
140		   m_view=ModView, f_view=FuncView,
141		   toggle_button = ToggleButton,
142                   output=maps:get(output, Config, []),
143                   def_proc_flags=maps:get(procflags, Config, []),
144                   def_port_flags=maps:get(portflags, Config, []),
145                   match_specs=MS
146                  }}.
147
148default_matchspecs() ->
149    [{Key,default_matchspecs(Key)} || Key <- [funcs,send,'receive']].
150default_matchspecs(Key) ->
151    Ms = get_default_matchspecs(Key),
152    [make_ms(Name,Term,FunStr) || {Name,Term,FunStr} <- Ms].
153
154get_default_matchspecs(funcs) ->
155    [{"Return Trace", [{'_', [], [{return_trace}]}],
156      "fun(_) -> return_trace() end"},
157     {"Exception Trace", [{'_', [], [{exception_trace}]}],
158      "fun(_) -> exception_trace() end"},
159     {"Message Caller", [{'_', [], [{message,{caller}}]}],
160      "fun(_) -> message(caller()) end"},
161     {"Message Dump", [{'_', [], [{message,{process_dump}}]}],
162      "fun(_) -> message(process_dump()) end"}];
163get_default_matchspecs(send) ->
164    [{"To local node", [{['$1','_'], [{'==',{node,'$1'},{node}}], []}],
165      "fun([Pid,_]) when node(Pid)==node() ->\n    true\nend"},
166     {"To remote node", [{['$1','_'], [{'=/=',{node,'$1'},{node}}], []}],
167      "fun([Pid,_]) when node(Pid)=/=node() ->\n    true\nend"}];
168get_default_matchspecs('receive') ->
169    [{"From local node", [{['$1','_','_'], [{'==','$1',{node}}], []}],
170      "fun([Node,_,_]) when Node==node() ->\n    true\nend"},
171     {"From remote node", [{['$1','_','_'], [{'=/=','$1',{node}}], []}],
172      "fun([Node,_,_]) when Node=/=node() ->\n    true\nend"}].
173
174
175create_proc_port_view(Parent) ->
176    Panel  = wxPanel:new(Parent),
177    MainSz = wxBoxSizer:new(?wxHORIZONTAL),
178    Style = ?wxLC_REPORT bor ?wxLC_HRULES,
179    Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]),
180    Nodes = wxListCtrl:new(Splitter, [{winid, ?NODES_WIN}, {style, Style}]),
181    ProcsPortsSplitter = wxSplitterWindow:new(Splitter, [{style, ?SASH_STYLE}]),
182    Procs = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PROC_WIN},{style,Style}]),
183    Ports = wxListCtrl:new(ProcsPortsSplitter, [{winid,?PORT_WIN},{style,Style}]),
184    Li = wxListItem:new(),
185    wxListItem:setText(Li, "Nodes"),
186    wxListCtrl:insertColumn(Nodes, 0, Li),
187
188    AddProc = fun({Name, Align, DefSize}, Col) ->
189			   wxListItem:setText(Li, Name),
190			   wxListItem:setAlign(Li, Align),
191			   wxListCtrl:insertColumn(Procs, Col, Li),
192			   wxListCtrl:setColumnWidth(Procs, Col, DefSize),
193			   Col + 1
194		   end,
195    Scale = observer_wx:get_scale(),
196    ProcListItems = [{"Process Id",    ?wxLIST_FORMAT_CENTER,  Scale*120},
197		     {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}],
198    lists:foldl(AddProc, 0, ProcListItems),
199
200    AddPort = fun({Name, Align, DefSize}, Col) ->
201			   wxListItem:setText(Li, Name),
202			   wxListItem:setAlign(Li, Align),
203			   wxListCtrl:insertColumn(Ports, Col, Li),
204			   wxListCtrl:setColumnWidth(Ports, Col, DefSize),
205			   Col + 1
206		   end,
207    PortListItems = [{"Port Id",    ?wxLIST_FORMAT_CENTER,  Scale*120},
208		     {"Trace Options", ?wxLIST_FORMAT_LEFT, Scale*300}],
209    lists:foldl(AddPort, 0, PortListItems),
210
211    wxListItem:destroy(Li),
212
213    wxSplitterWindow:setSashGravity(Splitter, 0.0),
214    wxSplitterWindow:setMinimumPaneSize(Splitter,50),
215    wxSplitterWindow:splitVertically(Splitter, Nodes, ProcsPortsSplitter,
216				     [{sashPosition, 155}]),
217    wxSizer:add(MainSz, Splitter, [{flag, ?wxEXPAND}, {proportion, 1}]),
218
219    wxSplitterWindow:setSashGravity(ProcsPortsSplitter, 0.5),
220    wxSplitterWindow:setMinimumPaneSize(ProcsPortsSplitter,50),
221    wxSplitterWindow:splitHorizontally(ProcsPortsSplitter, Procs, Ports,
222				       [{sashPosition, 182}]),
223
224    wxListCtrl:connect(Procs, command_list_item_right_click),
225    wxListCtrl:connect(Ports, command_list_item_right_click),
226    wxListCtrl:connect(Nodes, command_list_item_right_click),
227    wxListCtrl:connect(Nodes, command_list_item_selected),
228    wxListCtrl:connect(Procs, size, [{skip, true}]),
229    wxListCtrl:connect(Ports, size, [{skip, true}]),
230    wxListCtrl:connect(Nodes, size, [{skip, true}]),
231
232    wxListCtrl:setToolTip(Nodes, ?NO_NODES_HELP),
233    wxListCtrl:setToolTip(Procs, ?NO_P_HELP),
234    wxListCtrl:setToolTip(Ports, ?NO_P_HELP),
235
236    wxPanel:setSizer(Panel, MainSz),
237    wxWindow:setFocus(Procs),
238    {Panel, Nodes, Procs, Ports}.
239
240create_matchspec_view(Parent) ->
241    Panel  = wxPanel:new(Parent),
242    MainSz = wxBoxSizer:new(?wxHORIZONTAL),
243    Style = ?wxLC_REPORT bor ?wxLC_HRULES,
244    Splitter = wxSplitterWindow:new(Panel, [{style, ?SASH_STYLE}]),
245    Modules = wxListCtrl:new(Splitter, [{winid, ?MODULES_WIN},
246					{style, Style  bor ?wxLC_SINGLE_SEL}]),
247    Funcs   = wxListCtrl:new(Splitter, [{winid, ?FUNCS_WIN}, {style, Style}]),
248    Li = wxListItem:new(),
249
250    Scale = observer_wx:get_scale(),
251    wxListItem:setText(Li, "Modules"),
252    wxListCtrl:insertColumn(Modules, 0, Li),
253    wxListItem:setText(Li, "Functions"),
254    wxListCtrl:insertColumn(Funcs, 0, Li),
255    wxListCtrl:setColumnWidth(Funcs, 0, Scale*150),
256    wxListItem:setText(Li, "Match Spec"),
257    wxListCtrl:insertColumn(Funcs, 1, Li),
258    wxListCtrl:setColumnWidth(Funcs, 1, Scale*300),
259    wxListItem:destroy(Li),
260
261    wxSplitterWindow:setSashGravity(Splitter, 0.0),
262    wxSplitterWindow:setMinimumPaneSize(Splitter,50),
263    wxSplitterWindow:splitVertically(Splitter, Modules, Funcs, [{sashPosition, 155}]),
264    wxSizer:add(MainSz, Splitter,   [{flag, ?wxEXPAND}, {proportion, 1}]),
265
266    wxListCtrl:connect(Modules, size, [{skip, true}]),
267    wxListCtrl:connect(Funcs,   size, [{skip, true}]),
268    wxListCtrl:connect(Modules, command_list_item_selected),
269    wxListCtrl:connect(Modules, command_list_item_right_click),
270    wxListCtrl:connect(Funcs, command_list_item_right_click),
271    wxListCtrl:setToolTip(Panel, ?NO_TP_HELP),
272    wxPanel:setSizer(Panel, MainSz),
273    {Panel, Modules, Funcs}.
274
275create_menues(Parent) ->
276    Menus = [{"File",
277	      [#create_menu{id = ?LOAD_TRACEOPTS, text = "Load settings"},
278	       #create_menu{id = ?SAVE_TRACEOPTS, text = "Save settings"}]},
279	     {"Options",
280	      [#create_menu{id = ?TRACE_OUTPUT, text = "Output"},
281	       #create_menu{id = ?DEF_MS_FUNCS, text = "Default Match Specifications for Functions"},
282	       #create_menu{id = ?DEF_MS_SEND, text = "Default Match Specifications for 'send'"},
283	       #create_menu{id = ?DEF_MS_RECV, text = "Default Match Specifications for 'receive'"},
284	       #create_menu{id = ?DEF_PROC_OPTS, text = "Default Process Options"},
285	       #create_menu{id = ?DEF_PORT_OPTS, text = "Default Port Options"}]}
286	    ],
287    observer_wx:create_menus(Parent, Menus).
288
289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
290%%Main window
291handle_event(#wx{obj=Obj, event=#wxSize{size={W,_}}}, State) ->
292    case wx:getObjectType(Obj) =:= wxListCtrl of
293	true ->  observer_lib:set_listctrl_col_size(Obj, W);
294	false -> ok
295    end,
296    {noreply, State};
297
298handle_event(#wx{id=?ADD_ALL}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) ->
299    try
300	Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts),
301	Process = #titem{id=all, opts=Opts},
302	{noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})}
303    catch cancel -> {noreply, State}
304    end;
305
306handle_event(#wx{id=?ADD_NEW_PROCS}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) ->
307    try
308	Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts),
309	Process = #titem{id=new_processes, opts=Opts},
310	{noreply, do_add_processes([Process], State#state{def_proc_flags=Opts})}
311    catch cancel -> {noreply, State}
312    end;
313
314handle_event(#wx{id=?ADD_NEW_PORTS}, State = #state{panel=Parent, def_port_flags=TraceOpts}) ->
315    try
316	Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts),
317	Port = #titem{id=new_ports, opts=Opts},
318	{noreply, do_add_ports([Port], State#state{def_port_flags=Opts})}
319    catch cancel -> {noreply, State}
320    end;
321
322handle_event(#wx{id=?ADD_TP},
323	     State = #state{panel=Parent, nodes=Nodes, match_specs=Ms}) ->
324    Node = case Nodes of
325	       [N|_] -> N;
326	       [] -> node()
327	   end,
328    case observer_traceoptions_wx:trace_pattern(self(), Parent, Node, Ms) of
329	cancel ->
330	    {noreply, State};
331	Patterns ->
332	    {noreply, do_add_patterns(Patterns, State)}
333    end;
334
335handle_event(#wx{id=?MODULES_WIN, event=#wxList{type=command_list_item_selected, itemIndex=Row}},
336	     State = #state{tpatterns=TPs, m_view=Mview, f_view=Fview}) ->
337    Module = list_to_atom(wxListCtrl:getItemText(Mview, Row)),
338    update_functions_view(dict:fetch(Module, TPs), Fview),
339    {noreply, State};
340
341handle_event(#wx{id=?NODES_WIN,
342		 event=#wxList{type=command_list_item_selected}},
343	     State = #state{tpids=Tpids, tports=Tports, n_view=Nview,
344			    proc_view=ProcView, port_view=PortView, nodes=Ns}) ->
345    Nodes = get_selected_items(Nview, Ns),
346    update_p_view(Tpids, ProcView, Nodes),
347    update_p_view(Tports, PortView, Nodes),
348    {noreply, State};
349
350handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 1}},
351	     #state{panel = Panel,
352		    nodes = Nodes,
353		    tpids = TProcs,
354		    tports = TPorts,
355		    tpatterns = TPs0,
356		    toggle_button = ToggleBtn,
357		    output = Opts
358		   } = State) ->
359    try
360	TPs = dict:to_list(TPs0),
361	(TProcs == []) andalso (TPorts == []) andalso throw({error, "No processes or ports traced"}),
362	(Nodes == []) andalso throw({error, "No nodes traced"}),
363	HaveCallTrace = fun(#titem{opts=Os}) -> lists:member(functions,Os) end,
364	WStr = "Call trace actived but no trace patterns used",
365	(TPs == []) andalso lists:any(HaveCallTrace, TProcs) andalso
366	    observer_wx:create_txt_dialog(Panel, WStr, "Warning", ?wxICON_WARNING),
367
368	{TTB, LogWin}  = ttb_output_args(Panel, Opts),
369	{ok, _} = ttb:tracer(Nodes, TTB),
370	setup_ttb(TPs, TProcs, TPorts),
371	wxToggleButton:setLabel(ToggleBtn, "Stop Trace"),
372	{noreply, State#state{logwin=LogWin}}
373    catch {error, Msg} ->
374	    observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR),
375	    wxToggleButton:setValue(ToggleBtn, false),
376	    {noreply, State}
377    end;
378
379handle_event(#wx{event = #wxCommand{type = command_togglebutton_clicked, commandInt = 0}},
380	     #state{toggle_button = ToggleBtn} = State) ->
381    %%Stop tracing
382    ttb:stop(nofetch),
383    wxToggleButton:setLabel(ToggleBtn, "Start Trace"),
384    wxToggleButton:setValue(ToggleBtn, false),
385    {noreply, State#state{logwin=false}};
386
387handle_event(#wx{id=Id, obj=LogWin, event=Ev},
388	     #state{toggle_button = ToggleBtn, logwin=Latest} = State)
389  when Id =:= ?LOG_WIN; is_record(Ev, wxClose) ->
390    case LogWin of
391	Latest ->
392	    %%Stop tracing
393	    ttb:stop(nofetch),
394	    wxToggleButton:setLabel(ToggleBtn, "Start Trace"),
395	    wxToggleButton:setValue(ToggleBtn, false),
396	    {noreply, State#state{logwin=false}};
397	_ ->
398	    {noreply, State}
399    end;
400
401handle_event(#wx{id=?LOG_CLEAR, userData=TCtrl}, State) ->
402    wxTextCtrl:clear(TCtrl),
403    {noreply, State};
404
405handle_event(#wx{id=?LOG_SAVE, userData=TCtrl}, #state{panel=Panel} = State) ->
406    Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
407    case wxFileDialog:showModal(Dialog) of
408	?wxID_OK ->
409	    Path = wxFileDialog:getPath(Dialog),
410	    wxDialog:destroy(Dialog),
411	    wxTextCtrl:saveFile(TCtrl, [{file, Path}]);
412	_ ->
413	    wxDialog:destroy(Dialog),
414	    ok
415    end,
416    {noreply, State};
417
418handle_event(#wx{id = ?SAVE_TRACEOPTS},
419	     #state{panel = Panel} = State) ->
420    Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}]),
421    case wxFileDialog:showModal(Dialog) of
422	?wxID_OK ->
423	    Path = wxFileDialog:getPath(Dialog),
424	    write_file(Panel, Path, get_config(State));
425	_ ->
426	    ok
427    end,
428    wxDialog:destroy(Dialog),
429    {noreply, State};
430
431
432handle_event(#wx{id = ?LOAD_TRACEOPTS}, #state{panel = Panel} = State) ->
433    Dialog = wxFileDialog:new(Panel, [{style, ?wxFD_FILE_MUST_EXIST}]),
434    State2 = case wxFileDialog:showModal(Dialog) of
435		 ?wxID_OK ->
436		     Path = wxFileDialog:getPath(Dialog),
437		     read_settings(Path, State);
438		 _ ->
439		     State
440	     end,
441    wxDialog:destroy(Dialog),
442    {noreply, State2};
443
444handle_event(#wx{id=?PROC_WIN, event=#wxList{type=command_list_item_right_click}},
445	     State = #state{panel=Panel, proc_view=LCtrl, tpids=Tpids,
446			    n_view=Nview, nodes=Nodes}) ->
447    case get_visible_ps(Tpids, Nodes, Nview) of
448	[] ->
449	    ok;
450	Visible ->
451	    case get_selected_items(LCtrl, Visible) of
452		[] ->
453		    ok;
454		_ ->
455		    create_right_click_menu(
456		      Panel,
457		      [{?EDIT_PROCS, "Edit process options"},
458		       {?REMOVE_PROCS, "Remove processes"}])
459	    end
460    end,
461    {noreply, State};
462
463handle_event(#wx{id=?PORT_WIN, event=#wxList{type=command_list_item_right_click}},
464	     State = #state{panel=Panel, port_view=LCtrl, tports=Tports,
465			    n_view=Nview, nodes=Nodes}) ->
466    case get_visible_ps(Tports, Nodes, Nview) of
467	[] ->
468	    ok;
469	Visible ->
470	    case get_selected_items(LCtrl, Visible) of
471		[] ->
472		    ok;
473		_ ->
474		    create_right_click_menu(
475		      Panel,
476		      [{?EDIT_PORTS, "Edit port options"},
477		       {?REMOVE_PORTS, "Remove ports"}])
478	    end
479    end,
480    {noreply, State};
481
482handle_event(#wx{id=?MODULES_WIN,event=#wxList{type=command_list_item_right_click}},
483	     State = #state{panel=Panel, m_view=Mview, tpatterns=TPs}) ->
484    case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
485	[] ->
486	    ok;
487	_ ->
488	    create_right_click_menu(
489	      Panel,
490	      [{?REMOVE_MOD_MS, "Remove trace patterns"}])
491    end,
492    {noreply,State};
493
494handle_event(#wx{id=?FUNCS_WIN,event=#wxList{type=command_list_item_right_click}},
495	     State = #state{panel=Panel, m_view=Mview, f_view=Fview,
496			    tpatterns=TPs}) ->
497    case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
498	[] ->
499	    ok;
500	[Module] ->
501	    case get_selected_items(Fview, dict:fetch(Module, TPs)) of
502		[] ->
503		    ok;
504		_ ->
505		    create_right_click_menu(
506		      Panel,
507		      [{?EDIT_FUNCS_MS, "Edit matchspecs"},
508		       {?REMOVE_FUNCS_MS, "Remove trace patterns"}])
509	    end
510    end,
511    {noreply,State};
512
513handle_event(#wx{id=?NODES_WIN,event=#wxList{type=command_list_item_right_click}},
514	     State = #state{panel=Panel, n_view=Nview, nodes=Nodes}) ->
515    Menu =
516	case get_selected_items(Nview, Nodes) of
517	    [] ->
518		[{?ADD_NODES, "Add nodes"}];
519	    _ ->
520		[{?ADD_NODES, "Add nodes"},
521		 {?REMOVE_NODES, "Remove nodes"}]
522	end,
523    create_right_click_menu(Panel,Menu),
524    {noreply, State};
525
526handle_event(#wx{id=?EDIT_PROCS}, #state{panel=Panel, tpids=Tpids, proc_view=Procs} = State) ->
527    try
528	[#titem{opts=DefOpts}|_] = Selected = get_selected_items(Procs, Tpids),
529	Opts = observer_traceoptions_wx:process_trace(Panel, DefOpts),
530	Changed = [Tpid#titem{opts=Opts} || Tpid <- Selected],
531	{noreply, do_add_processes(Changed, State#state{def_proc_flags=Opts})}
532    catch _:_ ->
533	    {noreply, State}
534    end;
535
536handle_event(#wx{id=?REMOVE_PROCS},
537	     #state{tpids=Tpids, proc_view=LCtrl,
538		    n_view=Nview, nodes=Nodes} = State) ->
539    Selected = get_selected_items(LCtrl, Tpids),
540    Pids = Tpids -- Selected,
541    update_p_view(Pids, LCtrl, Nodes, Nview),
542    {noreply, State#state{tpids=Pids}};
543
544handle_event(#wx{id=?EDIT_PORTS}, #state{panel=Panel, tports=Tports, port_view=Ports} = State) ->
545    try
546	[#titem{opts=DefOpts}|_] = Selected = get_selected_items(Ports, Tports),
547	Opts = observer_traceoptions_wx:port_trace(Panel, DefOpts),
548	Changed = [Tport#titem{opts=Opts} || Tport <- Selected],
549	{noreply, do_add_ports(Changed, State#state{def_port_flags=Opts})}
550    catch _:_ ->
551	    {noreply, State}
552    end;
553
554handle_event(#wx{id=?REMOVE_PORTS},
555	     #state{tports=Tports, port_view=LCtrl,
556		    n_view=Nview, nodes=Nodes} = State) ->
557    Selected = get_selected_items(LCtrl, Tports),
558    Ports = Tports -- Selected,
559    update_p_view(Ports, LCtrl, Nodes, Nview),
560    {noreply, State#state{tports=Ports}};
561
562handle_event(#wx{id=?DEF_PROC_OPTS}, #state{panel=Panel, def_proc_flags=PO} = State) ->
563    try
564	Opts = observer_traceoptions_wx:process_trace(Panel, PO),
565	{noreply, State#state{def_proc_flags=Opts}}
566    catch _:_ ->
567	    {noreply, State}
568    end;
569
570handle_event(#wx{id=?DEF_PORT_OPTS}, #state{panel=Panel, def_port_flags=PO} = State) ->
571    try
572	Opts = observer_traceoptions_wx:port_trace(Panel, PO),
573	{noreply, State#state{def_port_flags=Opts}}
574    catch _:_ ->
575	    {noreply, State}
576    end;
577
578handle_event(#wx{id=?DEF_MS_FUNCS}, #state{panel=Panel, match_specs=Ms} = State) ->
579    try %% Return selected MS and sends new MS's to us
580	observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, funcs)
581    catch _:_ ->
582	    cancel
583    end,
584    {noreply, State};
585
586handle_event(#wx{id=?DEF_MS_SEND}, #state{panel=Panel, match_specs=Ms} = State) ->
587    try %% Return selected MS and sends new MS's to us
588	observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, send)
589    catch _:_ ->
590	    cancel
591    end,
592    {noreply, State};
593
594handle_event(#wx{id=?DEF_MS_RECV}, #state{panel=Panel, match_specs=Ms} = State) ->
595    try %% Return selected MS and sends new MS's to us
596	observer_traceoptions_wx:select_matchspec(self(), Panel, Ms, 'receive')
597    catch _:_ ->
598	    cancel
599    end,
600    {noreply, State};
601
602handle_event(#wx{id=?EDIT_FUNCS_MS}, #state{panel=Panel, tpatterns=TPs,
603					    f_view=LCtrl, m_view=Mview,
604					    match_specs=Mss
605					   } = State) ->
606    try
607	case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs))) of
608	    [] ->
609		throw({error,"No module selected"});
610	    [Module] ->
611		Selected = get_selected_items(LCtrl, dict:fetch(Module, TPs)),
612		Key = case Module of
613			  'Events' ->
614			      SelectedEvents =
615				  [Event || #tpattern{fa=Event} <- Selected],
616			      E1 = hd(SelectedEvents),
617			      case lists:all(fun(E) when E==E1 -> true;
618						(_) -> false
619					     end,
620					     SelectedEvents) of
621				  true -> E1;
622				  false -> throw({error,"Can not set match specs for multiple event types"})
623			      end;
624			  _ -> funcs
625		      end,
626		Ms = observer_traceoptions_wx:select_matchspec(self(), Panel,
627							       Mss, Key),
628		Changed = [TP#tpattern{ms=Ms} || TP <- Selected],
629		{noreply, do_add_patterns({Module, Changed}, State)}
630	end
631    catch {error, Msg} ->
632	    observer_wx:create_txt_dialog(Panel, Msg, "Error", ?wxICON_ERROR),
633	    {noreply, State};
634	  cancel ->
635	    {noreply, State}
636    end;
637
638handle_event(#wx{id=?REMOVE_FUNCS_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) ->
639    case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of
640	[] -> {noreply, State};
641	[Module] ->
642	    FMs0 = dict:fetch(Module, TPs0),
643	    Selected = get_selected_items(LCtrl, FMs0),
644	    FMs = FMs0 -- Selected,
645	    update_functions_view(FMs, LCtrl),
646	    TPs = case FMs of
647		      [] ->
648			  New = dict:erase(Module, TPs0),
649			  update_modules_view(lists:sort(dict:fetch_keys(New)), Module, Mview),
650			  New;
651		      _ ->
652			  dict:store(Module, FMs, TPs0)
653		  end,
654	    {noreply, State#state{tpatterns=TPs}}
655    end;
656
657handle_event(#wx{id=?REMOVE_MOD_MS}, #state{tpatterns=TPs0, f_view=LCtrl, m_view=Mview} = State) ->
658    case get_selected_items(Mview, lists:sort(dict:fetch_keys(TPs0))) of
659	[] -> {noreply, State};
660	[Module] ->
661	    update_functions_view([], LCtrl),
662	    TPs = dict:erase(Module, TPs0),
663	    update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview),
664	    {noreply, State#state{tpatterns=TPs}}
665    end;
666
667handle_event(#wx{id=?TRACE_OUTPUT}, #state{panel=Panel, output=Out0} = State) ->
668    try
669	Out = observer_traceoptions_wx:output(Panel, Out0),
670	{noreply, State#state{output=Out}}
671    catch _:_ ->
672	    {noreply, State}
673    end;
674
675handle_event(#wx{id=?ADD_NODES}, #state{panel=Panel, n_view=Nview, nodes=Ns0} = State) ->
676    try
677	Possible = [node()|nodes()] -- Ns0,
678	case Possible of
679	    [] ->
680		Msg = "Already selected all connected nodes\n"
681		    "Use the Nodes menu to connect to new nodes first.",
682		observer_wx:create_txt_dialog(Panel, Msg, "No available nodes", ?wxICON_INFORMATION),
683		throw(cancel);
684	    _ ->
685		Ns = lists:usort(Ns0 ++ observer_traceoptions_wx:select_nodes(Panel, Possible)),
686		update_nodes_view(Ns, Nview),
687		{noreply, State#state{nodes=Ns}}
688	end
689    catch cancel ->
690	    {noreply, State}
691    end;
692
693handle_event(#wx{id=?REMOVE_NODES}, #state{n_view=Nview, nodes=Ns0} = State) ->
694    Sel = get_selected_items(Nview, Ns0),
695    Ns = Ns0 -- Sel,
696    update_nodes_view(Ns, Nview),
697    {noreply, State#state{nodes = Ns}};
698
699handle_event(#wx{id=ID, event = What}, State) ->
700    io:format("~p:~p: Unhandled event: ~p, ~tp ~n", [?MODULE, ?LINE, ID, What]),
701    {noreply, State}.
702
703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
704handle_call(get_config, _, State) ->
705    Config0 = get_config(State),
706    Config = lists:keydelete(trace_p, 1, Config0),
707    {reply, maps:from_list(Config), State};
708handle_call(Msg, From, _State) ->
709    error({unhandled_call, Msg, From}).
710
711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
712handle_cast({add_processes, Pids}, State = #state{panel=Parent, def_proc_flags=TraceOpts}) ->
713    try
714	Opts = observer_traceoptions_wx:process_trace(Parent, TraceOpts),
715	POpts = [#titem{id=Pid, opts=Opts} || Pid <- Pids],
716	S = do_add_processes(POpts, State#state{def_proc_flags=Opts}),
717	{noreply, S}
718    catch cancel ->
719	    {noreply, State}
720    end;
721handle_cast({add_ports, Ports}, State = #state{panel=Parent, def_port_flags=TraceOpts}) ->
722    try
723	Opts = observer_traceoptions_wx:port_trace(Parent, TraceOpts),
724	POpts = [#titem{id=Id, opts=Opts} || Id <- Ports],
725	S = do_add_ports(POpts, State#state{def_port_flags=Opts}),
726	{noreply, S}
727    catch cancel ->
728	    {noreply, State}
729    end;
730handle_cast(Msg, _State) ->
731    error({unhandled_cast, Msg}).
732
733%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
734
735handle_info({active, _Node}, State=#state{parent=Parent}) ->
736    create_menues(Parent),
737    {noreply, State};
738
739handle_info(not_active, State) ->
740    {noreply, State};
741
742handle_info({update_ms, NewMs}, State) ->
743    {noreply, State#state{match_specs=NewMs}};
744
745handle_info(Any, State) ->
746    io:format("~p~p: received unexpected message: ~tp\n", [?MODULE, self(), Any]),
747    {noreply, State}.
748
749terminate(_Reason, #state{nodes=_Nodes}) ->
750    ttb:stop(nofetch),
751    ok.
752
753code_change(_, _, State) ->
754    {ok, State}.
755
756%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
757do_add_patterns({Module, NewPs}, State=#state{tpatterns=TPs0, m_view=Mview, f_view=Fview}) ->
758    Old = case dict:find(Module, TPs0) of
759	      {ok, Prev}  -> Prev;
760	      error -> []
761	  end,
762    case merge_patterns(NewPs, Old) of
763	{Old, [], []} ->
764	    State;
765	{MPatterns, _New, _Changed} ->
766	    %% if dynamicly updates update New and Changed
767	    TPs = dict:store(Module, MPatterns, TPs0),
768	    update_modules_view(lists:sort(dict:fetch_keys(TPs)), Module, Mview),
769	    update_functions_view(dict:fetch(Module, TPs), Fview),
770	    State#state{tpatterns=TPs}
771    end.
772
773do_add_processes(POpts, S0=#state{n_view=Nview, proc_view=LCtrl, tpids=OldPids, nodes=OldNodes}) ->
774    CheckFun = fun(Pid) -> is_pid(Pid) end,
775    {Pids, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl,
776					OldPids, OldNodes, CheckFun),
777    S0#state{tpids=Pids, nodes=Nodes}.
778
779do_add_ports(POpts, S0=#state{n_view=Nview, port_view=LCtrl, tports=OldPorts, nodes=OldNodes}) ->
780    CheckFun = fun(Port) -> is_port(Port) end,
781    {Ports, Nodes} = do_add_pid_or_port(POpts, Nview, LCtrl,
782					OldPorts, OldNodes, CheckFun),
783    S0#state{tports=Ports, nodes=Nodes}.
784
785do_add_pid_or_port(POpts, Nview, LCtrl, OldPs, Ns0, Check) ->
786    case merge_trace_items(POpts, OldPs) of
787	{OldPs, [], []} ->
788	    {OldPs,Ns0};
789	{Ps, New, _Changed} ->
790	    Ns1 = lists:usort([node(Id) || #titem{id=Id} <- New, Check(Id)]),
791	    Nodes = case ordsets:subtract(Ns1, Ns0) of
792			[] when Ns0==[] -> [observer_wx:get_active_node()];
793			[] -> Ns0; %% No new Nodes
794			NewNs -> ordsets:union(NewNs, Ns0)
795		    end,
796	    update_nodes_view(Nodes, Nview),
797	    update_p_view(Ps, LCtrl, Nodes, Nview),
798	    {Ps, Nodes}
799    end.
800
801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
802get_visible_ps(PidsOrPorts, [Node], _Nview) ->
803    %% If only one node, treat this as selected
804    get_visible_ps(PidsOrPorts, [Node]);
805get_visible_ps(PidsOrPorts, Nodes, Nview) ->
806    get_visible_ps(PidsOrPorts, get_selected_items(Nview, Nodes)).
807
808get_visible_ps(PidsOrPorts, Nodes) ->
809    %% Show pids/ports belonging to the selected nodes only (+ named pids/ports)
810    [P || P <- PidsOrPorts,
811	  is_atom(P#titem.id) orelse
812	      lists:member(node(P#titem.id),Nodes)].
813
814update_p_view(PidsOrPorts, LCtrl, Nodes, Nview) ->
815    update_p_view(get_visible_ps(PidsOrPorts, Nodes, Nview), LCtrl).
816update_p_view(PidsOrPorts, LCtrl, Nodes) ->
817    update_p_view(get_visible_ps(PidsOrPorts, Nodes), LCtrl).
818
819update_p_view(PidsOrPorts, LCtrl) ->
820    %% pid- or port-view
821    wxListCtrl:deleteAllItems(LCtrl),
822    wx:foldl(fun(#titem{id=Id, opts=Opts}, Row) ->
823		     _Item = wxListCtrl:insertItem(LCtrl, Row, ""),
824		     ?EVEN(Row) andalso
825			 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
826		     wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Id)),
827		     wxListCtrl:setItem(LCtrl, Row, 1, observer_lib:to_str(Opts)),
828		     Row+1
829	     end, 0, PidsOrPorts),
830    case PidsOrPorts of
831	[] ->
832	    wxListCtrl:setToolTip(LCtrl,?NO_P_HELP);
833	_ ->
834	    wxListCtrl:setToolTip(LCtrl,?P_HELP)
835    end.
836
837update_nodes_view(Nodes, LCtrl) ->
838    Selected =
839	case Nodes of
840	    [_] -> Nodes;
841	    _ -> get_selected_items(LCtrl, Nodes)
842	end,
843    wxListCtrl:deleteAllItems(LCtrl),
844    wx:foldl(fun(Node, Row) ->
845		     _Item = wxListCtrl:insertItem(LCtrl, Row, ""),
846		     ?EVEN(Row) andalso
847			 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
848		     wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Node)),
849		     lists:member(Node,Selected) andalso % keep selection
850			 wxListCtrl:setItemState(LCtrl, Row, 16#FFFF,
851						 ?wxLIST_STATE_SELECTED),
852		     Row+1
853	     end, 0, Nodes),
854    case Nodes of
855	[] ->
856	    wxListCtrl:setToolTip(LCtrl,?NO_NODES_HELP);
857	_ ->
858	    wxListCtrl:setToolTip(LCtrl,?NODES_HELP)
859    end.
860
861update_modules_view(Mods, Module, LCtrl) ->
862    wxListCtrl:deleteAllItems(LCtrl),
863    wx:foldl(fun(Mod, Row) ->
864		     _Item = wxListCtrl:insertItem(LCtrl, Row, ""),
865		     ?EVEN(Row) andalso
866			 wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
867		     wxListCtrl:setItem(LCtrl, Row, 0, observer_lib:to_str(Mod)),
868		     (Mod =:= Module) andalso
869			 wxListCtrl:setItemState(LCtrl, Row, 16#FFFF, ?wxLIST_STATE_SELECTED),
870		     Row+1
871	     end, 0, Mods),
872    Parent = wxListCtrl:getParent(LCtrl),
873    case Mods of
874	[] ->
875	    wxListCtrl:setToolTip(Parent,?NO_TP_HELP);
876	_ ->
877	    wxListCtrl:setToolTip(Parent,?TP_HELP)
878    end.
879
880update_functions_view(Funcs, LCtrl) ->
881    wxListCtrl:deleteAllItems(LCtrl),
882    wx:foldl(fun(#tpattern{m=M, fa=FA, ms=#match_spec{str=Ms}}, Row) ->
883		     _Item = wxListCtrl:insertItem(LCtrl, Row, ""),
884		     ?EVEN(Row) andalso wxListCtrl:setItemBackgroundColour(LCtrl, Row, ?BG_EVEN),
885		     FuncStr =
886			 case M of
887			     'Events' ->
888				 observer_lib:to_str(FA);
889			     _ ->
890				 observer_lib:to_str({func,FA})
891			 end,
892		     wxListCtrl:setItem(LCtrl, Row, 0, FuncStr),
893		     wxListCtrl:setItem(LCtrl, Row, 1, Ms),
894		     Row+1
895	     end, 0, Funcs).
896
897%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
898%% Trace items are processes and ports
899merge_trace_items([N1=#titem{id=NewP}|Ns], [N2=#titem{id=NewP}|Old])
900  when NewP==new_processes; NewP==new_ports; NewP==all ->
901    {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
902    {[N1|Ids], New, [{N2,N2}|Changed]};
903merge_trace_items([N1=#titem{id=NewP}|Ns], Old)
904  when NewP==new_processes; NewP==new_ports; NewP==all ->
905    {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
906    {[N1|Ids], [N1|New], Changed};
907merge_trace_items(Ns, [N2=#titem{id=NewP}|Old])
908  when NewP==new_processes; NewP==new_ports; NewP==all ->
909    {Ids, New, Changed} = merge_trace_items_1(Ns,Old),
910    {[N2|Ids], New, Changed};
911merge_trace_items(New, Old) ->
912    merge_trace_items_1(New, Old).
913
914merge_trace_items_1(New, Old) ->
915    merge(lists:sort(New), Old, #titem.id, [], [], []).
916
917merge_patterns(New, Old) ->
918    merge(lists:sort(New), Old, #tpattern.fa, [], [], []).
919
920merge([N|Ns], [N|Os], El, New, Ch, All) ->
921    merge(Ns, Os, El, New, Ch, [N|All]);
922merge([N|Ns], [O|Os], El, New, Ch, All)
923  when element(El, N) == element(El, O) ->
924    merge(Ns, Os, El, New, [{O,N}|Ch], [N|All]);
925merge([N|Ns], Os=[O|_], El, New, Ch, All)
926  when element(El, N) < element(El, O) ->
927    merge(Ns, Os, El, [N|New], Ch, [N|All]);
928merge(Ns=[N|_], [O|Os], El, New, Ch, All)
929  when element(El, N) > element(El, O) ->
930    merge(Ns, Os, El, New, Ch, [O|All]);
931merge([], Os, _El, New, Ch, All) ->
932    {lists:reverse(All, Os), New, Ch};
933merge(Ns, [], _El, New, Ch, All) ->
934    {lists:reverse(All, Ns), Ns++New, Ch}.
935
936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
937ttb_output_args(Parent, Opts) ->
938    ToWindow = proplists:get_value(window, Opts, true),
939    ToShell = proplists:get_value(shell, Opts, false),
940    ToFile = proplists:get_value(file, Opts, false),
941    ToWindow orelse ToShell orelse ToFile orelse
942	throw({error, "No output of trace"}),
943    {LogWin,Text} = create_logwindow(Parent, ToWindow),
944    Write = output_fun(Text, ToShell),
945    Shell = output_shell(ToFile, Write),
946    FileOpts = output_file(ToFile, proplists:get_value(wrap, Opts, false), Opts),
947    {[{file, {local,FileOpts}}|Shell], LogWin}.
948
949output_shell(true, false) ->
950    []; %% File only
951output_shell(true, Write) when is_function(Write) ->
952    [{shell, Write}];
953output_shell(false, Write) when is_function(Write) ->
954    [{shell, {only, Write}}].
955
956output_fun(false, false) -> false;
957output_fun(false, true) -> fun(Trace) -> io:put_chars(textformat(Trace)) end;
958output_fun(Text, false) ->
959    Env = wx:get_env(),
960    fun(Trace) ->
961	    wx:set_env(Env),
962	    wxTextCtrl:appendText(Text, textformat(Trace))
963    end;
964output_fun(Text, true) ->
965    Env = wx:get_env(),
966    fun(Trace) ->
967	    wx:set_env(Env),
968	    IoList = textformat(Trace),
969	    wxTextCtrl:appendText(Text, IoList),
970	    io:put_chars(textformat(Trace))
971    end.
972
973output_file(false, _, _Opts) ->
974    "ttb";  %% Will be ignored
975output_file(true, false, Opts) ->
976    proplists:get_value(filename, Opts, "ttb");
977output_file(true, true, Opts) ->
978    Name = proplists:get_value(filename, Opts, "ttb"),
979    Size = proplists:get_value(wrap_sz, Opts, 128),
980    Count = proplists:get_value(wrap_c, Opts, 8),
981    {wrap, Name, Size*1024, Count}.
982
983
984create_logwindow(_Parent, false) -> {false, false};
985create_logwindow(Parent, true) ->
986    Scale = observer_wx:get_scale(),
987    LogWin = wxFrame:new(Parent, ?LOG_WIN, "Trace Log", [{size, {750*Scale, 800*Scale}}]),
988    MB = wxMenuBar:new(),
989    File = wxMenu:new(),
990    wxMenu:append(File, ?LOG_CLEAR, "Clear Log\tCtrl-C"),
991    wxMenu:append(File, ?LOG_SAVE, "Save Log\tCtrl-S"),
992    wxMenu:append(File, ?wxID_CLOSE, "Close"),
993    wxMenuBar:append(MB, File, "File"),
994    wxFrame:setMenuBar(LogWin, MB),
995    Text = wxTextCtrl:new(LogWin, ?wxID_ANY,
996			  [{style, ?wxTE_MULTILINE bor ?wxTE_RICH2 bor
997				?wxTE_DONTWRAP bor ?wxTE_READONLY}]),
998    Font = observer_wx:get_attrib({font, fixed}),
999    Attr = wxTextAttr:new(?wxBLACK, [{font, Font}]),
1000    true = wxTextCtrl:setDefaultStyle(Text, Attr),
1001    wxFrame:connect(LogWin, close_window, [{skip, true}]),
1002    wxFrame:connect(LogWin, command_menu_selected, [{userData, Text}]),
1003    wxFrame:show(LogWin),
1004    {LogWin, Text}.
1005
1006setup_ttb(TPs, TPids, TPorts) ->
1007    _R1 = [setup_tps(FTP, []) || {_, FTP} <- TPs],
1008    _R2 = [ttb:p(Pid, dbg_flags(proc,Flags)) ||
1009	      #titem{id=Pid, opts=Flags} <- TPids],
1010    _R3 = [ttb:p(Port, dbg_flags(port,Flags)) ||
1011	      #titem{id=Port, opts=Flags} <- TPorts],
1012    ok.
1013
1014%% Sigh order is important
1015setup_tps([First=#tpattern{fa={_,'_'}}|Rest], Prev) ->
1016    setup_tp(First),
1017    [setup_tp(TP) || TP <- lists:reverse(Prev)],
1018    setup_tps(Rest, []);
1019setup_tps([First=#tpattern{fa={F,_}}|Rest], Prev = [#tpattern{fa={F,_}}|_]) ->
1020    setup_tps(Rest, [First|Prev]);
1021setup_tps([First|Rest], Prev) ->
1022    [setup_tp(TP) || TP <- lists:reverse(Prev)],
1023    setup_tps(Rest, [First]);
1024setup_tps([], Prev) ->
1025    [setup_tp(TP) || TP <- lists:reverse(Prev)].
1026
1027setup_tp(#tpattern{m='Events',fa=Event, ms=#match_spec{term=Ms}}) ->
1028    ttb:tpe(Event,Ms);
1029setup_tp(#tpattern{m=M,fa={F,A}, ms=#match_spec{term=Ms}}) ->
1030    ttb:tpl(M,F,A,Ms).
1031
1032dbg_flags(Type,Flags) ->
1033    [dbg_flag(Type,Flag) || Flag <- Flags].
1034
1035dbg_flag(_,send) -> s;
1036dbg_flag(_,'receive') -> r;
1037dbg_flag(proc,functions) -> c;
1038dbg_flag(proc,on_spawn) -> sos;
1039dbg_flag(proc,on_link) -> sol;
1040dbg_flag(proc,on_first_spawn) -> sofs;
1041dbg_flag(proc,on_first_link) -> sofl;
1042dbg_flag(proc,events) -> p;
1043dbg_flag(port,events) -> ports;
1044dbg_flag(_,Flag) -> Flag.
1045
1046textformat(Trace) when element(1, Trace) == trace_ts, tuple_size(Trace) >= 4 ->
1047    format_trace(Trace, tuple_size(Trace)-1, element(tuple_size(Trace),Trace));
1048textformat(Trace) when element(1, Trace) == drop, tuple_size(Trace) =:= 2 ->
1049    io_lib:format("*** Dropped ~p messages.~n", [element(2,Trace)]);
1050textformat(Trace) when element(1, Trace) == seq_trace, tuple_size(Trace) >= 3 ->
1051    io_lib:format("*** Seq trace not implmented.~n", []);
1052textformat(_) ->
1053    "".
1054
1055format_trace(Trace, Size, TS0={_,_,MS}) ->
1056    {_,{H,M,S}} = calendar:now_to_local_time(TS0),
1057    TS = io_lib:format("~.2.0w:~.2.0w:~.2.0w:~.6.0w", [H,M,S,MS]),
1058    From = element(2, Trace),
1059    case element(3, Trace) of
1060	'receive' ->
1061	    case element(4, Trace) of
1062		{dbg,ok} -> "";
1063		Message ->
1064		    io_lib:format("~s (~100p) << ~100tp~n", [TS,From,Message])
1065	    end;
1066	'send' ->
1067	    Message = element(4, Trace),
1068	    To = element(5, Trace),
1069	    io_lib:format("~s (~100p) ~100p ! ~100tp~n", [TS,From,To,Message]);
1070	call ->
1071	    case element(4, Trace) of
1072		MFA when Size == 5 ->
1073		    Message = element(5, Trace),
1074		    io_lib:format("~s (~100p) call ~ts (~100tp) ~n", [TS,From,ffunc(MFA),Message]);
1075		MFA ->
1076		    io_lib:format("~s (~100p) call ~ts~n", [TS,From,ffunc(MFA)])
1077	    end;
1078	return_from ->
1079	    MFA = element(4, Trace),
1080	    Ret = element(5, Trace),
1081	    io_lib:format("~s (~100p) returned from ~ts -> ~100tp~n", [TS,From,ffunc(MFA),Ret]);
1082	return_to ->
1083	    MFA = element(4, Trace),
1084	    io_lib:format("~s (~100p) returning to ~ts~n", [TS,From,ffunc(MFA)]);
1085	spawn when Size == 5 ->
1086	    Pid = element(4, Trace),
1087	    MFA = element(5, Trace),
1088	    io_lib:format("~s (~100p) spawn ~100p as ~ts~n", [TS,From,Pid,ffunc(MFA)]);
1089	Op ->
1090	    io_lib:format("~s (~100p) ~100p ~ts~n", [TS,From,Op,ftup(Trace,4,Size)])
1091    end.
1092
1093%%% These f* functions returns non-flat strings
1094
1095%% {M,F,[A1, A2, ..., AN]} -> "M:F(A1, A2, ..., AN)"
1096%% {M,F,A}                 -> "M:F/A"
1097ffunc({M,F,Argl}) when is_list(Argl) ->
1098    io_lib:format("~100p:~100tp(~ts)", [M, F, fargs(Argl)]);
1099ffunc({M,F,Arity}) ->
1100    io_lib:format("~100p:~100tp/~100p", [M,F,Arity]);
1101ffunc(X) -> io_lib:format("~100tp", [X]).
1102
1103%% Integer           -> "Integer"
1104%% [A1, A2, ..., AN] -> "A1, A2, ..., AN"
1105fargs(Arity) when is_integer(Arity) -> integer_to_list(Arity);
1106fargs([]) -> [];
1107fargs([A]) -> io_lib:format("~100tp", [A]);  %% last arg
1108fargs([A|Args]) -> [io_lib:format("~100tp,", [A]) | fargs(Args)];
1109fargs(A) -> io_lib:format("~100tp", [A]). % last or only arg
1110
1111%% {A_1, A_2, ..., A_N} -> "A_Index A_Index+1 ... A_Size"
1112ftup(Trace, Index, Index) ->
1113    io_lib:format("~100tp", [element(Index, Trace)]);
1114ftup(Trace, Index, Size) ->
1115    [io_lib:format("~100tp ", [element(Index, Trace)])
1116     | ftup(Trace, Index+1, Size)].
1117
1118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1119
1120get_config(#state{def_proc_flags = ProcFlags,
1121                  def_port_flags = PortFlags,
1122                  match_specs = MatchSpecs0,
1123                  tpatterns = TracePatterns,
1124                  output = Output}) ->
1125    MSToList = fun(#match_spec{name=Id, term=T, func=F}) ->
1126		       [{name,Id},{term,T},{func,F}]
1127	       end,
1128    MatchSpecs = [{ms,Key,[MSToList(MS) || MS <- MSs]} ||
1129		     {Key,MSs} <- MatchSpecs0],
1130    TPToTuple = fun(#tpattern{fa={F,A}, ms=Ms}) ->
1131                        {F,A,MSToList(Ms)}
1132		end,
1133    ModuleTermList = [{tp, Module, [TPToTuple(FTP) || FTP <- FTPs]} ||
1134			 {Module,FTPs} <- dict:to_list(TracePatterns)],
1135    [{procflags,ProcFlags},
1136     {portflags,PortFlags},
1137     {match_specs,MatchSpecs},
1138     {output,Output},
1139     {trace_p,ModuleTermList}].
1140
1141write_file(Frame, Filename, Config) ->
1142    Str =
1143	["%%% ",epp:encoding_to_string(utf8), "\n"
1144         "%%%\n%%% This file is generated by Observer\n",
1145	 "%%%\n%%% DO NOT EDIT!\n%%%\n",
1146	 [io_lib:format("~tp.~n",[MSTerm]) ||
1147             MSTerm <- proplists:get_value(match_specs, Config)],
1148	 io_lib:format("~p.~n",[lists:keyfind(procflags, 1, Config)]),
1149	 io_lib:format("~p.~n",[lists:keyfind(portflags, 1, Config)]),
1150	 io_lib:format("~tp.~n",[lists:keyfind(output, 1, Config)]),
1151	 [io_lib:format("~tp.~n",[ModuleTerm]) ||
1152             ModuleTerm <- proplists:get_value(trace_p, Config)]
1153	],
1154
1155    case file:write_file(Filename, unicode:characters_to_binary(Str)) of
1156	ok ->
1157	    success;
1158	{error, Reason} ->
1159	    FailMsg = file:format_error(Reason),
1160	    observer_wx:create_txt_dialog(Frame, FailMsg, "Error", ?wxICON_ERROR)
1161    end.
1162
1163read_settings(Filename, #state{match_specs=Ms0, def_proc_flags=ProcFs0, def_port_flags=PortFs0} = State) ->
1164    case file:consult(Filename) of
1165	{ok, Terms} ->
1166	    Ms = parse_ms(Terms, Ms0),
1167	    ProcFs1 = proplists:get_value(procflags, Terms, []) ++
1168		proplists:get_value(traceopts, Terms, []), % for backwards comp.
1169	    ProcFs = lists:usort(ProcFs0 ++ ProcFs1),
1170	    PortFs = lists:usort(PortFs0 ++
1171				     proplists:get_value(portflags, Terms, [])),
1172	    Out = proplists:get_value(output, Terms, []),
1173	    lists:foldl(fun parse_tp/2,
1174			State#state{match_specs=Ms, def_proc_flags=ProcFs,
1175				    def_port_flags=PortFs, output=Out},
1176			Terms);
1177	{error, _} ->
1178	    observer_wx:create_txt_dialog(State#state.panel,
1179					  "Could not load settings",
1180					  "Error", ?wxICON_ERROR),
1181	    State
1182    end.
1183
1184parse_ms(Terms, OldMSs) ->
1185    MSs =
1186	case [{Key,[make_ms(MS) || MS <- MSs]} || {ms,Key,MSs} <- Terms] of
1187	    [] ->
1188		case [make_ms(MS) || {ms,MS} <- Terms] of
1189		    [] ->
1190			[];
1191		    FuncMSs -> % for backwards compatibility
1192			[{funcs,FuncMSs}]
1193		end;
1194	    KeyMSs ->
1195		KeyMSs
1196	end,
1197    parse_ms_1(MSs, dict:from_list(OldMSs)).
1198
1199parse_ms_1([{Key,MSs} | T], Dict) ->
1200    parse_ms_1(T, dict:append_list(Key,MSs,Dict));
1201parse_ms_1([],Dict) ->
1202    [{Key,rm_dups(MSs,[])} || {Key,MSs} <- dict:to_list(Dict)].
1203
1204rm_dups([H|T],Acc) ->
1205    case lists:member(H,Acc) of
1206	true ->
1207	    rm_dups(T,Acc);
1208	false ->
1209	    rm_dups(T,[H|Acc])
1210    end;
1211rm_dups([],Acc) ->
1212    lists:reverse(Acc).
1213
1214make_ms(MS) ->
1215    [{func,FunStr},{name,Name},{term,Term}] = lists:keysort(1,MS),
1216    make_ms(Name,Term,FunStr).
1217
1218make_ms(Name, Term, FunStr) ->
1219    #match_spec{name=Name, term=Term, str=io_lib:format("~tw", [Term]), func = FunStr}.
1220
1221parse_tp({tp, Mod, FAs}, State) ->
1222    Patterns = [#tpattern{m=Mod,fa={F,A}, ms=make_ms(List)} ||
1223		   {F,A,List} <- FAs],
1224    do_add_patterns({Mod, Patterns}, State);
1225parse_tp(_, State) ->
1226    State.
1227
1228get_selected_items(Grid, Data) ->
1229    get_indecies(get_selected_items(Grid, -1, []), Data).
1230get_selected_items(Grid, Index, ItemAcc) ->
1231    Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL},
1232						{state, ?wxLIST_STATE_SELECTED}]),
1233    case Item of
1234	-1 ->
1235	    lists:reverse(ItemAcc);
1236	_ ->
1237	    get_selected_items(Grid, Item, [Item | ItemAcc])
1238    end.
1239
1240get_indecies(Items, Data) ->
1241    get_indecies(Items, 0, Data).
1242get_indecies([I|Rest], I, [H|T]) ->
1243    [H|get_indecies(Rest, I+1, T)];
1244get_indecies(Rest = [_|_], I, [_|T]) ->
1245    get_indecies(Rest, I+1, T);
1246get_indecies(_, _, _) ->
1247    [].
1248
1249create_right_click_menu(Panel,Menus) ->
1250    Menu = wxMenu:new(),
1251    [wxMenu:append(Menu,Id,Str) || {Id,Str} <- Menus],
1252    wxWindow:popupMenu(Panel, Menu),
1253    wxMenu:destroy(Menu).
1254