1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-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%%----------------------------------------------------------------------
21%% Purpose: Displays a sequence chart for trace events (messages/actions)
22%%----------------------------------------------------------------------
23
24-module(et_wx_viewer).
25
26-behaviour(gen_server).
27
28%% External exports
29-export([start_link/1]).
30
31%% gen_server callbacks
32-export([init/1, terminate/2, code_change/3,
33         handle_call/3, handle_cast/2, handle_info/2]).
34
35-include("../include/et.hrl").
36-include("et_internal.hrl").
37-include_lib("wx/include/wx.hrl").
38
39-define(unknown, "UNKNOWN").
40-define(initial_x, 10).
41-define(incr_x,    60).
42-define(initial_y, 15).
43-define(incr_y,    15).
44
45-record(state,
46        {parent_pid,           % Pid of parent process
47         auto_shutdown,        % Shutdown collector when last subscriber dies
48         collector_pid,        % Pid of collector process
49         event_order,          % Field to be used as primary key
50         trace_pattern,        % Collector trace pattern
51         active_filter,        % Name of the active filter
52         filters,              % List of possible filters
53	 filter_menu,
54         pending_actor,        % Pending actor - move or toggle
55         first_event,          % Key of first event (regardless of visibility)
56         last_event,           % Key of last event (regardless of visibility)
57         events_per_page,      % Maximum number of shown events
58         events,               % Queue containg all event keys (regardless of visibility)
59	 n_events,             % Number of events available in the collector
60         max_actors,           % Maximum number of shown actors
61         actors,               % List of known actors
62         refresh_needed,       % Refresh is needed in order to show all actors
63         detail_level,         % Show only events with lesser detail level
64         hide_actions,         % Hide/show events where to == from actor (bool)
65         hide_actors,          % Hide/show events with unknown actor (bool)
66	 display_all,
67	 context,              % display | print
68         title,                % GUI: Window title
69         frame,                % GUI: Window object
70         menubar,              % GUI: Menu bar object
71         packer,               % GUI: Packer object
72         width,                % GUI: Window width
73         height,               % GUI: Window height
74         scale,                % GUI: Scaling factor on canvas
75         normal_font,          % GUI: Font to be used on text labels
76         bold_font,            % GUI: Font to be used on text labels
77	 pen,
78	 brush,
79	 print_psdd,
80	 print_d,
81         canvas_width,         % GUI: Canvas width
82         canvas_height,        % GUI: Canvas height
83         canvas,               % GUI: Canvas object
84	 canvas_sizer,
85	 scroll_bar,           % GUI: Canvas scroll bar
86         y_pos,                % GUI: Current y position on canvas
87	 menu_data,
88	 checkbox_data,
89	 hide_actions_box,
90	 hide_actors_box,
91	 status_bar,
92	 event_file,
93	 wx_debug,             % GUI: WX debug level
94	 trap_exit}).          % trap_exit process flag
95
96
97-record(actor, {name, string, include, exclude}).
98-record(e, {pos, key, event}).
99
100%%%----------------------------------------------------------------------
101%%% Client side
102%%%----------------------------------------------------------------------
103
104start_link(Options) ->
105    case parse_opt(Options, default_state(), []) of
106        {ok, S, CollectorOpt} ->
107            case S#state.collector_pid of
108                CollectorPid when is_pid(CollectorPid) ->
109		    case gen_server:start_link(?MODULE, [S], []) of
110			{ok, Pid} when S#state.parent_pid =/= self() ->
111			    unlink(Pid),
112			    {ok, Pid};
113			Other ->
114			    Other
115		    end;
116                undefined ->
117                    case et_collector:start_link([{auto_shutdown, true} | CollectorOpt]) of
118                        {ok, CollectorPid} ->
119                            S2 = S#state{collector_pid = CollectorPid},
120                            case gen_server:start_link(?MODULE, [S2], []) of
121                                {ok, Pid} when S#state.parent_pid =/= self() ->
122                                    unlink(Pid),
123                                    {ok, Pid};
124                                Other ->
125                                    Other
126                            end;
127                        {error, Reason} ->
128                            {error, {et_collector, Reason}}
129                    end
130            end;
131        {error, Reason} ->
132            {error, Reason}
133    end.
134
135default_state() ->
136    #state{parent_pid          = self(),
137           collector_pid       = undefined,
138	   n_events            = 0,
139           detail_level        = ?detail_level_max,
140           active_filter       = ?DEFAULT_FILTER_NAME,
141           filters             = [?DEFAULT_FILTER],
142           event_order         = trace_ts,
143           events_per_page     = 100,
144           first_event         = first,
145           last_event          = first,
146           events              = queue_new(),
147           max_actors          = 5,
148           actors              = [create_actor(?unknown)],
149           pending_actor       = ?unknown,
150           hide_actions        = false,
151           hide_actors         = false,
152           display_all         = true,
153	   context             = display,
154           refresh_needed      = false,
155           scale               = 2,
156           canvas_height       = 0,
157           canvas_width        = 0,
158           width               = 800,
159           height              = 600,
160	   event_file          = filename:absname("et_viewer.etrace"),
161	   wx_debug            = 0,
162	   trap_exit           = true}.
163
164parse_opt([], S, CollectorOpt) ->
165    {ok, S, [{parent_pid, S#state.parent_pid} | CollectorOpt]};
166parse_opt([H | T], S, CollectorOpt) ->
167    case H of
168        {parent_pid, Parent} when is_pid(Parent); Parent =:= undefined ->
169            parse_opt(T, S#state{parent_pid = Parent}, CollectorOpt);
170	{wx_debug, Level} ->
171            parse_opt(T, S#state{wx_debug = Level}, CollectorOpt);
172	{trap_exit, Bool} when Bool =:= true; Bool =:= false->
173            parse_opt(T, S#state{trap_exit = Bool}, CollectorOpt);
174        {title, Title} ->
175            parse_opt(T, S#state{title = name_to_string(Title)}, CollectorOpt);
176        {detail_level, Level} when is_integer(Level),
177				   Level >= ?detail_level_min,
178				   Level =< ?detail_level_max ->
179            parse_opt(T, S#state{detail_level = Level}, CollectorOpt);
180        {detail_level, max} ->
181            parse_opt(T, S#state{detail_level = ?detail_level_max}, CollectorOpt);
182        {detail_level, min} ->
183            parse_opt(T, S#state{detail_level = ?detail_level_min}, CollectorOpt);
184        {scale, Scale} when is_integer(Scale), Scale > 0 ->
185            parse_opt(T, S#state{scale = Scale}, CollectorOpt);
186        {width, W} when is_integer(W), W > 0 ->
187            parse_opt(T, S#state{width = W, canvas_width = W}, CollectorOpt);
188        {height, WH} when is_integer(WH), WH > 0 ->
189            parse_opt(T, S#state{height = WH, canvas_height = WH}, CollectorOpt);
190        {collector_pid, Pid} when is_pid(Pid) ->
191            parse_opt(T, S#state{collector_pid = Pid}, CollectorOpt);
192        {collector_pid, undefined} ->
193            parse_opt(T, S#state{collector_pid = undefined}, CollectorOpt);
194        {active_filter, Name} when is_atom(Name) ->
195            parse_opt(T, S#state{active_filter = Name}, CollectorOpt);
196        {event_order, trace_ts} -> %% BUGBUG: Verify event_order with collector
197            CollectorOpt2 = [H | CollectorOpt],
198            parse_opt(T, S#state{event_order = trace_ts}, CollectorOpt2);
199        {event_order, event_ts} -> %% BUGBUG: Verify event_order with collector
200            CollectorOpt2 = [H | CollectorOpt],
201            parse_opt(T, S#state{event_order = event_ts}, CollectorOpt2);
202        {trace_port, _Port} ->
203            CollectorOpt2 = [H | CollectorOpt],
204            parse_opt(T, S, CollectorOpt2);
205        {trace_max_queue, _Queue} ->
206            CollectorOpt2 = [H | CollectorOpt],
207            parse_opt(T, S, CollectorOpt2);
208        {trace_pattern, _Pattern} ->
209            CollectorOpt2 = [H | CollectorOpt],
210            parse_opt(T, S, CollectorOpt2);
211        {trace_global, _Boolean} ->
212            CollectorOpt2 = [H | CollectorOpt],
213            parse_opt(T, S, CollectorOpt2);
214        {trace_client, _Client} ->
215            CollectorOpt2 = [H | CollectorOpt],
216            parse_opt(T, S, CollectorOpt2);
217        {dict_insert, {filter, Name}, Fun} ->
218	    if
219		is_atom(Name), is_function(Fun) ->
220		    F = #filter{name = Name, function = Fun},
221		    Filters = lists:keydelete(Name, #filter.name, S#state.filters),
222		    CollectorOpt2 = [H | CollectorOpt],
223		    parse_opt(T, S#state{filters = Filters ++ [F]}, CollectorOpt2);
224		true ->
225	            {error, {bad_option, H}}
226	    end;
227        {dict_insert, {subscriber, Pid}, _Val} ->
228	    if
229		is_pid(Pid) ->
230		    CollectorOpt2 = [H | CollectorOpt],
231		    parse_opt(T, S, CollectorOpt2);
232		true ->
233	            {error, {bad_option, H}}
234	    end;
235        {dict_insert, _Key, _Val} ->
236            CollectorOpt2 = [H | CollectorOpt],
237            parse_opt(T, S, CollectorOpt2);
238        {dict_delete, {filter, Name}} ->
239            Filters = lists:keydelete(Name, #filter.name, S#state.filters),
240            CollectorOpt2 = [H | CollectorOpt],
241            parse_opt(T, S#state{filters = Filters}, CollectorOpt2);
242        {dict_delete, _Key} ->
243            CollectorOpt2 = [H | CollectorOpt],
244            parse_opt(T, S, CollectorOpt2);
245        {max_events, _Max} ->
246	    %% Kept for backward compatibility
247            parse_opt(T,  S, CollectorOpt);
248        {max_actors, Max} when is_integer(Max), Max >= 0 ->
249            parse_opt(T,  S#state{max_actors = Max}, CollectorOpt);
250        {max_actors, Max} when Max =:= infinity ->
251            parse_opt(T,  S#state{max_actors = Max}, CollectorOpt);
252        {actors, ActorNames} when is_list(ActorNames) ->
253            ActorNames2 =
254                case lists:member(?unknown, ActorNames) of
255                    false -> [?unknown | ActorNames];
256                    true  -> ActorNames
257                end,
258            Actors = [create_actor(Name) || Name <- ActorNames2],
259            parse_opt(T, S#state{actors = Actors}, CollectorOpt);
260        {include, ActorNames} when is_list(ActorNames) ->
261            Actors = [opt_create_actor(Name, include, S) || Name <- ActorNames],
262            parse_opt(T, S#state{actors = Actors}, CollectorOpt);
263        {exclude, ActorNames} when is_list(ActorNames) ->
264            Actors = [opt_create_actor(Name, exclude, S) || Name <- ActorNames],
265            parse_opt(T, S#state{actors = Actors}, CollectorOpt);
266        {first_event, _FirstKey} ->
267	    %% NYI
268            parse_opt(T, S, CollectorOpt);
269        {hide_actors, Bool} when Bool =:= true; Bool =:= false ->
270            parse_opt(T,  S#state{hide_actors = Bool}, CollectorOpt);
271        {hide_actions, Bool} when Bool =:= true; Bool =:= false ->
272            parse_opt(T,  S#state{hide_actions = Bool}, CollectorOpt);
273	{hide_unknown, Bool} when Bool =:= true; Bool =:= false ->
274	    %% Kept for backward compatibility
275            parse_opt(T, S, CollectorOpt);
276        {display_mode, _Mode} ->
277	    %% Kept for backward compatibility
278            parse_opt(T,  S, CollectorOpt);
279        Bad ->
280            {error, {bad_option, Bad}}
281    end;
282parse_opt(BadList, _S, _CollectorOpt) ->
283    {error, {bad_option_list, BadList}}.
284
285do_dict_insert({filter, Name}, Fun, S) when is_atom(Name), is_function(Fun) ->
286    F = #filter{name = Name, function = Fun},
287    Filters = lists:keydelete(Name, #filter.name, S#state.filters),
288    Filters2 = lists:keysort(#filter.name, [F | Filters]),
289    S2 = create_filter_menu(S, S#state.active_filter, Filters2),
290    S2#state{filters = Filters2};
291do_dict_insert(_Key, _Val, S) ->
292    %%ok = error_logger:format("~p(~p): handle_info({et, {dict_insert, ~p, ~p}})~n",
293    %%		     [?MODULE, self(), Key, Val]),
294    S.
295
296do_dict_delete({filter, Name}, S) when is_atom(Name), Name =/= S#state.active_filter ->
297    Filters = lists:keydelete(Name, #filter.name, S#state.filters),
298    S2 = create_filter_menu(S, S#state.active_filter, Filters),
299    S2#state{filters = Filters};
300do_dict_delete(_Key, S) ->
301    %% ok = error_logger:format("~p(~p): handle_info({et, {dict_delete, ~p}})~n",
302    %%                          [?MODULE, self(), Key]),
303    S.
304
305%%%----------------------------------------------------------------------
306%%% Callback functions from gen_server
307%%%----------------------------------------------------------------------
308
309%%----------------------------------------------------------------------
310%% Func: init/1
311%% Returns: {ok, State}          |
312%%          {ok, State, Timeout} |
313%%          ignore               |
314%%          {stop, Reason}
315%%----------------------------------------------------------------------
316
317init([S]) when is_record(S, state) ->
318    process_flag(trap_exit, S#state.trap_exit),
319    case S#state.parent_pid of
320	undefined -> ok;
321	ParentPid -> link(ParentPid)
322    end,
323    _ = wx:new(),
324    _ = wx:debug(S#state.wx_debug),
325    et_collector:dict_insert(S#state.collector_pid,
326                             {subscriber, self()},
327                             ?MODULE),
328    S2 = create_main_window(S),
329    EventsPerPage = events_per_page(S2, S2#state.height),
330    S3 = revert_main_window(S2#state{events_per_page = EventsPerPage}),
331    Timeout = timeout(S3),
332    {ok, S3, Timeout}.
333
334%%----------------------------------------------------------------------
335%% Func: handle_call/3
336%% Returns: {reply, Reply, State}          |
337%%          {reply, Reply, State, Timeout} |
338%%          {noreply, State}               |
339%%          {noreply, State, Timeout}      |
340%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
341%%          {stop, Reason, State}            (terminate/2 is called)
342%%----------------------------------------------------------------------
343
344handle_call(get_collector_pid, _From, S) ->
345    Reply = S#state.collector_pid,
346    reply(Reply, S);
347handle_call(stop, _From, S) ->
348    wxFrame:destroy(S#state.frame),
349    opt_unlink(S#state.parent_pid),
350    {stop, shutdown, ok, S};
351handle_call({open_event, N}, _From, S) when is_integer(N), N > 0->
352    Reply = do_open_event(S, N),
353    reply(Reply, S);
354handle_call(Request, From, S) ->
355    ok = error_logger:format("~p(~p): handle_call(~tp, ~tp, ~tp)~n",
356			     [?MODULE, self(), Request, From, S]),
357    Reply = {error, {bad_request, Request}},
358    reply(Reply, S).
359
360%%----------------------------------------------------------------------
361%% Func: handle_cast/2
362%% Returns: {noreply, State}          |
363%%          {noreply, State, Timeout} |
364%%          {stop, Reason, State}            (terminate/2 is called)
365%%----------------------------------------------------------------------
366
367handle_cast(Msg, S) ->
368    ok = error_logger:format("~p(~p): handle_cast(~tp, ~tp)~n",
369			     [?MODULE, self(), Msg, S]),
370    noreply(S).
371
372
373%%----------------------------------------------------------------------
374%% Func: handle_info/2
375%% Returns: {noreply, State}          |
376%%          {noreply, State, Timeout} |
377%%          {stop, Reason, State}            (terminate/2 is called)
378%%----------------------------------------------------------------------
379
380handle_info({et, {more_events, N}}, S) ->
381    %% io:format("more events: ~p \n", [N]),
382    S4 =
383    	if
384	    N =:= S#state.n_events ->
385		S;
386	    true ->
387		Missing = S#state.events_per_page - queue_length(S#state.events),
388		if
389		    Missing =:= 0 ->
390			update_scroll_bar(S#state{n_events = N});
391		    Missing > 0 ->
392			OldEvents = queue_to_list(S#state.events),
393			{S2, NewEvents} =
394			    collect_more_events(S#state{n_events = N},
395						S#state.last_event,
396						Missing),
397			S3 = replace_events(S2, OldEvents ++ NewEvents),
398			refresh_main_window(S3)
399		end
400	end,
401    noreply(S4);
402handle_info({et, {insert_actors, ActorNames}}, S) when is_list(ActorNames) ->
403    Fun = fun(N, Actors) ->
404		  case lists:keymember(N, #actor.name, Actors) of
405		      true  -> Actors;
406		      false -> Actors ++ [create_actor(N)]
407		  end
408	  end,
409    Actors = lists:foldl(Fun, S#state.actors, ActorNames),
410    S2 = refresh_main_window(S#state{actors = Actors}),
411    noreply(S2);
412handle_info({et, {delete_actors, ActorNames}}, S) when is_list(ActorNames)->
413    Fun = fun(N, Actors) when N =:= ?unknown ->
414		  Actors;
415	     (N, Actors) ->
416		  lists:keydelete(N, #actor.name, Actors)
417	  end,
418    Actors = lists:foldl(Fun, S#state.actors, ActorNames),
419    S2 = refresh_main_window(S#state{actors = Actors}),
420    noreply(S2);
421handle_info({et, {dict_insert, Key, Val}}, S) ->
422    S2 = do_dict_insert(Key, Val, S),
423    noreply(S2);
424handle_info({et, {dict_delete, Key}}, S) ->
425    S2 = do_dict_delete(Key, S),
426    noreply(S2);
427handle_info({et, first}, S) ->
428    S2 = scroll_first(S),
429    noreply(S2);
430handle_info({et, prev}, S) ->
431    S2 = scroll_prev(S),
432    noreply(S2);
433handle_info({et, next}, S) ->
434    S2 = scroll_next(S),
435    noreply(S2);
436handle_info({et, last}, S) ->
437    S2 = scroll_last(S),
438    noreply(S2);
439handle_info({et, refresh}, S) ->
440    S2 = revert_main_window(S),
441    noreply(S2);
442handle_info({et, {display_mode, _Mode}}, S) ->
443    %% Kept for backward compatibility
444    noreply(S);
445handle_info({et, close}, S) ->
446    wxFrame:destroy(S#state.frame),
447    opt_unlink(S#state.parent_pid),
448    {stop, shutdown, S};
449handle_info(#wx{id=?wxID_HELP}, S) ->
450    HelpString =
451	"Vertical scroll:\n"
452	"\tUse mouse wheel and up/down arrows to scroll little.\n"
453	"\tUse page up/down and home/end buttons to scroll more.\n\n"
454	"Display details of an event:\n"
455	"\tLeft mouse click on the event label or the arrow.\n\n"
456	"Highlight actor (toggle):\n"
457	"\tLeft mouse click on the actor name tag.\n"
458	"\tThe actor name will be enclosed in square brackets [].\n\n"
459	"Exclude actor (toggle):\n"
460	"\tRight mouse click on the actor name tag.\n"
461	"\tThe actor name will be enclosed in round brackets ().\n\n"
462	"Move actor:\n"
463	"\tLeft mouse button drag and drop on actor name tag.\n\n"
464	"Display all (reset settings for hidden and/or highlighted actors):\n"
465	"\tPress the 'a' button.",
466    Dialog =
467	wxMessageDialog:new(S#state.frame, HelpString,
468			    [{style, 0
469				  bor ?wxOK
470				  bor ?wxICON_INFORMATION
471				  bor ?wxSTAY_ON_TOP},
472			     {caption, "Help"}]),
473    wxMessageDialog:showModal(Dialog),
474    noreply(S);
475handle_info(#wx{id=Id, event = #wxCommand{type = command_menu_selected}}, S=#state{filter_menu = {_,Data}}) ->
476    CollectorPid = S#state.collector_pid,
477    case get_value(Id, 3, S#state.menu_data) of
478	close ->
479	    wxFrame:destroy(S#state.frame),
480	    opt_unlink(S#state.parent_pid),
481	    {stop, shutdown, S};
482	up ->
483	    S2 = scroll_up(S),
484	    noreply(S2);
485	down ->
486	    S2 = scroll_down(S),
487	    noreply(S2);
488	first ->
489	    S2 = scroll_first(S),
490	    noreply(S2);
491	prev ->
492	    S2 = scroll_prev(S),
493	    noreply(S2);
494	next ->
495	    S2 = scroll_next(S),
496	    noreply(S2);
497	last ->
498	    S2 = scroll_last(S),
499	    noreply(S2);
500	refresh ->
501	    S2 = revert_main_window(S),
502	    noreply(S2);
503	{display_mode, _Mode} ->
504	    %% Kept for backward compatibility
505	    noreply(S);
506	display_all ->
507	    S2 = display_all(S),
508	    noreply(S2);
509	close_all ->
510	    close_all(S);
511	close_all_others ->
512	    close_all_others(S);
513	first_all ->
514	    et_collector:multicast(CollectorPid, first),
515	    noreply(S);
516	prev_all ->
517	    et_collector:multicast(CollectorPid, prev),
518	    noreply(S);
519	next_all ->
520	    et_collector:multicast(CollectorPid, next),
521	    noreply(S);
522	last_all ->
523	    et_collector:multicast(CollectorPid, last),
524	    noreply(S);
525	refresh_all ->
526	    et_collector:multicast(CollectorPid, refresh),
527	    noreply(S);
528	clear_all ->
529	    et_collector:clear_table(CollectorPid),
530	    et_collector:multicast(CollectorPid, refresh),
531	    noreply(S);
532	load_all ->
533	    Style = ?wxFD_OPEN bor ?wxFD_OVERWRITE_PROMPT,
534	    Msg = "Select a file to load events from",
535            S2 = case select_file(S#state.frame, Msg, S#state.event_file, Style) of
536                     {ok, NewFile} ->
537                         _ = et_collector:start_trace_client(CollectorPid, event_file, NewFile),
538                         S#state{event_file = NewFile};
539                     cancel ->
540                         S
541                 end,
542	    noreply(S2);
543	save_all ->
544	    Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
545	    Msg = "Select a file to save events to",
546            S2 = case select_file(S#state.frame, Msg, S#state.event_file, Style) of
547                     {ok, NewFile} ->
548                         ok = et_collector:save_event_file(CollectorPid, NewFile,
549                                                           [existing, write, keep]),
550                         S#state{event_file = NewFile};
551                     cancel ->
552                         S
553                 end,
554	    noreply(S2);
555	print_setup ->
556	    S2 = print_setup(S),
557	    noreply(S2);
558	print_one_page = Scope ->
559	    S2 = print(S, Scope),
560	    noreply(S2);
561	print_all_pages = Scope ->
562	    S2 = print(S, Scope),
563	    noreply(S2);
564	{open_viewer, Scale} ->
565	    Actors = [A#actor.name || A <- S#state.actors],
566	    open_viewer(Scale, S#state.active_filter, Actors, S),
567	    noreply(S);
568
569	_ ->
570	    case get_value(Id, 3, Data) of
571		{data, F=#filter{}, Scale} ->
572		    open_viewer(S#state.scale+Scale, F#filter.name, [?unknown], S);
573		{data, F=#filter{}} ->
574		    open_viewer(S#state.scale, F#filter.name, [?unknown], S);
575		false ->
576		    ok
577	    end,
578	    noreply(S)
579    end;
580handle_info(#wx{event = #wxCommand{type = command_slider_updated, commandInt = Level}}, S) ->
581    if
582	Level >= ?detail_level_min,
583	Level =< ?detail_level_max ->
584	    S2 = S#state{detail_level = Level},
585	    S3 = revert_main_window(S2),
586	    noreply(S3);
587
588	true ->
589	    noreply(S)
590    end;
591handle_info(#wx{id = Id, event = #wxCommand{type = command_checkbox_clicked, commandInt = Int}}, S) ->
592    case get_value(Id, 2, S#state.checkbox_data) of
593	hide_actions ->
594	    case Int of
595		1 ->
596		    S2 = S#state{hide_actions = true},
597		    S3 = revert_main_window(S2),
598		    noreply(S3);
599		0 ->
600		    S2 = S#state{hide_actions = false},
601		    S3 = revert_main_window(S2),
602		    noreply(S3)
603	    end;
604	hide_actors ->
605	    case Int of
606		1 ->
607		    S2 = S#state{hide_actors = true},
608		    S3 = revert_main_window(S2),
609		    noreply(S3);
610		0 ->
611		    S2 = S#state{hide_actors = false},
612		    S3 = revert_main_window(S2),
613		    noreply(S3)
614	    end;
615	false ->
616	    noreply(S)
617    end;
618handle_info(#wx{event = #wxMouse{type = left_down, x = X, y = Y}}, S) ->
619    S3 =
620	case y_to_n(Y, S) of
621	    actor ->
622		%% Actor click
623		case S#state.actors of
624		    [] ->
625			S;
626		    Actors ->
627			N = x_to_n(X, S),
628			A = lists:nth(N, Actors),
629			S#state{pending_actor = A}
630		end;
631	    {event, N} ->
632		%% Event click
633		do_open_event(S, N),
634		S
635	end,
636    noreply(S3);
637handle_info(#wx{event = #wxMouse{type = left_up}}, S) when S#state.pending_actor =:= undefined ->
638    noreply(S);
639handle_info(#wx{event = #wxMouse{type = left_up, x = X, y = Y}}, S) ->
640    S3 =
641	case y_to_n(Y, S) of
642	    actor ->
643		%% Actor click
644		case S#state.actors of
645		    [] ->
646			S;
647		    Actors ->
648			N = x_to_n(X, S),
649			A = lists:nth(N, Actors),
650			Pending = S#state.pending_actor,
651			if
652			    A#actor.name =:= Pending#actor.name ->
653				%% Toggle include actor
654				A2 = A#actor{include = not A#actor.include},
655				%% io:format("include ~p: ~p -> ~p\n",
656				%% [A#actor.name, A#actor.include, A2#actor.include]),
657				Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2),
658				DisplayAll = not lists:keymember(true, #actor.include, Actors2),
659				S2 = S#state{actors = Actors2, display_all = DisplayAll},
660				revert_main_window(S2);
661			    true ->
662				move_actor(Pending, A, Actors, S)
663			end
664		end;
665	    {event, _N} ->
666		%% Event click ignored
667		S
668	end,
669    noreply(S3#state{pending_actor = undefined});
670handle_info(#wx{event = #wxMouse{type = right_up, x = X, y = Y}}, S) ->
671    S3 =
672	case y_to_n(Y, S) of
673	    actor ->
674		%% Actor click
675		case S#state.actors of
676		    [] ->
677			S;
678		    Actors ->
679			%% Toggle exclude actor
680			N = x_to_n(X, S),
681			A = lists:nth(N, Actors),
682			A2 = A#actor{exclude = not A#actor.exclude},
683			Actors2 = lists:keyreplace(A#actor.name, #actor.name, Actors, A2),
684			S2 = S#state{actors = Actors2},
685			revert_main_window(S2)
686		end;
687	    {event, _N} ->
688		%% Event click ignored
689		S
690	end,
691    noreply(S3#state{pending_actor = undefined});
692handle_info(#wx{event = #wxKey{keyCode = KeyCode, shiftDown = SD}}, S) ->
693    case KeyCode of
694	$C when SD =:= true ->
695	    close_all(S);
696	$c ->
697	    close_all_others(S);
698	?WXK_HOME ->
699	    S2 = scroll_first(S),
700	    noreply(S2);
701	?WXK_END ->
702	    S2 = scroll_last(S),
703	    noreply(S2);
704	?WXK_UP ->
705	    S2 = scroll_up(S),
706	    noreply(S2);
707	?WXK_DOWN ->
708	    S2 = scroll_down(S),
709	    noreply(S2);
710	?WXK_PAGEUP ->
711	    S2 = scroll_prev(S),
712	    noreply(S2);
713	?WXK_PAGEDOWN ->
714	    S2 = scroll_next(S),
715	    noreply(S2);
716	$F when SD =:= true ->
717	    et_collector:multicast(S#state.collector_pid, first),
718	    noreply(S);
719	$F ->
720	    S2 = scroll_first(S),
721	    noreply(S2);
722	$P when SD =:= true ->
723	    et_collector:multicast(S#state.collector_pid, prev),
724	    noreply(S);
725	$P ->
726	    S2 = scroll_prev(S),
727	    noreply(S2);
728	$N when SD =:= true ->
729	    et_collector:multicast(S#state.collector_pid, next),
730	    noreply(S);
731	$N ->
732	    S2 = scroll_next(S),
733	    noreply(S2);
734	$L when SD =:= true ->
735	    et_collector:multicast(S#state.collector_pid, last),
736	    noreply(S);
737	$L ->
738	    S2 = scroll_last(S),
739	    noreply(S2);
740	$R when SD =:= true ->
741	    et_collector:multicast(S#state.collector_pid, refresh),
742	    noreply(S);
743	$R ->
744	    S2 = revert_main_window(S),
745	    noreply(S2);
746	$A ->
747	    S2 = display_all(S),
748	    noreply(S2);
749	$= ->
750	    Scale = S#state.scale,
751	    Actors = [A#actor.name || A <- S#state.actors],
752	    open_viewer(Scale, S#state.active_filter, Actors, S),
753	    noreply(S);
754	Int when Int =:= $+; Int =:= ?WXK_NUMPAD_ADD ->
755	    Scale = S#state.scale + 1,
756	    Actors = [A#actor.name || A <- S#state.actors],
757	    open_viewer(Scale, S#state.active_filter, Actors, S),
758	    noreply(S);
759	Int when Int =:= $-; Int =:= ?WXK_NUMPAD_SUBTRACT ->
760	    case S#state.scale of
761		1 ->
762		    ignore;
763		Scale ->
764		    Actors = [A#actor.name || A <- S#state.actors],
765		    open_viewer(Scale - 1, S#state.active_filter, Actors, S)
766	    end,
767	    noreply(S);
768	$0 ->
769	    case lists:keysearch(?DEFAULT_FILTER_NAME, #filter.name, S#state.filters) of
770		{value, F} when is_record(F, filter) ->
771		    open_viewer(S#state.scale, F#filter.name, [?unknown], S);
772		false ->
773		    ok
774	    end,
775	    noreply(S);
776	Int when is_integer(Int), Int > $0, Int =< $9 ->
777	    case catch lists:nth(Int-$0, S#state.filters) of
778		F when is_record(F, filter) ->
779		    open_viewer(S#state.scale, F#filter.name, [?unknown], S);
780		{'EXIT', _} ->
781		    ok
782	    end,
783	    noreply(S);
784
785	_ ->
786	    noreply(S)
787    end;
788handle_info(#wx{event = #wxScroll{type = scroll_changed}} = Wx, S) ->
789    _ = get_latest_scroll(Wx),
790    Pos = wxScrollBar:getThumbPosition(S#state.scroll_bar),
791    {_, LineTopY, LineBotY} = calc_y(S),
792    Range = LineBotY - LineTopY,
793    N = round(S#state.n_events * Pos / Range),
794    Diff =
795	case N - event_pos(S) of
796	    D when D < 0 -> D;
797	    D            -> D
798	end,
799    S2 = scroll_changed(S, Diff),
800    noreply(S2);
801handle_info(timeout, S) ->
802    noreply(S);
803handle_info({'EXIT', Pid, Reason}, S) ->
804    if
805	Pid =:= S#state.collector_pid ->
806	    io:format("collector died: ~tp\n\n", [Reason]),
807	    wxFrame:destroy(S#state.frame),
808	    {stop, Reason, S};
809	Pid =:= S#state.parent_pid ->
810	    wxFrame:destroy(S#state.frame),
811	    {stop, Reason, S};
812	true ->
813	    noreply(S)
814    end;
815handle_info(#wx{event = #wxClose{}}, S) ->
816    opt_unlink(S#state.parent_pid),
817    {stop, shutdown, S};
818handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot > 0 ->
819    S2 = scroll_up(S),
820    noreply(S2);
821handle_info(#wx{event = #wxMouse{type = mousewheel, wheelRotation = Rot}}, S) when Rot < 0 ->
822    S2 = scroll_down(S),
823    noreply(S2);
824handle_info(#wx{event = #wxSize{size = {OldW, OldH}}} = Wx, S) ->
825    #wx{event = #wxSize{type = size, size = {W, H}}} = get_latest_resize(Wx),
826    S2 = S#state{width = W, height = H, canvas_width = W, canvas_height = H},
827    EventsPerPage = events_per_page(S, H),
828    Diff = EventsPerPage - S#state.events_per_page,
829    S6 =
830	if
831	    OldW =:= W, OldH =:= H, S2#state.events_per_page =:= EventsPerPage ->
832		S2;
833	    Diff =:= 0 ->
834		refresh_main_window(S2);
835	    Diff > 0 ->
836		OldEvents = queue_to_list(S2#state.events),
837		{S3, NewEvents} = collect_more_events(S2, S2#state.last_event, Diff),
838		S4 = S3#state{events_per_page = EventsPerPage},
839		S5 = replace_events(S4, OldEvents ++ NewEvents),
840		refresh_main_window(S5);
841	    Diff < 0 ->
842		OldEvents = queue_to_list(S2#state.events),
843		RevEvents = delete_n(lists:reverse(OldEvents), abs(Diff)),
844		S3 = S2#state{events_per_page = EventsPerPage},
845		S4 = replace_events(S3, lists:reverse(RevEvents)),
846		refresh_main_window(S4)
847	end,
848    noreply(S6);
849handle_info(#wx{event = #wxMouse{type = enter_window}}, S) ->
850    wxWindow:setFocus(S#state.canvas), % Get keyboard focus
851    noreply(S);
852handle_info(#wx{event = #wxPaint{}}, S) ->
853    S2 = refresh_main_window(S),
854    noreply(S2);
855handle_info(#wx{event = #wxMouse{type = T, x=X,y=Y}}, S) ->
856    io:format("~tp ~tp\n", [T, {X,Y}]),
857    noreply(S);
858handle_info(Info, S) ->
859    ok = error_logger:format("~p(~p): handle_info(~tp, ~tp)~n",
860			     [?MODULE, self(), Info, S]),
861    noreply(S).
862
863%%----------------------------------------------------------------------
864%% Func: terminate/2
865%% Purpose: Shutdown the server
866%% Returns: any (ignored by gen_server)
867%%----------------------------------------------------------------------
868
869terminate(_Reason, _S) ->
870    ignore.
871
872%%----------------------------------------------------------------------
873%% Func: code_change/3
874%% Purpose: Convert process state when code is changed
875%% Returns: {ok, NewState}
876%%----------------------------------------------------------------------
877
878code_change(_OldVsn, S, _Extra) ->
879    {ok, S}.
880
881%%%----------------------------------------------------------------------
882%%% Handle stuff
883%%%----------------------------------------------------------------------
884
885reply(Reply, S) ->
886    Timeout = timeout(S),
887    {reply, Reply, S, Timeout}.
888
889noreply(S) ->
890    Timeout = timeout(S),
891    {noreply, S, Timeout}.
892
893timeout(_S) ->
894    infinity.
895
896scroll_first(S) ->
897    EventsPerPage = S#state.events_per_page,
898    {S2, NewEvents} =
899	collect_more_events(S, first, EventsPerPage),
900    S3 =
901	case NewEvents of
902	    [] ->
903		S2;
904	    [FirstE | _] ->
905		S2#state{first_event = FirstE}
906	end,
907    S4 = replace_events(S3, NewEvents),
908    refresh_main_window(S4).
909
910scroll_last(S) ->
911    case collect_more_events(S, last, -1) of
912	{_, []} ->
913	    scroll_first(S);
914	{S2, NewEvents} ->
915	    [FirstE | _] = NewEvents,
916	    S3 = replace_events(S2#state{first_event = FirstE}, NewEvents),
917	    refresh_main_window(S3)
918    end.
919
920scroll_prev(S) ->
921    scroll_up(S, S#state.events_per_page).
922
923scroll_next(S) ->
924    scroll_down(S, S#state.events_per_page).
925
926scroll_up(S) ->
927    scroll_up(S, calc_scroll(S)).
928
929scroll_up(S, Expected) ->
930    N = queue_length(S#state.events),
931    EventsPerPage = S#state.events_per_page,
932    Expected2 = adjust_expected(Expected, N, EventsPerPage),
933    OldEvents = queue_to_list(S#state.events),
934    case collect_more_events(S, S#state.first_event, -Expected2) of
935	{_, []} ->
936	    S;
937	{S2, NewEvents} ->
938	    NewN = length(NewEvents),
939	    if
940		N + NewN > EventsPerPage ->
941		    RevAllEvents = lists:reverse(OldEvents, lists:reverse(NewEvents)),
942		    TooMany = N + NewN - EventsPerPage,
943		    case delete_n(RevAllEvents, TooMany) of
944			[] ->
945				S;
946			[LastE | _] = RevEvents ->
947			    Events = lists:reverse(RevEvents),
948			    S3 = replace_events(S2#state{last_event = LastE}, Events),
949			    refresh_main_window(S3)
950		    end;
951		true ->
952		    Events = NewEvents ++ OldEvents,
953		    LastE = lists:last(Events),
954		    S3 = replace_events(S2#state{last_event = LastE}, Events),
955		    refresh_main_window(S3)
956	    end
957    end.
958
959scroll_down(S) ->
960    scroll_down(S, calc_scroll(S)).
961
962scroll_down(S, Expected) ->
963    N = queue_length(S#state.events),
964    EventsPerPage = S#state.events_per_page,
965    Expected2 = adjust_expected(Expected, N, EventsPerPage),
966    OldEvents = queue_to_list(S#state.events),
967    case collect_more_events(S, S#state.last_event, Expected2) of
968	{_, []} ->
969	    case collect_more_events(S, S#state.first_event, N - EventsPerPage) of
970		{_, []} ->
971		    S;
972		{S2, NewEvents} ->
973		    Events = NewEvents ++ OldEvents,
974		    [FirstE | _] = Events,
975		    S3 = replace_events(S2#state{first_event = FirstE}, Events),
976		    refresh_main_window(S3)
977	    end;
978	{S2, NewEvents} ->
979	    AllEvents = OldEvents ++ NewEvents,
980	    case delete_n(AllEvents, length(NewEvents)) of
981		[] ->
982		    scroll_first(S);
983		Events ->
984		    [FirstE | _] = Events,
985		    S3 = replace_events(S2#state{first_event = FirstE}, Events),
986		    refresh_main_window(S3)
987	    end
988    end.
989
990scroll_changed(S, Expected) ->
991    if
992	Expected =:= 0 ->
993	    refresh_main_window(S);
994	Expected < 0 ->
995	    %% Up
996	    OldPos = event_pos(S),
997	    NewPos = lists:max([OldPos + Expected, 0]),
998	    case S#state.first_event of
999		    #e{key = Key, pos = OldPos} ->
1000		    jump_up(S, Key, OldPos, NewPos);
1001		first ->
1002		    scroll_first(S);
1003		last ->
1004		    scroll_last(S)
1005            end;
1006	true ->
1007	    %% Down
1008	    OldPos = event_pos(S),
1009	    NewPos = lists:min([OldPos + Expected, S#state.n_events]),
1010	    case S#state.first_event of
1011		    #e{key = Key, pos = OldPos} ->
1012		    jump_down(S, Key, OldPos, NewPos);
1013		first = Key ->
1014		    jump_down(S, Key, 0, NewPos);
1015		last ->
1016		    scroll_last(S)
1017	    end
1018    end.
1019
1020jump_up(S, OldKey, OldPos, NewPos) ->
1021    Try = NewPos - OldPos -1,
1022    Order = S#state.event_order,
1023    PrevE =
1024        if NewPos =:= 0 ->
1025                first;
1026           true ->
1027                Fun = fun(Event, #e{pos = P}) when P >= NewPos ->
1028                              Key = et_collector:make_key(Order, Event),
1029                              #e{event = Event, key = Key, pos = P - 1};
1030                         (_E, Acc) ->
1031                              Acc
1032                      end,
1033                et_collector:iterate(S#state.collector_pid,
1034                                     OldKey,
1035                                     Try,
1036                                     Fun,
1037                                     #e{key = OldKey, pos = OldPos})
1038        end,
1039    case collect_more_events(S, PrevE, S#state.events_per_page) of
1040	{_, []} ->
1041	    S;
1042	{S2, Events} ->
1043	    [FirstE | _] = Events,
1044	    S3 = replace_events(S2#state{first_event = FirstE}, Events),
1045	    refresh_main_window(S3)
1046    end.
1047
1048jump_down(S, OldKey, OldPos, NewPos) ->
1049    Try = NewPos - OldPos,
1050    Order = S#state.event_order,
1051    Fun = fun(Event, #e{pos = P}) when P < NewPos ->
1052		  Key = et_collector:make_key(Order, Event),
1053		  #e{event = Event, key = Key, pos = P + 1};
1054	     (_, Acc) ->
1055		  Acc
1056	  end,
1057    PrevE = et_collector:iterate(S#state.collector_pid,
1058				 OldKey,
1059				 Try,
1060				 Fun,
1061				 #e{key = OldKey, pos = OldPos}),
1062    case collect_more_events(S, PrevE, S#state.events_per_page) of
1063	{_, []} ->
1064	    S;
1065	{S2, Events} ->
1066	    [FirstE | _] = Events,
1067	    S3 = replace_events(S2#state{first_event = FirstE}, Events),
1068	    refresh_main_window(S3)
1069    end.
1070
1071adjust_expected(Expected, N, EventsPerPage) ->
1072    if
1073	N < EventsPerPage ->
1074	    EventsPerPage - N;
1075	Expected < EventsPerPage ->
1076	    Expected;
1077	true ->
1078	    EventsPerPage
1079    end.
1080
1081calc_scroll(S) ->
1082    lists:max([S#state.events_per_page div 3, 1]).
1083
1084revert_main_window(S) ->
1085    {S2, Events} = revert(S),
1086    S3 = replace_events(S2, Events),
1087    refresh_main_window(S3).
1088
1089revert(S) ->
1090    EventsPerPage = S#state.events_per_page,
1091    %% Find previous event
1092    case collect_more_events(S, S#state.first_event, -1) of
1093	{_, []} ->
1094	    collect_more_events(S, first, EventsPerPage);
1095	{S2, [_PrevEvent]} ->
1096	    collect_more_events(S, S2#state.first_event, EventsPerPage)
1097    end.
1098
1099delete_n(List, 0) ->
1100    List;
1101delete_n([], _) ->
1102    [];
1103delete_n([_ | Tail], N) when N > 0 ->
1104    delete_n(Tail, N - 1).
1105
1106pick_n(Rest, 0, Acc) ->
1107    {lists:reverse(Acc), Rest};
1108pick_n([], _N, Acc) ->
1109    {lists:reverse(Acc), []};
1110pick_n([Head | Tail], N, Acc) when N > 0 ->
1111    pick_n(Tail, N - 1, [Head | Acc]).
1112
1113close_all(S) ->
1114    _ = close_all_others(S),
1115    wxFrame:destroy(S#state.frame),
1116    opt_unlink(S#state.parent_pid),
1117    {stop, shutdown, S}.
1118
1119close_all_others(S) ->
1120    Fun =
1121	fun({{subscriber, Pid}, _}) ->
1122		if Pid =:= self() ->
1123			ok;
1124		    true ->
1125			unlink(Pid),
1126			Pid ! {et, close},
1127                        ok
1128		end
1129	end,
1130    All = et_collector:dict_match(S#state.collector_pid,
1131				  {{subscriber, '_'}, '_'}),
1132    lists:foreach(Fun, All),
1133    noreply(S).
1134
1135opt_unlink(Pid) ->
1136    if
1137	Pid =:= undefined ->
1138	    ignore;
1139	true ->
1140	    unlink(Pid)
1141    end.
1142
1143%%%----------------------------------------------------------------------
1144%%% Clone viewer
1145%%%----------------------------------------------------------------------
1146
1147open_viewer(Scale, FilterName, Actors, S) ->
1148    Filters = [{dict_insert, {filter, F#filter.name}, F#filter.function}
1149	       || F <- S#state.filters],
1150    Options =
1151	[{parent_pid,    S#state.parent_pid},
1152	 {title,         S#state.title},
1153	 {collector_pid, S#state.collector_pid},
1154	 {detail_level,  S#state.detail_level},
1155	 {active_filter, FilterName},
1156	 {event_order,   S#state.event_order},
1157	 {first_event,   S#state.first_event},
1158	 {max_actors,    S#state.max_actors},
1159	 {hide_actions,  S#state.hide_actions},
1160	 {hide_actors,   S#state.hide_actors},
1161	 {actors,        Actors},
1162	 {scale,         Scale},
1163	 {width,         S#state.width},
1164	 {height,        S#state.height} | Filters],
1165    case start_link(Options) of
1166	{ok, _ViewerPid} ->
1167	    %% unlink(ViewerPid),
1168	    ok;
1169	{error, Reason} ->
1170	    ok = error_logger:format("~p: Failed to start a new window: ~tp~n",
1171				     [?MODULE, Reason])
1172    end.
1173
1174%%%----------------------------------------------------------------------
1175%%% Handle graphics
1176%%%----------------------------------------------------------------------
1177
1178create_main_window(S) ->
1179    {NormalFont, BoldFont} = select_fonts(S#state.scale),
1180    Name    = name_to_string(S#state.active_filter),
1181    Title   = case S#state.title of
1182		  undefined -> atom_to_list(?MODULE);
1183		  Explicit  -> name_to_string(Explicit)
1184	      end,
1185    Frame   = wxFrame:new(wx:null(),
1186			  ?wxID_ANY,
1187			  Title ++ " (filter: " ++ Name ++  ")",
1188			  [{size, {S#state.width, S#state.height}}]),
1189    StatusBar = wxFrame:createStatusBar(Frame),
1190
1191    Panel   = wxPanel:new(Frame, []),
1192    Bar     = wxMenuBar:new(),
1193    wxFrame:setMenuBar(Frame,Bar),
1194    MainSizer = wxBoxSizer:new(?wxVERTICAL),
1195
1196    MenuData = lists:flatten([create_file_menu(Bar),
1197			      create_viewer_menu(Bar),
1198			      create_collector_menu(Bar)]),
1199    FilterMenu = wxMenu:new([]),
1200    S2 = create_filter_menu(S#state{filter_menu = {FilterMenu,[]}},
1201			    S#state.active_filter,
1202			    S#state.filters),
1203    wxMenuBar:append(Bar, FilterMenu, "Filters and scaling"),
1204    create_help_menu(Bar),
1205
1206    OptSizer     = wxBoxSizer:new(?wxHORIZONTAL),
1207    CheckSizer   = wxBoxSizer:new(?wxVERTICAL),
1208    HideActions  = wxCheckBox:new(Panel, ?wxID_ANY, "Hide From=To"),
1209    wxCheckBox:setValue(HideActions, S#state.hide_actions),
1210    HideActors   = wxCheckBox:new(Panel, ?wxID_ANY, "Hide (excluded actors)"),
1211    wxCheckBox:setValue(HideActors, S#state.hide_actors),
1212    CheckBoxData = [{wxCheckBox:getId(HideActions), hide_actions},
1213		    {wxCheckBox:getId(HideActors), hide_actors}],
1214    wxPanel:connect(Panel, command_checkbox_clicked),
1215    _ = wxSizer:add(CheckSizer, HideActions),
1216    _ = wxSizer:add(CheckSizer,HideActors),
1217    _ = wxSizer:add(OptSizer, CheckSizer, [{border, 10}, {flag, ?wxALL}]),
1218    DetailLevelBox = wxStaticBoxSizer:new(?wxHORIZONTAL,
1219					  Panel,
1220					  [{label, "Detail level"}]),
1221    DetailLevel = wxSlider:new(Panel, ?wxID_ANY,
1222			       S#state.detail_level,
1223			       ?detail_level_min,
1224			       ?detail_level_max,
1225			       [{style, ?wxSL_LABELS},
1226				{size, {200,-1}}]),
1227    wxStatusBar:setStatusText(StatusBar, where_text(S)),
1228    wxFrame:connect(Frame, command_slider_updated),
1229    _ = wxSizer:add(DetailLevelBox, DetailLevel),
1230    _ = wxSizer:add(OptSizer, DetailLevelBox, [{border, 10}, {flag, ?wxALL}]),
1231    _ = wxSizer:addStretchSpacer(OptSizer),
1232    _ = wxSizer:add(MainSizer, OptSizer),
1233    _ = wxSizer:add(MainSizer, wxStaticLine:new(Panel, [{style, ?wxLI_HORIZONTAL}]),
1234                    [{flag, ?wxEXPAND}]),
1235
1236    CanvasSizer = wxBoxSizer:new(?wxHORIZONTAL),
1237    Canvas = wxPanel:new(Panel, [{style, ?wxFULL_REPAINT_ON_RESIZE}]),
1238    {CanvasW,CanvasH} = wxPanel:getSize(Canvas),
1239    ScrollBar = wxScrollBar:new(Panel, ?wxID_ANY, [{style, ?wxSB_VERTICAL}]),
1240
1241    _ = wxSizer:add(CanvasSizer, Canvas, [{flag, ?wxEXPAND}, {proportion, 1}]),
1242    _ = wxSizer:add(CanvasSizer, ScrollBar, [{flag, ?wxEXPAND}]),
1243    _ = wxSizer:add(MainSizer, CanvasSizer, [{flag, ?wxEXPAND}, {proportion, 1}]),
1244    wxPanel:connect(Canvas, left_down),
1245    wxPanel:connect(Canvas, left_up),
1246    wxPanel:connect(Canvas, right_up),
1247    wxPanel:connect(Canvas, size),
1248    Self = self(),
1249    wxPanel:connect(Canvas, paint, [{callback,  %% Needed on windows
1250				     fun(Ev, _) ->
1251					     DC = wxPaintDC:new(Canvas),
1252					     wxPaintDC:destroy(DC),
1253					     Self ! Ev
1254				     end}]),
1255    wxPanel:connect(Canvas, key_down),
1256    wxPanel:connect(Canvas, enter_window, [{skip, true}]),
1257    wxFrame:connect(Frame, command_menu_selected),
1258    wxFrame:connect(Frame, close_window),
1259    wxFrame:connect(ScrollBar, scroll_changed),
1260    wxPanel:setSize(Panel, {S#state.width, S#state.height}),
1261    wxPanel:setSizer(Panel, MainSizer),
1262    wxFrame:show(Frame),
1263    wxPanel:setFocus(Canvas),
1264    wxPanel:connect(Canvas, mousewheel),
1265
1266    S3 = S2#state{title = Title,
1267		  frame = Frame, packer = Panel,
1268		  normal_font = NormalFont, bold_font = BoldFont,
1269		  canvas_width = CanvasW, canvas_height = CanvasH,
1270		  canvas = Canvas,
1271		  canvas_sizer = CanvasSizer,
1272		  scroll_bar = ScrollBar,
1273		  y_pos = ?initial_y * S#state.scale,
1274		  pen = wxPen:new(),
1275		  brush = wxBrush:new(),
1276		  print_d = undefined,
1277		  print_psdd = undefined,
1278		  menu_data = MenuData,
1279		  checkbox_data = CheckBoxData,
1280		  hide_actions_box = HideActions,
1281		  hide_actors_box = HideActors,
1282		  status_bar = StatusBar},
1283    DC = wxClientDC:new(Canvas),
1284    S4 = draw_all_actors(S3, DC),
1285    wxClientDC:destroy(DC),
1286    S4.
1287
1288where_text(#state{n_events = N} = S) ->
1289    Pos = event_pos(S),
1290    lists:concat([Pos, " (", N, ")"]).
1291
1292event_pos(#state{first_event = E, events = Events, n_events = Last}) ->
1293    case E of
1294	#e{pos = Pos} ->
1295	    Pos;
1296	first ->
1297	    case queue_length(Events) of
1298		0 ->
1299		    0;
1300		_ ->
1301		    1
1302	    end;
1303	last ->
1304	    Last
1305    end.
1306
1307init_printers(#state{print_d = undefined, print_psdd = undefined} = S) ->
1308    PD = wxPrintData:new(),
1309    PSDD = wxPageSetupDialogData:new(PD),
1310    wxPrintData:setPaperId(PD, ?wxPAPER_A4),
1311    wxPageSetupDialogData:setMarginTopLeft(PSDD, {15,15}),
1312    wxPageSetupDialogData:setMarginBottomRight(PSDD, {15,15}),
1313    S#state{print_d = PD, print_psdd = PSDD};
1314init_printers(#state{} = S) ->
1315    S.
1316
1317select_fonts(Scale) when is_integer(Scale) ->
1318    Size =
1319	case Scale of
1320	    1 -> 5;
1321	    2 -> 10;
1322	    3 -> 14;
1323	    4 -> 20;
1324	    S -> S*6
1325	end,
1326    {wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
1327     wxFont:new(Size, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxBOLD,[])}.
1328
1329get_value(Key, Pos, TupleList) when is_list(TupleList)->
1330    case lists:keysearch(Key, 1, TupleList) of
1331	{value, Tuple} when is_tuple(Tuple)->
1332	    element(Pos, Tuple);
1333	false ->
1334	    false
1335    end.
1336
1337menuitem(Menu, Id, Text, UserData) ->
1338    Item = wxMenu:append(Menu, Id, Text),
1339    {wxMenuItem:getId(Item), Item, UserData}.
1340
1341create_file_menu(Bar) ->
1342    Menu = wxMenu:new([]),
1343    Data = [menuitem(Menu, ?wxID_ANY, "Clear all events in the Collector", clear_all),
1344	    menuitem(Menu, ?wxID_ANY, "Load events to the Collector from file", load_all),
1345	    menuitem(Menu, ?wxID_ANY, "Save all events in the Collector to file", save_all),
1346
1347	    menuitem(Menu, ?wxID_PRINT_SETUP, "Print setup", print_setup),
1348	    menuitem(Menu, ?wxID_ANY, "Print current page", print_one_page),
1349	    menuitem(Menu, ?wxID_PRINT, "Print all pages", print_all_pages),
1350
1351	    menuitem(Menu, ?wxID_ANY, "Close this Viewer", close),
1352	    menuitem(Menu, ?wxID_ANY, "Close all other Viewers, but this (c)", close_all_others),
1353	    menuitem(Menu, ?wxID_ANY, "Close all Viewers and the Collector)   (C) ", close_all)],
1354    _ = wxMenu:insertSeparator(Menu, 3),
1355    _ = wxMenu:insertSeparator(Menu, 7),
1356    wxMenuBar:append(Bar, Menu, "File"),
1357    Data.
1358
1359create_viewer_menu(Bar) ->
1360    Menu   = wxMenu:new([]),
1361    _ = wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll this Viewer"),
1362                          [{enable, false}]),
1363    _ = wxMenu:appendSeparator(Menu),
1364    D1 = [menuitem(Menu, ?wxID_ANY, "First    (f)", first),
1365	  menuitem(Menu, ?wxID_ANY, "Last     (l)", last),
1366	  menuitem(Menu, ?wxID_ANY, "Prev     (p)", prev),
1367	  menuitem(Menu, ?wxID_ANY, "Next     (n)", next),
1368	  menuitem(Menu, ?wxID_ANY, "Refresh  (r)", refresh)],
1369    _ = wxMenu:appendSeparator(Menu),
1370    D2 = [menuitem(Menu, ?wxID_ANY, "Up   5   (Up)", up),
1371	  menuitem(Menu, ?wxID_ANY, "Down 5   (Down)", down)],
1372    _ = wxMenu:appendSeparator(Menu),
1373    _ = wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Actor visibility in this Viewer"),
1374                          [{enable, false}]),
1375    _ = wxMenu:appendSeparator(Menu),
1376    D3 = [menuitem(Menu, ?wxID_ANY, "Display all actors (a)", display_all)],
1377    _ = wxMenuBar:append(Bar, Menu, "Viewer"),
1378    [D1,D2,D3].
1379
1380create_collector_menu(Bar) ->
1381    Menu = wxMenu:new([]),
1382    _ = wxMenuItem:enable(wxMenu:append(Menu, ?wxID_ANY, "Scroll all Viewers"),
1383                          [{enable, false}]),
1384    _ = wxMenu:appendSeparator(Menu),
1385    Data = [menuitem(Menu, ?wxID_ANY, "First   (F)", first_all),
1386	    menuitem(Menu, ?wxID_ANY, "Last    (L)", last_all),
1387	    menuitem(Menu, ?wxID_ANY, "Prev    (P)", prev_all),
1388	    menuitem(Menu, ?wxID_ANY, "Next    (N)", next_all),
1389	    menuitem(Menu, ?wxID_ANY, "Refresh (R)", refresh_all)],
1390    _ = wxMenuBar:append(Bar, Menu, "Collector"),
1391    Data.
1392
1393create_filter_menu(S=#state{filter_menu = {Menu,Data}}, ActiveFilterName, Filters) ->
1394     wx:foreach(fun({_,I,_}) ->
1395			wxMenu:delete(Menu,I);
1396		   (I) ->
1397			try
1398			    wxMenu:delete(Menu,I)
1399			catch
1400			    _:Reason ->
1401				io:format("Could not delete item: ~tp, because ~tp.\n", [I, Reason])
1402			end
1403		end,
1404		Data),
1405     Item = fun(F, {N, Acc}) when F#filter.name =:= all ->
1406		    Label = lists:concat([pad_string(F#filter.name, 20), "(0)"]),
1407		    {N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]};
1408	       (F, {N, Acc}) ->
1409		    Label = lists:concat([pad_string(F#filter.name, 20), "(", N, ")"]),
1410		    {N+1, [menuitem(Menu, ?wxID_ANY, Label, {data, F})|Acc]}
1411	    end,
1412     D1 = [I1 = wxMenu:append(Menu, ?wxID_ANY, "Same Filter New Scale"),
1413	   wxMenu:appendSeparator(Menu)],
1414     wxMenuItem:enable(I1, [{enable,false}]),
1415     {value, Filter} = lists:keysearch(ActiveFilterName, #filter.name, Filters),
1416     Same    = lists:concat([pad_string(ActiveFilterName, 20), "(=) same    scale"]),
1417     Larger  = lists:concat([pad_string(ActiveFilterName, 20), "(+) bigger  scale"]),
1418     Smaller = lists:concat([pad_string(ActiveFilterName, 20), "(-) smaller scale"]),
1419     D2 = [menuitem(Menu, ?wxID_ANY, Same, {data, Filter, 0}),
1420	   menuitem(Menu, ?wxID_ANY, Smaller, {data, Filter, -1}),
1421	   menuitem(Menu, ?wxID_ANY, Larger, {data, Filter, 1}),
1422	   wxMenu:appendSeparator(Menu),
1423	   I2 = wxMenu:append(Menu, ?wxID_ANY, "New Filter Same Scale"),
1424	   wxMenu:appendSeparator(Menu)],
1425     _ = wxMenuItem:enable(I2, [{enable,false}]),
1426     {_,D3} = lists:foldl(Item, {1,[]}, Filters),
1427     S#state{filter_menu = {Menu, lists:flatten([D1,D2,D3])}}.
1428
1429create_help_menu(Bar) ->
1430    Menu = wxMenu:new([]),
1431    _ = menuitem(Menu, ?wxID_HELP, "Info", help),
1432    wxMenuBar:append(Bar, Menu, "Help").
1433
1434clear_canvas(S) ->
1435    DC = wxClientDC:new(S#state.canvas),
1436    wxDC:setBackground(DC, ?wxWHITE_BRUSH), %% Needed on mac
1437    wxDC:clear(DC),
1438    {CanvasW, CanvasH} = wxPanel:getSize(S#state.canvas),
1439    wxSizer:recalcSizes(S#state.canvas_sizer),
1440    S2 = S#state{refresh_needed = false,
1441		 y_pos          = ?initial_y * S#state.scale,
1442		 canvas_width   = CanvasW,
1443		 canvas_height  = CanvasH,
1444		 events         = queue_new()},
1445    S3 = draw_all_actors(S2, DC),
1446    wxClientDC:destroy(DC),
1447    S3.
1448
1449replace_events(S, []) ->
1450    S#state{first_event = first,
1451	    last_event = first,
1452	    events = queue_new()};
1453replace_events(S, Events) ->
1454    Queue = lists:foldl(fun(E, Q) -> queue_in(E, Q) end, queue_new(), Events),
1455    S#state{events = Queue}.
1456
1457refresh_main_window(S) ->
1458    wx:batch(fun() ->
1459		     S2 = clear_canvas(S),
1460		     S3 = update_scroll_bar(S2),
1461		     display_events(S3, queue_to_list(S#state.events))
1462	     end).
1463
1464display_events(S, []) ->
1465    S;
1466display_events(S, Events) ->
1467    DC = wxClientDC:new(S#state.canvas),
1468    S2 = lists:foldl(fun(E, State) -> display_event(E, State, DC) end, S, Events),
1469    wxClientDC:destroy(DC),
1470    S2.
1471
1472collect_more_events(S, PrevKey = first, Try) ->
1473    PrevE = #e{event = undefined, key = PrevKey, pos = 0},
1474    S2 = S#state{first_event = PrevE, last_event = PrevE},
1475    do_collect_more_events(S2, Try, PrevE, []);
1476collect_more_events(S, PrevKey = last, Try) ->
1477    PrevE = #e{event = undefined, key = PrevKey, pos = S#state.n_events},
1478    S2 = S#state{first_event = PrevE, last_event = PrevE},
1479    do_collect_more_events(S2, Try, PrevE, []);
1480collect_more_events(S, #e{} = PrevE, Try) ->
1481    do_collect_more_events(S, Try, PrevE, []).
1482
1483do_collect_more_events(#state{collector_pid = Collector,
1484			      event_order = Order,
1485			      active_filter = Active,
1486			      filters = Filters} = S,
1487		       Try,
1488		       PrevE,
1489		       Acc) ->
1490    Incr =
1491	if
1492	    Try < 0 -> -1;
1493	    true    ->  1
1494	end,
1495    PrevKey = PrevE#e.key,
1496    {value, #filter{function = FilterFun}} =
1497	lists:keysearch(Active, #filter.name, Filters),
1498    {_S, _Incr, _Order, _Active, _FilterFun, LastE, NewEvents} =
1499	et_collector:iterate(Collector,
1500			     PrevKey,
1501			     Try,
1502			     fun collect_event/2,
1503			     {S, Incr, Order, Active, FilterFun, PrevE, []}),
1504    Expected = abs(Try),
1505    Actual = length(NewEvents),
1506    Missing = Expected - Actual,
1507    {S2, Acc2, Try2} =
1508	if
1509	    Try < 0 ->
1510		{S#state{first_event = LastE}, NewEvents ++ Acc, -Missing};
1511	    true ->
1512		TmpEvents = lists:reverse(NewEvents),
1513		{S#state{last_event = LastE}, Acc ++ TmpEvents, Missing}
1514	end,
1515    if
1516	Missing =/= 0, PrevKey =/= LastE#e.key ->
1517	    do_collect_more_events(S2, Try2, LastE, Acc2);
1518	true ->
1519	    {S2, Acc2}
1520    end.
1521
1522collect_event(Event, {S, Incr, Order, Active, FilterFun, #e{pos = PrevPos}, Events}) ->
1523    Key = et_collector:make_key(Order, Event),
1524    E = #e{event = Event, key = Key, pos = PrevPos + Incr},
1525    {LastE, Events2} =
1526	case catch FilterFun(Event) of
1527	    true ->
1528		case is_hidden(Event#event.from, Event#event.to, S) of
1529		    true ->
1530			{E, Events};
1531		    false ->
1532			{E, [E | Events]}
1533		end;
1534	    {true, Event2} ->
1535		Key2 = et_collector:make_key(Order, Event2),
1536		E2 = E#e{event = Event2, key = Key2},
1537		case is_hidden(Event2#event.from, Event2#event.to, S) of
1538		    true ->
1539			{E2, Events};
1540		    false ->
1541			{E2, [E2 | Events]}
1542		end;
1543	    false ->
1544		{E, Events};
1545	    Bad ->
1546		Contents = {bad_filter, S#state.active_filter, Bad, Event},
1547		Event2 = Event#event{contents = Contents,
1548				     from     = bad_filter,
1549				     to       = bad_filter},
1550		E2 = E#e{event = Event2},
1551		{E2, [E2 | Events]}
1552	end,
1553    {S, Incr, Order, Active, FilterFun, LastE, Events2}.
1554
1555display_event(#e{event = Event} = E, S, DC)
1556  when Event#event.detail_level =< S#state.detail_level ->
1557    {FromRefresh, From} = ensure_actor(Event#event.from, S, DC),
1558    {FromName, FromPos, S2} = From,
1559    {ToRefresh, To} = ensure_actor(Event#event.to, S2, DC),
1560    {ToName, ToPos, S3} = To,
1561    S4 =
1562	if
1563	    FromRefresh =/= false, ToRefresh =/= false ->
1564		S3#state{refresh_needed = true,
1565			 events = queue_in(E, S3#state.events)};
1566	    FromName =:= ToName ->
1567		case S#state.hide_actions of
1568		    true ->
1569			S3;
1570		    false ->
1571			Label = name_to_string(Event#event.label),
1572			draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC)
1573		end;
1574	    true ->
1575		Label = name_to_string(Event#event.label),
1576		draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S3, DC)
1577	end,
1578    S4;
1579display_event(#e{}, S, _DC) ->
1580    S.
1581
1582draw_named_arrow(Label, FromName, ToName, FromPos, ToPos, E, S, DC) ->
1583    case S#state.y_pos + (?incr_y *  S#state.scale) of
1584	_ when S#state.hide_actors =:= true, FromName =:= ?unknown ->
1585	    S;
1586	_ when S#state.hide_actors =:= true, ToName =:= ?unknown ->
1587	    S;
1588	Y when  Y > S#state.canvas_height ->
1589	    S#state{refresh_needed = true,
1590		    events = queue_in(E, S#state.events)};
1591	Y ->
1592	    S2 = S#state{y_pos = Y, events = queue_in(E, S#state.events)},
1593	    S3 = draw_arrow(FromPos, ToPos, S2, DC),
1594	    draw_label(Label, FromName, ToName, FromPos, ToPos, S3, DC)
1595    end.
1596
1597draw_arrow(Pos, Pos, S, _DC) ->
1598    S;
1599draw_arrow(FromPos, ToPos, S, DC) ->
1600    Y = S#state.y_pos,
1601    wxPen:setColour(S#state.pen, ?wxBLACK),
1602    wxDC:setPen(DC, S#state.pen),
1603    wxDC:drawLine(DC, {FromPos , Y}, {ToPos, Y}),
1604
1605    %% Draw arrow head
1606    Radians = calc_angle({FromPos, Y}, {ToPos, Y}),
1607    Len = 5,
1608    Radians2 = Radians + 3.665191429188092,
1609    Radians3 = Radians + 2.617993877991494,
1610    {X3, Y3} = calc_point({ToPos, Y}, Len, Radians2),
1611    {X4, Y4} = calc_point({ToPos, Y}, Len, Radians3),
1612    Points = [{round(ToPos), round(Y)},
1613	      {round(X3), round(Y3)},
1614	      {round(X4), round(Y4)}],
1615    wxBrush:setColour(S#state.brush, ?wxBLACK),
1616    wxDC:setBrush(DC, S#state.brush),
1617    wxDC:drawPolygon(DC, Points, []),
1618    S.
1619
1620 %% Calclulate angle in radians for a line between two points
1621calc_angle({X1, Y1}, {X2, Y2}) ->
1622    math:atan2((Y2 - Y1), (X2 - X1)).
1623
1624 %% Calc new point at a given distance and angle from another point
1625calc_point({X, Y}, Length, Radians) ->
1626    X2 = round(X + Length * math:cos(Radians)),
1627    Y2 = round(Y + Length * math:sin(Radians)),
1628    {X2, Y2}.
1629
1630draw_label(Label, FromName, ToName, FromPos, ToPos, S, DC) ->
1631    Color =
1632	if
1633	    FromName =:= ?unknown,
1634	    ToName   =:= ?unknown -> {2,  71, 254};% blue
1635	    FromName =:= ?unknown -> {255,126,0};  % orange
1636	    ToName   =:= ?unknown -> {255,126,0};  % orange
1637	    FromPos  =:= ToPos    -> {2,  71, 254};% blue
1638	    true                  -> {227,38, 54}  % red
1639	end,
1640    Scale = S#state.scale,
1641    X = lists:min([FromPos, ToPos]) + (6 * Scale),
1642    Y = S#state.y_pos,
1643    write_text(Label, X, Y, Color, S#state.normal_font, S, DC),
1644    S.
1645
1646draw_all_actors(S, DC) ->
1647    Scale = S#state.scale,
1648    Fun = fun(A, X) ->
1649		  case draw_actor(A, X, S, DC) of
1650		      true ->
1651			  X + (?incr_x * Scale);
1652		      false ->
1653			  X
1654		  end
1655	  end,
1656    lists:foldl(Fun, ?initial_x * Scale, S#state.actors),
1657    S.
1658
1659%% Returns: {NeedsRefreshBool, {ActorPos, NewsS, NewActors}}
1660ensure_actor(Name, S, DC) ->
1661    do_ensure_actor(Name, S, S#state.actors, 0, DC).
1662
1663do_ensure_actor(Name, S, [H | _], N, _DC) when H#actor.name =:= Name ->
1664    Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
1665    {false, {Name, Pos, S}};
1666do_ensure_actor(Name, S, [H | T], N, DC) ->
1667    if
1668    	S#state.hide_actors, H#actor.exclude ->
1669	    do_ensure_actor(Name, S, T, N, DC);
1670	true ->
1671	    do_ensure_actor(Name, S, T, N + 1, DC)
1672    end;
1673do_ensure_actor(Name, S, [], N, DC) ->
1674    %% A brand new actor, let's see if it does fit
1675    Pos = (?initial_x + (N * ?incr_x)) * S#state.scale,
1676    MaxActors = S#state.max_actors,
1677    if
1678	is_integer(MaxActors), N > MaxActors ->
1679	    %% Failed on max_actors limit, put into unknown
1680	    %% Assume that unknown always is in actor list
1681	    ensure_actor(?unknown, S, DC);
1682	Pos > (S#state.canvas_width - ((?initial_x - 15) * S#state.scale)) ->
1683	    %% New actor does not fit in canvas, refresh needed
1684	    A = create_actor(Name),
1685	    draw_actor(A, Pos, S, DC),
1686	    {true, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}};
1687	true ->
1688	    %% New actor fits in canvas. Draw the new actor.
1689	    A = create_actor(Name),
1690	    draw_actor(A, Pos, S, DC),
1691	    {false, {Name, Pos, S#state{actors = S#state.actors ++ [A]}}}
1692    end.
1693
1694draw_actor(A, LineX, S, DC) ->
1695    if
1696	S#state.hide_actors, A#actor.exclude ->
1697	    false;
1698	true ->
1699	    Scale = S#state.scale,
1700	    TextX = LineX - (5 * Scale),
1701	    {TextY, LineTopY, LineBotY} = calc_y(S),
1702	    Color =
1703		case A#actor.name of
1704		    ?unknown -> {255,126,0};% orange
1705		    _        -> {227,38,54} % red
1706		end,
1707	    {String, Font} =
1708		if
1709		    S#state.context =:= display, A#actor.exclude ->
1710			{"(" ++ A#actor.string ++ ")", S#state.normal_font};
1711		    S#state.context =:= display, A#actor.include ->
1712			{"[" ++ A#actor.string ++ "]", S#state.bold_font};
1713		    true  ->
1714			{A#actor.string, S#state.normal_font}
1715		end,
1716	    write_text(String, TextX, TextY, Color, Font, S, DC),
1717	    wxPen:setColour(S#state.pen, Color),
1718	    wxDC:setPen(DC, S#state.pen),
1719	    wxDC:drawLines(DC, [{LineX, LineTopY}, {LineX, LineBotY}]),
1720	    true
1721    end.
1722
1723calc_y(#state{canvas_height = Height, scale = Scale}) ->
1724    TextY    = ?initial_y * Scale,
1725    LineTopY = round(TextY + ((?incr_y  / 2) * Scale)),
1726    LineBotY = Height,
1727    %% LineBotY = round(Height - ((?incr_y / 2) * Scale)),
1728    {TextY, LineTopY, LineBotY}.
1729
1730display_all(S) ->
1731    Actors = S#state.actors,
1732    Actors2 = [A#actor{include = false, exclude = false} || A <- Actors],
1733    S2 = S#state{actors = Actors2,
1734		 display_all = true,
1735		 hide_actions = false,
1736		 hide_actors = false},
1737    wxCheckBox:setValue(S2#state.hide_actions_box, S2#state.hide_actions),
1738    wxCheckBox:setValue(S2#state.hide_actors_box, S2#state.hide_actors),
1739    revert_main_window(S2).
1740
1741is_hidden(A, S) ->
1742    case S#state.display_all of
1743	true ->
1744	    A#actor.exclude;
1745	false ->
1746	    A#actor.exclude orelse not A#actor.include
1747    end.
1748
1749is_hidden(From, To, S) ->
1750    Actors = S#state.actors,
1751    DisplayAll = S#state.display_all,
1752    FromMatch = lists:keysearch(From, #actor.name, Actors),
1753    ToMatch = lists:keysearch(To, #actor.name, Actors),
1754    case {FromMatch, ToMatch} of
1755	{false, false} ->
1756	    not DisplayAll;
1757	{false, {value, T}} ->
1758	    is_hidden(T, S);
1759	{{value, F}, false} ->
1760	    is_hidden(F, S);
1761	{{value, F}, {value, T}} when DisplayAll ->
1762	    is_hidden(F, S) orelse is_hidden(T, S);
1763	{{value, F}, {value, T}} when F#actor.include; T#actor.include ->
1764	    F#actor.exclude orelse T#actor.exclude;
1765	{{value, _F}, {value, _T}}->
1766	    true
1767    end.
1768
1769move_actor(From, To, Actors, S) ->
1770    Pos      = #actor.name,
1771    ToName   = To#actor.name,
1772    FromName = From#actor.name,
1773    ToIx     = actor_index(ToName, Pos, Actors),
1774    FromIx   = actor_index(FromName, Pos, Actors),
1775    if
1776	FromIx =/= 0, ToIx =/= 0, ToIx > FromIx ->
1777	    Actors2 = lists:keydelete(FromName, Pos, Actors),
1778	    Actors3 = insert_actor_after(From, To, Actors2),
1779	    S2 = S#state{actors = Actors3},
1780	    refresh_main_window(S2);
1781	FromIx =/= 0, ToIx =/= 0 ->
1782	    Actors2 = lists:keydelete(FromName, Pos, Actors),
1783	    Actors3 = insert_actor_before(From, To, Actors2),
1784	    S2 = S#state{actors = Actors3},
1785	    refresh_main_window(S2);
1786	true ->
1787	    %% Ignore
1788	    S
1789    end.
1790
1791insert_actor_after(From, To, [H | T]) ->
1792    case To#actor.name =:= H#actor.name of
1793	true  -> [H, From | T];
1794	false -> [H | insert_actor_after(From, To, T)]
1795    end;
1796insert_actor_after(_From, _To, []) ->
1797    [].
1798
1799insert_actor_before(From, To, [H | T]) ->
1800    case To#actor.name =:= H#actor.name of
1801	true  -> [From, H | T];
1802	false -> [H | insert_actor_before(From, To, T)]
1803    end;
1804insert_actor_before(_From, _To, []) ->
1805    [].
1806
1807actor_index(_Key, _Pos, []) ->
1808    0;
1809actor_index(Key, Pos, [H | T]) ->
1810    case Key =:= element(Pos, H) of
1811	false -> actor_index(Key, Pos, T) + 1;
1812	true  -> 1
1813    end.
1814
1815y_to_n(Y, S) ->
1816    Y2   = ((Y / S#state.scale) - ?initial_y + (?incr_y / 2)),
1817    N    = round(Y2 / ?incr_y - 0.2),
1818    MaxN = queue_length(S#state.events),
1819    if
1820	N =< 0   -> actor;
1821	N > MaxN -> actor;
1822	true     -> {event, N}
1823    end.
1824
1825x_to_n(X, S) ->
1826    Scale   = S#state.scale,
1827    Len = length(S#state.actors),
1828    X2 = X - (?initial_x * Scale),
1829    N  = X2 / (?incr_x * Scale),
1830    N2 = trunc(N + 1.5),
1831    if
1832	N2 > Len -> Len;
1833	N2 < 1   -> 1;
1834	true     -> N2
1835    end.
1836
1837write_text(Text, X, Y, Color, Font, S, DC) ->
1838    wxDC:setFont(DC, Font),
1839    wxDC:setTextForeground(DC, Color),
1840    wxDC:drawText(DC, Text, {X, round(Y - (?incr_y * S#state.scale / 2))-3}).
1841
1842do_open_event(S, N) ->
1843    Events = queue_to_list(S#state.events),
1844    S2 = S#state{events = list_to_queue(Events)},
1845    case catch lists:nth(N, Events) of
1846	{'EXIT', _} ->
1847	    {error, {no_such_event, N}};
1848	#e{key = Key} ->
1849	    Pid = S#state.collector_pid,
1850	    Fun = fun create_contents_window/2,
1851	    Prev = et_collector:iterate(Pid, Key, -1),
1852	    {S2, Res} =
1853		if
1854		    Prev =:= Key ->
1855			et_collector:iterate(Pid, first, 1, Fun, {S2, []});
1856		    true ->
1857			et_collector:iterate(Pid, Prev, 1, Fun, {S2, []})
1858		end,
1859	    case Res of
1860		[] ->
1861		    {error, no_contents_viewer_started};
1862		[Single] ->
1863		    Single;
1864		Multi ->
1865		    {error, {too_many, Multi}}
1866	    end
1867    end.
1868
1869create_contents_window(Event, {S, Res}) ->
1870    Options = [{viewer_pid, self()},
1871	       {event, Event},
1872	       {event_order, S#state.event_order},
1873	       {active_filter, S#state.active_filter},
1874	       {wx_debug, S#state.wx_debug}
1875	       | S#state.filters],
1876    case catch et_wx_contents_viewer:start_link(Options) of
1877	{ok, Pid} ->
1878	    {S, [{ok, Pid} | Res]};
1879	{error, Reason} ->
1880	    ok = error_logger:format("~p(~p): create_contents_window(~tp) ->~n     ~tp~n",
1881				     [?MODULE, self(), Options, Reason]),
1882	    {S, [{error, Reason} | Res]};
1883	Stuff ->
1884	    {S, [{error, {stuff, Stuff}} | Res]}
1885    end.
1886
1887print_setup(S) ->
1888    S2 = #state{print_psdd = PSDD0, print_d = PD0} = init_printers(S),
1889
1890    wxPageSetupDialogData:setPrintData(PSDD0, PD0),
1891    PSD = wxPageSetupDialog:new(S#state.frame, [{data,PSDD0}]),
1892    wxPageSetupDialog:showModal(PSD),
1893
1894    PSDD1 = wxPageSetupDialog:getPageSetupData(PSD),
1895    PD1 = wxPageSetupDialogData:getPrintData(PSDD1),
1896
1897    %% Create new objects using copy constructor
1898    PD = wxPrintData:new(PD1),
1899    PsDD = wxPageSetupDialogData:new(PSDD1),
1900    wxPageSetupDialog:destroy(PSD),
1901    wxPageSetupDialogData:destroy(PSDD0),
1902    wxPrintData:destroy(PD0),
1903    S2#state{print_psdd=PsDD, print_d=PD}.
1904
1905print(#state{print_d = undefined, print_psdd = undefined} = S, Scope) ->
1906    S2 = print_setup(S),
1907    print(S2, Scope);
1908print(#state{print_psdd = PSDD, print_d = PD} = S, Scope) ->
1909    PDD = wxPrintDialogData:new(PD),
1910    wxPrintDialogData:enablePrintToFile(PDD, true),
1911    wxPrintDialogData:enablePageNumbers(PDD, true),
1912    wxPrintDialogData:enableSelection(PDD, true),
1913    Tab = ets:new(?MODULE, [public]),
1914    GetPageInfo =
1915	fun(This) ->
1916		{_, _, PW, PH} = wxPrintout:getPaperRectPixels(This),
1917		PrinterS = S#state{context = printer,
1918				   canvas_width = PW,
1919				   canvas_height = PH},
1920		EventsPerPage = events_per_page(PrinterS, PH),
1921		PagedEvents = paged_events(PrinterS, Scope, EventsPerPage),
1922		[ets:insert(Tab, PE) || PE <- PagedEvents],
1923		ets:insert(Tab, PrinterS),
1924		NumPages = length(PagedEvents),
1925		{1, NumPages, 1, NumPages}
1926	end,
1927    HasPage =
1928	fun(_This, Page) ->
1929		Size = ets:info(Tab, size),
1930		NumPages = Size - 1,
1931		(Page >= 1) andalso (Page =< NumPages)
1932	end,
1933    OnPrintPage =
1934	fun(This, Page) ->
1935		wxPrintout:mapScreenSizeToPageMargins(This, PSDD),
1936		[PrinterS] = ets:lookup(Tab, state),
1937		Events = ets:lookup_element(Tab, Page, 2),
1938		DC = wxPrintout:getDC(This),
1939		PrinterS2 = draw_all_actors(PrinterS, DC),
1940		PrinterS3 = PrinterS2#state{y_pos = ?initial_y * PrinterS2#state.scale},
1941		lists:foldl(fun(E, State) -> display_event(E, State, DC) end,
1942			    PrinterS3,
1943			    Events),
1944		true
1945	end,
1946    Printout1 = wxPrintout:new("Print", OnPrintPage,
1947			       [{getPageInfo, GetPageInfo}, {hasPage, HasPage}]),
1948    Printout2 = wxPrintout:new("Print", OnPrintPage,
1949			       [{getPageInfo, GetPageInfo}, {hasPage, HasPage}]),
1950    Preview = wxPrintPreview:new(Printout1, [{printoutForPrinting, Printout2}, {data,PDD}]),
1951    case wxPrintPreview:isOk(Preview) of
1952	true ->
1953	    PF = wxPreviewFrame:new(Preview, S#state.frame, []),
1954	    wxPreviewFrame:centre(PF, [{dir, ?wxBOTH}]),
1955	    wxPreviewFrame:initialize(PF),
1956	    wxPreviewFrame:centre(PF),
1957	    wxPreviewFrame:show(PF),
1958	    OnClose = fun(_Wx, EventRef) -> ets:delete(Tab), wxEvent:skip(EventRef) end,
1959	    wxPreviewFrame:connect(PF, close_window, [{callback, OnClose}]);
1960	false ->
1961	    io:format("Could not create preview window.\n"
1962		      "Perhaps your current printer is not set correctly?~n", []),
1963	    wxPrintPreview:destroy(Preview),
1964	    ets:delete(Tab)
1965    end,
1966    S.
1967
1968paged_events(S, Scope, EventsPerPage) ->
1969   {_, Events} =
1970	case Scope of
1971	    print_one_page ->
1972		revert(S#state{events_per_page = EventsPerPage});
1973	    print_all_pages ->
1974		collect_more_events(S, first, S#state.n_events)
1975	end,
1976    split_list(Events, EventsPerPage).
1977
1978split_list(List, N) when is_integer(N), N > 0 ->
1979    do_split_list(List, N, 1, []).
1980
1981do_split_list([], _N, _Page, Acc) ->
1982    lists:reverse(Acc);
1983do_split_list(List, N, Page, Acc) ->
1984    {Items, Rest} = pick_n(List, N, []),
1985    do_split_list(Rest, N, Page + 1, [{Page, Items} | Acc]).
1986
1987get_latest_resize(#wx{obj = ObjRef, event = #wxSize{}} = Wx) ->
1988    receive
1989	#wx{obj = ObjRef, event = #wxSize{}} = Wx2 ->
1990	    get_latest_resize(Wx2)
1991    after 100 ->
1992	    Wx
1993    end.
1994
1995get_latest_scroll(#wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx) ->
1996    receive
1997	#wx{obj = ObjRef, event = #wxScroll{type = scroll_changed}} = Wx2 ->
1998	    get_latest_scroll(Wx2)
1999    after 100 ->
2000	    Wx
2001    end.
2002
2003update_scroll_bar(#state{scroll_bar      = ScrollBar,
2004			 status_bar      = StatusBar,
2005			 events_per_page = EventsPerPage,
2006			 n_events        = N} = S) ->
2007    Opts = [{refresh, true}],
2008    {_, LineTopY, LineBotY} = calc_y(S),
2009    Range = LineBotY - LineTopY,
2010    EventPos =
2011	case event_pos(S) of
2012	    1 -> 0;
2013	    P -> P
2014	end,
2015    if
2016	N =/= 0,
2017	EventsPerPage =/= 0 ->
2018	    PixelsPerEvent = Range / EventsPerPage,
2019	    Share = EventsPerPage / N,
2020	    wxScrollBar:setScrollbar(ScrollBar,
2021                                     trunc(EventPos * Share * PixelsPerEvent),
2022				     round(Share * Range),
2023				     Range,
2024				     round(Share * Range),
2025				     Opts);
2026	true ->
2027	    wxScrollBar:setScrollbar(ScrollBar,
2028				     0,
2029				     Range,
2030				     Range,
2031				     Range,
2032				     Opts)
2033    end,
2034    wxStatusBar:setStatusText(StatusBar, where_text(S)),
2035    S.
2036
2037events_per_page(S, PageHeight) ->
2038    EventsPerPage = ((PageHeight - (?initial_y * S#state.scale)) div (?incr_y * S#state.scale)),
2039    lists:max([1, EventsPerPage]).
2040
2041select_file(Frame, Message, DefaultFile, Style) ->
2042    Dialog = wxFileDialog:new(Frame,
2043                              [{message, Message},
2044                               {defaultDir, filename:dirname(DefaultFile)},
2045                               {defaultFile, filename:basename(DefaultFile)},
2046                               {style, Style}]),
2047    Choice =
2048        case wxMessageDialog:showModal(Dialog) of
2049            ?wxID_CANCEL ->  cancel;
2050            ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)}
2051        end,
2052    wxFileDialog:destroy(Dialog),
2053    Choice.
2054
2055%%%----------------------------------------------------------------------
2056%%% String padding of actors
2057%%%----------------------------------------------------------------------
2058
2059opt_create_actor(Name, Tag, S) ->
2060    Actors = S#state.actors,
2061    New =
2062	case lists:keysearch(Name, #actor.name, Actors) of
2063	    {value, Old} -> Old;
2064	    false        -> create_actor(Name)
2065	end,
2066    case Tag of
2067	include -> New#actor{include = true};
2068	exclude -> New#actor{exclude = true}
2069    end.
2070
2071create_actor(Name) ->
2072    String = name_to_string(Name),
2073    %% PaddedString = pad_string(String, 8),
2074    #actor{name = Name, string = String, include = false, exclude = false}.
2075
2076name_to_string(Name) ->
2077    case catch io_lib:format("~ts", [Name]) of
2078        {'EXIT', _} -> lists:flatten(io_lib:format("~tw", [Name]));
2079        GoodString  -> lists:flatten(GoodString)
2080    end.
2081
2082pad_string(Atom, MinLen) when is_atom(Atom) ->
2083    pad_string(atom_to_list(Atom), MinLen);
2084pad_string(String, MinLen) when is_integer(MinLen), MinLen >= 0 ->
2085    Len = string:length(String),
2086    case Len >= MinLen of
2087        true ->
2088            String;
2089        false ->
2090            String ++ lists:duplicate(MinLen - Len, $ )
2091    end.
2092
2093%%%----------------------------------------------------------------------
2094%%% Queue management
2095%%%----------------------------------------------------------------------
2096
2097queue_new() ->
2098    {0, [], []}.
2099
2100queue_in(X, {Size, In, Out}) ->
2101    {Size + 1, [X | In], Out}.
2102
2103%% queue_out(Q) ->
2104%%     case Q of
2105%%         {Size, In, [H | Out]} -> {{value, H}, {Size - 1, In, Out}};
2106%%         {Size, [], []}        -> {empty, {Size, [], []}};
2107%%         {Size, In, _}         -> queue_out({Size, [], lists:reverse(In)})
2108%% end.
2109
2110queue_to_list({_Size, [], Out}) ->
2111    Out;
2112queue_to_list({_Size, In, Out}) ->
2113    Out ++ lists:reverse(In).
2114
2115queue_length({Size, _In, _Out}) ->
2116    Size.
2117
2118list_to_queue(List) when is_list(List) ->
2119    {length(List), [], List}.
2120