%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2011. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%----------------------------------------------------------------------
%% Purpose: Displays a sequence chart for trace events (messages/actions)
%%----------------------------------------------------------------------
-module(et_wx_viewer).
-behaviour(gen_server).
%% External exports
-export([start_link/1]).
%% gen_server callbacks
-export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2]).
-include("../include/et.hrl").
-include("et_internal.hrl").
-include_lib("wx/include/wx.hrl").
-define(unknown, "UNKNOWN").
-define(initial_x, 10).
-define(incr_x, 60).
-define(initial_y, 15).
-define(incr_y, 15).
-record(state,
{parent_pid, % Pid of parent process
auto_shutdown, % Shutdown collector when last subscriber dies
collector_pid, % Pid of collector process
event_order, % Field to be used as primary key
trace_pattern, % Collector trace pattern
active_filter, % Name of the active filter
filters, % List of possible filters
filter_menu,
pending_actor, % Pending actor - move or toggle
first_event, % Key of first event (regardless of visibility)
last_event, % Key of last event (regardless of visibility)
events_per_page, % Maximum number of shown events
events, % Queue containg all event keys (regardless of visibility)
n_events, % Number of events available in the collector
max_actors, % Maximum number of shown actors
actors, % List of known actors
refresh_needed, % Refresh is needed in order to show all actors
detail_level, % Show only events with lesser detail level
hide_actions, % Hide/show events where to == from actor (bool)
hide_actors, % Hide/show events with unknown actor (bool)
display_all,
context, % display | print
title, % GUI: Window title
frame, % GUI: Window object
menubar, % GUI: Menu bar object
packer, % GUI: Packer object
width, % GUI: Window width
height, % GUI: Window height
scale, % GUI: Scaling factor on canvas
normal_font, % GUI: Font to be used on text labels
bold_font, % GUI: Font to be used on text labels
pen,
brush,
print_psdd,
print_d,
canvas_width, % GUI: Canvas width
canvas_height, % GUI: Canvas height
canvas, % GUI: Canvas object
canvas_sizer,
scroll_bar, % GUI: Canvas scroll bar
y_pos, % GUI: Current y position on canvas
menu_data,
checkbox_data,
hide_actions_box,
hide_actors_box,
status_bar,
event_file,
wx_debug, % GUI: WX debug level
trap_exit}). % trap_exit process flag
-record(actor, {name, string, include, exclude}).
-record(e, {pos, key, event}).
%%%----------------------------------------------------------------------
%%% Client side
%%%----------------------------------------------------------------------
start_link(Options) ->
case parse_opt(Options, default_state(), []) of
{ok, S, CollectorOpt} ->
case S#state.collector_pid of
CollectorPid when is_pid(CollectorPid) ->
case gen_server:start_link(?MODULE, [S], []) of
{ok, Pid} when S#state.parent_pid =/= self() ->
unlink(Pid),
{ok, Pid};
Other ->
Other
end;
undefined ->
case et_collector:start_link([{auto_shutdown, true} | CollectorOpt]) of
{ok, CollectorPid} ->
S2 = S#state{collector_pid = CollectorPid},
case gen_server:start_link(?MODULE, [S2], []) of
{ok, Pid} when S#state.parent_pid =/= self() ->
unlink(Pid),
{ok, Pid};
Other ->
Other
end;
{error, Reason} ->
{error, {et_collector, Reason}}
end
end;
{error, Reason} ->
{error, Reason}
end.
default_state() ->
#state{parent_pid = self(),
collector_pid = undefined,
n_events = 0,
detail_level = ?detail_level_max,
active_filter = ?DEFAULT_FILTER_NAME,
filters = [?DEFAULT_FILTER],
event_order = trace_ts,
events_per_page = 100,
first_event = first,
last_event = first,
events = queue_new(),
max_actors = 5,
actors = [create_actor(?unknown)],
pending_actor = ?unknown,
hide_actions = false,
hide_actors = false,
display_all = true,
context = display,
refresh_needed = false,
scale = 2,
canvas_height = 0,
canvas_width = 0,
width = 800,
height = 600,
event_file = filename:absname("et_viewer.etrace"),
wx_debug = 0,
trap_exit = true}.
parse_opt([], S, CollectorOpt) ->
{ok, S, [{parent_pid, S#state.parent_pid} | CollectorOpt]};
parse_opt([H | T], S, CollectorOpt) ->
case H of
{parent_pid, Parent} when is_pid(Parent); Parent =:= undefined ->
parse_opt(T, S#state{parent_pid = Parent}, CollectorOpt);
{wx_debug, Level} ->
parse_opt(T, S#state{wx_debug = Level}, CollectorOpt);
{trap_exit, Bool} when Bool =:= true; Bool =:= false->
parse_opt(T, S#state{trap_exit = Bool}, CollectorOpt);
{title, Title} ->
parse_opt(T, S#state{title = name_to_string(Title)}, CollectorOpt);
{detail_level, Level} when is_integer(Level),
Level >= ?detail_level_min,
Level =< ?detail_level_max ->
parse_opt(T, S#state{detail_level = Level}, CollectorOpt);
{detail_level, max} ->
parse_opt(T, S#state{detail_level = ?detail_level_max}, CollectorOpt);
{detail_level, min} ->
parse_opt(T, S#state{detail_level = ?detail_level_min}, CollectorOpt);
{scale, Scale} when is_integer(Scale), Scale > 0 ->
parse_opt(T, S#state{scale = Scale}, CollectorOpt);
{width, W} when is_integer(W), W > 0 ->
parse_opt(T, S#state{width = W, canvas_width = W}, CollectorOpt);
{height, WH} when is_integer(WH), WH > 0 ->
parse_opt(T, S#state{height = WH, canvas_height = WH}, CollectorOpt);
{collector_pid, Pid} when is_pid(Pid) ->
parse_opt(T, S#state{collector_pid = Pid}, CollectorOpt);
{collector_pid, undefined} ->
parse_opt(T, S#state{collector_pid = undefined}, CollectorOpt);
{active_filter, Name} when is_atom(Name) ->
parse_opt(T, S#state{active_filter = Name}, CollectorOpt);
{event_order, trace_ts} -> %% BUGBUG: Verify event_order with collector
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S#state{event_order = trace_ts}, CollectorOpt2);
{event_order, event_ts} -> %% BUGBUG: Verify event_order with collector
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S#state{event_order = event_ts}, CollectorOpt2);
{trace_port, _Port} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{trace_max_queue, _Queue} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{trace_pattern, _Pattern} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{trace_global, _Boolean} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{trace_client, _Client} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{dict_insert, {filter, Name}, Fun} ->
if
is_atom(Name), is_function(Fun) ->
F = #filter{name = Name, function = Fun},
Filters = lists:keydelete(Name, #filter.name, S#state.filters),
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S#state{filters = Filters ++ [F]}, CollectorOpt2);
true ->
{error, {bad_option, H}}
end;
{dict_insert, {subscriber, Pid}, _Val} ->
if
is_pid(Pid) ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
true ->
{error, {bad_option, H}}
end;
{dict_insert, _Key, _Val} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{dict_delete, {filter, Name}} ->
Filters = lists:keydelete(Name, #filter.name, S#state.filters),
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S#state{filters = Filters}, CollectorOpt2);
{dict_delete, _Key} ->
CollectorOpt2 = [H | CollectorOpt],
parse_opt(T, S, CollectorOpt2);
{max_events, _Max} ->
%% Kept for backward compatibility
parse_opt(T, S, CollectorOpt);
{max_actors, Max} when is_integer(Max), Max >= 0 ->
parse_opt(T, S#state{max_actors = Max}, CollectorOpt);
{max_actors, Max} when Max =:= infinity ->
parse_opt(T, S#state{max_actors = Max}, CollectorOpt);
{actors, ActorNames} when is_list(ActorNames) ->
ActorNames2 =
case lists:member(?unknown, ActorNames) of
false -> [?unknown | ActorNames];
true -> ActorNames
end,
Actors = [create_actor(Name) || Name <- ActorNames2],
parse_opt(T, S#state{actors = Actors}, CollectorOpt);
{include, ActorNames} when is_list(ActorNames) ->
Actors = [opt_create_actor(Name, include, S#state.actors) || Name <- ActorNames],
parse_opt(T, S#state{actors = Actors}, CollectorOpt);
{exclude, ActorNames} when is_list(ActorNames) ->
Actors = [opt_create_actor(Name, exclude, S#state.actors) || Name <- ActorNames],
parse_opt(T, S#state{actors = Actors}, CollectorOpt);
{first_event, _FirstKey} ->
%% NYI
parse_opt(T, S, CollectorOpt);
{hide_actors, Bool} when Bool =:= true; Bool =:= false ->
parse_opt(T, S#state{hide_actors = Bool}, CollectorOpt);
{hide_actions, Bool} when Bool =:= true; Bool =:= false ->
parse_opt(T, S#state{hide_actions = Bool}, CollectorOpt);
{hide_unknown, Bool} when Bool =:= true; Bool =:= false ->
%% Kept for backward compatibility
parse_opt(T, S, CollectorOpt);
{display_mode, _Mode} ->
%% Kept for backward compatibility
parse_opt(T, S, CollectorOpt);
Bad ->
{error, {bad_option, Bad}}
end;
parse_opt(BadList, _S, _CollectorOpt) ->
{error, {bad_option_list, BadList}}.
do_dict_insert({filter, Name}, Fun, S) when is_atom(Name), is_function(Fun) ->
F = #filter{name = Name, function = Fun},
Filters = lists:keydelete(Name, #filter.name, S#state.filters),
Filters2 = lists:keysort(#filter.name, [F | Filters]),
S2 = create_filter_menu(S, S#state.active_filter, Filters2),
S2#state{filters = Filters2};
do_dict_insert(_Key, _Val, S) ->
%%ok = error_logger:format("~p(~p): handle_info({et, {dict_insert, ~p, ~p}})~n",
%% [?MODULE, self(), Key, Val]),
S.
do_dict_delete({filter, Name}, S) when is_atom(Name), Name =/= S#state.active_filter ->
Filters = lists:keydelete(Name, #filter.name, S#state.filters),
S2 = create_filter_menu(S, S#state.active_filter, Filters),
S2#state{filters = Filters};
do_dict_delete(_Key, S) ->
%% ok = error_logger:format("~p(~p): handle_info({et, {dict_delete, ~p}})~n",
%% [?MODULE, self(), Key]),
S.
%%%----------------------------------------------------------------------
%%% 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:new(),
wx:debug(S#state.wx_debug),
et_collector:dict_insert(S#state.collector_pid,
{subscriber, self()},
?MODULE),
S2 = create_main_window(S),
EventsPerPage = events_per_page(S2, S2#state.height),
S3 = revert_main_window(S2#state{events_per_page = EventsPerPage}),
Timeout = timeout(S3),
{ok, S3, Timeout}.
%%----------------------------------------------------------------------
%% 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(get_collector_pid, _From, S) ->
Reply = S#state.collector_pid,
reply(Reply, S);
handle_call(stop, _From, S) ->
wxFrame:destroy(S#state.frame),
opt_unlink(S#state.parent_pid),
{stop, shutdown, ok, S};
handle_call({open_event, N}, _From, S) when is_integer(N), N > 0->
Reply = do_open_event(S, N),
reply(Reply, S);
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_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info({et, {more_events, N}}, S) ->
%% io:format("more events: ~p \n", [N]),
S4 =
if
N =:= S#state.n_events ->
S;
true ->
Missing = S#state.events_per_page - queue_length(S#state.events),
if
Missing =:= 0 ->
update_scroll_bar(S#state{n_events = N});
Missing > 0 ->
OldEvents = queue_to_list(S#state.events),
{S2, NewEvents} =
collect_more_events(S#state{n_events = N},
S#state.last_event,
Missing),
S3 = replace_events(S2, OldEvents ++ NewEvents),
refresh_main_window(S3)
end
end,
noreply(S4);
handle_info({et, {insert_actors, ActorNames}}, S) when is_list(ActorNames) ->
Fun = fun(N, Actors) ->
case lists:keymember(N, #actor.name, Actors) of
true -> Actors;
false -> Actors ++ [create_actor(N)]
end
end,
Actors = lists:foldl(Fun, S#state.actors, ActorNames),
S2 = refresh_main_window(S#state{actors = Actors}),
noreply(S2);
handle_info({et, {delete_actors, ActorNames}}, S) when is_list(ActorNames)->
Fun = fun(N, Actors) when N =:= ?unknown ->
Actors;
(N, Actors) ->
lists:keydelete(N, #actor.name, Actors)
end,
Actors = lists:foldl(Fun, S#state.actors, ActorNames),
S2 = refresh_main_window(S#state{actors = Actors}),
noreply(S2);
handle_info({et, {dict_insert, Key, Val}}, S) ->
S2 = do_dict_insert(Key, Val, S),
noreply(S2);
handle_info({et, {dict_delete, Key}}, S) ->
S2 = do_dict_delete(Key, S),
noreply(S2);
handle_info({et, first}, S) ->
S2 = scroll_first(S),
noreply(S2);
handle_info({et, prev}, S) ->
S2 = scroll_prev(S),
noreply(S2);
handle_info({et, next}, S) ->
S2 = scroll_next(S),
noreply(S2);
handle_info({et, last}, S) ->
S2 = scroll_last(S),
noreply(S2);
handle_info({et, refresh}, S) ->
S2 = revert_main_window(S),
noreply(S2);
handle_info({et, {display_mode, _Mode}}, S) ->
%% Kept for backward compatibility
noreply(S);
handle_info({et, close}, S) ->
wxFrame:destroy(S#state.frame),
opt_unlink(S#state.parent_pid),
{stop, shutdown, S};
handle_info(#wx{id=?wxID_HELP}, S) ->
HelpString =
"Vertical scroll:\n"
"\tUse mouse wheel and up/down arrows to scroll little.\n"
"\tUse page up/down and home/end buttons to scroll more.\n\n"
"Display details of an event:\n"
"\tLeft mouse click on the event label or the arrow.\n\n"
"Highlight actor (toggle):\n"
"\tLeft mouse click on the actor name tag.\n"
"\tThe actor name will be enclosed in square brackets [].\n\n"
"Exclude actor (toggle):\n"
"\tRight mouse click on the actor name tag.\n"
"\tThe actor name will be enclosed in round brackets ().\n\n"
"Move actor:\n"
"\tLeft mouse button drag and drop on actor name tag.\n\n"
"Display all (reset settings for hidden and/or highlighted actors):\n"
"\tPress the 'a' button.",
Dialog =
wxMessageDialog:new(S#state.frame, HelpString,
[{style, 0
bor ?wxOK
bor ?wxICON_INFORMATION
bor ?wxSTAY_ON_TOP},
{caption, "Help"}]),
wxMessageDialog:showModal(Dialog),
noreply(S);
handle_info(#wx{id=Id, event = #wxCommand{type = command_menu_selected}}, S=#state{filter_menu = {_,Data}}) ->
CollectorPid = S#state.collector_pid,
case get_value(Id, 3, S#state.menu_data) of
close ->
wxFrame:destroy(S#state.frame),
opt_unlink(S#state.parent_pid),
{stop, shutdown, S};
up ->
S2 = scroll_up(S),
noreply(S2);
down ->
S2 = scroll_down(S),
noreply(S2);
first ->
S2 = scroll_first(S),
noreply(S2);
prev ->
S2 = scroll_prev(S),
noreply(S2);
next ->
S2 = scroll_next(S),
noreply(S2);
last ->
S2 = scroll_last(S),
noreply(S2);
refresh ->
S2 = revert_main_window(S),
noreply(S2);
{display_mode, _Mode} ->
%% Kept for backward compatibility
noreply(S);
display_all ->
S2 = display_all(S),
noreply(S2);
close_all ->
close_all(S);
close_all_others ->
close_all_others(S);
first_all ->
et_collector:multicast(CollectorPid, first),
noreply(S);
prev_all ->
et_collector:multicast(CollectorPid, prev),
noreply(S);
next_all ->
et_collector:multicast(CollectorPid, next),
noreply(S);
last_all ->
et_collector:multicast(CollectorPid, last),
noreply(S);
refresh_all ->
et_collector:multicast(CollectorPid, refresh),
noreply(S);
clear_all ->
et_collector:clear_table(CollectorPid),
et_collector:multicast(CollectorPid, refresh),
noreply(S);
load_all ->
Style = ?wxFD_OPEN bor ?wxFD_OVERWRITE_PROMPT,
Msg = "Select a file to load events from",
S2 =
case select_file(S#state.frame, Msg, S#state.event_file, Style) of
{ok, NewFile} ->
et_collector:start_trace_client(CollectorPid, event_file, NewFile),
S#state{event_file = NewFile};
cancel ->
S
end,
noreply(S2);
save_all ->
Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
Msg = "Select a file to save events to",
S2 =
case select_file(S#state.frame, Msg, S#state.event_file, Style) of
{ok, NewFile} ->
et_collector:save_event_file(CollectorPid, NewFile, [existing, write, keep]),
S#state{event_file = NewFile};
cancel ->
S
end,
noreply(S2);
print_setup ->
S2 = print_setup(S),
noreply(S2);
print_one_page = Scope ->
S2 = print(S, Scope),
noreply(S2);
print_all_pages = Scope ->
S2 = print(S, Scope),
noreply(S2);
{open_viewer, Scale} ->
Actors = [A#actor.name || A <- S#state.actors],
open_viewer(Scale, S#state.active_filter, Actors, S),
noreply(S);
_ ->
case get_value(Id, 3, Data) of
{data, F=#filter{}, Scale} ->
open_viewer(S#state.scale+Scale, F#filter.name, [?unknown], S);
{data, F=#filter{}} ->
open_viewer(S#state.scale, F#filter.name, [?unknown], S);
false ->
ok
end,
noreply(S)
end;
handle_info(#wx{event = #wxCommand{type = command_slider_updated, commandInt = Level}}, S) ->
if
Level >= ?detail_level_min,
Level =< ?detail_level_max ->
S2 = S#state{detail_level = Level},
S3 = revert_main_window(S2),
noreply(S3);
true ->
noreply(S)
end;
handle_info(#wx{id = Id, event = #wxCommand{type = command_checkbox_clicked, commandInt = Int}}, S) ->
case get_value(Id, 2, S#state.checkbox_data) of
hide_actions ->
case Int of
1 ->
S2 = S#state{hide_actions = true},
S3 = revert_main_window(S2),
noreply(S3);
0 ->
S2 = S#state{hide_actions = false},
S3 = revert_main_window(S2),
noreply(S3)
end;
hide_actors ->
case Int of
1 ->
S2 = S#state{hide_actors = true},
S3 = revert_main_window(S2),
noreply(S3);
0 ->
S2 = S#state{hide_actors = false},
S3 = revert_main_window(S2),
noreply(S3)
end;
false ->
noreply(S)
end;
handle_info(#wx{event = #wxMouse{type = left_down, x = X, y = Y}}, S) ->
S3 =
case y_to_n(Y, S) of
actor ->
%% Actor click
case S#state.actors of
[] ->
S;
Actors ->
N = x_to_n(X, S),
A = lists:nth(N, Actors),
S#state{pending_actor = A}
end;
{event, N} ->
%% Event click
do_open_event(S, N),
S
end,
noreply(S3);
handle_info(#wx{event = #wxMouse{type = left_up}}, S) when S#state.pending_actor =:= undefined ->
noreply(S);
handle_info(#wx{event = #wxMouse{type = left_up, x = X, y = Y}}, S) ->
S3 =
case y_to_n(Y, S) of
actor ->
%% Actor click
case S#state.actors of
[] ->
S;
Actors ->
N = x_to_n(X, S),
A = lists:nth(N, Actors),
Pending = S#state.pending_actor,
if
A#actor.name =:= Pending#actor.name ->
%% Toggle include actor
A2 = A#actor{include = not A#actor.include},
%% io:format("include ~p: ~p -> ~p\n",
%% [A#actor.name, A#actor.include, A2#actor.include]),
Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2),
DisplayAll = not lists:keymember(true, #actor.include, Actors2),
S2 = S#state{actors = Actors2, display_all = DisplayAll},
revert_main_window(S2);
true ->
move_actor(Pending, A, Actors, S)
end
end;
{event, _N} ->
%% Event click ignored
S
end,
noreply(S3#state{pending_actor = undefined});
handle_info(#wx{event = #wxMouse{type = right_up, x = X, y = Y}}, S) ->
S3 =
case y_to_n(Y, S) of
actor ->
%% Actor click
case S#state.actors of
[] ->
S;
Actors ->
%% Toggle exclude actor
N = x_to_n(X, S),
A = lists:nth(N, Actors),
A2 = A#actor{exclude = not A#actor.exclude},
Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2),
S2 = S#state{actors = Actors2},
revert_main_window(S2)
end;
{event, _N} ->
%% Event click ignored
S
end,
noreply(S3#state{pending_actor = undefined});
handle_info(#wx{event = #wxKey{keyCode = KeyCode, shiftDown = SD}}, S) ->
case KeyCode of
$C when SD =:= true ->
close_all(S);
$c ->
close_all_others(S);
?WXK_HOME ->
S2 = scroll_first(S),
noreply(S2);
?WXK_END ->
S2 = scroll_last(S),
noreply(S2);
?WXK_UP ->
S2 = scroll_up(S),
noreply(S2);
?WXK_DOWN ->
S2 = scroll_down(S),
noreply(S2);
?WXK_PAGEUP ->
S2 = scroll_prev(S),
noreply(S2);
?WXK_PAGEDOWN ->
S2 = scroll_next(S),
noreply(S2);
$F when SD =:= true ->
et_collector:multicast(S#state.collector_pid, first),
noreply(S);
$F ->
S2 = scroll_first(S),
noreply(S2);
$P when SD =:= true ->
et_collector:multicast(S#state.collector_pid, prev),
noreply(S);
$P ->
S2 = scroll_prev(S),
noreply(S2);
$N when SD =:= true ->
et_collector:multicast(S#state.collector_pid, next),
noreply(S);
$N ->
S2 = scroll_next(S),
noreply(S2);
$L when SD =:= true ->
et_collector:multicast(S#state.collector_pid, last),
noreply(S);
$L ->
S2 = scroll_last(S),
noreply(S2);
$R when SD =:= true ->
et_collector:multicast(S#state.collector_pid, refresh),
noreply(S);
$R ->
S2 = revert_main_window(S),
noreply(S2);
$A ->
S2 = display_all(S),
noreply(S2);
$= ->
Scale = S#state.scale,
Actors = [A#actor.name || A <- S#state.actors],
open_viewer(Scale, S#state.active_filter, Actors, S),
noreply(S);
Int when Int =:= $+; Int =:= ?WXK_NUMPAD_ADD ->
Scale = S#state.scale + 1,
Actors = [A#actor.name || A <- S#state.actors],
open_viewer(Scale, S#state.active_filter, Actors, S),
noreply(S);
Int when Int =:= $-; Int =:= ?WXK_NUMPAD_SUBTRACT ->
case S#state.scale of
1 ->
ignore;
Scale ->
Actors = [A#actor.name || A <- S#state.actors],
open_viewer(Scale - 1, S#state.active_filter, Actors, S)
end,
noreply(S);
$0 ->
case lists:keysearch(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of
{value, F} when is_record(F, filter) ->
open_viewer(S#state.scale, F#filter.name, [?unknown], S);
false ->
ok
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) ->
open_viewer(S#state.scale, F#filter.name, [?unknown], S);
{'EXIT', _} ->
ok
end,
noreply(S);
_ ->
noreply(S)
end;
handle_info(#wx{event = #wxScroll{type = scroll_changed}} = Wx, S) ->
get_latest_scroll(Wx),
Pos = wxScrollBar:getThumbPosition(S#state.scroll_bar),
{_, LineTopY, LineBotY} = calc_y(S),
Range = LineBotY - LineTopY,
N = round(S#state.n_events * Pos / Range),
Diff =
case N - event_pos(S) of
D when D < 0 -> D - 1;
D -> D + 1
end,
S2 = scroll_changed(S, Diff),
noreply(S2);
handle_info(timeout, S) ->
noreply(S);
handle_info({'EXIT', Pid, Reason}, S) ->
if
Pid =:= S#state.collector_pid ->
io:format("collector died: ~p\n\n", [Reason]),
wxFrame:destroy(S#state.frame),
{stop, Reason, S};
Pid =:= S#state.parent_pid ->
wxFrame:destroy(S#state.frame),
{stop, Reason, S};
true ->
noreply(S)
end;
handle_info(#wx{event = #wxClose{}}, S) ->
opt_unlink(S#state.parent_pid),
{stop, shutdown, S};
handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot > 0 ->
S2 = scroll_up(S),
noreply(S2);
handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot < 0 ->
S2 = scroll_down(S),
noreply(S2);
handle_info(#wx{event = #wxSize{size = {OldW, OldH}}} = Wx, S) ->
#wx{event = #wxSize{type = size, size = {W, H}}} = get_latest_resize(Wx),
S2 = S#state{width = W, height = H, canvas_width = W, canvas_height = H},
EventsPerPage = events_per_page(S, H),
Diff = EventsPerPage - S#state.events_per_page,
S6 =
if
OldW =:= W, OldH =:= H, S2#state.events_per_page =:= EventsPerPage ->
S2;
Diff =:= 0 ->
refresh_main_window(S2);
Diff > 0 ->
OldEvents = queue_to_list(S2#state.events),
{S3, NewEvents} = collect_more_events(S2, S2#state.last_event, Diff),
S4 = S3#state{events_per_page = EventsPerPage},
S5 = replace_events(S4, OldEvents ++ NewEvents),
refresh_main_window(S5);
Diff < 0 ->
OldEvents = queue_to_list(S2#state.events),
RevEvents = delete_n(lists:reverse(OldEvents), abs(Diff)),
S3 = S2#state{events_per_page = EventsPerPage},
S4 = replace_events(S3, lists:reverse(RevEvents)),
refresh_main_window(S4)
end,
noreply(S6);
handle_info(#wx{event = #wxMouse{type = enter_window}}, S) ->
wxWindow:setFocus(S#state.canvas), % Get keyboard focus
noreply(S);
handle_info(#wx{event = #wxPaint{}}, S) ->
S2 = refresh_main_window(S),
noreply(S2);
handle_info(#wx{event = #wxMouse{type = T, x=X,y=Y}}, S) ->
io:format("~p ~p\n", [T, {X,Y}]),
noreply(S);
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 stuff
%%%----------------------------------------------------------------------
reply(Reply, S) ->
Timeout = timeout(S),
{reply, Reply, S, Timeout}.
noreply(S) ->
Timeout = timeout(S),
{noreply, S, Timeout}.
timeout(_S) ->
infinity.
scroll_first(S) ->
EventsPerPage = S#state.events_per_page,
{S2, NewEvents} =
collect_more_events(S, first, EventsPerPage),
S3 =
case NewEvents of
[] ->
S2;
[FirstE | _] ->
S2#state{first_event = FirstE}
end,
S4 = replace_events(S3, NewEvents),
refresh_main_window(S4).
scroll_last(S) ->
case collect_more_events(S, last, -1) of
{_, []} ->
scroll_first(S);
{S2, NewEvents} ->
[FirstE | _] = NewEvents,
S3 = replace_events(S2#state{first_event = FirstE}, NewEvents),
refresh_main_window(S3)
end.
scroll_prev(S) ->
scroll_up(S, S#state.events_per_page).
scroll_next(S) ->
scroll_down(S, S#state.events_per_page).
scroll_up(S) ->
scroll_up(S, calc_scroll(S)).
scroll_up(S, Expected) ->
N = queue_length(S#state.events),
EventsPerPage = S#state.events_per_page,
Expected2 = adjust_expected(Expected, N, EventsPerPage),
OldEvents = queue_to_list(S#state.events),
case collect_more_events(S, S#state.first_event, -Expected2) of
{_, []} ->
S;
{S2, NewEvents} ->
NewN = length(NewEvents),
if
N + NewN > EventsPerPage ->
RevAllEvents = lists:reverse(OldEvents, lists:reverse(NewEvents)),
TooMany = N + NewN - EventsPerPage,
case delete_n(RevAllEvents, TooMany) of
[] ->
S;
[LastE | _] = RevEvents ->
Events = lists:reverse(RevEvents),
S3 = replace_events(S2#state{last_event = LastE}, Events),
refresh_main_window(S3)
end;
true ->
Events = NewEvents ++ OldEvents,
LastE = lists:last(Events),
S3 = replace_events(S2#state{last_event = LastE}, Events),
refresh_main_window(S3)
end
end.
scroll_down(S) ->
scroll_down(S, calc_scroll(S)).
scroll_down(S, Expected) ->
N = queue_length(S#state.events),
EventsPerPage = S#state.events_per_page,
Expected2 = adjust_expected(Expected, N, EventsPerPage),
OldEvents = queue_to_list(S#state.events),
case collect_more_events(S, S#state.last_event, Expected2) of
{_, []} ->
case collect_more_events(S, S#state.first_event, N - EventsPerPage) of
{_, []} ->
S;
{S2, NewEvents} ->
Events = NewEvents ++ OldEvents,
[FirstE | _] = Events,
S3 = replace_events(S2#state{first_event = FirstE}, Events),
refresh_main_window(S3)
end;
{S2, NewEvents} ->
AllEvents = OldEvents ++ NewEvents,
case delete_n(AllEvents, length(NewEvents)) of
[] ->
scroll_first(S);
Events ->
[FirstE | _] = Events,
S3 = replace_events(S2#state{first_event = FirstE}, Events),
refresh_main_window(S3)
end
end.
scroll_changed(S, Expected) ->
if
Expected =:= 0 ->
refresh_main_window(S);
Expected < 0 ->
%% Up
OldPos = event_pos(S),
NewPos = lists:max([OldPos + Expected, 0]),
case S#state.first_event of
#e{key = Key, pos = OldPos} ->
jump_up(S, Key, OldPos, NewPos);
first ->
scroll_first(S);
last ->
scroll_last(S)
end;
true ->
%% Down
OldPos = event_pos(S),
NewPos = lists:min([OldPos + Expected, S#state.n_events]),
case S#state.first_event of
#e{key = Key, pos = OldPos} ->
jump_down(S, Key, OldPos, NewPos);
first = Key ->
jump_down(S, Key, 0, NewPos);
last ->
scroll_last(S)
end
end.
jump_up(S, OldKey, OldPos, NewPos) ->
Try = NewPos - OldPos,
Order = S#state.event_order,
Fun = fun(Event, #e{pos = P}) when P >= NewPos ->
Key = et_collector:make_key(Order, Event),
#e{event = Event, key = Key, pos = P - 1};
(_, Acc) ->
Acc
end,
PrevE = et_collector:iterate(S#state.collector_pid,
OldKey,
Try,
Fun,
#e{key = OldKey, pos = OldPos}),
case collect_more_events(S, PrevE, S#state.events_per_page) of
{_, []} ->
S;
{S2, Events} ->
[FirstE | _] = Events,
S3 = replace_events(S2#state{first_event = FirstE}, Events),
refresh_main_window(S3)
end.
jump_down(S, OldKey, OldPos, NewPos) ->
Try = NewPos - OldPos,
Order = S#state.event_order,
Fun = fun(Event, #e{pos = P}) when P < NewPos ->
Key = et_collector:make_key(Order, Event),
#e{event = Event, key = Key, pos = P + 1};
(_, Acc) ->
Acc
end,
PrevE = et_collector:iterate(S#state.collector_pid,
OldKey,
Try,
Fun,
#e{key = OldKey, pos = OldPos}),
case collect_more_events(S, PrevE, S#state.events_per_page) of
{_, []} ->
S;
{S2, Events} ->
[FirstE | _] = Events,
S3 = replace_events(S2#state{first_event = FirstE}, Events),
refresh_main_window(S3)
end.
adjust_expected(Expected, N, EventsPerPage) ->
if
N < EventsPerPage ->
EventsPerPage - N;
Expected < EventsPerPage ->
Expected;
true ->
EventsPerPage
end.
calc_scroll(S) ->
lists:max([S#state.events_per_page div 3, 1]).
revert_main_window(S) ->
{S2, Events} = revert(S),
S3 = replace_events(S2, Events),
refresh_main_window(S3).
revert(S) ->
EventsPerPage = S#state.events_per_page,
%% Find previous event
case collect_more_events(S, S#state.first_event, -1) of
{_, []} ->
collect_more_events(S, first, EventsPerPage);
{S2, [_PrevEvent]} ->
collect_more_events(S, S2#state.first_event, EventsPerPage)
end.
delete_n(List, 0) ->
List;
delete_n([], _) ->
[];
delete_n([_ | Tail], N) when N > 0 ->
delete_n(Tail, N - 1).
pick_n(Rest, 0, Acc) ->
{lists:reverse(Acc), Rest};
pick_n([], _N, Acc) ->
{lists:reverse(Acc), []};
pick_n([Head | Tail], N, Acc) when N > 0 ->
pick_n(Tail, N - 1, [Head | Acc]).
close_all(S) ->
close_all_others(S),
wxFrame:destroy(S#state.frame),
opt_unlink(S#state.parent_pid),
{stop, shutdown, S}.
close_all_others(S) ->
Fun =
fun({{subscriber, Pid}, _}) ->
if
Pid =:= self() ->
ignore;
true ->
unlink(Pid),
Pid ! {et, close}
end
end,
All = et_collector:dict_match(S#state.collector_pid,
{{subscriber, '_'}, '_'}),
lists:foreach(Fun, All),
noreply(S).
opt_unlink(Pid) ->
if
Pid =:= undefined ->
ignore;
true ->
unlink(Pid)
end.
%%%----------------------------------------------------------------------
%%% Clone viewer
%%%----------------------------------------------------------------------
open_viewer(Scale, FilterName, Actors, S) ->
Filters = [{dict_insert, {filter, F#filter.name}, F#filter.function}
|| F <- S#state.filters],
Options =
[{parent_pid, S#state.parent_pid},
{title, S#state.title},
{collector_pid, S#state.collector_pid},
{detail_level, S#state.detail_level},
{active_filter, FilterName},
{event_order, S#state.event_order},
{first_event, S#state.first_event},
{max_actors, S#state.max_actors},
{hide_actions, S#state.hide_actions},
{hide_actors, S#state.hide_actors},
{actors, Actors},
{scale, Scale},
{width, S#state.width},
{height, S#state.height} | Filters],
case start_link(Options) of
{ok, _ViewerPid} ->
%% unlink(ViewerPid),
ok;
{error, Reason} ->
ok = error_logger:format("~p: Failed to start a new window: ~p~n",
[?MODULE, Reason])
end.
%%%----------------------------------------------------------------------
%%% Handle graphics
%%%----------------------------------------------------------------------
create_main_window(S) ->
{NormalFont, BoldFont} = select_fonts(S#state.scale),
Name = name_to_string(S#state.active_filter),
Title = case S#state.title of
undefined -> atom_to_list(?MODULE);
Explicit -> name_to_string(Explicit)
end,
Frame = wxFrame:new(wx:null(),
?wxID_ANY,
Title ++ " (filter: " ++ Name ++ ")",
[{size, {S#state.width, S#state.height}}]),
StatusBar = wxFrame:createStatusBar(Frame),
Panel = wxPanel:new(Frame, []),
Bar = wxMenuBar:new(),
wxFrame:setMenuBar(Frame,Bar),
MainSizer = wxBoxSizer:new(?wxVERTICAL),
MenuData = lists:flatten([create_file_menu(Bar),
create_viewer_menu(Bar),
create_collector_menu(Bar)]),
FilterMenu = wxMenu:new([]),
S2 = create_filter_menu(S#state{filter_menu = {FilterMenu,[]}},
S#state.active_filter,
S#state.filters),
wxMenuBar:append(Bar, FilterMenu, "Filters and scaling"),
create_help_menu(Bar),
OptSizer = wxBoxSizer:new(?wxHORIZONTAL),
CheckSizer = wxBoxSizer:new(?wxVERTICAL),
HideActions = wxCheckBox:new(Panel, ?wxID_ANY, "Hide From=To"),
wxCheckBox:setValue(HideActions, S#state.hide_actions),
HideActors = wxCheckBox:new(Panel, ?wxID_ANY, "Hide (excluded actors)"),
wxCheckBox:setValue(HideActors, S#state.hide_actors),
CheckBoxData = [{wxCheckBox:getId(HideActions), hide_actions},
{wxCheckBox:getId(HideActors), hide_actors}],
wxPanel:connect(Panel, command_checkbox_clicked),
wxSizer:add(CheckSizer, HideActions),
wxSizer:add(CheckSizer,HideActors),
wxSizer:add(OptSizer, CheckSizer, [{border, 10}, {flag, ?wxALL}]),
DetailLevelBox = wxStaticBoxSizer:new(?wxHORIZONTAL,
Panel,
[{label, "Detail level"}]),
DetailLevel = wxSlider:new(Panel, ?wxID_ANY,
S#state.detail_level,
?detail_level_min,
?detail_level_max,
[{style, ?wxSL_LABELS},
{size, {200,-1}}]),
wxStatusBar:setStatusText(StatusBar, where_text(S)),
wxFrame:connect(Frame, command_slider_updated),
wxSizer:add(DetailLevelBox, DetailLevel),
wxSizer:add(OptSizer, DetailLevelBox, [{border, 10}, {flag, ?wxALL}]),
wxSizer:addStretchSpacer(OptSizer),
wxSizer:add(MainSizer, OptSizer),
wxSizer:add(MainSizer,
wxStaticLine:new(Panel, [{style, ?wxLI_HORIZONTAL}]),
[{flag, ?wxEXPAND}]),
CanvasSizer = wxBoxSizer:new(?wxHORIZONTAL),
Canvas = wxPanel:new(Panel, [{style, ?wxFULL_REPAINT_ON_RESIZE}]),
{CanvasW,CanvasH} = wxPanel:getSize(Canvas),
ScrollBar = wxScrollBar:new(Panel, ?wxID_ANY, [{style, ?wxSB_VERTICAL}]),
wxSizer:add(CanvasSizer, Canvas, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxSizer:add(CanvasSizer, ScrollBar, [{flag, ?wxEXPAND}]),
wxSizer:add(MainSizer, CanvasSizer, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxPanel:connect(Canvas, left_down),
wxPanel:connect(Canvas, left_up),
wxPanel:connect(Canvas, right_up),
wxPanel:connect(Canvas, size),
Self = self(),
wxPanel:connect(Canvas, paint, [{callback, %% Needed on windows
fun(Ev, _) ->
DC = wxPaintDC:new(Canvas),
wxPaintDC:destroy(DC),
Self ! Ev
end}]),
wxPanel:connect(Canvas, key_down),
wxPanel:connect(Canvas, enter_window, [{skip, true}]),
wxFrame:connect(Frame, command_menu_selected),
wxFrame:connect(Frame, close_window),
wxFrame:connect(ScrollBar, scroll_changed),
wxPanel:setSize(Panel, {S#state.width, S#state.height}),
wxPanel:setSizer(Panel, MainSizer),
wxFrame:show(Frame),
wxPanel:setFocus(Canvas),
wxPanel:connect(Canvas, mousewheel),
S3 = S2#state{title = Title,
frame = Frame, packer = Panel,
normal_font = NormalFont, bold_font = BoldFont,
canvas_width = CanvasW, canvas_height = CanvasH,
canvas = Canvas,
canvas_sizer = CanvasSizer,
scroll_bar = ScrollBar,
y_pos = ?initial_y * S#state.scale,
pen = wxPen:new(),
brush = wxBrush:new(),
print_d = undefined,
print_psdd = undefined,
menu_data = MenuData,
checkbox_data = CheckBoxData,
hide_actions_box = HideActions,
hide_actors_box = HideActors,
status_bar = StatusBar},
DC = wxClientDC:new(Canvas),
S4 = draw_all_actors(S3, DC),
wxClientDC:destroy(DC),
S4.
where_text(#state{n_events = N} = S) ->
Pos = event_pos(S),
lists:concat([Pos, " (", N, ")"]).
event_pos(#state{first_event = E, events = Events, n_events = Last}) ->
case E of
#e{pos = Pos} ->
Pos;
first ->
case queue_length(Events) of
0 ->
0;
_ ->
1
end;
last ->
Last
end.
init_printers(#state{print_d = undefined, print_psdd = undefined} = S) ->
PD = wxPrintData:new(),
PSDD = wxPageSetupDialogData:new(PD),
wxPrintData:setPaperId(PD, ?wxPAPER_A4),
wxPageSetupDialogData:setMarginTopLeft(PSDD, {15,15}),
wxPageSetupDialogData:setMarginBottomRight(PSDD, {15,15}),
S#state{print_d = PD, print_psdd = PSDD};
init_printers(#state{} = S) ->
S.
select_fonts(Scale) when is_integer(Scale) ->
Size =
case Scale of
1 -> 5;
2 -> 10;
3 -> 14;
4 -> 20;
S -> S*6
end,
{wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxBOLD,[])}.
get_value(Key, Pos, TupleList) when is_list(TupleList)->
case lists:keysearch(Key, 1, TupleList) of
{value, Tuple} when is_tuple(Tuple)->
element(Pos, Tuple);
false ->
false
end.
menuitem(Menu, Id, Text, UserData) ->
Item = wxMenu:append(Menu, Id, Text),
{wxMenuItem:getId(Item), Item, UserData}.
create_file_menu(Bar) ->
Menu = wxMenu:new([]),
Data = [
menuitem(Menu, ?wxID_ANY, "Clear all events in the Collector", clear_all),
menuitem(Menu, ?wxID_ANY, "Load events to the Collector from file", load_all),
menuitem(Menu, ?wxID_ANY, "Save all events in the Collector to file", save_all),
menuitem(Menu, ?wxID_PRINT_SETUP, "Print setup", print_setup),
menuitem(Menu, ?wxID_ANY, "Print current page", print_one_page),
menuitem(Menu, ?wxID_PRINT, "Print all pages", print_all_pages),
menuitem(Menu, ?wxID_ANY, "Close this Viewer", close),
menuitem(Menu, ?wxID_ANY, "Close all other Viewers, but this (c)", close_all_others),
menuitem(Menu, ?wxID_ANY, "Close all Viewers and the Collector) (C) ", close_all)
],
wxMenu:insertSeparator(Menu, 3),
wxMenu:insertSeparator(Menu, 7),
wxMenuBar:append(Bar, Menu, "File"),
Data.
create_viewer_menu(Bar) ->
Menu = wxMenu:new([]),
wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll this Viewer"),
[{enable, false}]),
wxMenu:appendSeparator(Menu),
D1 = [
menuitem(Menu, ?wxID_ANY, "First (f)", first),
menuitem(Menu, ?wxID_ANY, "Last (l)", last),
menuitem(Menu, ?wxID_ANY, "Prev (p)", prev),
menuitem(Menu, ?wxID_ANY, "Next (n)", next),
menuitem(Menu, ?wxID_ANY, "Refresh (r)", refresh)
],
wxMenu:appendSeparator(Menu),
D2 = [
menuitem(Menu, ?wxID_ANY, "Up 5 (Up)", up),
menuitem(Menu, ?wxID_ANY, "Down 5 (Down)", down)
],
wxMenu:appendSeparator(Menu),
wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Actor visibility in this Viewer"),
[{enable, false}]),
wxMenu:appendSeparator(Menu),
D3 = [menuitem(Menu, ?wxID_ANY, "Display all actors (a)", display_all)],
wxMenuBar:append(Bar, Menu, "Viewer"),
[D1,D2,D3].
create_collector_menu(Bar) ->
Menu = wxMenu:new([]),
wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll all Viewers"),
[{enable, false}]),
wxMenu:appendSeparator(Menu),
Data = [
menuitem(Menu, ?wxID_ANY, "First (F)", first_all),
menuitem(Menu, ?wxID_ANY, "Last (L)", last_all),
menuitem(Menu, ?wxID_ANY, "Prev (P)", prev_all),
menuitem(Menu, ?wxID_ANY, "Next (N)", next_all),
menuitem(Menu, ?wxID_ANY, "Refresh (R)", refresh_all)
],
wxMenuBar:append(Bar, Menu, "Collector"),
Data.
create_filter_menu(S=#state{filter_menu = {Menu,Data}}, ActiveFilterName, Filters) ->
wx:foreach(fun({_,I,_}) ->
wxMenu:delete(Menu,I);
(I) ->
try
wxMenu:delete(Menu,I)
catch
_:Reason ->
io:format("Could not delete item: ~p, because ~p.\n", [I, Reason])
end
end,
Data),
Item = fun(F, {N, Acc}) when F#filter.name =:= all ->
Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]),
{N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]};
(F, {N, Acc}) ->
Label = lists:concat([pad_string(F#filter.name, 20), "(", N, ")"]),
{N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]}
end,
D1 = [I1 = wxMenu:append(Menu, ?wxID_ANY, "Same Filter New Scale"),
wxMenu:appendSeparator(Menu)],
wxMenuItem:enable(I1, [{enable,false}]),
{value, Filter} = lists:keysearch(ActiveFilterName, #filter.name, Filters),
Same = lists:concat([pad_string(ActiveFilterName, 20), "(=) same scale"]),
Larger = lists:concat([pad_string(ActiveFilterName, 20), "(+) bigger scale"]),
Smaller = lists:concat([pad_string(ActiveFilterName, 20), "(-) smaller scale"]),
D2 = [
menuitem(Menu, ?wxID_ANY, Same, {data, Filter, 0}),
menuitem(Menu, ?wxID_ANY, Smaller, {data, Filter, -1}),
menuitem(Menu, ?wxID_ANY, Larger, {data, Filter, 1}),
wxMenu:appendSeparator(Menu),
I2 = wxMenu:append(Menu, ?wxID_ANY, "New Filter Same Scale"),
wxMenu:appendSeparator(Menu)
],
wxMenuItem:enable(I2, [{enable,false}]),
{_,D3} = lists:foldl(Item, {1,[]}, Filters),
S#state{filter_menu = {Menu, lists:flatten([D1,D2,D3])}}.
create_help_menu(Bar) ->
Menu = wxMenu:new([]),
menuitem(Menu, ?wxID_HELP, "Info", help),
wxMenuBar:append(Bar, Menu, "Help").
clear_canvas(S) ->
DC = wxClientDC:new(S#state.canvas),
wxDC:setBackground(DC, ?wxWHITE_BRUSH), %% Needed on mac
wxDC:clear(DC),
{CanvasW, CanvasH} = wxPanel:getSize(S#state.canvas),
wxSizer:recalcSizes(S#state.canvas_sizer),
S2 = S#state{refresh_needed = false,
y_pos = ?initial_y * S#state.scale,
canvas_width = CanvasW,
canvas_height = CanvasH,
events = queue_new()},
S3 = draw_all_actors(S2, DC),
wxClientDC:destroy(DC),
S3.
replace_events(S, []) ->
S#state{first_event = first,
last_event = first,
events = queue_new()};
replace_events(S, Events) ->
Queue = lists:foldl(fun(E, Q) -> queue_in(E, Q) end, queue_new(), Events),
S#state{events = Queue}.
refresh_main_window(S) ->
wx:batch(fun() ->
S2 = clear_canvas(S),
S3 = update_scroll_bar(S2),
display_events(S3, queue_to_list(S#state.events))
end).
display_events(S, []) ->
S;
display_events(S, Events) ->
DC = wxClientDC:new(S#state.canvas),
S2 = lists:foldl(fun(E, State) -> display_event(E, State, DC) end, S, Events),
wxClientDC:destroy(DC),
S2.
collect_more_events(S, PrevKey = first, Try) ->
PrevE = #e{event = undefined, key = PrevKey, pos = 0},
S2 = S#state{first_event = PrevE, last_event = PrevE},
do_collect_more_events(S2, Try, PrevE, []);
collect_more_events(S, PrevKey = last, Try) ->
PrevE = #e{event = undefined, key = PrevKey, pos = S#state.n_events},
S2 = S#state{first_event = PrevE, last_event = PrevE},
do_collect_more_events(S2, Try, PrevE, []);
collect_more_events(S, #e{} = PrevE, Try) ->
do_collect_more_events(S, Try, PrevE, []).
do_collect_more_events(#state{collector_pid = Collector,
event_order = Order,
active_filter = Active,
filters = Filters} = S,
Try,
PrevE,
Acc) ->
Incr =
if
Try < 0 -> -1;
true -> 1
end,
PrevKey = PrevE#e.key,
{value, #filter{function = FilterFun}} =
lists:keysearch(Active, #filter.name, Filters),
{_S, _Incr, _Order, _Active, _FilterFun, LastE, NewEvents} =
et_collector:iterate(Collector,
PrevKey,
Try,
fun collect_event/2,
{S, Incr, Order, Active, FilterFun, PrevE, []}),
Expected = abs(Try),
Actual = length(NewEvents),
Missing = Expected - Actual,
{S2, Acc2, Try2} =
if
Try < 0 ->
{S#state{first_event = LastE}, NewEvents ++ Acc, -Missing};
true ->
TmpEvents = lists:reverse(NewEvents),
{S#state{last_event = LastE}, Acc ++ TmpEvents, Missing}
end,
if
Missing =/= 0, PrevKey =/= LastE#e.key ->
do_collect_more_events(S2, Try2, LastE, Acc2);
true ->
{S2, Acc2}
end.
collect_event(Event, {S, Incr, Order, Active, FilterFun, #e{pos = PrevPos}, Events}) ->
Key = et_collector:make_key(Order, Event),
E = #e{event = Event, key = Key, pos = PrevPos + Incr},
{LastE, Events2} =
case catch FilterFun(Event) of
true ->
case is_hidden(Event#event.from, Event#event.to, S) of
true ->
{E, Events};
false ->
{E, [E | Events]}
end;
{true, Event2} ->
Key2 = et_collector:make_key(Order, Event2),
E2 = E#e{event = Event2, key = Key2},
case is_hidden(Event2#event.from, Event2#event.to, S) of
true ->
{E2, Events};
false ->
{E2, [E2 | Events]}
end;
false ->
{E, Events};
Bad ->
Contents = {bad_filter, S#state.active_filter, Bad, Event},
Event2 = Event#event{contents = Contents,
from = bad_filter,
to = bad_filter},
E2 = E#e{event = Event2},
{E2, [E2 | Events]}
end,
{S, Incr, Order, Active, FilterFun, LastE, Events2}.
display_event(#e{event = Event} = E, S, DC)
when Event#event.detail_level =< S#state.detail_level ->
{FromRefresh, From} = ensure_actor(Event#event.from, S, DC),
{FromName, FromPos, S2} = From,
{ToRefresh, To} = ensure_actor(Event#event.to, S2, DC),
{ToName, ToPos, S3} = To,
S4 =
if
FromRefresh =/= false, ToRefresh =/= false ->
S3#state{refresh_needed = true,
events = queue_in(E, S3#state.events)};
FromName =:= ToName ->
case S#state.hide_actions of
true ->
S3;
false ->
Label = name_to_string(Event#event.label),
draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC)
end;
true ->
Label = name_to_string(Event#event.label),
draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC)
end,
S4;
display_event(#e{}, S, _DC) ->
S.
draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S, DC) ->
case S#state.y_pos + (?incr_y * S#state.scale) of
_ when S#state.hide_actors =:= true, FromName =:= ?unknown ->
S;
_ when S#state.hide_actors =:= true, ToName =:= ?unknown ->
S;
Y when Y > S#state.canvas_height ->
S#state{refresh_needed = true,
events = queue_in(E, S#state.events)};
Y ->
S2 = S#state{y_pos = Y, events = queue_in(E, S#state.events)},
S3 = draw_arrow(FromPos, ToPos, S2, DC),
draw_label(Label, FromName, ToName, FromPos, ToPos, S3, DC)
end.
draw_arrow(Pos, Pos, S, _DC) ->
S;
draw_arrow(FromPos, ToPos, S, DC) ->
Y = S#state.y_pos,
wxPen:setColour(S#state.pen, ?wxBLACK),
wxDC:setPen(DC, S#state.pen),
wxDC:drawLine(DC, {FromPos , Y}, {ToPos, Y}),
%% Draw arrow head
Radians = calc_angle({FromPos, Y}, {ToPos, Y}),
Len = 5,
Radians2 = Radians + 3.665191429188092,
Radians3 = Radians + 2.617993877991494,
{X3, Y3} = calc_point({ToPos, Y}, Len, Radians2),
{X4, Y4} = calc_point({ToPos, Y}, Len, Radians3),
Points = [{round(ToPos), round(Y)},
{round(X3), round(Y3)},
{round(X4), round(Y4)}],
wxBrush:setColour(S#state.brush, ?wxBLACK),
wxDC:setBrush(DC, S#state.brush),
wxDC:drawPolygon(DC, Points, []),
S.
%% Calclulate angle in radians for a line between two points
calc_angle({X1, Y1}, {X2, Y2}) ->
math:atan2((Y2 - Y1), (X2 - X1)).
%% Calc new point at a given distance and angle from another point
calc_point({X, Y}, Length, Radians) ->
X2 = round(X + Length * math:cos(Radians)),
Y2 = round(Y + Length * math:sin(Radians)),
{X2, Y2}.
draw_label(Label, FromName, ToName, FromPos, ToPos, S, DC) ->
Color =
if
FromName =:= ?unknown,
ToName =:= ?unknown -> {2, 71, 254};% blue
FromName =:= ?unknown -> {255,126,0}; % orange
ToName =:= ?unknown -> {255,126,0}; % orange
FromPos =:= ToPos -> {2, 71, 254};% blue
true -> {227,38, 54} % red
end,
Scale = S#state.scale,
X = lists:min([FromPos, ToPos]) + (6 * Scale),
Y = S#state.y_pos,
write_text(Label, X, Y, Color, S#state.normal_font, S, DC),
S.
draw_all_actors(S, DC) ->
Scale = S#state.scale,
Fun = fun(A, X) ->
case draw_actor(A, X, S, DC) of
true ->
X + (?incr_x * Scale);
false ->
X
end
end,
lists:foldl(Fun, ?initial_x * Scale, S#state.actors),
S.
%% Returns: {NeedsRefreshBool, {ActorPos, NewsS, NewActors}}
ensure_actor(Name, S, DC) ->
do_ensure_actor(Name, S, S#state.actors, 0, DC).
do_ensure_actor(Name, S, [H | _], N, _DC) when H#actor.name =:= Name ->
Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
{false, {Name, Pos, S}};
do_ensure_actor(Name, S, [H | T], N, DC) ->
if
S#state.hide_actors, H#actor.exclude ->
do_ensure_actor(Name, S, T, N, DC);
true ->
do_ensure_actor(Name, S, T, N + 1, DC)
end;
do_ensure_actor(Name, S, [], N, DC) ->
%% A brand new actor, let's see if it does fit
Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
MaxActors = S#state.max_actors,
if
is_integer(MaxActors), N > MaxActors ->
%% Failed on max_actors limit, put into unknown
%% Assume that unknown always is in actor list
ensure_actor(?unknown, S, DC);
Pos > (S#state.canvas_width - ((?initial_x - 15) * S#state.scale)) ->
%% New actor does not fit in canvas, refresh needed
A = create_actor(Name),
draw_actor(A, Pos, S, DC),
{true, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}};
true ->
%% New actor fits in canvas. Draw the new actor.
A = create_actor(Name),
draw_actor(A, Pos, S, DC),
{false, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}}
end.
draw_actor(A, LineX, S, DC) ->
if
S#state.hide_actors, A#actor.exclude ->
false;
true ->
Scale = S#state.scale,
TextX = LineX - (5 * Scale),
{TextY, LineTopY, LineBotY} = calc_y(S),
Color =
case A#actor.name of
?unknown -> {255,126,0};% orange
_ -> {227,38,54} % red
end,
{String, Font} =
if
S#state.context =:= display, A#actor.exclude ->
{"(" ++ A#actor.string ++ ")", S#state.normal_font};
S#state.context =:= display, A#actor.include ->
{"[" ++ A#actor.string ++ "]", S#state.bold_font};
true ->
{A#actor.string, S#state.normal_font}
end,
write_text(String, TextX, TextY, Color, Font, S, DC),
wxPen:setColour(S#state.pen, Color),
wxDC:setPen(DC, S#state.pen),
wxDC:drawLines(DC, [{LineX, LineTopY}, {LineX, LineBotY}]),
true
end.
calc_y(#state{canvas_height = Height, scale = Scale}) ->
TextY = ?initial_y * Scale,
LineTopY = round(TextY + ((?incr_y / 2) * Scale)),
LineBotY = Height,
%% LineBotY = round(Height - ((?incr_y / 2) * Scale)),
{TextY, LineTopY, LineBotY}.
display_all(S) ->
Actors = S#state.actors,
Actors2 = [A#actor{include = false, exclude = false} || A <- Actors],
S2 = S#state{actors = Actors2,
display_all = true,
hide_actions = false,
hide_actors = false},
wxCheckBox:setValue(S2#state.hide_actions_box, S2#state.hide_actions),
wxCheckBox:setValue(S2#state.hide_actors_box, S2#state.hide_actors),
revert_main_window(S2).
is_hidden(A, S) ->
case S#state.display_all of
true ->
A#actor.exclude;
false ->
A#actor.exclude orelse not A#actor.include
end.
is_hidden(From, To, S) ->
Actors = S#state.actors,
DisplayAll = S#state.display_all,
FromMatch = lists:keysearch(From, #actor.name, Actors),
ToMatch = lists:keysearch(To, #actor.name, Actors),
case {FromMatch, ToMatch} of
{false, false} ->
not DisplayAll;
{false, {value, T}} ->
is_hidden(T, S);
{{value, F}, false} ->
is_hidden(F, S);
{{value, F}, {value, T}} when DisplayAll ->
is_hidden(F, S) orelse is_hidden(T, S);
{{value, F}, {value, T}} when F#actor.include; T#actor.include ->
F#actor.exclude orelse T#actor.exclude;
{{value, _F}, {value, _T}}->
true
end.
move_actor(From, To, Actors, S) ->
Pos = #actor.name,
ToName = To#actor.name,
FromName = From#actor.name,
ToIx = actor_index(ToName, Pos, Actors),
FromIx = actor_index(FromName, Pos, Actors),
if
FromIx =/= 0, ToIx =/= 0, ToIx > FromIx ->
Actors2 = lists:keydelete(FromName, Pos, Actors),
Actors3 = insert_actor_after(From, To, Actors2),
S2 = S#state{actors = Actors3},
refresh_main_window(S2);
FromIx =/= 0, ToIx =/= 0 ->
Actors2 = lists:keydelete(FromName, Pos, Actors),
Actors3 = insert_actor_before(From, To, Actors2),
S2 = S#state{actors = Actors3},
refresh_main_window(S2);
true ->
%% Ignore
S
end.
insert_actor_after(From, To, [H | T]) ->
case To#actor.name =:= H#actor.name of
true -> [H, From | T];
false -> [H | insert_actor_after(From, To, T)]
end;
insert_actor_after(_From, _To, []) ->
[].
insert_actor_before(From, To, [H | T]) ->
case To#actor.name =:= H#actor.name of
true -> [From, H | T];
false -> [H | insert_actor_before(From, To, T)]
end;
insert_actor_before(_From, _To, []) ->
[].
actor_index(_Key, _Pos, []) ->
0;
actor_index(Key, Pos, [H | T]) ->
case Key =:= element(Pos, H) of
false -> actor_index(Key, Pos, T) + 1;
true -> 1
end.
y_to_n(Y, S) ->
Y2 = ((Y / S#state.scale) - ?initial_y + (?incr_y / 2)),
N = round(Y2 / ?incr_y - 0.2),
MaxN = queue_length(S#state.events),
if
N =< 0 -> actor;
N > MaxN -> actor;
true -> {event, N}
end.
x_to_n(X, S) ->
Scale = S#state.scale,
Len = length(S#state.actors),
X2 = X - (?initial_x * Scale),
N = X2 / (?incr_x * Scale),
N2 = trunc(N + 1.5),
if
N2 > Len -> Len;
N2 < 1 -> 1;
true -> N2
end.
write_text(Text, X, Y, Color, Font, S, DC) ->
wxDC:setFont(DC, Font),
wxDC:setTextForeground(DC, Color),
wxDC:drawText(DC, Text, {X, round(Y - (?incr_y * S#state.scale / 2))-3}).
do_open_event(S, N) ->
Events = queue_to_list(S#state.events),
S2 = S#state{events = list_to_queue(Events)},
case catch lists:nth(N, Events) of
{'EXIT', _} ->
{error, {no_such_event, N}};
#e{key = Key} ->
Pid = S#state.collector_pid,
Fun = fun create_contents_window/2,
Prev = et_collector:iterate(Pid, Key, -1),
{S2, Res} =
if
Prev =:= Key ->
et_collector:iterate(Pid, first, 1, Fun, {S2, []});
true ->
et_collector:iterate(Pid, Prev, 1, Fun, {S2, []})
end,
case Res of
[] ->
{error, no_contents_viewer_started};
[Single] ->
Single;
Multi ->
{error, {too_many, Multi}}
end
end.
create_contents_window(Event, {S, Res}) ->
Options = [{viewer_pid, self()},
{event, Event},
{event_order, S#state.event_order},
{active_filter, S#state.active_filter},
{wx_debug, S#state.wx_debug}
| S#state.filters],
case catch et_wx_contents_viewer:start_link(Options) of
{ok, Pid} ->
{S, [{ok, Pid} | Res]};
{error, Reason} ->
ok = error_logger:format("~p(~p): create_contents_window(~p) ->~n ~p~n",
[?MODULE, self(), Options, Reason]),
{S, [{error, Reason} | Res]};
Stuff ->
{S, [{error, {stuff, Stuff}} | Res]}
end.
print_setup(S) ->
S2 = #state{print_psdd = PSDD0, print_d = PD0} = init_printers(S),
wxPageSetupDialogData:setPrintData(PSDD0, PD0),
PSD = wxPageSetupDialog:new(S#state.frame, [{data,PSDD0}]),
wxPageSetupDialog:showModal(PSD),
PSDD1 = wxPageSetupDialog:getPageSetupData(PSD),
PD1 = wxPageSetupDialogData:getPrintData(PSDD1),
%% Create new objects using copy constructor
PD = wxPrintData:new(PD1),
PsDD = wxPageSetupDialogData:new(PSDD1),
wxPageSetupDialog:destroy(PSD),
wxPageSetupDialogData:destroy(PSDD0),
wxPrintData:destroy(PD0),
S2#state{print_psdd=PsDD, print_d=PD}.
print(#state{print_d = undefined, print_psdd = undefined} = S, Scope) ->
S2 = print_setup(S),
print(S2, Scope);
print(#state{print_psdd = PSDD, print_d = PD} = S, Scope) ->
PDD = wxPrintDialogData:new(PD),
wxPrintDialogData:enablePrintToFile(PDD, true),
wxPrintDialogData:enablePageNumbers(PDD, true),
wxPrintDialogData:enableSelection(PDD, true),
Tab = ets:new(?MODULE, [public]),
GetPageInfo =
fun(This) ->
{_, _, PW, PH} = wxPrintout:getPaperRectPixels(This),
PrinterS = S#state{context = printer,
canvas_width = PW,
canvas_height = PH},
EventsPerPage = events_per_page(PrinterS, PH),
PagedEvents = paged_events(PrinterS, Scope, EventsPerPage),
[ets:insert(Tab, PE) || PE <- PagedEvents],
ets:insert(Tab, PrinterS),
NumPages = length(PagedEvents),
{1, NumPages, 1, NumPages}
end,
HasPage =
fun(_This, Page) ->
Size = ets:info(Tab, size),
NumPages = Size - 1,
(Page >= 1) andalso (Page =< NumPages)
end,
OnPrintPage =
fun(This, Page) ->
wxPrintout:mapScreenSizeToPageMargins(This, PSDD),
[PrinterS] = ets:lookup(Tab, state),
Events = ets:lookup_element(Tab, Page, 2),
DC = wxPrintout:getDC(This),
PrinterS2 = draw_all_actors(PrinterS, DC),
PrinterS3 = PrinterS2#state{y_pos = ?initial_y * PrinterS2#state.scale},
lists:foldl(fun(E, State) -> display_event(E, State, DC) end,
PrinterS3,
Events),
true
end,
Printout1 = wxPrintout:new("Print", OnPrintPage,
[{getPageInfo, GetPageInfo}, {hasPage, HasPage}]),
Printout2 = wxPrintout:new("Print", OnPrintPage,
[{getPageInfo, GetPageInfo}, {hasPage, HasPage}]),
Preview = wxPrintPreview:new(Printout1, [{printoutForPrinting, Printout2}, {data,PDD}]),
case wxPrintPreview:isOk(Preview) of
true ->
PF = wxPreviewFrame:new(Preview, S#state.frame, []),
wxPreviewFrame:centre(PF, [{dir, ?wxBOTH}]),
wxPreviewFrame:initialize(PF),
wxPreviewFrame:centre(PF),
wxPreviewFrame:show(PF),
OnClose = fun(_Wx, EventRef) -> ets:delete(Tab), wxEvent:skip(EventRef) end,
wxPreviewFrame:connect(PF, close_window, [{callback, OnClose}]);
false ->
io:format("Could not create preview window.\n"
"Perhaps your current printer is not set correctly?~n", []),
wxPrintPreview:destroy(Preview),
ets:delete(Tab)
end,
S.
paged_events(S, Scope, EventsPerPage) ->
{_, Events} =
case Scope of
print_one_page ->
revert(S#state{events_per_page = EventsPerPage});
print_all_pages ->
collect_more_events(S, first, S#state.n_events)
end,
split_list(Events, EventsPerPage).
split_list(List, N) when is_integer(N), N > 0 ->
do_split_list(List, N, 1, []).
do_split_list([], _N, _Page, Acc) ->
lists:reverse(Acc);
do_split_list(List, N, Page, Acc) ->
{Items, Rest} = pick_n(List, N, []),
do_split_list(Rest, N, Page + 1, [{Page, Items} | Acc]).
get_latest_resize(#wx{obj = ObjRef, event = #wxSize{}} = Wx) ->
receive
#wx{obj = ObjRef, event = #wxSize{}} = Wx2 ->
get_latest_resize(Wx2)
after 100 ->
Wx
end.
get_latest_scroll(#wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx) ->
receive
#wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx2 ->
get_latest_scroll(Wx2)
after 100 ->
Wx
end.
update_scroll_bar(#state{scroll_bar = ScrollBar,
status_bar = StatusBar,
events_per_page = EventsPerPage,
n_events = N} = S) ->
Opts = [{refresh, true}],
{_, LineTopY, LineBotY} = calc_y(S),
Range = LineBotY - LineTopY,
EventPos =
case event_pos(S) of
1 -> 0;
P -> P
end,
if
N =/= 0,
EventsPerPage =/= 0 ->
PixelsPerEvent = Range / EventsPerPage,
Share = EventsPerPage / N,
wxScrollBar:setScrollbar(ScrollBar,
trunc(EventPos * Share * PixelsPerEvent),
round(Share * Range),
Range,
round(Share * Range),
Opts);
true ->
wxScrollBar:setScrollbar(ScrollBar,
0,
Range,
Range,
Range,
Opts)
end,
wxStatusBar:setStatusText(StatusBar, where_text(S)),
S.
events_per_page(S, PageHeight) ->
EventsPerPage = ((PageHeight - (?initial_y * S#state.scale)) div (?incr_y * S#state.scale)),
lists:max([1, EventsPerPage]).
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.
%%%----------------------------------------------------------------------
%%% String padding of actors
%%%----------------------------------------------------------------------
opt_create_actor(Name, Tag, S) ->
Actors = S#state.actors,
New =
case lists:keysearch(Name, #actor.name, Actors) of
{value, Old} -> Old;
false -> create_actor(Name)
end,
case Tag of
include -> New#actor{include = true};
exclude -> New#actor{exclude = true}
end.
create_actor(Name) ->
String = name_to_string(Name),
%% PaddedString = pad_string(String, 8),
#actor{name = Name, string = String, include = false, exclude = false}.
name_to_string(Name) ->
case catch io_lib:format("~s", [Name]) of
{'EXIT', _} -> lists:flatten(io_lib:format("~w", [Name]));
GoodString -> lists:flatten(GoodString)
end.
pad_string(Atom, MinLen) when is_atom(Atom) ->
pad_string(atom_to_list(Atom), MinLen);
pad_string(String, MinLen) when is_integer(MinLen), MinLen >= 0 ->
Len = length(String),
case Len >= MinLen of
true ->
String;
false ->
String ++ lists:duplicate(MinLen - Len, $ )
end.
%%%----------------------------------------------------------------------
%%% Queue management
%%%----------------------------------------------------------------------
queue_new() ->
{0, [], []}.
queue_in(X, {Size, In, Out}) ->
{Size + 1, [X | In], Out}.
%% queue_out(Q) ->
%% case Q of
%% {Size, In, [H | Out]} -> {{value, H}, {Size - 1, In, Out}};
%% {Size, [], []} -> {empty, {Size, [], []}};
%% {Size, In, _} -> queue_out({Size, [], lists:reverse(In)})
%% end.
queue_to_list({_Size, [], Out}) ->
Out;
queue_to_list({_Size, In, Out}) ->
Out ++ lists:reverse(In).
queue_length({Size, _In, _Out}) ->
Size.
list_to_queue(List) when is_list(List) ->
{length(List), [], List}.