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