From e2d565532d25024c1c0552d8eaaddf90eed88629 Mon Sep 17 00:00:00 2001 From: Siri Hansen Date: Thu, 11 Jul 2013 11:27:29 +0200 Subject: observer: add wx version of crashdump_viewer The old web base crashdump_viewer is now removed. --- lib/observer/src/cdv_virtual_list.erl | 425 ++++++++++++++++++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 lib/observer/src/cdv_virtual_list.erl (limited to 'lib/observer/src/cdv_virtual_list.erl') diff --git a/lib/observer/src/cdv_virtual_list.erl b/lib/observer/src/cdv_virtual_list.erl new file mode 100644 index 0000000000..03feadad45 --- /dev/null +++ b/lib/observer/src/cdv_virtual_list.erl @@ -0,0 +1,425 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +-module(cdv_virtual_list). + +-behaviour(wx_object). + +-export([start_link/2, start_link/3, start_detail_win/1]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_cast/2]). + +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). + +%% Defines +-define(COL_ID, 0). +-define(ID_DETAILS, 202). + +%% Records + +-record(sort, + { + sort_key, + sort_incr=true + }). + +-record(holder, {parent, + info, + last_row, + sort, + attrs, + callback + }). + +-record(state, {grid, + panel, + detail_wins=[], + holder, + callback, + trunc_warn=[], + menu_cols=[], % columns to show in right click menu + menu_items=[]}). % right click menu items for the selected row + +start_link(ParentWin, Callback) -> + wx_object:start_link({local,Callback},?MODULE, + [ParentWin, Callback, all], []). + +start_link(ParentWin, Callback, Owner) -> + wx_object:start_link(?MODULE, [ParentWin, Callback, Owner], []). + +start_detail_win(Id) -> + Callback = + case Id of + "<"++_ -> + cdv_proc_wx; + "#Port"++_ -> + cdv_port_wx; + _ -> + case catch list_to_integer(Id) of + NodeId when is_integer(NodeId) -> + cdv_dist_wx; + _ -> + cdv_mod_wx + end + end, + start_detail_win(Callback,Id). +start_detail_win(Callback,Id) -> + wx_object:cast(Callback,{start_detail_win,Id}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +init([ParentWin, Callback, Owner]) -> + {Holder,TW} = spawn_table_holder(Callback, Owner), + Panel = wxPanel:new(ParentWin), + {Grid,MenuCols} = create_list_box(Panel, Holder, Callback, Owner), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, + {border,4}]), + + wxWindow:setSizer(Panel, Sizer), + + State = #state{grid=Grid, + panel=Panel, + holder=Holder, + callback=Callback, + trunc_warn=TW, + menu_cols=MenuCols + }, + {Panel, State}. + +%% UI-creation + +create_list_box(Panel, Holder, Callback, Owner) -> + Style = + ?wxLC_SINGLE_SEL bor ?wxLC_REPORT bor ?wxLC_VIRTUAL bor + ?wxLC_HRULES bor ?wxHSCROLL bor ?wxVSCROLL, + ListCtrl = wxListCtrl:new(Panel, [{style, Style}, + {onGetItemText, + fun(_, Row, Col) -> + call(Holder, {get_row, self(), Row, Col}) + end}, + {onGetItemAttr, + fun(_, Item) -> + call(Holder, {get_attr, self(), Item}) + end} + ]), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(ListCtrl, Col, Li), + wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize), + Col + 1 + end, + ListItems = Callback:col_spec(), + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + wxListCtrl:setItemCount(ListCtrl, 0), + wxListCtrl:connect(ListCtrl, size, [{skip, true}]), + wxListCtrl:connect(ListCtrl, command_list_col_click), + + + %% If detail pages can be opened from this list - catch double + %% click and right click + DetailCols = + case catch Callback:get_detail_cols(Owner) of + {DC,DoubleClick} when is_list(DC), DC=/=[] -> + wxListCtrl:connect(ListCtrl, command_list_item_right_click), + if DoubleClick -> + wxListCtrl:connect(ListCtrl, command_list_item_activated); + true -> + ok + end, + DC; + _ -> + [] + end, + + {ListCtrl,DetailCols}. + +do_start_detail_win(undefined, State) -> + State; +do_start_detail_win(Id, #state{panel=Panel,detail_wins=Opened, + callback=Callback}=State) -> + NewOpened = + case lists:keyfind(Id, 1, Opened) of + false -> + case cdv_detail_win:start(Id, Panel, Callback) of + {error, _} -> + Opened; + IW -> + [{Id, IW} | Opened] + end; + {_, IW} -> + wxFrame:raise(IW), + Opened + end, + State#state{detail_wins=NewOpened}. + +call(Holder, What) when is_atom(Holder) -> + call(whereis(Holder), What); +call(Holder, What) when is_pid(Holder) -> + Ref = erlang:monitor(process, Holder), + Holder ! What, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Holder, Res} -> + erlang:demonitor(Ref), + Res + after 5000 -> + io:format("Hanging call ~p~n",[What]), + "" + end; +call(_,_) -> + "". + + +%%%%%%%%%%%%%%%%%%%%%%% Callbacks %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_info({holder_updated, Count}, State=#state{grid=Grid}) -> + wxListCtrl:setItemCount(Grid, Count), + Count > 0 andalso wxListCtrl:refreshItems(Grid, 0, Count-1), + + {noreply, State}; + +handle_info({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> + Opened2 = lists:keydelete(Id, 1, Opened), + {noreply, State#state{detail_wins=Opened2}}; + +handle_info(active, State) -> + crashdump_viewer_wx:set_status(State#state.trunc_warn), + {noreply, State}; + +handle_info(not_active, State) -> + {noreply, State}; + +handle_info(new_dump, + #state{grid=Grid,detail_wins=Opened, + holder=Holder,callback=Callback}=State) -> + lists:foreach(fun({_Id, IW}) -> wxFrame:destroy(IW) end, Opened), + wxListCtrl:deleteAllItems(Grid), + Ref = erlang:monitor(process,Holder), + Holder ! stop, + receive {'DOWN',Ref,_,_,_} -> ok end, + {NewHolder,TW} = spawn_table_holder(Callback, all), + {noreply, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; + +handle_info(Info, State) -> + io:format("~p:~p, Unexpected info: ~p~n", [?MODULE, ?LINE, Info]), + {noreply, State}. + +terminate(_Reason, #state{holder=Holder}) -> + Holder ! stop, + ok. + +code_change(_, _, State) -> + {ok, State}. + +handle_call(Msg, _From, State) -> + io:format("~p:~p: Unhandled call ~p~n",[?MODULE, ?LINE, Msg]), + {reply, ok, State}. + +handle_cast({start_detail_win,Id}, State) -> + State2 = do_start_detail_win(Id, State), + {noreply, State2}; + +handle_cast(Msg, State) -> + io:format("~p:~p: Unhandled cast ~p~n", [?MODULE, ?LINE, Msg]), + {noreply, State}. + +%%%%%%%%%%%%%%%%%%%%LOOP%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +handle_event(#wx{id=MenuId, + event=#wxCommand{type = command_menu_selected}}, + #state{menu_items=MenuItems} = State) -> + case lists:keyfind(MenuId,1,MenuItems) of + {MenuId,Id} -> + start_detail_win(Id); + false -> + ok + end, + {noreply, State}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, + #state{grid=Grid}=State) -> + observer_lib:set_listctrl_col_size(Grid, W), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_right_click, + itemIndex=Row}}, + #state{panel=Panel, holder=Holder, menu_cols=MenuCols} = State) -> + Menu = wxMenu:new(), + MenuItems = + lists:flatmap( + fun(Col) -> + MenuId = ?ID_DETAILS + Col, + ColText = call(Holder, {get_row, self(), Row, Col}), + case ColText of + "[]" -> []; + _ -> + What = + case catch list_to_integer(ColText) of + NodeId when is_integer(NodeId) -> + "node " ++ ColText; + _ -> + ColText + end, + Text = "Properties for " ++ What, + wxMenu:append(Menu, MenuId, Text), + [{MenuId,ColText}] + end + end, + MenuCols), + wxWindow:popupMenu(Panel, Menu), + wxMenu:destroy(Menu), + {noreply,State#state{menu_items=MenuItems}}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + #state{holder=Holder}=State) -> + Holder ! {change_sort, Col}, + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_activated, + itemIndex=Row}}, + #state{holder=Holder} = State) -> + Id = call(Holder, {get_row, self(), Row, id}), + start_detail_win(Id), + {noreply, State}; + +handle_event(Event, State) -> + io:format("~p:~p: handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%TABLE HOLDER%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +spawn_table_holder(Callback, Owner) -> + {Info,TW} = Callback:get_info(Owner), + Attrs = observer_lib:create_attrs(), + Parent = self(), + Holder = + case Owner of + all -> + Name = list_to_atom(atom_to_list(Callback) ++ "__holder"), + spawn_link( + fun() -> + register(Name,self()), + init_table_holder(Parent, Attrs, Callback, Info) + end), + Name; + _ -> + spawn_link( + fun() -> + init_table_holder(Parent, Attrs, Callback, Info) + end) + end, + {Holder,TW}. + +init_table_holder(Parent, Attrs, Callback, Info0) -> + Info = array:from_list(Info0), + S = handle_update(Info, #holder{parent=Parent, + info=Info, + sort=#sort{sort_key= + Callback:col_to_elem(id)}, + attrs=Attrs, + callback=Callback + }), + table_holder(S). + +table_holder(#holder{callback=Callback, attrs=Attrs}=S0) -> + receive + _M={get_row, From, Row, Col} -> + %% erlang:display(_M), + State = get_row(From, Row, Col, S0), + table_holder(State); + _M={get_attr, From, Row} -> + %% erlang:display(_M), + get_attr(From, Row, Attrs), + table_holder(S0); + _M={change_sort, Col} -> + %% erlang:display(_M), + State = change_sort(Callback:col_to_elem(Col), S0), + table_holder(State); + stop -> + ok; + What -> + io:format("Table holder got ~p~n",[What]), + table_holder(S0) + end. + +change_sort(Col, S0=#holder{parent=Parent, info=Info0, sort=Sort0}) -> + NRows = array:size(Info0), + InfoList0 = array:to_list(Info0), + {Sort, InfoList}=sort(Col, Sort0, InfoList0), + Info = array:from_list(InfoList), + Parent ! {holder_updated, NRows}, + S0#holder{info=Info, last_row=undefined, sort=Sort}. + +handle_update(Info0, S0=#holder{parent=Parent, sort=Sort}) -> + NRows = array:size(Info0), + InfoList0 = array:to_list(Info0), + {_Sort, InfoList} = do_sort(Sort, InfoList0), + Info = array:from_list(InfoList), + Parent ! {holder_updated, NRows}, + S0#holder{info=Info, last_row=undefined}. + +sort(Col, Opt=#sort{sort_key=Col, sort_incr=Bool}, Table) -> + do_sort(Opt#sort{sort_incr=not Bool}, Table); +sort(Col, Sort,Table) -> + do_sort(Sort#sort{sort_key=Col, sort_incr=true}, Table). + +do_sort(Sort=#sort{sort_key=Col, sort_incr=true}, Table) -> + {Sort, lists:keysort(Col, Table)}; +do_sort(Sort=#sort{sort_key=Col, sort_incr=false}, Table) -> + {Sort, lists:reverse(lists:keysort(Col, Table))}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +get_cell_data(Callback, ColNo, RowInfo) -> + case element(Callback:col_to_elem(ColNo), RowInfo) of + undefined -> ""; + Cell -> try Callback:format(Cell) catch error:undef -> Cell end + end. + +get_row(From, Row, Col, + #holder{callback=Callback, last_row={Row,RowInfo}}=State) -> + Data = get_cell_data(Callback, Col, RowInfo), + From ! {self(), observer_lib:to_str(Data)}, + State; +get_row(From, Row, Col, #holder{callback=Callback, info=Info}=S0) -> + {Data,State} = + case Row >= array:size(Info) of + true -> + {"",S0}; + false -> + RowInfo = array:get(Row, Info), + CellData = get_cell_data(Callback, Col, RowInfo), + {CellData,S0#holder{last_row={Row,RowInfo}}} + end, + From ! {self(), observer_lib:to_str(Data)}, + State. + +get_attr(From, Row, Attrs) -> + Attribute = case Row rem 2 =:= 0 of + true -> Attrs#attrs.even; + false -> Attrs#attrs.odd + end, + From ! {self(), Attribute}. -- cgit v1.2.3