diff options
-rw-r--r-- | lib/observer/src/Makefile | 2 | ||||
-rw-r--r-- | lib/observer/src/observer_tv.hrl | 34 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_table.erl | 806 | ||||
-rw-r--r-- | lib/observer/src/observer_tv_wx.erl | 600 |
4 files changed, 1442 insertions, 0 deletions
diff --git a/lib/observer/src/Makefile b/lib/observer/src/Makefile index 514c6e441c..92319434c0 100644 --- a/lib/observer/src/Makefile +++ b/lib/observer/src/Makefile @@ -48,6 +48,8 @@ MODULES= \ observer_sys_wx \ observer_trace_wx \ observer_traceoptions_wx \ + observer_tv_table \ + observer_tv_wx \ ttb \ ttb_et diff --git a/lib/observer/src/observer_tv.hrl b/lib/observer/src/observer_tv.hrl new file mode 100644 index 0000000000..05e4f928d0 --- /dev/null +++ b/lib/observer/src/observer_tv.hrl @@ -0,0 +1,34 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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% + +-record(tab, {name, + id = ignore, + size, + memory=0, %% In bytes + owner, + reg_name, + protection = public, + type=set, + keypos=1, + heir=none, + compressed=false, + fixed=false, + %% Mnesia Info + storage, + index + }). diff --git a/lib/observer/src/observer_tv_table.erl b/lib/observer/src/observer_tv_table.erl new file mode 100644 index 0000000000..3228f7e571 --- /dev/null +++ b/lib/observer/src/observer_tv_table.erl @@ -0,0 +1,806 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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(observer_tv_table). + +-export([start_link/2]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-export([get_table/3]). + +-import(observer_pro_wx, [to_str/1]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_tv.hrl"). + +-define(ID_TABLE_INFO, 400). +-define(ID_REFRESH, 401). +-define(ID_REFRESH_INTERVAL, 402). +-define(ID_EDIT, 403). +-define(ID_DELETE, 404). +-define(ID_SEARCH, 405). + +-define(SEARCH_ENTRY, 420). +-define(GOTO_ENTRY, 421). + +-define(DEFAULT_COL_WIDTH, 100). + +-record(state, + { + parent, + frame, + grid, + status, + sizer, + search, + selected, + node=node(), + columns, + pid, + source, + tab, + attrs + }). + +-record(opt, + { + sort_key=2, + sort_incr=true + }). + +-record(attrs, {even, odd, deleted, changed, searched}). + +-record(search, + {enable=true, % Subwindow is enabled + win, % Sash Sub window obj + name, % name + + search, % Search input ctrl + goto, % Goto input ctrl + radio, % Radio buttons + + find % Search string + }). + +-record(find, {start, % start pos + strlen, % Found + found % false + }). + +start_link(Parent, Opts) -> + wx_object:start_link(?MODULE, [Parent, Opts], []). + +init([Parent, Opts]) -> + Source = proplists:get_value(type, Opts), + Table = proplists:get_value(table, Opts), + Node = proplists:get_value(node, Opts), + Title0 = atom_to_list(Table#tab.name) ++ " @ " ++ atom_to_list(Node), + Title = case Source of + ets -> "TV Ets: " ++ Title0; + mnesia -> "TV Mnesia: " ++ Title0 + end, + Frame = wxFrame:new(Parent, ?wxID_ANY, Title, [{size, {800, 300}}]), + IconFile = filename:join(code:priv_dir(observer), "erlang_observer.png"), + Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]), + wxFrame:setIcon(Frame, Icon), + wxIcon:destroy(Icon), + MenuBar = wxMenuBar:new(), + create_menus(MenuBar), + wxFrame:setMenuBar(Frame, MenuBar), + %% wxFrame:setAcceleratorTable(Frame, AccelTable), + wxMenu:connect(Frame, command_menu_selected), + + StatusBar = wxFrame:createStatusBar(Frame, []), + try + TabId = table_id(Table), + ColumnNames = column_names(Node, Source, TabId), + KeyPos = key_pos(Node, Source, TabId), + + Attrs = create_attrs(), + + Self = self(), + Holder = spawn_link(fun() -> + init_table_holder(Self, Table, Source, + length(ColumnNames), Node, Attrs) + end), + + Panel = wxPanel:new(Frame), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxLC_REPORT bor ?wxLC_VIRTUAL bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Panel, [{style, Style}, + {onGetItemText, + fun(_, Item,Col) -> get_row(Holder, Item, Col+1) end}, + {onGetItemAttr, + fun(_, Item) -> get_attr(Holder, Item) end} + ]), + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, command_list_col_click), + wxListCtrl:connect(Grid, size, [{skip, true}]), + wxWindow:setFocus(Grid), + + Search = search_area(Panel), + wxSizer:add(Sizer, Grid, + [{flag, ?wxEXPAND bor ?wxALL}, {proportion, 1}, {border, 5}]), + wxSizer:add(Sizer, Search#search.win, + [{flag,?wxEXPAND bor ?wxLEFT bor ?wxRIGHT bor + ?wxRESERVE_SPACE_EVEN_IF_HIDDEN}, + {border, 5}]), + wxWindow:setSizer(Panel, Sizer), + wxSizer:hide(Sizer, Search#search.win), + + Cols = add_columns(Grid, 0, ColumnNames), + wxFrame:show(Frame), + {Panel, #state{frame=Frame, grid=Grid, status=StatusBar, search=Search, + sizer = Sizer, + parent=Parent, columns=Cols, + pid=Holder, source=Source, tab=Table#tab{keypos=KeyPos}, + attrs=Attrs}} + catch node_or_table_down -> + wxFrame:destroy(Frame), + stop + end. + +add_columns(Grid, Start, ColumnNames) -> + Li = wxListItem:new(), + AddListEntry = fun(Name, Col) -> + wxListItem:setText(Li, to_str(Name)), + wxListItem:setAlign(Li, ?wxLIST_FORMAT_LEFT), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, ?DEFAULT_COL_WIDTH), + Col + 1 + end, + Cols = lists:foldl(AddListEntry, Start, ColumnNames), + wxListItem:destroy(Li), + Cols. + +create_menus(MB) -> + File = wxMenu:new(), + wxMenu:append(File, ?ID_TABLE_INFO, "Table Information\tCtrl-I"), + wxMenu:append(File, ?wxID_CLOSE, "Close"), + wxMenuBar:append(MB, File, "File"), + Edit = wxMenu:new(), + wxMenu:append(Edit, ?ID_EDIT, "Edit Object"), + wxMenu:append(Edit, ?ID_DELETE, "Delete Object\tCtrl-D"), + wxMenu:appendSeparator(Edit), + wxMenu:append(Edit, ?ID_SEARCH, "Search\tCtrl-S"), + wxMenu:appendSeparator(Edit), + wxMenu:append(Edit, ?ID_REFRESH, "Refresh\tCtrl-R"), + wxMenu:append(Edit, ?ID_REFRESH_INTERVAL, "Refresh interval..."), + wxMenuBar:append(MB, Edit, "Edit"), + Help = wxMenu:new(), + wxMenu:append(Help, ?wxID_HELP, "Help"), + wxMenuBar:append(MB, Help, "Help"), + ok. + +search_area(Parent) -> + HSz = wxBoxSizer:new(?wxHORIZONTAL), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Find:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC1, [{proportion,3}, {flag, ?wxEXPAND}]), + Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"), + wxRadioButton:setValue(Nbtn, true), + wxSizer:add(HSz,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"), + wxSizer:add(HSz,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"), + wxSizer:add(HSz,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]), + wxSizer:add(HSz, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]), + wxSizer:add(HSz, wxStaticText:new(Parent, ?wxID_ANY, "Goto Entry:"), + [{flag,?wxALIGN_CENTER_VERTICAL}]), + TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]), + wxSizer:add(HSz, TC2, [{proportion,0}, {flag, ?wxEXPAND}]), + wxTextCtrl:connect(TC1, command_text_updated), + wxTextCtrl:connect(TC1, command_text_enter), + wxTextCtrl:connect(TC1, kill_focus), + wxTextCtrl:connect(TC2, command_text_enter), + wxWindow:connect(Parent, command_button_clicked), + + #search{name='Search Area', win=HSz, + search=TC1,goto=TC2,radio={Nbtn,Pbtn,Cbtn}}. + +edit(Index, #state{pid=Pid, frame=Frame}) -> + Str = get_row(Pid, Index, all), + Dialog = wxTextEntryDialog:new(Frame, "Edit object:", [{value, Str}]), + case wxTextEntryDialog:showModal(Dialog) of + ?wxID_OK -> + New = wxTextEntryDialog:getValue(Dialog), + wxTextEntryDialog:destroy(Dialog), + case Str =:= New of + true -> ok; + false -> + complete_edit(Index, New, Pid) + end; + ?wxID_CANCEL -> + wxTextEntryDialog:destroy(Dialog) + end. + +complete_edit(Row, New0, Pid) -> + New = case lists:reverse(New0) of + [$.|_] -> New0; + _ -> New0 ++ "." + end, + try + {ok, Tokens, _} = erl_scan:string(New), + {ok, Term} = erl_parse:parse_term(Tokens), + Pid ! {edit, Row, Term} + catch _:{badmatch, {error, {_, _, Err}}} -> + self() ! {error, ["Parse error: ", Err]}; + _Err -> + self() ! {error, ["Syntax error in: ", New]} + end. + +handle_event(#wx{id=?ID_REFRESH},State = #state{pid=Pid}) -> + Pid ! refresh, + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + State = #state{pid=Pid}) -> + Pid ! {sort, Col+1}, + {noreply, State}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> + wx:batch(fun() -> + Cols = wxListCtrl:getColumnCount(Grid), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(Grid, I) + end, W-2, lists:seq(0, Cols - 2)), + Size = max(?DEFAULT_COL_WIDTH, Last), + wxListCtrl:setColumnWidth(Grid, Cols-1, Size) + end), + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, + State = #state{pid=Pid, grid=Grid, status=StatusBar}) -> + N = wxListCtrl:getItemCount(Grid), + Str = get_row(Pid, Index, all), + wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w: ~s",[N, Str])), + {noreply, State#state{selected=Index}}; + +handle_event(#wx{event=#wxList{type=command_list_item_activated, itemIndex=Index}}, + State) -> + edit(Index, State), + {noreply, State}; + +handle_event(#wx{id=?ID_EDIT}, State = #state{selected=undefined}) -> + {noreply, State}; +handle_event(#wx{id=?ID_EDIT}, State = #state{selected=Index}) -> + edit(Index, State), + {noreply, State}; + +handle_event(#wx{id=?ID_DELETE}, State = #state{selected=undefined}) -> + {noreply, State}; +handle_event(#wx{id=?ID_DELETE}, + State = #state{pid=Pid, status=StatusBar, selected=Index}) -> + Str = get_row(Pid, Index, all), + Pid ! {delete, Index}, + wxStatusBar:setStatusText(StatusBar, io_lib:format("Deleted object: ~s",[Str])), + {noreply, State}; + +handle_event(#wx{id=?wxID_CLOSE}, State) -> + {stop, normal, State}; + +handle_event(Help = #wx{id=?wxID_HELP}, State = #state{parent=Parent}) -> + Parent ! Help, + {noreply, State}; + +handle_event(#wx{id=?GOTO_ENTRY, event=#wxCommand{cmdString=Str}}, + State = #state{grid=Grid}) -> + try + Row0 = list_to_integer(Str), + Row1 = min(0, Row0), + Row = max(wxListCtrl:getItemCount(Grid)-1,Row1), + wxListCtrl:ensureVisible(Grid, Row), + ok + catch _:_ -> ok + end, + {noreply, State}; + +%% Search functionality +handle_event(#wx{id=?ID_SEARCH}, + State = #state{sizer=Sz, search=Search}) -> + wxSizer:show(Sz, Search#search.win), + wxWindow:setFocus(Search#search.search), + wxSizer:layout(Sz), + {noreply, State}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxFocus{}}, + State = #state{search=Search, pid=Pid}) -> + Pid ! {mark_search_hit, false}, + {noreply, State#state{search=Search#search{find=undefined}}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=""}}, + State = #state{search=Search, pid=Pid}) -> + Pid ! {mark_search_hit, false}, + {noreply, State#state{search=Search#search{find=undefined}}}; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{type=command_text_enter,cmdString=Str}}, + State = #state{grid=Grid, pid=Pid, status=SB, + search=Search=#search{radio={Next0, _, Case0}, + find=Find}}) + when Find =/= undefined -> + Dir = wxRadioButton:getValue(Next0) xor wx_misc:getKeyState(?WXK_SHIFT), + Case = wxCheckBox:getValue(Case0), + Pos = if Find#find.found, Dir -> %% Forward Continuation + Find#find.start+1; + Find#find.found -> %% Backward Continuation + Find#find.start-1; + Dir -> %% Forward wrap + 0; + true -> %% Backward wrap + wxListCtrl:getItemCount(Grid)-1 + end, + Pid ! {mark_search_hit, false}, + case search(Pid, Str, Pos, Dir, Case) of + false -> + wxStatusBar:setStatusText(SB, "Not found"), + Pid ! {mark_search_hit, Find#find.start}, + wxListCtrl:refreshItem(Grid, Find#find.start), + {noreply, State#state{search=Search#search{find=#find{found=false}}}}; + Row -> + wxListCtrl:ensureVisible(Grid, Row), + wxListCtrl:refreshItem(Grid, Row), + Status = "Found: (Hit Enter for next, Shift-Enter for previous)", + wxStatusBar:setStatusText(SB, Status), + {noreply, State#state{search=Search#search{find=#find{start=Row, found=true}}}} + end; +handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}}, + State = #state{grid=Grid, pid=Pid, status=SB, + search=Search=#search{radio={Next0, _, Case0}, + find=Find}}) -> + try + Dir = wxRadioButton:getValue(Next0), + Case = wxCheckBox:getValue(Case0), + Start = case Dir of + true -> 0; + false -> wxListCtrl:getItemCount(Grid)-1 + end, + Cont = case Find of + undefined -> + #find{start=Start, strlen=length(Str)}; + #find{strlen=Old} when Old < length(Str) -> + Find#find{start=Start, strlen=length(Str)}; + _ -> + Find#find{strlen=length(Str)} + end, + + Pid ! {mark_search_hit, false}, + case search(Pid, Str, Cont#find.start, Dir, Case) of + false -> + wxStatusBar:setStatusText(SB, "Not found"), + {noreply, State}; + Row -> + wxListCtrl:ensureVisible(Grid, Row), + wxListCtrl:refreshItem(Grid, Row), + Status = "Found: (Hit Enter for next, Shift-Enter for previous)", + wxStatusBar:setStatusText(SB, Status), + {noreply, State#state{search=Search#search{find=#find{start=Row, found=true}}}} + end + catch _:_ -> {noreply, State} + end; + +handle_event(#wx{id=?ID_TABLE_INFO}, + State = #state{frame=Frame, node=Node, source=Source, tab=Table}) -> + observer_tv_wx:display_table_info(Frame, Node, Source, Table), + {noreply, State}; + +handle_event(Event, State) -> + io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_sync_event(Event, _Obj, _State) -> + io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), + ok. + +handle_call(Event, From, State) -> + io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), + {noreply, State}. + +handle_cast(Event, State) -> + io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_info({no_rows, N}, State = #state{grid=Grid, status=StatusBar}) -> + wxListCtrl:setItemCount(Grid, N), + wxStatusBar:setStatusText(StatusBar, io_lib:format("Objects: ~w",[N])), + {noreply, State}; +handle_info({new_cols, New}, State = #state{grid=Grid, columns=Cols0}) -> + Cols = add_columns(Grid, Cols0, New), + {noreply, State#state{columns=Cols}}; +handle_info({refresh, Min, Max}, State = #state{grid=Grid}) -> + wxListCtrl:refreshItems(Grid, Min, Max), + {noreply, State}; +handle_info({error, Error}, State = #state{frame=Frame}) -> + Dlg = wxMessageDialog:new(Frame, Error), + wxMessageDialog:showModal(Dlg), + wxMessageDialog:destroy(Dlg), + {noreply, State}; + +handle_info(Event, State) -> + io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +terminate(_Event, #state{pid=Pid, attrs=Attrs}) -> + %% ListItemAttr are not auto deleted + #attrs{odd=Odd, deleted=D, changed=Ch, searched=S} = Attrs, + wxListItemAttr:destroy(Odd), + wxListItemAttr:destroy(D), + wxListItemAttr:destroy(Ch), + wxListItemAttr:destroy(S), + unlink(Pid), + exit(Pid, window_closed), + ok. + +code_change(_, _, State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Table holder needs to be in a separate process otherwise +%% the callback get_row/3 may deadlock if the process do +%% wx calls when callback is invoked. +get_row(Table, Item, Column) -> + Ref = erlang:monitor(process, Table), + Table ! {get_row, self(), Item, Column}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +get_attr(Table, Item) -> + Ref = erlang:monitor(process, Table), + Table ! {get_attr, self(), Item}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +search(Table, Str, Row, Dir, Case) -> + Ref = erlang:monitor(process, Table), + Table ! {search, [Str, Row, Dir, Case]}, + receive + {'DOWN', Ref, _, _, _} -> ""; + {Table, Res} -> + erlang:demonitor(Ref), + Res + end. + +-record(holder, {node, parent, pid, + table=[], n=0, columns, + temp=[], + search, + source, tabid, + sort, + key, + type, + attrs + }). + +init_table_holder(Parent, Table, MnesiaOrEts, Cols, Node, Attrs) -> + TabId = case Table#tab.id of + ignore -> Table#tab.name; + Id -> Id + end, + self() ! refresh, + table_holder(#holder{node=Node, parent=Parent, + source=MnesiaOrEts, tabid=TabId, columns=Cols, + sort=#opt{sort_key=Table#tab.keypos, sort_incr=true}, + type=Table#tab.type, key=Table#tab.keypos, + attrs=Attrs}). + +table_holder(S0 = #holder{parent=Parent, pid=Pid, table=Table}) -> + receive + {get_attr, From, Row} -> + get_attr(From, Row, S0), + table_holder(S0); + {get_row, From, Row, Col} -> + get_row(From, Row, Col, Table), + table_holder(S0); + {Pid, Data} -> + S1 = handle_new_data_chunk(Data, S0), + table_holder(S1); + {sort, Col} -> + table_holder(sort(Col, S0)); + {search, Data} -> + table_holder(search(Data, S0)); + {mark_search_hit, Row} -> + Old = S0#holder.search, + is_integer(Old) andalso (Parent ! {refresh, Old, Old}), + table_holder(S0#holder{search=Row}); + refresh when is_pid(Pid) -> + %% Already getting the table... + %% io:format("ignoring refresh", []), + table_holder(S0); + refresh -> + GetTab = rpc:call(S0#holder.node, ?MODULE, get_table, + [self(), S0#holder.tabid, S0#holder.source]), + table_holder(S0#holder{pid=GetTab}); + {delete, Row} -> + delete_row(Row, S0), + table_holder(S0); + {edit, Row, Term} -> + edit_row(Row, Term, S0), + table_holder(S0); + What -> + io:format("Table holder got ~p~n",[What]), + table_holder(S0) + end. + +handle_new_data_chunk(Data, S0 = #holder{columns=Cols, parent=Parent}) -> + S1 = #holder{columns=NewCols} = handle_new_data_chunk2(Data, S0), + case NewCols =:= Cols of + true -> S1; + false -> + Parent ! {new_cols, lists:seq(Cols+1, NewCols)}, + S1 + end. + +handle_new_data_chunk2('$end_of_table', + S0 = #holder{parent=Parent, sort=Opt, + key=Key, + table=Old, temp=New}) -> + Table = merge(Old, New, Key), + N = length(Table), + Parent ! {no_rows, N}, + sort(Opt#opt.sort_key, S0#holder{n=N, pid=undefine, + sort=Opt#opt{sort_key = undefined}, + table=Table, temp=[]}); +handle_new_data_chunk2(Data, S0 = #holder{columns=Cols0, source=ets, temp=Tab0}) -> + {Tab, Cols} = parse_ets_data(Data, Cols0, Tab0), + S0#holder{columns=Cols, temp=Tab}; +handle_new_data_chunk2(Data, S0 = #holder{source=mnesia, temp=Tab}) -> + S0#holder{temp=(Data ++ Tab)}. + +parse_ets_data([[Rec]|Rs], C, Tab) -> + parse_ets_data(Rs, max(tuple_size(Rec), C), [Rec|Tab]); +parse_ets_data([Recs|Rs], C0, Tab0) -> + {Tab, Cols} = parse_ets_data(Recs, C0, Tab0), + parse_ets_data(Rs, Cols, Tab); +parse_ets_data([], Cols, Tab) -> + {Tab, Cols}. + +sort(Col, S=#holder{n=N, parent=Parent, sort=Opt0, table=Table0}) -> + {Opt, Table} = sort(Col, Opt0, Table0), + Parent ! {refresh, 0, N}, + S#holder{sort=Opt, table=Table}. + +sort(Col, Opt = #opt{sort_key=Col, sort_incr=Bool}, Table) -> + {Opt#opt{sort_incr=not Bool}, lists:reverse(Table)}; +sort(Col, #opt{sort_incr=true}, Table) -> + {#opt{sort_key=Col}, keysort(Col, Table)}; +sort(Col, #opt{sort_incr=false}, Table) -> + {#opt{sort_key=Col}, lists:reverse(keysort(Col, Table))}. + +keysort(Col, Table) -> + Sort = fun([A0|_], [B0|_]) -> + A = try element(Col, A0) catch _:_ -> [] end, + B = try element(Col, B0) catch _:_ -> [] end, + case A == B of + true -> A0 =< B0; + false -> A < B + end; + (A0, B0) when is_tuple(A0), is_tuple(B0) -> + A = try element(Col, A0) catch _:_ -> [] end, + B = try element(Col, B0) catch _:_ -> [] end, + case A == B of + true -> A0 =< B0; + false -> A < B + end + end, + lists:sort(Sort, Table). + +search([Str, Row, Dir0, CaseSens], + S=#holder{parent=Parent, table=Table}) -> + Opt = case CaseSens of + true -> []; + false -> [caseless] + end, + {ok, Re} = re:compile(Str, Opt), + Dir = case Dir0 of + true -> 1; + false -> -1 + end, + Res = search(Row, Dir, Re, Table), + Parent ! {self(), Res}, + S#holder{search=Res}. + +search(Row, Dir, Re, Table) -> + Res = try lists:nth(Row+1, Table) of + Term -> + Str = io_lib:format("~w", [Term]), + re:run(Str, Re) + catch _:_ -> no_more + end, + case Res of + nomatch -> search(Row+Dir, Dir, Re, Table); + no_more -> false; + {match,_} -> Row + end. + +get_row(From, Row, Col, Table) -> + case lists:nth(Row+1, Table) of + [Object|_] when Col =:= all -> + From ! {self(), io_lib:format("~w", [Object])}; + [Object|_] when tuple_size(Object) >= Col -> + From ! {self(), io_lib:format("~w", [element(Col, Object)])}; + _ -> + From ! {self(), ""} + end. + +get_attr(From, Row, #holder{attrs=Attrs, search=Row}) -> + What = Attrs#attrs.searched, + From ! {self(), What}; +get_attr(From, Row, #holder{table=Table, attrs=Attrs}) -> + What = case lists:nth(Row+1, Table) of + [_|deleted] -> Attrs#attrs.deleted; + [_|changed] -> Attrs#attrs.changed; + [_|new] -> Attrs#attrs.changed; + _ when (Row rem 2) > 0 -> + Attrs#attrs.odd; + _ -> + Attrs#attrs.even + end, + From ! {self(), What}. + +merge([], New, _Key) -> + [[N] || N <- New]; %% First time +merge(Old, New, Key) -> + merge2(keysort(Key, Old), keysort(Key, New), Key). + +merge2([[Obj|_]|Old], [Obj|New], Key) -> + [[Obj]|merge2(Old, New, Key)]; +merge2([[A|_]|Old], [B|New], Key) + when element(Key, A) == element(Key, B) -> + [[B|changed]|merge2(Old, New, Key)]; +merge2([[A|_]|Old], New = [B|_], Key) + when element(Key, A) < element(Key, B) -> + [[A|deleted]|merge2(Old, New, Key)]; +merge2(Old = [[A|_]|_], [B|New], Key) + when element(Key, A) > element(Key, B) -> + [[B|new]|merge2(Old, New, Key)]; +merge2([], New, _Key) -> + [[N|new] || N <- New]; +merge2(Old, [], _Key) -> + [[O|deleted] || [O|_] <- Old]. + + +delete_row(Row, S0 = #holder{parent=Parent}) -> + case delete(Row, S0) of + ok -> + self() ! refresh; + {error, Err} -> + Parent ! {error, "Could not delete object: " ++ Err} + end. + + +delete(Row, #holder{tabid=Id, table=Table, + source=Source, node=Node}) -> + [Object|_] = lists:nth(Row+1, Table), + try + case Source of + ets -> + true = rpc:call(Node, ets, delete_object, [Id, Object]); + mnesia -> + ok = rpc:call(Node, mnesia, dirty_delete_object, [Id, Object]) + end, + ok + catch _:_Error -> + {error, "node or table is not available"} + end. + +edit_row(Row, Term, S0 = #holder{parent=Parent}) -> + case delete(Row, S0) of + ok -> + case insert(Term, S0) of + ok -> self() ! refresh; + Err -> Parent ! {error, Err} + end; + {error, Err} -> + Parent ! {error, "Could not edit object: " ++ Err} + end. + +insert(Object, #holder{tabid=Id, source=Source, node=Node}) -> + try + case Source of + ets -> + true = rpc:call(Node, ets, insert, [Id, Object]); + mnesia -> + ok = rpc:call(Node, mnesia, dirty_write, [Id, Object]) + end, + ok + catch _:_Error -> + {error, "node or table is not available"} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +get_table(Parent, Table, Module) -> + spawn(fun() -> + link(Parent), + get_table2(Parent, Table, Module) + end). + +get_table2(Parent, Table, Type) -> + Size = case Type of + ets -> ets:info(Table, size); + mnesia -> mnesia:table_info(Table, size) + end, + case Size > 0 of + false -> + Parent ! {self(), '$end_of_table'}, + normal; + true when Type =:= ets -> + Mem = ets:info(Table, memory), + Average = Mem div Size, + NoElements = max(10, 20000 div Average), + get_ets_loop(Parent, ets:match(Table, '$1', NoElements)); + true -> + Mem = mnesia:table_info(Table, memory), + Average = Mem div Size, + NoElements = max(10, 20000 div Average), + Ms = [{'$1', [], ['$1']}], + Get = fun() -> + get_mnesia_loop(Parent, mnesia:select(Table, Ms, NoElements, read)) + end, + %% Not a transaction, we don't want to grab locks when inspecting the table + mnesia:async_dirty(Get) + end. + +get_ets_loop(Parent, '$end_of_table') -> + Parent ! {self(), '$end_of_table'}; +get_ets_loop(Parent, {Match, Cont}) -> + Parent ! {self(), Match}, + get_ets_loop(Parent, ets:match(Cont)). + +get_mnesia_loop(Parent, '$end_of_table') -> + Parent ! {self(), '$end_of_table'}; +get_mnesia_loop(Parent, {Match, Cont}) -> + Parent ! {self(), Match}, + get_ets_loop(Parent, mnesia:select(Cont)). + +column_names(Node, Type, Table) -> + case Type of + ets -> [1, 2]; + mnesia -> + Attrs = rpc:call(Node, mnesia, table_info, [Table, attributes]), + is_list(Attrs) orelse throw(node_or_table_down), + ["Record Name"|Attrs] + end. + +table_id(#tab{id=ignore, name=Name}) -> Name; +table_id(#tab{id=Id}) -> Id. + +key_pos(_, mnesia, _) -> 2; +key_pos(Node, ets, TabId) -> + KeyPos = rpc:call(Node, ets, info, [TabId, keypos]), + is_integer(KeyPos) orelse throw(node_or_table_down), + KeyPos. + +create_attrs() -> + Font = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT), + Text = wxSystemSettings:getColour(?wxSYS_COLOUR_LISTBOXTEXT), + #attrs{even = wx:typeCast(wx:null(), wxListItemAttr), + odd = wxListItemAttr:new(Text, {240,240,255}, Font), + deleted = wxListItemAttr:new({240,30,30}, {100,100,100}, Font), + changed = wxListItemAttr:new(Text, {255,215,0}, Font), + searched = wxListItemAttr:new(Text, {235,215,90}, Font) + }. diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl new file mode 100644 index 0000000000..10b29cb1b7 --- /dev/null +++ b/lib/observer/src/observer_tv_wx.erl @@ -0,0 +1,600 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011. 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(observer_tv_wx). + +-export([start_link/2, display_table_info/4]). + +%% wx_object callbacks +-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, + handle_event/2, handle_sync_event/3, handle_cast/2]). + +-export([get_table_list/1]). %% RPC called move to runtime tools? + +-export([get_wx_parent/1, interval_dialog/5]). + +-import(observer_pro_wx, [to_str/1]). + +-behaviour(wx_object). +-include_lib("wx/include/wx.hrl"). +-include("observer_defs.hrl"). +-include("observer_tv.hrl"). + +-define(GRID, 500). +-define(ID_REFRESH, 401). +-define(ID_REFRESH_INTERVAL, 402). +-define(ID_ETS, 403). +-define(ID_MNESIA, 404). +-define(ID_UNREADABLE, 405). +-define(ID_SYSTEM_TABLES, 406). +-define(ID_TABLE_INFO, 407). + +-record(opt, {type=ets, + sys_hidden=true, + unread_hidden=true, + sort_key=2, + sort_incr=true + }). + +-record(state, + { + parent, + grid, + node=node(), + opt=#opt{}, + selected, + tabs, + refr_timer=false, + refr_intv=30 + }). + +start_link(Notebook, Parent) -> + wx_object:start_link(?MODULE, [Notebook, Parent], []). + +init([Notebook, Parent]) -> + Panel = wxPanel:new(Notebook), + Sizer = wxBoxSizer:new(?wxVERTICAL), + Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES, + Grid = wxListCtrl:new(Panel, [{winid, ?GRID}, {style, Style}]), + wxSizer:add(Sizer, Grid, [{flag, ?wxEXPAND bor ?wxALL}, + {proportion, 1}, {border, 5}]), + wxWindow:setSizer(Panel, Sizer), + Li = wxListItem:new(), + AddListEntry = fun({Name, Align, DefSize}, Col) -> + wxListItem:setText(Li, Name), + wxListItem:setAlign(Li, Align), + wxListCtrl:insertColumn(Grid, Col, Li), + wxListCtrl:setColumnWidth(Grid, Col, DefSize), + Col + 1 + end, + ListItems = [{"Table Name", ?wxLIST_FORMAT_LEFT, 200}, + {"Table Id", ?wxLIST_FORMAT_RIGHT, 100}, + {"Objects", ?wxLIST_FORMAT_RIGHT, 100}, + {"Size (kB)", ?wxLIST_FORMAT_RIGHT, 100}, + {"Owner Pid", ?wxLIST_FORMAT_CENTER, 150}, + {"Owner Name", ?wxLIST_FORMAT_LEFT, 200} + ], + lists:foldl(AddListEntry, 0, ListItems), + wxListItem:destroy(Li), + + wxListCtrl:connect(Grid, command_list_item_activated), + wxListCtrl:connect(Grid, command_list_item_selected), + wxListCtrl:connect(Grid, command_list_col_click), + wxListCtrl:connect(Grid, size, [{skip, true}]), + + wxWindow:setFocus(Grid), + {Panel, #state{grid=Grid, parent=Parent}}. + +handle_event(#wx{id=?ID_REFRESH}, + State = #state{node=Node, grid=Grid, opt=Opt}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + {noreply, State#state{tabs=Tabs}}; + +handle_event(#wx{event=#wxList{type=command_list_col_click, col=Col}}, + State = #state{node=Node, grid=Grid, + opt=Opt0=#opt{sort_key=Key, sort_incr=Bool}}) -> + Opt = case Col+2 of + Key -> Opt0#opt{sort_incr=not Bool}; + NewKey -> Opt0#opt{sort_key=NewKey} + end, + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, tabs=Tabs}}; + +handle_event(#wx{id=Id}, State = #state{node=Node, grid=Grid, opt=Opt0}) + when Id >= ?ID_ETS, Id =< ?ID_SYSTEM_TABLES -> + Opt = case Id of + ?ID_ETS -> Opt0#opt{type=ets}; + ?ID_MNESIA -> Opt0#opt{type=mnesia}; + ?ID_UNREADABLE -> Opt0#opt{unread_hidden= not Opt0#opt.unread_hidden}; + ?ID_SYSTEM_TABLES -> Opt0#opt{sys_hidden= not Opt0#opt.sys_hidden} + end, + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{opt=Opt, tabs=Tabs}}; + +handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) -> + wx:batch(fun() -> + Cols = wxListCtrl:getColumnCount(Grid), + Last = lists:foldl(fun(I, Last) -> + Last - wxListCtrl:getColumnWidth(Grid, I) + end, W-2, lists:seq(0, Cols - 2)), + Size = max(200, Last), + wxListCtrl:setColumnWidth(Grid, Cols-1, Size) + end), + {noreply, State}; + +handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated, + itemIndex=Index}}, + State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) -> + Table = lists:nth(Index+1, Tabs), + case Table#tab.protection of + private -> + self() ! {error, "Table has 'private' protection and can not be read"}; + _ -> + observer_tv_table:start_link(Grid, [{node,Node}, {type,Type}, {table,Table}]) + end, + {noreply, State}; + +handle_event(#wx{event=#wxList{type=command_list_item_selected, itemIndex=Index}}, + State) -> + {noreply, State#state{selected=Index}}; + +handle_event(#wx{id=?ID_TABLE_INFO}, + State = #state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs, selected=Sel}) -> + case Sel of + undefined -> + {noreply, State}; + R when is_integer(R) -> + Table = lists:nth(Sel+1, Tabs), + Parent = get_wx_parent(Grid), + display_table_info(Parent, Node, Type, Table), + {noreply, State} + end; + +handle_event(#wx{id=?ID_REFRESH_INTERVAL}, + State = #state{grid=Grid, refr_timer=Timer0, refr_intv=Intv0}) -> + Parent = get_wx_parent(Grid), + case interval_dialog(Parent, Timer0 /= false, Intv0, 10, 5*60) of + cancel -> + {noreply, State}; + {true, Intv} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {ok, Timer} = timer:send_interval(Intv * 1000, refresh_interval), + {noreply, State#state{refr_timer=Timer, refr_intv=Intv}}; + {false, _} -> + case Timer0 of + false -> ok; + _ -> timer:cancel(Timer0) + end, + {noreply, State#state{refr_timer=false}} + end; + +handle_event(Event, State) -> + io:format("~p:~p, handle event ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_sync_event(Event, _Obj, _State) -> + io:format("~p:~p, handle sync_event ~p\n", [?MODULE, ?LINE, Event]), + ok. + +handle_call(Event, From, State) -> + io:format("~p:~p, handle call (~p) ~p\n", [?MODULE, ?LINE, From, Event]), + {noreply, State}. + +handle_cast(Event, State) -> + io:format("~p:~p, handle cast ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +handle_info(refresh_interval, State = #state{node=Node, grid=Grid, opt=Opt}) -> + io:format("refresh interval ~p~n", [time()]), + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + {noreply, State#state{tabs=Tabs}}; + +handle_info({active, Node}, + State = #state{parent=Parent, grid=Grid, opt=Opt, + refr_timer = Refr, refr_intv=Intv}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + create_menus(Parent, Opt), + Timer = case Refr of + true -> + {ok, Ref} = timer:send_interval(Intv*1000, refresh_interval), + Ref; + false -> + false + end, + {noreply, State#state{node=Node, tabs=Tabs, refr_timer=Timer}}; + +handle_info(not_active, State = #state{refr_timer = Timer0}) -> + Timer = case Timer0 of + false -> false; + true -> true; + Timer0 -> + timer:cancel(Timer0), + true + end, + {noreply, State#state{refr_timer=Timer}}; + +handle_info({node, Node}, State = #state{grid=Grid, opt=Opt}) -> + Tables = get_tables(Node, Opt), + Tabs = update_grid(Grid, Opt, Tables), + wxWindow:setFocus(Grid), + {noreply, State#state{node=Node, tabs=Tabs}}; + +handle_info({error, Error}, State) -> + handle_error(Error), + {noreply, State}; + +handle_info(Event, State) -> + io:format("~p:~p, handle info ~p\n", [?MODULE, ?LINE, Event]), + {noreply, State}. + +terminate(Event, _State) -> + io:format("~p:~p, terminate ~p\n", [?MODULE, ?LINE, Event]), + ok. + +code_change(_, _, State) -> + State. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +create_menus(Parent, #opt{sys_hidden=Sys, unread_hidden=UnR, type=Type}) -> + MenuEntries = [{"View", + [#create_menu{id = ?ID_TABLE_INFO, text = "Table information\tCtrl-I"}, + separator, + #create_menu{id = ?ID_ETS, text = "&Ets Tables", + type=radio, check=Type==ets}, + #create_menu{id = ?ID_MNESIA, text = "&Mnesia Tables", + type=radio, check=Type==mnesia}, + separator, + #create_menu{id = ?ID_UNREADABLE, text = "View &Unreadable Tables", + type=check, check=not UnR}, + #create_menu{id = ?ID_SYSTEM_TABLES, text = "View &System Tables", + type=check, check=not Sys}, + separator, + #create_menu{id = ?ID_REFRESH, text = "Refresh\tCtrl-R"}, + #create_menu{id = ?ID_REFRESH_INTERVAL, text = "Refresh Interval..."} + ]}], + observer_wx:create_menus(Parent, MenuEntries). + +get_tables(Node, Opt) -> + case rpc:call(Node, ?MODULE, get_table_list, [Opt]) of + {badrpc, Error} -> + self() ! {error, Error}, + []; + {error, Error} -> + self() ! {error, Error}, + []; + Result -> + Result + end. + +get_table_list(#opt{type=ets, unread_hidden=HideUnread, sys_hidden=HideSys}) -> + Info = fun(Id, Acc) -> + try + TabId = case ets:info(Id, named_table) of + true -> ignore; + false -> Id + end, + Name = ets:info(Id, name), + Protection = ets:info(Id, protection), + ignore(HideUnread andalso Protection == private, unreadable), + Owner = ets:info(Id, owner), + RegName = case catch process_info(Owner, registered_name) of + [] -> ignore; + {registered_name, ProcName} -> ProcName + end, + ignore(HideSys andalso ordsets:is_element(RegName, sys_processes()), system_tab), + ignore(HideSys andalso ordsets:is_element(Name, sys_tables()), system_tab), + ignore((RegName == mnesia_monitor) + andalso Name /= schema + andalso is_atom((catch mnesia:table_info(Name, where_to_read))), mnesia_tab), + Memory = ets:info(Id, memory) * erlang:system_info(wordsize), + Tab = #tab{name = Name, + id = TabId, + protection = Protection, + owner = Owner, + size = ets:info(Id, size), + reg_name = RegName, + type = ets:info(Id, type), + keypos = ets:info(Id, keypos), + heir = ets:info(Id, heir), + memory = Memory, + compressed = ets:info(Id, compressed), + fixed = ets:info(Id, fixed) + }, + [Tab|Acc] + catch _:_What -> + %% io:format("Skipped ~p: ~p ~n",[Id, _What]), + Acc + end + end, + lists:foldl(Info, [], ets:all()); +get_table_list(#opt{type=mnesia, sys_hidden=HideSys}) -> + Owner = ets:info(schema, owner), + Owner /= undefined orelse + throw({error, "Mnesia is not running on: " ++ atom_to_list(node())}), + {registered_name, RegName} = process_info(Owner, registered_name), + Info = fun(Id, Acc) -> + try + Name = Id, + ignore(HideSys andalso ordsets:is_element(Name, mnesia_tables()), system_tab), + ignore(Name =:= schema, mnesia_tab), + Storage = mnesia:table_info(Id, storage_type), + Tab0 = #tab{name = Name, + owner = Owner, + size = mnesia:table_info(Id, size), + reg_name = RegName, + type = mnesia:table_info(Id, type), + keypos = 2, + memory = mnesia:table_info(Id, memory) * erlang:system_info(wordsize), + storage = Storage, + index = mnesia:table_info(Id, index) + }, + Tab = if Storage == disc_only_copies -> + Tab0#tab{fixed = element(2, dets:info(Id, safe_fixed)) /= []}; + (Storage == ram_copies) orelse + (Storage == disc_copies) -> + Tab0#tab{fixed = ets:info(Id, fixed), + compressed = ets:info(Id, compressed)}; + true -> Tab0 + end, + [Tab|Acc] + catch _:_What -> + %% io:format("Skipped ~p: ~p ~n",[Id, _What]), + Acc + end + end, + lists:foldl(Info, [], mnesia:system_info(tables)). + +display_table_info(Parent, Node, Source, Table) -> + Title = "Table Info: " ++ atom_to_list(Table#tab.name), + Frame = wxMiniFrame:new(Parent, ?wxID_ANY, Title, + [{style, ?wxCAPTION bor ?wxCLOSE_BOX bor ?wxRESIZE_BORDER}]), + + IdInfo = {"Identification and Owner", + [{"Name", Table#tab.name}, + {"Id", case Table#tab.id of + ignore -> Table#tab.name; + Id -> Id + end}, + {"Named table", Table#tab.id == ignore}, + {"Owner", Table#tab.owner}, + {"Owner Name", case Table#tab.reg_name of + ignore -> "-"; + Id -> Id + end}, + {"Heir", Table#tab.heir}, + {"Node", Node}]}, + MnesiaSettings = case Source of + ets -> []; + mnesia -> + [{"Local storage type", case Table#tab.storage of + unknown -> "Not available"; + ST -> ST + end}, + {"Index positions", list_to_strings(Table#tab.index)}] + end, + Settings = {"Settings", + [{"Source", Source}, + {"Key Position", Table#tab.keypos}, + {"Table Type", Table#tab.type}, + {"Protection Mode", Table#tab.protection}, + {"Fixed", Table#tab.fixed} + | MnesiaSettings ]}, + Memory = {"Memory Usage", + [{"Number of objects", Table#tab.size}, + {"Memory allocated", integer_to_list(Table#tab.memory div 1024) ++ "kB"}, + {"Compressed", Table#tab.compressed}]}, + + Sizer = display_info_wx(Frame, [IdInfo, Settings, Memory]), + wxSizer:setSizeHints(Sizer, Frame), + wxFrame:center(Frame), + wxFrame:show(Frame). + +list_to_strings([]) -> "None"; +list_to_strings([A]) -> integer_to_list(A); +list_to_strings([A,B]) -> + integer_to_list(A) ++ " ," ++ list_to_strings(B). + +get_wx_parent(Window) -> + Parent = wxWindow:getParent(Window), + case wx:is_null(Parent) of + true -> Window; + false -> get_wx_parent(Parent) + end. + +display_info_wx(Frame, Info) -> + Panel = wxPanel:new(Frame), + wxWindow:setBackgroundColour(Panel, {255,255,255}), + Sizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:addSpacer(Sizer, 5), + Add = fun(BoxInfo) -> + Box = create_box(Panel, BoxInfo), + wxSizer:add(Sizer, Box, [{flag, ?wxEXPAND bor ?wxALL}, + {border, 5}]) + end, + [Add(I) || I <- Info], + wxSizer:addSpacer(Sizer, 5), + wxWindow:setSizerAndFit(Panel, Sizer), + Sizer. + +create_box(Panel, {Title, Info}) -> + Box = wxStaticBoxSizer:new(?wxHORIZONTAL, Panel, [{label, Title}]), + Left = wxBoxSizer:new(?wxVERTICAL), + Right = wxBoxSizer:new(?wxVERTICAL), + Expand = [{flag, ?wxEXPAND}], + AddRow = fun({Desc, Value}) -> + wxSizer:add(Left, wxStaticText:new(Panel, ?wxID_ANY, Desc ++ ":"), Expand), + wxSizer:add(Right, wxStaticText:new(Panel, ?wxID_ANY, to_str(Value)), Expand) + end, + [AddRow(Entry) || Entry <- Info], + wxSizer:add(Box, Left), + wxSizer:addSpacer(Box, 10), + wxSizer:add(Box, Right), + wxSizer:addSpacer(Box, 30), + Box. + +sys_tables() -> + [ac_tab, asn1, + cdv_dump_index_table, cdv_menu_table, cdv_decode_heap_table, + cell_id, cell_pos, clist, + cover_internal_data_table, cover_collected_remote_data_table, cover_binary_code_table, + code, code_names, cookies, + corba_policy, corba_policy_associations, + dets, dets_owners, dets_registry, + disk_log_names, disk_log_pids, + eprof, erl_atom_cache, erl_epmd_nodes, + etop_accum_tab, etop_tr, + ets_coverage_data, + file_io_servers, + gs_mapping, gs_names, gstk_db, + gstk_grid_cellid, gstk_grid_cellpos, gstk_grid_id, + httpd, + id, + ign_req_index, ign_requests, + index, + inet_cache, inet_db, inet_hosts, + 'InitialReferences', + int_db, + interpreter_includedirs_macros, + ir_WstringDef, + lmcounter, locks, +% mnesia_decision, + mnesia_gvar, mnesia_stats, +% mnesia_transient_decision, + pg2_table, + queue, + schema, + shell_records, + snmp_agent_table, snmp_local_db2, snmp_mib_data, snmp_note_store, snmp_symbolic_ets, + tkFun, tkLink, tkPriv, + ttb, ttb_history_table, + udp_fds, udp_pids + ]. + +sys_processes() -> + [auth, code_server, global_name_server, inet_db, + mnesia_recover, net_kernel, timer_server, wxe_master]. + +mnesia_tables() -> + [ir_AliasDef, ir_ArrayDef, ir_AttributeDef, ir_ConstantDef, + ir_Contained, ir_Container, ir_EnumDef, ir_ExceptionDef, + ir_IDLType, ir_IRObject, ir_InterfaceDef, ir_ModuleDef, + ir_ORB, ir_OperationDef, ir_PrimitiveDef, ir_Repository, + ir_SequenceDef, ir_StringDef, ir_StructDef, ir_TypedefDef, + ir_UnionDef, logTable, logTransferTable, mesh_meas, + mesh_type, mnesia_clist, orber_CosNaming, + orber_objkeys, user + ]. + +handle_error(Foo) -> + try + Str = io_lib:format("ERROR: ~s~n",[Foo]), + display_info(Str) + catch _:_ -> + display_info(io_lib:format("ERROR: ~p~n",[Foo])) + end, + ok. + +display_info(Str) -> + Dlg = wxMessageDialog:new(wx:null(), Str), + wxMessageDialog:showModal(Dlg), + wxMessageDialog:destroy(Dlg), + ok. + +interval_dialog(Parent, Enabled, Value, Min, Max) -> + Dialog = wxDialog:new(Parent, ?wxID_ANY, "Update Interval", + [{style, ?wxDEFAULT_DIALOG_STYLE bor + ?wxRESIZE_BORDER}]), + Panel = wxPanel:new(Dialog), + Check = wxCheckBox:new(Panel, ?wxID_ANY, "Periodical refresh"), + wxCheckBox:setValue(Check, Enabled), + Style = ?wxSL_HORIZONTAL bor ?wxSL_AUTOTICKS bor ?wxSL_LABELS, + Slider = wxSlider:new(Panel, ?wxID_ANY, Value, Min, Max, + [{style, Style}, {size, {200, -1}}]), + wxWindow:enable(Slider, [{enable, Enabled}]), + InnerSizer = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), + Flags = [{flag, ?wxEXPAND bor ?wxALL}, {border, 2}], + wxSizer:add(InnerSizer, Check, Flags), + wxSizer:add(InnerSizer, Slider, Flags), + wxPanel:setSizer(Panel, InnerSizer), + TopSizer = wxBoxSizer:new(?wxVERTICAL), + wxSizer:add(TopSizer, Panel, [{flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + wxSizer:add(TopSizer, Buttons, [{flag, ?wxEXPAND}]), + wxWindow:setSizerAndFit(Dialog, TopSizer), + wxSizer:setSizeHints(TopSizer, Dialog), + wxCheckBox:connect(Check, command_checkbox_clicked, + [{callback, fun(#wx{event=#wxCommand{commandInt=Enable0}},_) -> + Enable = Enable0 > 0, + wxWindow:enable(Slider, [{enable, Enable}]) + end}]), + Res = case wxDialog:showModal(Dialog) of + ?wxID_OK -> + {wxCheckBox:isChecked(Check), wxSlider:getValue(Slider)}; + ?wxID_CANCEL -> + cancel + end, + wxDialog:destroy(Dialog), + Res. + + +update_grid(Grid, Opt, Tables) -> + wx:batch(fun() -> update_grid2(Grid, Opt, Tables) end). +update_grid2(Grid, #opt{sort_key=Sort,sort_incr=Dir}, Tables) -> + wxListCtrl:deleteAllItems(Grid), + Update = + fun(#tab{name = Name, id = Id, owner = Owner, size = Size, memory = Memory, + protection = Protection, reg_name = RegName}, Row) -> + _Item = wxListCtrl:insertItem(Grid, Row, ""), + if (Row rem 2) =:= 0 -> + wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255}); + true -> ignore + end, + if Protection == private -> + wxListCtrl:setItemTextColour(Grid, Row, {200,130,50}); + true -> ignore + end, + + lists:foreach(fun({_, ignore}) -> ignore; + ({Col, Val}) -> + wxListCtrl:setItem(Grid, Row, Col, to_str(Val)) + end, + [{0,Name}, {1,Id}, {2,Size}, {3, Memory div 1024}, + {4,Owner}, {5,RegName}]), + %% wxListCtrl:setItemData(Grid, Item, Item), + Row + 1 + end, + ProcInfo = case Dir of + false -> lists:reverse(lists:keysort(Sort, Tables)); + true -> lists:keysort(Sort, Tables) + end, + lists:foldl(Update, 0, ProcInfo), + ProcInfo. + +ignore(true, Reason) -> throw(Reason); +ignore(_,_ ) -> ok. |