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