%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% -module(cdv_virtual_list_wx). -behaviour(wx_object). -export([start_link/2, start_link/3, start_detail_win/1, start_detail_win/2]). %% 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) -> case Id of "<"++_ -> start_detail_win(Id, process); "#Port"++_ -> start_detail_win(Id, port); _ -> io:format("cdv: unknown identifier: ~tp~n",[Id]), ignore end. start_detail_win(Id, process) -> start_detail_win_2(cdv_proc_cb, Id); start_detail_win(Id, port) -> start_detail_win_2(cdv_port_cb, Id); start_detail_win(Id, node) -> start_detail_win_2(cdv_dist_cb, Id); start_detail_win(Id, module) -> start_detail_win_2(cdv_mod_cb, Id); start_detail_win(Id, ets) -> start_detail_win_2(cdv_ets_cb, Id); start_detail_win(Id, sched) -> start_detail_win_2(cdv_sched_cb, Id). start_detail_win_2(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(), Scale = observer_wx:get_scale(), AddListEntry = fun({Name, Align, DefSize}, Col) -> wxListItem:setText(Li, Name), wxListItem:setAlign(Li, Align), wxListCtrl:insertColumn(ListCtrl, Col, Li), wxListCtrl:setColumnWidth(ListCtrl, Col, DefSize*Scale), 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, holder=Holder,callback=Callback}=State) -> NewOpened = case lists:keyfind(Id, 1, Opened) of false -> Data = call(Holder, {get_data, self(), Id}), case cdv_detail_wx:start_link(Id, Data, Panel, Callback, cdv) 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 ~tp~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(active, State) -> cdv_wx:set_status(State#state.trunc_warn), {noreply, State}; handle_info(Info, State) -> io:format("~p:~p, Unexpected info: ~tp~n", [?MODULE, ?LINE, Info]), {noreply, State}. terminate(_Reason, #state{holder=Holder}) -> Holder ! stop, ok. code_change(_, _, State) -> {ok, State}. handle_call(new_dump, _From, #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), {reply, ok, State#state{detail_wins=[],holder=NewHolder,trunc_warn=TW}}; handle_call(Msg, _From, State) -> io:format("~p:~p: Unhandled call ~tp~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({detail_win_closed, Id},#state{detail_wins=Opened}=State) -> Opened2 = lists:keydelete(Id, 1, Opened), {noreply, State#state{detail_wins=Opened2}}; handle_cast(Msg, State) -> io:format("~p:~p: Unhandled cast ~tp~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,Type,Id} -> start_detail_win(Id, Type); 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({Type, Col}) -> MenuId = ?ID_DETAILS + Col, ColText = call(Holder, {get_row, self(), Row, Col}), case ColText of Empty when Empty=="[]"; Empty=="" -> []; _ -> What = case catch list_to_integer(ColText) of NodeId when is_integer(NodeId), Type =:= node -> "node " ++ ColText; _ -> ColText end, Text = "Properties for " ++ What, wxMenu:append(Menu, MenuId, Text), [{MenuId,Type,ColText}] end end, MenuCols), case MenuItems of [] -> wxMenu:destroy(Menu); _ -> wxWindow:popupMenu(Panel, Menu), wxMenu:destroy(Menu) end, {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, menu_cols=MenuCols} = State) -> case MenuCols of [{Type, _}|_] -> Id = call(Holder, {get_row, self(), Row, id}), start_detail_win(Id, Type); _ -> ignore end, {noreply, State}; handle_event(Event, State) -> io:format("~p:~p: handle event ~tp\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, InfoList0) -> Sort = #sort{sort_key=Callback:col_to_elem(id)}, {_Sort, InfoList} = do_sort(Sort,InfoList0), Info = array:from_list(InfoList), NRows = array:size(Info), Parent ! {holder_updated, NRows}, table_holder(#holder{parent=Parent, info=Info, sort=Sort, attrs=Attrs, callback=Callback}). table_holder(#holder{callback=Callback, attrs=Attrs, info=Info}=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); _M={get_data, From, Id} -> search_id(From, Id, Callback, Info), table_holder(S0); stop -> ok; What -> io:format("Table holder got ~tp~n",[What]), table_holder(S0) end. search_id(From, Id, Callback, Info) -> Find = fun(_, RowInfo, _) -> search_id(Callback, RowInfo, Id) end, Res = try array:foldl(Find, not_found, Info) catch Data -> Data end, From ! {self(), Res}, ok. search_id(Callback, RowInfo, Id) -> case observer_lib:to_str(get_cell_data(Callback, id, RowInfo)) of Id -> throw(RowInfo); _Str -> not_found 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}. 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}.