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-module(observer_port_wx). 20 21-export([start_link/3]). 22 23%% wx_object callbacks 24-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, 25 handle_event/2, handle_sync_event/3, handle_cast/2]). 26 27-behaviour(wx_object). 28-include_lib("wx/include/wx.hrl"). 29-include("observer_defs.hrl"). 30 31-define(GRID, 300). 32-define(ID_REFRESH, 301). 33-define(ID_REFRESH_INTERVAL, 302). 34-define(ID_PORT_INFO, 303). 35-define(ID_PORT_INFO_SELECTED, 304). 36-define(ID_TRACE_PORTS, 305). 37-define(ID_TRACE_NAMES, 306). 38-define(ID_TRACE_NEW, 307). 39-define(ID_TRACE_ALL, 308). 40-define(ID_CLOSE_PORT, 309). 41 42-define(TRACE_PORTS_STR, "Trace selected ports"). 43-define(TRACE_NAMES_STR, "Trace selected ports, " 44 "if a process have a registered name " 45 "processes with same name will be traced on all nodes"). 46 47-record(port, 48 {id, 49 connected, 50 name, 51 controls, 52 slot, 53 id_str, 54 links, 55 monitors, 56 monitored_by, 57 parallelism, 58 locking, 59 queue_size, 60 memory, 61 inet}). 62 63-record(opt, {sort_key=2, 64 sort_incr=true, 65 odd_bg 66 }). 67 68-record(state, 69 { 70 parent, 71 grid, 72 panel, 73 node={node(),true}, 74 opt=#opt{}, 75 right_clicked_port, 76 ports, 77 timer, 78 open_wins=[] 79 }). 80 81start_link(Notebook, Parent, Config) -> 82 wx_object:start_link(?MODULE, [Notebook, Parent, Config], []). 83 84init([Notebook, Parent, Config]) -> 85 Panel = wxPanel:new(Notebook), 86 Sizer = wxBoxSizer:new(?wxVERTICAL), 87 Style = ?wxLC_REPORT bor ?wxLC_HRULES, 88 Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), 89 wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, 90 {proportion, 1}, {border, 5}]), 91 wxWindow:setSizer(Panel, Sizer), 92 Li = wxListItem:new(), 93 AddListEntry = fun({Name, Align, DefSize}, Col) -> 94 wxListItem:setText(Li, Name), 95 wxListItem:setAlign(Li, Align), 96 wxListCtrl:insertColumn(Grid, Col, Li), 97 wxListCtrl:setColumnWidth(Grid, Col, DefSize), 98 Col + 1 99 end, 100 Scale = observer_wx:get_scale(), 101 ListItems = [{"Id", ?wxLIST_FORMAT_LEFT, Scale*150}, 102 {"Connected", ?wxLIST_FORMAT_LEFT, Scale*150}, 103 {"Name", ?wxLIST_FORMAT_LEFT, Scale*150}, 104 {"Controls", ?wxLIST_FORMAT_LEFT, Scale*200}, 105 {"Slot", ?wxLIST_FORMAT_RIGHT, Scale*50}], 106 lists:foldl(AddListEntry, 0, ListItems), 107 wxListItem:destroy(Li), 108 109 wxListCtrl:connect(Grid, command_list_item_right_click), 110 wxListCtrl:connect(Grid, command_list_item_activated), 111 wxListCtrl:connect(Grid, command_list_col_click), 112 wxListCtrl:connect(Grid, size, [{skip, true}]), 113 114 wxWindow:setFocus(Grid), 115 Even = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOX), 116 Odd = observer_lib:mix(Even, wxSystemSettings:getColour(?wxSYS_COLOUR_HIGHLIGHT), 0.8), 117 Opt = #opt{odd_bg=Odd}, 118 {Panel, #state{grid=Grid, parent=Parent, panel=Panel, timer=Config, opt=Opt}}. 119 120handle_event(#wx{id=?ID_REFRESH}, 121 State = #state{node=Node, grid=Grid, opt=Opt}) -> 122 Ports0 = get_ports(Node), 123 Ports = update_grid(Grid, sel(State), Opt, Ports0), 124 {noreply, State#state{ports=Ports}}; 125 126handle_event(#wx{obj=Obj, event=#wxClose{}}, #state{open_wins=Opened} = State) -> 127 NewOpened = 128 case lists:keytake(Obj,2,Opened) of 129 false -> Opened; 130 {value,_,Rest} -> Rest 131 end, 132 {noreply, State#state{open_wins=NewOpened}}; 133 134handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, 135 State = #state{node=Node, grid=Grid, 136 opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> 137 Opt = case Col+2 of 138 Key -> Opt0#opt{sort_incr=not Bool}; 139 NewKey -> Opt0#opt{sort_key=NewKey} 140 end, 141 Ports0 = get_ports(Node), 142 Ports = update_grid(Grid, sel(State), Opt, Ports0), 143 wxWindow:setFocus(Grid), 144 {noreply, State#state{opt=Opt, ports=Ports}}; 145 146handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> 147 observer_lib:set_listctrl_col_size(Grid, W), 148 {noreply, State}; 149 150handle_event(#wx{event=#wxList{type=command_list_item_activated, 151 itemIndex=Index}}, 152 State=#state{grid=Grid, ports=Ports, open_wins=Opened}) -> 153 Port = lists:nth(Index+1, Ports), 154 NewOpened = display_port_info(Grid, Port, Opened), 155 {noreply, State#state{open_wins=NewOpened}}; 156 157handle_event(#wx{event=#wxList{type=command_list_item_right_click, 158 itemIndex=Index}}, 159 State=#state{panel=Panel, ports=Ports}) -> 160 case Index of 161 -1 -> 162 {noreply, State}; 163 _ -> 164 Port = lists:nth(Index+1, Ports), 165 Menu = wxMenu:new(), 166 wxMenu:append(Menu, ?ID_PORT_INFO, 167 "Port info for " ++ erlang:port_to_list(Port#port.id)), 168 wxMenu:append(Menu, ?ID_TRACE_PORTS, 169 "Trace selected ports", 170 [{help, ?TRACE_PORTS_STR}]), 171 wxMenu:append(Menu, ?ID_TRACE_NAMES, 172 "Trace selected ports by name (all nodes)", 173 [{help, ?TRACE_NAMES_STR}]), 174 wxMenu:append(Menu, ?ID_CLOSE_PORT, 175 "Close " ++ erlang:port_to_list(Port#port.id)), 176 wxWindow:popupMenu(Panel, Menu), 177 wxMenu:destroy(Menu), 178 {noreply, State#state{right_clicked_port=Port}} 179 end; 180 181handle_event(#wx{id=?ID_PORT_INFO}, 182 State = #state{grid=Grid, right_clicked_port=Port, 183 open_wins=Opened}) -> 184 case Port of 185 undefined -> 186 {noreply, State}; 187 _ -> 188 NewOpened = display_port_info(Grid, Port, Opened), 189 {noreply, State#state{right_clicked_port=undefined, 190 open_wins=NewOpened}} 191 end; 192 193handle_event(#wx{id=?ID_PORT_INFO_SELECTED}, 194 State = #state{grid=Grid, ports=Ports, open_wins=Opened}) -> 195 case get_selected_items(Grid,Ports) of 196 [] -> 197 observer_wx:create_txt_dialog(State#state.panel, "No selected ports", 198 "Port Info", ?wxICON_EXCLAMATION), 199 {noreply, State}; 200 Selected -> 201 NewOpened = lists:foldl(fun(P,O) -> display_port_info(Grid, P, O) end, 202 Opened, Selected), 203 {noreply, State#state{open_wins = NewOpened}} 204 end; 205 206handle_event(#wx{id=?ID_CLOSE_PORT}, State = #state{right_clicked_port=Port}) -> 207 case Port of 208 undefined -> 209 {noreply, State}; 210 _ -> 211 erlang:port_close(Port#port.id), 212 {noreply, State#state{right_clicked_port=undefined}} 213 end; 214 215handle_event(#wx{id=?ID_TRACE_PORTS}, #state{grid=Grid, ports=Ports}=State) -> 216 case get_selected_items(Grid, Ports) of 217 [] -> 218 observer_wx:create_txt_dialog(State#state.panel, "No selected ports", 219 "Tracer", ?wxICON_EXCLAMATION); 220 Selected -> 221 SelectedIds = [Port#port.id || Port <- Selected], 222 observer_trace_wx:add_ports(SelectedIds) 223 end, 224 {noreply, State}; 225 226handle_event(#wx{id=?ID_TRACE_NAMES}, #state{grid=Grid, ports=Ports}=State) -> 227 case get_selected_items(Grid, Ports) of 228 [] -> 229 observer_wx:create_txt_dialog(State#state.panel, "No selected ports", 230 "Tracer", ?wxICON_EXCLAMATION); 231 Selected -> 232 IdsOrRegs = 233 [case Port#port.name of 234 [] -> Port#port.id; 235 Name -> Name 236 end || Port <- Selected], 237 observer_trace_wx:add_ports(IdsOrRegs) 238 end, 239 {noreply, State}; 240 241handle_event(#wx{id=?ID_TRACE_NEW, event=#wxCommand{type=command_menu_selected}}, State) -> 242 observer_trace_wx:add_ports([new_ports]), 243 {noreply, State}; 244 245handle_event(#wx{id=?ID_REFRESH_INTERVAL}, 246 State = #state{grid=Grid, timer=Timer0}) -> 247 Timer = observer_lib:interval_dialog(Grid, Timer0, 10, 5*60), 248 {noreply, State#state{timer=Timer}}; 249 250handle_event(#wx{obj=MoreEntry,event=#wxMouse{type=left_down},userData={more,More}}, State) -> 251 observer_lib:add_scroll_entries(MoreEntry,More), 252 {noreply, State}; 253 254handle_event(#wx{event=#wxMouse{type=left_down}, userData=TargetPid}, State) -> 255 observer ! {open_link, TargetPid}, 256 {noreply, State}; 257 258handle_event(#wx{obj=Obj, event=#wxMouse{type=enter_window}}, State) -> 259 wxTextCtrl:setForegroundColour(Obj,{0,0,100,255}), 260 {noreply, State}; 261 262handle_event(#wx{obj=Obj, event=#wxMouse{type=leave_window}}, State) -> 263 wxTextCtrl:setForegroundColour(Obj,?wxBLUE), 264 {noreply, State}; 265 266handle_event(Event, _State) -> 267 error({unhandled_event, Event}). 268 269handle_sync_event(_Event, _Obj, _State) -> 270 ok. 271 272handle_call(get_config, _, #state{timer=Timer}=State) -> 273 {reply, observer_lib:timer_config(Timer), State}; 274 275handle_call(Event, From, _State) -> 276 error({unhandled_call, Event, From}). 277 278handle_cast(Event, _State) -> 279 error({unhandled_cast, Event}). 280 281handle_info({portinfo_open, PortIdStr}, 282 State = #state{node={ActiveNodeName,ActiveAvailable}, grid=Grid, 283 opt=Opt, open_wins=Opened}) -> 284 NodeName = node(list_to_port(PortIdStr)), 285 Available = 286 case NodeName of 287 ActiveNodeName -> 288 ActiveAvailable; 289 _ -> 290 portinfo_available(NodeName) 291 end, 292 if Available -> 293 Ports0 = get_ports({NodeName,Available}), 294 Port = lists:keyfind(PortIdStr, #port.id_str, Ports0), 295 NewOpened = 296 case Port of 297 false -> 298 self() ! {error,"No such port: " ++ PortIdStr}, 299 Opened; 300 _ -> 301 display_port_info(Grid, Port, Opened) 302 end, 303 Ports = 304 case NodeName of 305 ActiveNodeName -> 306 update_grid(Grid, sel(State), Opt, Ports0); 307 _ -> 308 State#state.ports 309 end, 310 {noreply, State#state{ports=Ports, open_wins=NewOpened}}; 311 true -> 312 popup_unavailable_info(NodeName), 313 {noreply, State} 314 end; 315 316handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt, 317 ports=OldPorts}) -> 318 case get_ports(Node) of 319 OldPorts -> 320 %% no change 321 {noreply, State}; 322 Ports0 -> 323 Ports = update_grid(Grid, sel(State), Opt, Ports0), 324 {noreply, State#state{ports=Ports}} 325 end; 326 327handle_info({active, NodeName}, State = #state{parent=Parent, grid=Grid, opt=Opt, 328 timer=Timer0}) -> 329 Available = portinfo_available(NodeName), 330 Available orelse popup_unavailable_info(NodeName), 331 Ports0 = get_ports({NodeName,Available}), 332 Ports = update_grid(Grid, sel(State), Opt, Ports0), 333 wxWindow:setFocus(Grid), 334 create_menus(Parent), 335 Timer = observer_lib:start_timer(Timer0, 10), 336 {noreply, State#state{node={NodeName,Available}, ports=Ports, timer=Timer}}; 337 338handle_info(not_active, State = #state{timer = Timer0}) -> 339 Timer = observer_lib:stop_timer(Timer0), 340 {noreply, State#state{timer=Timer}}; 341 342handle_info({info, {port_info_not_available,NodeName}}, 343 State = #state{panel=Panel}) -> 344 Str = io_lib:format("Can not fetch port info from ~p.~n" 345 "Too old OTP version.",[NodeName]), 346 observer_lib:display_info_dialog(Panel, Str), 347 {noreply, State}; 348 349handle_info({error, Error}, #state{panel=Panel} = State) -> 350 Str = io_lib:format("ERROR: ~ts~n",[Error]), 351 observer_lib:display_info_dialog(Panel, Str), 352 {noreply, State}; 353 354handle_info(_Event, State) -> 355 {noreply, State}. 356 357terminate(_Event, _State) -> 358 ok. 359 360code_change(_, _, State) -> 361 State. 362 363%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 364 365create_menus(Parent) -> 366 MenuEntries = 367 [{"View", 368 [#create_menu{id = ?ID_PORT_INFO_SELECTED, 369 text = "Port info for selected ports\tCtrl-I"}, 370 separator, 371 #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, 372 #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} 373 ]}, 374 {"Trace", 375 [#create_menu{id=?ID_TRACE_PORTS, text="Trace selected ports"}, 376 #create_menu{id=?ID_TRACE_NAMES, text="Trace selected ports by name (all nodes)"}, 377 #create_menu{id=?ID_TRACE_NEW, text="Trace new ports"} 378 ]} 379 ], 380 observer_wx:create_menus(Parent, MenuEntries). 381 382get_ports({_NodeName,false}) -> 383 []; 384get_ports({NodeName,true}) -> 385 case get_ports2(NodeName) of 386 Error = {error, _} -> 387 self() ! Error, 388 []; 389 Res -> 390 Res 391 end. 392get_ports2(NodeName) -> 393 case rpc:call(NodeName, observer_backend, get_port_list, []) of 394 {badrpc, Error} -> 395 {error, Error}; 396 Error = {error, _} -> 397 Error; 398 Result -> 399 [list_to_portrec(Port) || Port <- Result] 400 end. 401 402list_to_portrec(PL) -> 403 %% PortInfo: 404 %% {registered_name, RegisteredName :: atom()} | 405 %% {id, Index :: integer() >= 0} | 406 %% {connected, Pid :: pid()} | 407 %% {links, Pids :: [pid()]} | 408 %% {name, String :: string()} | 409 %% {input, Bytes :: integer() >= 0} | 410 %% {output, Bytes :: integer() >= 0} | 411 %% {os_pid, OsPid :: integer() >= 0 | undefined}, 412 PortId = proplists:get_value(port_id, PL), 413 #port{id = PortId, 414 id_str = erlang:port_to_list(PortId), 415 slot = proplists:get_value(id, PL), 416 connected = proplists:get_value(connected, PL), 417 links = proplists:get_value(links, PL, []), 418 name = proplists:get_value(registered_name, PL, []), 419 monitors = proplists:get_value(monitors, PL, []), 420 monitored_by = proplists:get_value(monitored_by, PL, []), 421 controls = proplists:get_value(name, PL), 422 parallelism = proplists:get_value(parallelism, PL), 423 locking = proplists:get_value(locking, PL), 424 queue_size = proplists:get_value(queue_size, PL, 0), 425 memory = proplists:get_value(memory, PL, 0), 426 inet = proplists:get_value(inet, PL, [])}. 427 428portrec_to_list(#port{id = Id, 429 slot = Slot, 430 connected = Connected, 431 links = Links, 432 name = Name, 433 monitors = Monitors, 434 monitored_by = MonitoredBy, 435 controls = Controls, 436 parallelism = Parallelism, 437 locking = Locking, 438 queue_size = QueueSize, 439 memory = Memory, 440 inet = Inet}) -> 441 [{id,Id}, 442 {slot,Slot}, 443 {connected,Connected}, 444 {links,Links}, 445 {name,Name}, 446 {monitors,Monitors}, 447 {monitored_by,MonitoredBy}, 448 {controls,Controls}, 449 {parallelism,Parallelism}, 450 {locking,Locking}, 451 {queue_size,QueueSize}, 452 {memory,Memory} | 453 Inet]. 454 455display_port_info(Parent, PortRec, Opened) -> 456 PortIdStr = PortRec#port.id_str, 457 case lists:keyfind(PortIdStr,1,Opened) of 458 false -> 459 Frame = do_display_port_info(Parent, PortRec), 460 [{PortIdStr,Frame}|Opened]; 461 {_,Win} -> 462 wxFrame:raise(Win), 463 Opened 464 end. 465 466do_display_port_info(Parent0, PortRec) -> 467 Parent = observer_lib:get_wx_parent(Parent0), 468 Title = "Port Info: " ++ PortRec#port.id_str, 469 Scale = observer_wx:get_scale(), 470 Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, 471 [{style, ?wxSYSTEM_MENU bor ?wxCAPTION 472 bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}, 473 {size,{Scale * 600, Scale * 400}}]), 474 ScrolledWin = wxScrolledWindow:new(Frame,[{style,?wxHSCROLL bor ?wxVSCROLL}]), 475 wxScrolledWindow:enableScrolling(ScrolledWin,true,true), 476 wxScrolledWindow:setScrollbars(ScrolledWin,20,20,0,0), 477 Sizer = wxBoxSizer:new(?wxVERTICAL), 478 wxWindow:setSizer(ScrolledWin,Sizer), 479 Port = portrec_to_list(PortRec), 480 Fields0 = port_info_fields(Port), 481 _UpFields = observer_lib:display_info(ScrolledWin, Sizer, Fields0), 482 wxFrame:center(Frame), 483 wxFrame:connect(Frame, close_window, [{skip, true}]), 484 wxFrame:show(Frame), 485 Frame. 486 487 488 489port_info_fields(Port0) -> 490 {InetStruct,Port} = inet_extra_fields(Port0), 491 Struct = 492 [{"Overview", 493 [{"Registered Name", name}, 494 {"Connected", {click,connected}}, 495 {"Slot", slot}, 496 {"Controls", controls}, 497 {"Parallelism", parallelism}, 498 {"Locking", locking}, 499 {"Queue Size", {bytes,queue_size}}, 500 {"Memory", {bytes,memory}}]}, 501 {scroll_boxes, 502 [{"Links",1,{click,links}}, 503 {"Monitors",1,{click,filter_monitor_info()}}, 504 {"Monitored by",1,{click,monitored_by}}]} | InetStruct], 505 observer_lib:fill_info(Struct, Port). 506 507inet_extra_fields(Port) -> 508 Statistics = proplists:get_value(statistics,Port,[]), 509 Options = proplists:get_value(options,Port,[]), 510 Struct = 511 case proplists:get_value(controls,Port) of 512 Inet when Inet=="tcp_inet"; Inet=="udp_inet"; Inet=="sctp_inet" -> 513 [{"Inet", 514 [{"Local Address", {inet,local_address}}, 515 {"Local Port Number", local_port}, 516 {"Remote Address", {inet,remote_address}}, 517 {"Remote Port Number", remote_port}]}, 518 {"Statistics", 519 [stat_name_and_unit(Key) || {Key,_} <- Statistics]}, 520 {"Options", 521 [{atom_to_list(Key),Key} || {Key,_} <- Options]}]; 522 _ -> 523 [] 524 end, 525 Port1 = lists:keydelete(statistics,1,Port), 526 Port2 = lists:keydelete(options,1,Port1), 527 {Struct,Port2 ++ Statistics ++ Options}. 528 529stat_name_and_unit(recv_avg) -> 530 {"Average package size received", {bytes,recv_avg}}; 531stat_name_and_unit(recv_cnt) -> 532 {"Number of packets received", recv_cnt}; 533stat_name_and_unit(recv_dvi) -> 534 {"Average packet size deviation received", {bytes,recv_dvi}}; 535stat_name_and_unit(recv_max) -> 536 {"Largest packet received", {bytes,recv_max}}; 537stat_name_and_unit(recv_oct) -> 538 {"Total received", {bytes,recv_oct}}; 539stat_name_and_unit(send_avg) -> 540 {"Average packet size sent", {bytes, send_avg}}; 541stat_name_and_unit(send_cnt) -> 542 {"Number of packets sent", send_cnt}; 543stat_name_and_unit(send_max) -> 544 {"Largest packet sent", {bytes, send_max}}; 545stat_name_and_unit(send_oct) -> 546 {"Total sent", {bytes, send_oct}}; 547stat_name_and_unit(send_pend) -> 548 {"Data waiting to be sent from driver", {bytes,send_pend}}; 549stat_name_and_unit(Key) -> 550 {atom_to_list(Key), Key}. 551 552filter_monitor_info() -> 553 fun(Data) -> 554 Ms = proplists:get_value(monitors, Data), 555 [Pid || {process, Pid} <- Ms] 556 end. 557 558update_grid(Grid, Sel, Opt, Ports) -> 559 wx:batch(fun() -> update_grid2(Grid, Sel, Opt, Ports) end). 560update_grid2(Grid, Sel, #opt{sort_key=Sort,sort_incr=Dir, odd_bg=BG}, Ports) -> 561 wxListCtrl:deleteAllItems(Grid), 562 Update = 563 fun(#port{id = Id, 564 slot = Slot, 565 connected = Connected, 566 name = Name, 567 controls = Ctrl}, 568 Row) -> 569 _Item = wxListCtrl:insertItem(Grid, Row, ""), 570 if (Row rem 2) =:= 1 -> 571 wxListCtrl:setItemBackgroundColour(Grid, Row, BG); 572 true -> ignore 573 end, 574 575 lists:foreach(fun({Col, Val}) -> 576 wxListCtrl:setItem(Grid, Row, Col, 577 observer_lib:to_str(Val)) 578 end, 579 [{0,Id},{1,Connected},{2,Name},{3,Ctrl},{4,Slot}]), 580 case lists:member(Id, Sel) of 581 true -> 582 wxListCtrl:setItemState(Grid, Row, 16#FFFF, ?wxLIST_STATE_SELECTED); 583 false -> 584 wxListCtrl:setItemState(Grid, Row, 0, ?wxLIST_STATE_SELECTED) 585 end, 586 Row + 1 587 end, 588 PortInfo = case Dir of 589 false -> lists:reverse(lists:keysort(Sort, Ports)); 590 true -> lists:keysort(Sort, Ports) 591 end, 592 lists:foldl(Update, 0, PortInfo), 593 PortInfo. 594 595sel(#state{grid=Grid, ports=Ports}) -> 596 [Id || #port{id=Id} <- get_selected_items(Grid, Ports)]. 597 598get_selected_items(Grid, Data) -> 599 get_indecies(get_selected_items(Grid, -1, []), Data). 600get_selected_items(Grid, Index, ItemAcc) -> 601 Item = wxListCtrl:getNextItem(Grid, Index, [{geometry, ?wxLIST_NEXT_ALL}, 602 {state, ?wxLIST_STATE_SELECTED}]), 603 case Item of 604 -1 -> 605 lists:reverse(ItemAcc); 606 _ -> 607 get_selected_items(Grid, Item, [Item | ItemAcc]) 608 end. 609 610get_indecies(Items, Data) -> 611 get_indecies(Items, 0, Data). 612get_indecies([I|Rest], I, [H|T]) -> 613 [H|get_indecies(Rest, I+1, T)]; 614get_indecies(Rest = [_|_], I, [_|T]) -> 615 get_indecies(Rest, I+1, T); 616get_indecies(_, _, _) -> 617 []. 618 619portinfo_available(NodeName) -> 620 _ = rpc:call(NodeName, code, ensure_loaded, [observer_backend]), 621 case rpc:call(NodeName, erlang, function_exported, 622 [observer_backend, get_port_list, 0]) of 623 true -> true; 624 false -> false 625 end. 626 627popup_unavailable_info(NodeName) -> 628 self() ! {info, {port_info_not_available, NodeName}}, 629 ok. 630