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