aboutsummaryrefslogtreecommitdiffstats
path: root/lib/et/src/et_wx_contents_viewer.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/et/src/et_wx_contents_viewer.erl')
-rw-r--r--lib/et/src/et_wx_contents_viewer.erl700
1 files changed, 700 insertions, 0 deletions
diff --git a/lib/et/src/et_wx_contents_viewer.erl b/lib/et/src/et_wx_contents_viewer.erl
new file mode 100644
index 0000000000..8a8d9ef1ee
--- /dev/null
+++ b/lib/et/src/et_wx_contents_viewer.erl
@@ -0,0 +1,700 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Displays details of a trace event
+%%----------------------------------------------------------------------
+
+-module(et_wx_contents_viewer).
+
+-behaviour(wx_object).
+
+%% External exports
+-export([start_link/1,
+ stop/1]).
+
+%% gen_server callbacks
+-export([init/1, terminate/2, code_change/3,
+ handle_call/3, handle_cast/2, handle_info/2,
+ handle_event/2]).
+
+-include("../include/et.hrl").
+-include("et_internal.hrl").
+-include_lib("wx/include/wx.hrl").
+
+-record(state, {parent_pid, % Pid of parent process
+ viewer_pid, % Pid of viewer process
+ event_order, % Field to be used as primary key
+ event, % The original event
+ filtered_event, % Event processed by active filter
+ active_filter, % Name of the active filter
+ filters, % List of possible filters
+ win, % GUI: Frame object
+ frame, % GUI: Frame object
+ panel, % GUI: Panel object
+ width, % GUI: Window width
+ height,
+ editor,
+ menu_data, % GUI: Window height
+ wx_debug, % GUI: WX debug level
+ trap_exit}). % trap_exit process flag
+
+%%%----------------------------------------------------------------------
+%%% Client side
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% start_link(Options) -> {ok, ContentsPid} | {error, Reason}
+%%
+%% Start a viewer for the event contents as window in GS
+%%
+%% Options = [option()]
+%%
+%% option() =
+%%
+%% {parent_pid, pid()} | % Pid of parent process
+%% {viewer_pid, pid()} | % Pid of viewer process
+%% {event_order, event_order()} | % Field to be used as primary key
+%% {active_filter, atom()} | % Name of the active filter
+%% {filter, atom(), fun()} % A named filter fun
+%%
+%% event_order() = 'trace_ts' | 'event_ts'
+%% ContentsPid = pid()
+%% Reason = term()
+%%----------------------------------------------------------------------
+
+start_link(Options) ->
+ case parse_opt(Options, default_state()) of
+ {ok, S} ->
+ try
+ WxRef = wx_object:start_link(?MODULE, [S], []),
+ Pid = wx_object:get_pid(WxRef),
+ if
+ S#state.parent_pid =/= self() ->
+ unlink(Pid);
+ true ->
+ ignore
+ end,
+ {ok, Pid}
+ catch
+ error:Reason ->
+ {error, {'EXIT', Reason, erlang:get_stacktrace()}}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+default_state() ->
+ #state{parent_pid = self(),
+ viewer_pid = undefined,
+ active_filter = ?DEFAULT_FILTER_NAME,
+ filters = [?DEFAULT_FILTER],
+ width = 600,
+ height = 300,
+ wx_debug = 0,
+ trap_exit = true}.
+
+parse_opt([], S) ->
+ Name = S#state.active_filter,
+ Filters = S#state.filters,
+ if
+ S#state.event =:= undefined ->
+ {error, {badarg, no_event}};
+ is_atom(Name) ->
+ case lists:keysearch(Name, #filter.name, Filters) of
+ {value, F} when is_record(F, filter) ->
+ {ok, S#state{active_filter = Name}};
+ false ->
+ {error, {badarg, {no_such_filter, Name, Filters}}}
+ end
+ end;
+parse_opt([H | T], S) ->
+ case H of
+ {parent_pid, ParentPid} when is_pid(ParentPid); ParentPid =:= undefined ->
+ parse_opt(T, S#state{parent_pid = ParentPid});
+ {viewer_pid, ViewerPid} when is_pid(ViewerPid) ->
+ parse_opt(T, S#state{viewer_pid = ViewerPid});
+ {wx_debug, Level} ->
+ parse_opt(T, S#state{wx_debug = Level});
+ {trap_exit, Bool} when Bool =:= true; Bool =:= false->
+ parse_opt(T, S#state{trap_exit = Bool});
+ {event_order, trace_ts} ->
+ parse_opt(T, S#state{event_order = trace_ts});
+ {event_order, event_ts} ->
+ parse_opt(T, S#state{event_order = event_ts});
+ {event, Event} when is_record(Event, event) ->
+ parse_opt(T, S#state{event = Event});
+ {active_filter, Name} when is_atom(Name) ->
+ parse_opt(T, S#state{active_filter = Name});
+ F when is_record(F, filter),
+ is_atom(F#filter.name),
+ is_function(F#filter.function) ->
+ Filters = lists:keydelete(F#filter.name, #filter.name, S#state.filters),
+ Filters2 = lists:keysort(#filter.name, [F | Filters]),
+ parse_opt(T, S#state{filters = Filters2});
+ {width, Width} when is_integer(Width), Width > 0 ->
+ parse_opt(T, S#state{width = Width});
+ {height, Height} when is_integer(Height), Height > 0 ->
+ parse_opt(T, S#state{height = Height});
+ Bad ->
+ {error, {bad_option, Bad}}
+ end;
+parse_opt(BadList, _S) ->
+ {error, {bad_option_list, BadList}}.
+
+%%----------------------------------------------------------------------
+%% stop(ContentsPid) -> ok
+%%
+%% Stops a contents viewer process
+%%
+%% ContentsPid = pid()
+%%----------------------------------------------------------------------
+
+stop(ContentsPid) when is_pid(ContentsPid) ->
+ Type = process,
+ MonitorRef = erlang:monitor(Type, ContentsPid),
+ ContentsPid ! {stop, self()},
+ receive
+ {'DOWN', MonitorRef, Type, ContentsPid, shutdown} ->
+ ok;
+ {'DOWN', MonitorRef, Type, ContentsPid, Reason} ->
+ {error, Reason}
+ end.
+
+%% call(Frame, Request) ->
+%% wx_object:call(Frame, Request, infinity).
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+
+init([S]) when is_record(S, state) ->
+ process_flag(trap_exit, S#state.trap_exit),
+ case S#state.parent_pid of
+ undefined -> ok;
+ ParentPid -> link(ParentPid)
+ end,
+ wx:debug(S#state.wx_debug),
+ S2 = create_window(S),
+ {S2#state.frame, S2}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_call/3
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_call(Request, From, S) ->
+ ok = error_logger:format("~p(~p): handle_call(~p, ~p, ~p)~n",
+ [?MODULE, self(), Request, From, S]),
+ Reply = {error, {bad_request, Request}},
+ {reply, Reply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_cast/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_cast(Msg, S) ->
+ ok = error_logger:format("~p(~p): handle_cast(~p, ~p)~n",
+ [?MODULE, self(), Msg, S]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_event/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_event(#wx{id = Id,
+ event = #wxCommand{type = command_menu_selected}},
+ S) ->
+ case proplists:get_value(Id, S#state.menu_data) of
+ undefined ->
+ ignore;
+ Data when is_record(Data, filter) ->
+ F = Data,
+ ChildState= S#state{active_filter = F#filter.name},
+ case wx_object:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid =/= self() ->
+ unlink(Pid);
+ _ ->
+ ignore
+ end;
+ {hide, Actors} ->
+ send_viewer_event(S, {delete_actors, Actors});
+ {show, Actors} ->
+ send_viewer_event(S, {insert_actors, Actors});
+ {mode, Mode} ->
+ send_viewer_event(S, {mode, Mode});
+ Nyi ->
+ ok = error_logger:format("~p: click ~p ignored (nyi)~n",
+ [?MODULE, Nyi])
+ end,
+ case Id of
+ ?wxID_EXIT ->
+ wxFrame:destroy(S#state.frame),
+ opt_unlink(S#state.parent_pid),
+ {stop, shutdown, S};
+ ?wxID_SAVE ->
+ Event = S#state.event,
+ TimeStamp =
+ case S#state.event_order of
+ trace_ts -> Event#event.trace_ts;
+ event_ts -> Event#event.event_ts
+ end,
+ FileName = lists:flatten(["et_contents_viewer_", now_to_string(TimeStamp), ".txt"]),
+ Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
+ Msg = "Select a file to the events to",
+ case select_file(S#state.frame, Msg, filename:absname(FileName), Style) of
+ {ok, FileName2} ->
+ Bin = list_to_binary(event_to_string(Event, S#state.event_order)),
+ file:write_file(FileName2, Bin);
+ cancel ->
+ ok
+ end,
+ {noreply, S};
+ ?wxID_PRINT ->
+ Html = wxHtmlEasyPrinting:new([{parentWindow, S#state.win}]),
+ Text = "<pre>" ++ wxTextCtrl:getValue(S#state.editor) ++ "</pre>",
+ wxHtmlEasyPrinting:previewText(Html, Text),
+ {noreply, S};
+ _ ->
+ {noreply, S}
+ end;
+handle_event(#wx{event = #wxKey{rawCode = KeyCode}}, S) ->
+ case KeyCode of
+ $c ->
+ wxFrame:destroy(S#state.frame),
+ opt_unlink(S#state.parent_pid),
+ {stop, normal, S};
+ $f ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ send_viewer_event(S, {delete_actors, [From]}),
+ {noreply, S};
+ $t ->
+ E = S#state.filtered_event,
+ To = E#event.to,
+ send_viewer_event(S, {delete_actors, [To]}),
+ {noreply, S};
+ $b ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ send_viewer_event(S, {delete_actors, [From, To]}),
+ {noreply, S};
+
+ $F ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ send_viewer_event(S, {insert_actors, [From]}),
+ {noreply, S};
+ $T ->
+ E = S#state.filtered_event,
+ To = E#event.to,
+ send_viewer_event(S, {insert_actors, [To]}),
+ {noreply, S};
+ $B ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ send_viewer_event(S, {insert_actors, [From, To]}),
+ {noreply, S};
+
+ $s ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ First = et_collector:make_key(S#state.event_order, E),
+ Mode = {search_actors, forward, First, [From, To]},
+ send_viewer_event(S, {mode, Mode}),
+ {noreply, S};
+ $r ->
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ First = et_collector:make_key(S#state.event_order, E),
+ Mode = {search_actors, reverse, First, [From, To]},
+ send_viewer_event(S, {mode, Mode}),
+ {noreply, S};
+ $a ->
+ send_viewer_event(S, {mode, all}),
+ {noreply, S};
+
+ $0 ->
+ case lists:keysearch(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of
+ {value, F} when is_record(F, filter) ->
+ ChildState= S#state{active_filter = F#filter.name},
+ case wx_object:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid =/= self() ->
+ unlink(Pid);
+ _ ->
+ ignore
+ end;
+ false ->
+ ignore
+ end,
+ {noreply, S};
+ Int when is_integer(Int), Int > $0, Int =< $9 ->
+ case catch lists:nth(Int-$0, S#state.filters) of
+ F when is_record(F, filter) ->
+ ChildState= S#state{active_filter = F#filter.name},
+ case wx_object:start_link(?MODULE, [ChildState], []) of
+ {ok, Pid} when S#state.parent_pid =/= self() ->
+ unlink(Pid);
+ _ ->
+ ignore
+ end;
+ {'EXIT', _} ->
+ ignore
+ end,
+ {noreply, S};
+
+ _ ->
+ io:format("~p: ignored: ~p~n", [?MODULE, KeyCode]),
+ {noreply, S}
+ end;
+handle_event(#wx{event = #wxClose{}}, S) ->
+ opt_unlink(S#state.parent_pid),
+ {stop, shutdown, S};
+handle_event(#wx{event = #wxSize{size = {W, H}}}, S) ->
+ S2 = S#state{width = W, height = H},
+ {noreply, S2};
+handle_event(Wx = #wx{}, S) ->
+ io:format("~p got an unexpected event: ~p\n", [self(), Wx]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/2
+%% Returns: {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State} (terminate/2 is called)
+%%----------------------------------------------------------------------
+
+handle_info({stop, _From}, S) ->
+ wxFrame:destroy(S#state.frame),
+ opt_unlink(S#state.parent_pid),
+ {stop, shutdown, S};
+handle_info({'EXIT', Pid, Reason}, S) ->
+ if
+ Pid =:= S#state.parent_pid ->
+ wxFrame:destroy(S#state.frame),
+ opt_unlink(S#state.parent_pid),
+ {stop, Reason, S};
+ true ->
+ {noreply, S}
+ end;
+handle_info(Info, S) ->
+ ok = error_logger:format("~p(~p): handle_info(~p, ~p)~n",
+ [?MODULE, self(), Info, S]),
+ {noreply, S}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+
+terminate(_Reason, _S) ->
+ ignore.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+
+code_change(_OldVsn, S, _Extra) ->
+ {ok, S}.
+
+%%%----------------------------------------------------------------------
+%%% Handle graphics
+%%%----------------------------------------------------------------------
+
+opt_unlink(Pid) ->
+ if
+ Pid =:= undefined ->
+ ignore;
+ true ->
+ unlink(Pid)
+ end.
+
+create_window(S) ->
+ H = S#state.height,
+ W = S#state.width,
+ Name = S#state.active_filter,
+ Title = lists:concat([?MODULE, " (filter: ", Name, ")"]),
+ WinOpt = [{size, {W,H}}],
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, WinOpt),
+ wxFrame:createStatusBar(Frame),
+
+ Panel = wxPanel:new(Frame, []),
+ Bar = wxMenuBar:new(),
+ wxFrame:setMenuBar(Frame,Bar),
+ create_file_menu(Bar),
+ Editor = wxTextCtrl:new(Panel, ?wxID_ANY, [{style, 0
+ bor ?wxDEFAULT
+ bor ?wxTE_MULTILINE
+ bor ?wxTE_READONLY
+ bor ?wxTE_DONTWRAP}]),
+ Font = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
+ TextAttr = wxTextAttr:new(?wxBLACK, [{font, Font}]),
+ wxTextCtrl:setDefaultStyle(Editor, TextAttr),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(Sizer, Editor, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ FilteredEvent = config_editor(Editor, S),
+ S2 = S#state{win = Frame, panel = Panel, filtered_event = FilteredEvent},
+ HideData = create_hide_menu(Bar, S2),
+ SearchData = create_search_menu(Bar, S2),
+ FilterData = create_filter_menu(Bar, S#state.filters),
+ wxFrame:connect(Frame, command_menu_selected, []),
+ wxFrame:connect(Frame, key_up),
+ wxFrame:connect(Frame, close_window, [{skip,true}]),
+ wxFrame:setFocus(Frame),
+ wxPanel:setSizer(Panel, Sizer),
+ wxFrame:show(Frame),
+ S2#state{menu_data = HideData++SearchData++FilterData, editor = Editor, frame = Frame}.
+
+menuitem(Menu, Id, Text, UserData) ->
+ Item = wxMenu:append(Menu, Id, Text),
+ {wxMenuItem:getId(Item), UserData}.
+
+create_file_menu(Bar) ->
+ Menu = wxMenu:new([]),
+ wxMenu:append(Menu, ?wxID_SAVE, "Save"),
+ wxMenu:append(Menu, ?wxID_PRINT,"Print"),
+ wxMenu:appendSeparator(Menu),
+ wxMenu:append(Menu, ?wxID_EXIT, "Close"),
+ wxMenuBar:append(Bar, Menu, "File").
+
+create_filter_menu(Bar, Filters) ->
+ Menu = wxMenu:new([]),
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Select Filter"), [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ Item = fun(F, {N,Acc}) when F#filter.name =:= ?DEFAULT_FILTER_NAME->
+ Label = lists:concat([pad_string(F#filter.name, 20, $\ , right), "(0)"]),
+ MenuItem = menuitem(Menu, ?wxID_ANY, Label, F),
+ {N + 1, [MenuItem|Acc]};
+ (F, {N, Acc}) ->
+ Name = F#filter.name,
+ Label = lists:concat([pad_string(Name, 20, $\ , right), "(", N, ")"]),
+ MenuItem = menuitem(Menu, ?wxID_ANY, Label, F),
+ {N + 1, [MenuItem|Acc]}
+ end,
+ Filters2 = lists:keysort(#filter.name, Filters),
+ {_,MenuData} = lists:foldl(Item, {1, []}, Filters2),
+ wxMenuBar:append(Bar, Menu, "Filters"),
+ MenuData.
+
+create_hide_menu(Bar, S) ->
+ Menu = wxMenu:new([]),
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ MenuData =
+ if
+ S#state.viewer_pid =:= undefined ->
+ ignore;
+ From =:= To ->
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Hide actor in Viewer "),
+ [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ Hide = menuitem(Menu, ?wxID_ANY, "From=To (f|t|b)", {hide, [From]}),
+ wxMenu:appendSeparator(Menu),
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Show actor in Viewer "),
+ [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ Show = menuitem(Menu, ?wxID_ANY, "From=To (F|T|B)", {show, [From]}),
+ [Show,Hide];
+ true ->
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Hide actor in Viewer "),
+ [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ Hide = [menuitem(Menu, ?wxID_ANY, "From (f)", {hide, [From]}),
+ menuitem(Menu, ?wxID_ANY, "To (t)", {hide, [To]}),
+ menuitem(Menu, ?wxID_ANY, "Both (b)", {hide, [From, To]})],
+ wxMenu:appendSeparator(Menu),
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Show actor in Viewer "),
+ [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ Show = [menuitem(Menu, ?wxID_ANY, "From (F)", {show, [From]}),
+ menuitem(Menu, ?wxID_ANY, "To (T)", {show, [To]}),
+ menuitem(Menu, ?wxID_ANY, "Both (B)", {show, [From, To]})],
+ Show++Hide
+ end,
+ wxMenuBar:append(Bar, Menu, "Hide"),
+ MenuData.
+
+create_search_menu(Bar, S) ->
+ Menu = wxMenu:new([]),
+ E = S#state.filtered_event,
+ From = E#event.from,
+ To = E#event.to,
+ wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Search in Viewer "),
+ [{enable, false}]),
+ wxMenu:appendSeparator(Menu),
+ MenuData =
+ if
+ S#state.viewer_pid =:= undefined ->
+ [menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})];
+ From =:= To ->
+ Key = et_collector:make_key(S#state.event_order, E),
+ ModeS = {search_actors, forward, Key, [From]},
+ ModeR = {search_actors, reverse, Key, [From]},
+ [menuitem(Menu, ?wxID_ANY, "Forward from this event (s)", {mode, ModeS}),
+ menuitem(Menu, ?wxID_ANY, "Reverse from this event (r)", {mode, ModeR}),
+ menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})];
+ true ->
+ Key = et_collector:make_key(S#state.event_order, E),
+ ModeS = {search_actors, forward, Key, [From, To]},
+ ModeR = {search_actors, reverse, Key, [From, To]},
+ [menuitem(Menu, ?wxID_ANY, "Forward from this event (s)", {mode, ModeS}),
+ menuitem(Menu, ?wxID_ANY, "Reverse from this event (r)", {mode, ModeR}),
+ menuitem(Menu, ?wxID_ANY, "Abort search. Display all (a)", {mode, all})]
+ end,
+ wxMenuBar:append(Bar, Menu, "Search"),
+ MenuData.
+
+config_editor(Editor, S) ->
+ Event = S#state.event,
+ Name = S#state.active_filter,
+ {value, F} = lists:keysearch(Name, #filter.name, S#state.filters),
+ FilterFun = F#filter.function,
+ case catch FilterFun(Event) of
+ true ->
+ do_config_editor(Editor, Event, lightblue, S#state.event_order);
+ {true, Event2} when is_record(Event2, event) ->
+ do_config_editor(Editor, Event2, lightblue, S#state.event_order);
+ false ->
+ do_config_editor(Editor, Event, red, S#state.event_order);
+ Bad ->
+ Contents = {bad_filter, Name, Bad},
+ BadEvent = Event#event{contents = Contents},
+ do_config_editor(Editor, BadEvent, red, S#state.event_order)
+ end.
+
+do_config_editor(Editor, Event, _Colour, TsKey) ->
+ String = event_to_string(Event, TsKey),
+ wxTextCtrl:appendText(Editor, String),
+ Event.
+
+%%%----------------------------------------------------------------------
+%%% String handling
+%%%----------------------------------------------------------------------
+
+term_to_string(Term) ->
+ case catch io_lib:format("~s", [Term]) of
+ {'EXIT', _} -> io_lib:format("~p", [Term]);
+ GoodString -> GoodString
+ end.
+
+now_to_string({Mega, Sec, Micro} = Now)
+ when is_integer(Mega), is_integer(Sec), is_integer(Micro) ->
+ {{Y, Mo, D}, {H, Mi, S}} = calendar:now_to_universal_time(Now),
+ lists:concat([Y, "-",
+ pad_string(Mo, 2, $0, left), "-",
+ pad_string(D, 2, $0, left),
+ "T",
+ pad_string(H, 2, $0, left), ":",
+ pad_string(Mi, 2, $0, left), ":",
+ pad_string(S, 2, $0, left), ".",
+ Micro]);
+now_to_string(Other) ->
+ term_to_string(Other).
+
+event_to_string(Event, TsKey) ->
+ ReportedTs = Event#event.trace_ts,
+ ParsedTs = Event#event.event_ts,
+ Deep =
+ ["DETAIL LEVEL: ", term_to_string(Event#event.detail_level),
+ "\nLABEL: ", term_to_string(Event#event.label),
+ case Event#event.from =:= Event#event.to of
+ true ->
+ ["\nACTOR: ", term_to_string(Event#event.from)];
+ false ->
+ ["\nFROM: ", term_to_string(Event#event.from),
+ "\nTO: ", term_to_string(Event#event.to)]
+ end,
+ case ReportedTs =:= ParsedTs of
+ true ->
+ ["\nPARSED: ", now_to_string(ParsedTs)];
+ false ->
+ case TsKey of
+ trace_ts ->
+ ["\nTRACE_TS: ", now_to_string(ReportedTs),
+ "\nEVENT_TS: ", now_to_string(ParsedTs)];
+ event_ts ->
+ ["\nEVENT_TS: ", now_to_string(ParsedTs),
+ "\nTRACE_TS: ", now_to_string(ReportedTs)]
+ end
+ end,
+ "\nCONTENTS:\n\n", term_to_string(Event#event.contents)],
+ lists:flatten(Deep).
+
+pad_string(Int, MinLen, Char, Dir) when is_integer(Int) ->
+ pad_string(integer_to_list(Int), MinLen, Char, Dir);
+pad_string(Atom, MinLen, Char, Dir) when is_atom(Atom) ->
+ pad_string(atom_to_list(Atom), MinLen, Char, Dir);
+pad_string(String, MinLen, Char, Dir) when is_integer(MinLen), MinLen >= 0 ->
+ Len = length(String),
+ case {Len >= MinLen, Dir} of
+ {true, _} ->
+ String;
+ {false, right} ->
+ String ++ lists:duplicate(MinLen - Len, Char);
+ {false, left} ->
+ lists:duplicate(MinLen - Len, Char) ++ String
+ end.
+
+send_viewer_event(S, Event) ->
+ case S#state.viewer_pid of
+ ViewerPid when is_pid(ViewerPid) ->
+ ViewerPid ! {et, Event};
+ undefined ->
+ ignore
+ end.
+
+select_file(Frame, Message, DefaultFile, Style) ->
+ Dialog = wxFileDialog:new(Frame,
+ [{message, Message},
+ {defaultDir, filename:dirname(DefaultFile)},
+ {defaultFile, filename:basename(DefaultFile)},
+ {style, Style}]),
+ Choice =
+ case wxMessageDialog:showModal(Dialog) of
+ ?wxID_CANCEL -> cancel;
+ ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)}
+ end,
+ wxFileDialog:destroy(Dialog),
+ Choice.