1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2011-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19 20-module(observer_traceoptions_wx). 21 22-include_lib("wx/include/wx.hrl"). 23-include("observer_defs.hrl"). 24 25-export([process_trace/2, port_trace/2, trace_pattern/4, select_nodes/2, 26 output/2, select_matchspec/4]). 27 28process_trace(Parent, Default) -> 29 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", 30 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), 31 Panel = wxPanel:new(Dialog), 32 MainSz = wxBoxSizer:new(?wxVERTICAL), 33 PanelSz = wxBoxSizer:new(?wxHORIZONTAL), 34 LeftSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Tracing options"}]), 35 RightSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Inheritance options:"}]), 36 37 FuncBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace function call", []), 38 check_box(FuncBox, lists:member(functions, Default)), 39 ArityBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace arity instead of arguments", []), 40 check_box(ArityBox, lists:member(functions, Default)), 41 SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []), 42 check_box(SendBox, lists:member(send, Default)), 43 RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []), 44 check_box(RecBox, lists:member('receive', Default)), 45 EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace process events", []), 46 check_box(EventBox, lists:member(events, Default)), 47 SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of processes", []), 48 check_box(SchedBox, lists:member(running_procs, Default)), 49 ExitBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of exiting processes", []), 50 check_box(ExitBox, lists:member(exiting, Default)), 51 GCBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace garbage collections", []), 52 check_box(GCBox, lists:member(garbage_collection, Default)), 53 54 {SpawnBox, SpwnAllRadio, SpwnFirstRadio} = 55 optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "spawn"), 56 {LinkBox, LinkAllRadio, LinkFirstRadio} = 57 optionpage_top_right(Panel, RightSz, [{flag, ?wxBOTTOM},{border, 5}], "link"), 58 SpawnBool = lists:member(on_spawn, Default) orelse lists:member(on_first_spawn, Default), 59 LinkBool = lists:member(on_link, Default) orelse lists:member(on_first_link, Default), 60 check_box(SpawnBox, SpawnBool), 61 check_box(LinkBox, LinkBool), 62 enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]), 63 enable(LinkBox, [LinkAllRadio, LinkFirstRadio]), 64 [wxRadioButton:setValue(Radio, lists:member(Opt, Default)) || 65 {Radio, Opt} <- [{SpwnAllRadio, on_spawn}, {SpwnFirstRadio, on_first_spawn}, 66 {LinkAllRadio, on_link}, {LinkFirstRadio, on_first_link}]], 67 68 [wxSizer:add(LeftSz, CheckBox, []) || CheckBox <- [FuncBox,ArityBox,SendBox,RecBox,EventBox,SchedBox,ExitBox,GCBox]], 69 wxSizer:add(LeftSz, 150, -1), 70 71 wxSizer:add(PanelSz, LeftSz, [{flag, ?wxEXPAND}, {proportion,1}]), 72 wxSizer:add(PanelSz, RightSz,[{flag, ?wxEXPAND}, {proportion,1}]), 73 wxPanel:setSizer(Panel, PanelSz), 74 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), 75 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 76 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 77 wxWindow:setSizerAndFit(Dialog, MainSz), 78 wxSizer:setSizeHints(MainSz, Dialog), 79 wxCheckBox:connect(SpawnBox, command_checkbox_clicked, 80 [{callback, fun(#wx{event=#wxCommand{}},_) -> 81 enable(SpawnBox, [SpwnAllRadio, SpwnFirstRadio]) 82 end}]), 83 wxCheckBox:connect(LinkBox, command_checkbox_clicked, 84 [{callback, fun(#wx{event=#wxCommand{}},_) -> 85 enable(LinkBox, [LinkAllRadio, LinkFirstRadio]) 86 end}]), 87 88 case wxDialog:showModal(Dialog) of 89 ?wxID_OK -> 90 All = [{SendBox, send}, {RecBox, 'receive'}, 91 {FuncBox, functions}, {ArityBox, arity}, 92 {EventBox, events}, {SchedBox, running_procs}, 93 {ExitBox, exiting}, {GCBox, garbage_collection}, 94 {{SpawnBox, SpwnAllRadio}, on_spawn}, 95 {{SpawnBox,SpwnFirstRadio}, on_first_spawn}, 96 {{LinkBox, LinkAllRadio}, on_link}, 97 {{LinkBox, LinkFirstRadio}, on_first_link}], 98 Check = fun({Box, Radio}) -> 99 wxCheckBox:getValue(Box) andalso wxRadioButton:getValue(Radio); 100 (Box) -> 101 wxCheckBox:getValue(Box) 102 end, 103 Opts = [Id || {Tick, Id} <- All, Check(Tick)], 104 wxDialog:destroy(Dialog), 105 lists:reverse(Opts); 106 ?wxID_CANCEL -> 107 wxDialog:destroy(Dialog), 108 throw(cancel) 109 end. 110 111port_trace(Parent, Default) -> 112 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Port Options", 113 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), 114 Panel = wxPanel:new(Dialog), 115 MainSz = wxBoxSizer:new(?wxVERTICAL), 116 OptsSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Tracing options"}]), 117 118 SendBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace send message", []), 119 check_box(SendBox, lists:member(send, Default)), 120 RecBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace receive message", []), 121 check_box(RecBox, lists:member('receive', Default)), 122 EventBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace port events", []), 123 check_box(EventBox, lists:member(events, Default)), 124 SchedBox = wxCheckBox:new(Panel, ?wxID_ANY, "Trace scheduling of ports", []), 125 check_box(SchedBox, lists:member(running_ports, Default)), 126 127 [wxSizer:add(OptsSz, CheckBox, []) || CheckBox <- [SendBox,RecBox,EventBox,SchedBox]], 128 wxSizer:add(OptsSz, 150, -1), 129 130 wxPanel:setSizer(Panel, OptsSz), 131 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND}, {proportion,1}]), 132 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 133 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 134 wxWindow:setSizerAndFit(Dialog, MainSz), 135 wxSizer:setSizeHints(MainSz, Dialog), 136 137 case wxDialog:showModal(Dialog) of 138 ?wxID_OK -> 139 All = [{SendBox, send}, {RecBox, 'receive'}, 140 {EventBox, events}, {SchedBox, running_ports}], 141 Opts = [Id || {Tick, Id} <- All, wxCheckBox:getValue(Tick)], 142 wxDialog:destroy(Dialog), 143 lists:reverse(Opts); 144 ?wxID_CANCEL -> 145 wxDialog:destroy(Dialog), 146 throw(cancel) 147 end. 148 149trace_pattern(ParentPid, Parent, Node, MatchSpecs) -> 150 try 151 {Module,MFAs,MatchSpec} = 152 case module_selector(Parent, Node) of 153 {'$trace_event',Event} -> 154 MS = select_matchspec(ParentPid, Parent, MatchSpecs, Event), 155 {'Events',[{'Events',Event}],MS}; 156 Mod -> 157 MFAs0 = function_selector(Parent, Node, Mod), 158 MS = select_matchspec(ParentPid, Parent, MatchSpecs, funcs), 159 {Mod,MFAs0,MS} 160 end, 161 {Module, [#tpattern{m=M,fa=FA,ms=MatchSpec} || {M,FA} <- MFAs]} 162 catch cancel -> cancel 163 end. 164 165select_nodes(Parent, Nodes) -> 166 Choices = [{atom_to_list(X), X} || X <- Nodes], 167 check_selector(Parent, Choices). 168 169module_selector(Parent, Node) -> 170 Scale = observer_wx:get_scale(), 171 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Select Module or Event", 172 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, 173 {size, {400*Scale, 400*Scale}}]), 174 Panel = wxPanel:new(Dialog), 175 PanelSz = wxBoxSizer:new(?wxVERTICAL), 176 MainSz = wxBoxSizer:new(?wxVERTICAL), 177 178 TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY), 179 ListBox = wxListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_SINGLE}]), 180 wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]), 181 wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), 182 wxPanel:setSizer(Panel, PanelSz), 183 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, 184 {border, 5}, {proportion, 1}]), 185 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 186 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, 187 {border, 5}, {proportion, 0}]), 188 wxWindow:setSizer(Dialog, MainSz), 189 OkId = wxDialog:getAffirmativeId(Dialog), 190 OkButt = wxWindow:findWindowById(OkId), 191 wxWindow:disable(OkButt), 192 wxWindow:setFocus(TxtCtrl), 193 %% init data 194 Modules = get_modules(Node), 195 Events = [{"Messages sent",{'$trace_event',send}}, 196 {"Messages received",{'$trace_event','receive'}}], 197 AllModules = Events ++ [{atom_to_list(X), X} || X <- Modules], 198 filter_listbox_data("", AllModules, ListBox), 199 wxTextCtrl:connect(TxtCtrl, command_text_updated, 200 [{callback, fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> 201 filter_listbox_data(Input, AllModules, ListBox) 202 end}]), 203 wxListBox:connect(ListBox, command_listbox_doubleclicked, 204 [{callback, fun(_, _) -> wxDialog:endModal(Dialog, ?wxID_OK) end}]), 205 wxListBox:connect(ListBox, command_listbox_selected, 206 [{callback, fun(#wx{event=#wxCommand{commandInt=Id}}, _) -> 207 Id >= 0 andalso wxWindow:enable(OkButt) 208 end}]), 209 210 case wxDialog:showModal(Dialog) of 211 ?wxID_OK -> 212 SelId = wxListBox:getSelection(ListBox), 213 case SelId >= 0 of 214 true -> 215 Module = wxListBox:getClientData(ListBox, SelId), 216 wxDialog:destroy(Dialog), 217 Module; 218 false -> 219 wxDialog:destroy(Dialog), 220 throw(cancel) 221 end; 222 ?wxID_CANCEL -> 223 wxDialog:destroy(Dialog), 224 throw(cancel) 225 end. 226 227function_selector(Parent, Node, Module) -> 228 Functions = observer_wx:try_rpc(Node, Module, module_info, [functions]), 229 Externals = observer_wx:try_rpc(Node, Module, module_info, [exports]), 230 231 Choices = lists:usort([{Name, Arity} || {Name, Arity} <- Externals ++ Functions, 232 not(erl_internal:guard_bif(Name, Arity))]), 233 ParsedChoices = parse_function_names(Choices), 234 case check_selector(Parent, ParsedChoices) of 235 [] -> [{Module, {'_', '_'}}]; 236 FAs -> 237 [{Module, {F, A}} || {F,A} <- FAs] 238 end. 239 240check_selector(Parent, ParsedChoices) -> 241 Scale = observer_wx:get_scale(), 242 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Functions", 243 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, 244 {size, {400*Scale, 400*Scale}}]), 245 246 Panel = wxPanel:new(Dialog), 247 PanelSz = wxBoxSizer:new(?wxVERTICAL), 248 MainSz = wxBoxSizer:new(?wxVERTICAL), 249 250 TxtCtrl = wxTextCtrl:new(Panel, ?wxID_ANY), 251 ListBox = wxCheckListBox:new(Panel, ?wxID_ANY, [{style, ?wxLB_EXTENDED}]), 252 wxSizer:add(PanelSz, TxtCtrl, [{flag, ?wxEXPAND}]), 253 wxSizer:add(PanelSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), 254 SelAllBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Check Visible"}]), 255 DeSelAllBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Uncheck Visible"}]), 256 ButtonSz = wxBoxSizer:new(?wxHORIZONTAL), 257 [wxSizer:add(ButtonSz, Button, []) || Button <- [SelAllBtn, DeSelAllBtn]], 258 wxSizer:add(PanelSz, ButtonSz, [{flag, ?wxEXPAND bor ?wxALL}, 259 {border, 5}, {proportion, 0}]), 260 wxPanel:setSizer(Panel, PanelSz), 261 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, 262 {border, 5}, {proportion, 1}]), 263 264 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 265 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, 266 {border, 5}, {proportion, 0}]), 267 wxWindow:setSizer(Dialog, MainSz), 268 wxWindow:setFocus(TxtCtrl), 269 %% Init 270 filter_listbox_data("", ParsedChoices, ListBox, false), 271 %% Setup Event handling 272 wxTextCtrl:connect(TxtCtrl, command_text_updated, 273 [{callback, 274 fun(#wx{event=#wxCommand{cmdString=Input}}, _) -> 275 filter_listbox_data(Input, ParsedChoices, ListBox, false) 276 end}]), 277 Self = self(), 278 279 %% Sigh clientdata in checklistbox crashes on windows, wx-bug I presume. 280 %% Don't have time to investigate now, workaround file bug report later 281 GetClientData = fun(LB, N) -> 282 String = wxListBox:getString(LB, N), 283 {_, Data} = lists:keyfind(String, 1, ParsedChoices), 284 Data 285 end, 286 wxCheckListBox:connect(ListBox, command_checklistbox_toggled, 287 [{callback, 288 fun(#wx{event=#wxCommand{commandInt=N}}, _) -> 289 Self ! {ListBox, wxCheckListBox:isChecked(ListBox, N), 290 GetClientData(ListBox, N)} 291 end}]), 292 Check = fun(Id, Bool) -> 293 wxCheckListBox:check(ListBox, Id, [{check, Bool}]), 294 Self ! {ListBox, Bool, GetClientData(ListBox, Id)} 295 end, 296 wxButton:connect(SelAllBtn, command_button_clicked, 297 [{callback, fun(#wx{}, _) -> 298 Count = wxListBox:getCount(ListBox), 299 [Check(SelId, true) || 300 SelId <- lists:seq(0, Count-1), 301 not wxCheckListBox:isChecked(ListBox, SelId)] 302 end}]), 303 wxButton:connect(DeSelAllBtn, command_button_clicked, 304 [{callback, fun(#wx{}, _) -> 305 Count = wxListBox:getCount(ListBox), 306 [Check(SelId, false) || 307 SelId <- lists:seq(0, Count-1), 308 wxCheckListBox:isChecked(ListBox, SelId)] 309 end}]), 310 case wxDialog:showModal(Dialog) of 311 ?wxID_OK -> 312 wxDialog:destroy(Dialog), 313 get_checked(ListBox, []); 314 ?wxID_CANCEL -> 315 wxDialog:destroy(Dialog), 316 get_checked(ListBox, []), 317 throw(cancel) 318 end. 319 320get_checked(ListBox, Acc) -> 321 receive 322 {ListBox, true, FA} -> 323 get_checked(ListBox, [FA|lists:delete(FA,Acc)]); 324 {ListBox, false, FA} -> 325 get_checked(ListBox, lists:delete(FA,Acc)) 326 after 0 -> 327 lists:reverse(Acc) 328 end. 329 330select_matchspec(Pid, Parent, AllMatchSpecs, Key) -> 331 {MatchSpecs,RestMS} = 332 case lists:keytake(Key,1,AllMatchSpecs) of 333 {value,{Key,MSs0},Rest} -> {MSs0,Rest}; 334 false -> {[],AllMatchSpecs} 335 end, 336 Scale = observer_wx:get_scale(), 337 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Trace Match Specifications", 338 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}, 339 {size, {400*Scale, 400*Scale}}]), 340 341 Panel = wxPanel:new(Dialog), 342 PanelSz = wxBoxSizer:new(?wxVERTICAL), 343 MainSz = wxBoxSizer:new(?wxVERTICAL), 344 TxtSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Match specification:"}]), 345 BtnSz = wxBoxSizer:new(?wxHORIZONTAL), 346 SavedSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Saved match specifications:"}]), 347 348 TextCtrl = create_styled_txtctrl(Panel), 349 wxSizer:add(TxtSz, TextCtrl, [{flag, ?wxEXPAND}, {proportion, 1}]), 350 351 AddMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "New"}]), 352 EditMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Edit"}]), 353 DelMsBtn = wxButton:new(Panel, ?wxID_ANY, [{label, "Delete"}]), 354 wxSizer:add(BtnSz, AddMsBtn), 355 wxSizer:add(BtnSz, EditMsBtn), 356 wxSizer:add(BtnSz, DelMsBtn), 357 358 ListBox = wxListBox:new(Panel, ?wxID_ANY, []), 359 wxSizer:add(SavedSz, ListBox, [{flag, ?wxEXPAND}, {proportion, 1}]), 360 wxSizer:add(PanelSz, TxtSz, [{flag, ?wxEXPAND}, {proportion, 1}]), 361 wxSizer:add(PanelSz, BtnSz), 362 wxSizer:add(PanelSz, SavedSz, [{flag, ?wxEXPAND}, {proportion, 1}]), 363 364 wxWindow:setSizer(Panel, PanelSz), 365 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, 366 {border, 5}, {proportion, 1}]), 367 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 368 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, 369 {border, 5}, {proportion, 0}]), 370 wxWindow:setSizer(Dialog, MainSz), 371 OkId = wxDialog:getAffirmativeId(Dialog), 372 OkButt = wxWindow:findWindowById(OkId), 373 wxWindow:disable(OkButt), 374 wxWindow:disable(EditMsBtn), 375 wxWindow:disable(DelMsBtn), 376 377 Choices = ms_names(MatchSpecs), 378 filter_listbox_data("", Choices, ListBox), 379 380 Add = fun(_,_) -> 381 case edit_ms(TextCtrl, new, Dialog) of 382 Ms = #match_spec{} -> 383 add_and_select(-1, Ms, ListBox), 384 wxWindow:enable(OkButt), 385 wxWindow:enable(EditMsBtn), 386 wxWindow:enable(DelMsBtn); 387 Else -> Else 388 end 389 end, 390 Edit = fun(_,_) -> 391 SelId = wxListBox:getSelection(ListBox), 392 case SelId >= 0 of 393 true -> 394 #match_spec{name=Name} = wxListBox:getClientData(ListBox,SelId), 395 case edit_ms(TextCtrl, Name, Dialog) of 396 Ms = #match_spec{} -> 397 add_and_select(SelId, Ms, ListBox), 398 wxWindow:enable(OkButt), 399 wxWindow:enable(EditMsBtn), 400 wxWindow:enable(DelMsBtn); 401 Else -> Else 402 end; 403 false -> 404 ok 405 end 406 end, 407 Del = fun(_,_) -> 408 SelId = wxListBox:getSelection(ListBox), 409 case SelId >= 0 of 410 true -> 411 wxListBox:delete(ListBox, SelId); 412 false -> 413 ok 414 end 415 end, 416 Sel = fun(#wx{event=#wxCommand{commandInt=Id}}, _) -> 417 case Id >= 0 of 418 true -> 419 wxWindow:enable(OkButt), 420 wxWindow:enable(EditMsBtn), 421 wxWindow:enable(DelMsBtn), 422 #match_spec{func=Str} = wxListBox:getClientData(ListBox,Id), 423 wxStyledTextCtrl:setText(TextCtrl, Str); 424 false -> 425 try 426 wxWindow:disable(OkButt), 427 wxWindow:disable(EditMsBtn), 428 wxWindow:disable(DelMsBtn) 429 catch _:_ -> ok 430 end 431 end 432 end, 433 wxButton:connect(AddMsBtn, command_button_clicked, [{callback,Add}]), 434 wxButton:connect(EditMsBtn, command_button_clicked, [{callback,Edit}]), 435 wxButton:connect(DelMsBtn, command_button_clicked, [{callback,Del}]), 436 wxListBox:connect(ListBox, command_listbox_selected, [{callback, Sel}]), 437 case wxDialog:showModal(Dialog) of 438 ?wxID_OK -> 439 SelId = wxListBox:getSelection(ListBox), 440 Count = wxListBox:getCount(ListBox), 441 MSs = [wxListBox:getClientData(ListBox, Id) || 442 Id <- lists:seq(0, Count-1)], 443 Pid ! {update_ms, [{Key,MSs}|RestMS]}, 444 MS = lists:nth(SelId+1, MSs), 445 wxDialog:destroy(Dialog), 446 MS; 447 ?wxID_CANCEL -> 448 wxDialog:destroy(Dialog), 449 throw(cancel) 450 end. 451 452output(Parent, Default) -> 453 Dialog = wxDialog:new(Parent, ?wxID_ANY, "Process Options", 454 [{style, ?wxDEFAULT_DIALOG_STYLE bor ?wxRESIZE_BORDER}]), 455 Panel = wxPanel:new(Dialog), 456 MainSz = wxBoxSizer:new(?wxVERTICAL), 457 PanelSz = wxStaticBoxSizer:new(?wxVERTICAL, Panel, [{label, "Output"}]), 458 459 %% Output select 460 WinB = wxCheckBox:new(Panel, ?wxID_ANY, "Window", []), 461 check_box(WinB, proplists:get_value(window, Default, true)), 462 ShellB = wxCheckBox:new(Panel, ?wxID_ANY, "Shell", []), 463 check_box(ShellB, proplists:get_value(shell, Default, false)), 464 [wxSizer:add(PanelSz, CheckBox, []) || CheckBox <- [WinB, ShellB]], 465 GetFileOpts = ttb_file_options(Panel, PanelSz, Default), 466 %% Set sizer and show dialog 467 wxPanel:setSizer(Panel, PanelSz), 468 wxSizer:add(MainSz, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {proportion,1}, {border, 3}]), 469 Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), 470 wxSizer:add(MainSz, Buttons, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), 471 wxWindow:setSizerAndFit(Dialog, MainSz), 472 wxSizer:setSizeHints(MainSz, Dialog), 473 case wxDialog:showModal(Dialog) of 474 ?wxID_OK -> 475 Res = [{window, wxCheckBox:getValue(WinB)}, 476 {shell, wxCheckBox:getValue(ShellB)} | GetFileOpts()], 477 wxDialog:destroy(Dialog), 478 Res; 479 ?wxID_CANCEL -> 480 wxDialog:destroy(Dialog), 481 throw(cancel) 482 end. 483 484edit_ms(TextCtrl, Label0, Parent) -> 485 Str = ensure_last_is_dot(wxStyledTextCtrl:getText(TextCtrl)), 486 try 487 MatchSpec = ms_from_string(Str), 488 Label = case Label0 == new of 489 true -> get_label(Parent); 490 _ -> Label0 491 end, 492 #match_spec{name=Label, term=MatchSpec, 493 str=io_lib:format("~tw",[MatchSpec]), 494 func=Str} 495 catch 496 throw:cancel -> 497 ok; 498 throw:Error -> 499 observer_wx:create_txt_dialog(Parent, Error, "Error", ?wxICON_ERROR), 500 ok 501 end. 502 503get_label(Frame) -> 504 Dialog = wxTextEntryDialog:new(Frame, "Enter alias: "), 505 case wxDialog:showModal(Dialog) of 506 ?wxID_OK -> 507 wxTextEntryDialog:getValue(Dialog); 508 ?wxID_CANCEL -> 509 throw(cancel) 510 end. 511 512ms_from_string(Str) -> 513 try 514 Tokens = case erl_scan:string(Str) of 515 {ok, Ts, _} -> Ts; 516 {error, {SLine, SMod, SError}, _} -> 517 throw(io_lib:format("~w: ~ts", [SLine,SMod:format_error(SError)])) 518 end, 519 Exprs = case erl_parse:parse_exprs(Tokens) of 520 {ok, T} -> T; 521 {error, {PLine, PMod, PError}} -> 522 throw(io_lib:format("~w: ~ts", [PLine,PMod:format_error(PError)])) 523 end, 524 Term = case Exprs of 525 [{'fun', _, {clauses, Clauses}}|_] -> 526 case ms_transform:transform_from_shell(dbg,Clauses,orddict:new()) of 527 {error, [{_,[{MSLine,Mod,MSInfo}]}],_} -> 528 throw(io_lib:format("~w: ~tp", [MSLine,Mod:format_error(MSInfo)])); 529 {error, _} -> 530 throw("Could not convert fun() to match spec"); 531 Ms -> 532 Ms 533 end; 534 [Expr|_] -> 535 erl_parse:normalise(Expr) 536 end, 537 case erlang:match_spec_test([], Term, trace) of 538 {ok, _, _, _} -> Term; 539 {error, List} -> throw([[Error, $\n] || {_, Error} <- List]) 540 end 541 catch error:_Reason -> 542 %% io:format("Bad term: ~ts~n ~tp in ~tp~n", [Str, _Reason, Stacktrace]), 543 throw("Invalid term") 544 end. 545 546add_and_select(Id, MS0, ListBox) -> 547 [{Str,User}] = ms_names([MS0]), 548 Sel = case Id >= 0 of 549 true -> 550 wxListBox:setString(ListBox, Id, Str), 551 wxListBox:setClientData(ListBox, Id, User), 552 Id; 553 false -> 554 wxListBox:append(ListBox, Str, User) 555 end, 556 wxListBox:setSelection(ListBox, Sel). 557 558filter_listbox_data(Input, Data, ListBox) -> 559 filter_listbox_data(Input, Data, ListBox, true). 560 561filter_listbox_data(Input, Data, ListBox, AddClientData) -> 562 FilteredData = [X || X = {Str, _} <- Data, 563 re:run(Str, Input, [unicode]) =/= nomatch], 564 wxListBox:clear(ListBox), 565 wxListBox:appendStrings(ListBox, [Str || {Str,_} <- FilteredData]), 566 AddClientData andalso 567 wx:foldl(fun({_, Term}, N) -> 568 wxListBox:setClientData(ListBox, N, Term), 569 N+1 570 end, 0, FilteredData), 571 FilteredData. 572 573get_modules(Node) -> 574 lists:sort([Module || {Module, _} <- observer_wx:try_rpc(Node, code, all_loaded, [])]). 575 576optionpage_top_right(Panel, TopRightSz, Options, Text) -> 577 Sizer = wxBoxSizer:new(?wxVERTICAL), 578 ChkBox = wxCheckBox:new(Panel, ?wxID_ANY, "Inherit on " ++ Text, []), 579 RadioSz = wxBoxSizer:new(?wxVERTICAL), 580 Radio1 = wxRadioButton:new(Panel, ?wxID_ANY, "All " ++ Text, [{style, ?wxRB_GROUP}]), 581 Radio2 = wxRadioButton:new(Panel, ?wxID_ANY, "First " ++ Text ++ " only", []), 582 wxSizer:add(Sizer, ChkBox, []), 583 wxSizer:add(RadioSz, Radio1, []), 584 wxSizer:add(RadioSz, Radio2, []), 585 wxSizer:add(Sizer, RadioSz, [{flag, ?wxLEFT},{border, 20}]), 586 wxSizer:add(TopRightSz, Sizer, Options), 587 {ChkBox, Radio1, Radio2}. 588 589 590create_styled_txtctrl(Parent) -> 591 FixedFont = observer_wx:get_attrib({font, fixed}), 592 Ed = wxStyledTextCtrl:new(Parent), 593 wxStyledTextCtrl:styleClearAll(Ed), 594 wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont), 595 wxStyledTextCtrl:setLexer(Ed, ?wxSTC_LEX_ERLANG), 596 wxStyledTextCtrl:setMarginType(Ed, 1, ?wxSTC_MARGIN_NUMBER), 597 wxStyledTextCtrl:setSelectionMode(Ed, ?wxSTC_SEL_LINES), 598 wxStyledTextCtrl:setUseHorizontalScrollBar(Ed, false), 599 600 Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}}, 601 {?wxSTC_ERLANG_COMMENT, {160,53,35}}, 602 {?wxSTC_ERLANG_VARIABLE, {150,100,40}}, 603 {?wxSTC_ERLANG_NUMBER, {5,5,100}}, 604 {?wxSTC_ERLANG_KEYWORD, {130,40,172}}, 605 {?wxSTC_ERLANG_STRING, {170,45,132}}, 606 {?wxSTC_ERLANG_OPERATOR, {30,0,0}}, 607 {?wxSTC_ERLANG_ATOM, {0,0,0}}, 608 {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}}, 609 {?wxSTC_ERLANG_CHARACTER,{236,155,172}}, 610 {?wxSTC_ERLANG_MACRO, {40,144,170}}, 611 {?wxSTC_ERLANG_RECORD, {40,100,20}}, 612 {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}], 613 SetStyle = fun({Style, Color}) -> 614 wxStyledTextCtrl:styleSetFont(Ed, Style, FixedFont), 615 wxStyledTextCtrl:styleSetForeground(Ed, Style, Color) 616 end, 617 [SetStyle(Style) || Style <- Styles], 618 wxStyledTextCtrl:setKeyWords(Ed, 0, keyWords()), 619 Ed. 620 621 622keyWords() -> 623 L = ["after","begin","case","try","cond","catch","andalso","orelse", 624 "end","fun","if","let","of","receive","when","bnot","not", 625 "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"], 626 lists:flatten([K ++ " " || K <- L] ++ [0]). 627 628 629enable(CheckBox, Radio) -> 630 case wxCheckBox:isChecked(CheckBox) of 631 false -> 632 [wxWindow:disable(R) || R <- Radio]; 633 true -> 634 [wxWindow:enable(R) || R <- Radio] 635 end. 636 637 638check_box(ChkBox, Bool) -> 639 case Bool of 640 true -> 641 wxCheckBox:set3StateValue(ChkBox, ?wxCHK_CHECKED); 642 false -> 643 ignore 644 end. 645 646parse_function_names(Choices) -> 647 StrList = [{atom_to_list(Name) ++ "/" ++ integer_to_list(Arity), Term} 648 || Term = {Name, Arity} <- Choices], 649 parse_function_names(StrList, []). 650 651parse_function_names([], Acc) -> 652 lists:reverse(Acc); 653parse_function_names([{H, Term}|T], Acc) -> 654 IsFun = re:run(H, ".*-fun-\\d*?-", [unicode,ucp]), 655 IsLc = re:run(H, ".*-lc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]), 656 IsLbc = re:run(H, ".*-lbc\\$\\^\\d*?/\\d*?-\\d*?-", [unicode,ucp]), 657 Parsed = 658 if IsFun =/= nomatch -> "Fun: " ++ H; 659 IsLc =/= nomatch -> "List comprehension: " ++ H; 660 IsLbc =/= nomatch -> "Bit comprehension: " ++ H; 661 true -> 662 H 663 end, 664 parse_function_names(T, [{Parsed, Term} | Acc]). 665 666ms_names(MatchSpecList) -> 667 MsOrAlias = fun(#match_spec{name = A, str = M}) -> 668 case A of 669 "" -> M; 670 _ -> A ++ " " ++ M 671 end 672 end, 673 [{MsOrAlias(X), X} || X <- MatchSpecList]. 674 675ensure_last_is_dot([]) -> 676 "."; 677ensure_last_is_dot(String) -> 678 case lists:last(String) =:= $. of 679 true -> 680 String; 681 false -> 682 String ++ "." 683 end. 684 685ttb_file_options(Panel, Sizer, Default) -> 686 Top = wxBoxSizer:new(?wxVERTICAL), 687 NameS = wxBoxSizer:new(?wxHORIZONTAL), 688 FileBox = wxCheckBox:new(Panel, ?wxID_ANY, "File (Using ttb file tracer)", []), 689 check_box(FileBox, proplists:get_value(file, Default, false)), 690 wxSizer:add(Sizer, FileBox), 691 Desc = wxStaticText:new(Panel, ?wxID_ANY, "File"), 692 FileName = proplists:get_value(filename, Default, "ttb"), 693 FileT = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {150,-1}}, {value, FileName}]), 694 FileB = wxButton:new(Panel, ?wxID_ANY, [{label, "Browse"}]), 695 wxSizer:add(NameS, Desc, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), 696 wxSizer:add(NameS, FileT, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALIGN_CENTER_VERTICAL}]), 697 wxSizer:add(NameS, FileB, [{proportion, 0}, {flag, ?wxALIGN_CENTER_VERTICAL}]), 698 699 WrapB = wxCheckBox:new(Panel, ?wxID_ANY, "Wrap logs"), 700 WrapSz = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_sz, Default, 128), 701 64, 10*1024, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), 702 WrapC = wxSlider:new(Panel, ?wxID_ANY, proplists:get_value(wrap_c, Default, 8), 703 2, 100, [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}]), 704 705 wxSizer:add(Top, NameS, [{flag, ?wxEXPAND}]), 706 wxSizer:add(Top, WrapB, []), 707 wxSizer:add(Top, WrapSz,[{flag, ?wxEXPAND}]), 708 wxSizer:add(Top, WrapC, [{flag, ?wxEXPAND}]), 709 wxSizer:add(Sizer, Top, [{flag, ?wxEXPAND bor ?wxLEFT},{border, 20}]), 710 711 Enable = fun(UseFile, UseWrap0) -> 712 UseWrap = UseFile andalso UseWrap0, 713 [wxWindow:enable(W, [{enable, UseFile}]) || W <- [Desc,FileT,FileB,WrapB]], 714 [wxWindow:enable(W, [{enable, UseWrap}]) || W <- [WrapSz, WrapC]], 715 check_box(WrapB, UseWrap0) 716 end, 717 Enable(proplists:get_value(file, Default, false), proplists:get_value(wrap, Default, false)), 718 wxPanel:connect(FileBox, command_checkbox_clicked, 719 [{callback, fun(_,_) -> 720 Enable(wxCheckBox:getValue(FileBox), 721 wxCheckBox:getValue(WrapB)) 722 end}]), 723 wxPanel:connect(WrapB, command_checkbox_clicked, 724 [{callback, fun(_,_) -> 725 Enable(true, wxCheckBox:getValue(WrapB)) 726 end}]), 727 wxPanel:connect(FileB, command_button_clicked, 728 [{callback, fun(_,_) -> get_file(FileT) end}]), 729 fun() -> 730 [{file, wxCheckBox:getValue(FileBox)}, 731 {filename, wxTextCtrl:getValue(FileT)}, 732 {wrap, wxCheckBox:getValue(WrapB)}, 733 {wrap_sz, wxSlider:getValue(WrapSz)}, 734 {wrap_c, wxSlider:getValue(WrapC)}] 735 end. 736 737get_file(Text) -> 738 Str = wxTextCtrl:getValue(Text), 739 Dialog = wxFileDialog:new(Text, 740 [{message, "Select a file"}, 741 {defaultFile, Str}]), 742 case wxDialog:showModal(Dialog) of 743 ?wxID_OK -> 744 Dir = wxFileDialog:getDirectory(Dialog), 745 File = wxFileDialog:getFilename(Dialog), 746 wxTextCtrl:setValue(Text, filename:join(Dir, File)); 747 _ -> ok 748 end, 749 wxFileDialog:destroy(Dialog). 750