%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2017. 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% %%%------------------------------------------------------------------- %%% File : wx_object.erl %%% Author : Dan Gudmundsson %%% Description : Frame work for erlang sub-classes. %%% %%% Created : 25 Nov 2008 by Dan Gudmundsson %%%------------------------------------------------------------------- %% %% @doc wx_object - Generic wx object behaviour %% %% This is a behaviour module that can be used for "sub classing" %% wx objects. It works like a regular gen_server module and creates %% a server per object. %% %% NOTE: Currently no form of inheritance is implemented. %% %% %% The user module should export: %% %% init(Args) should return
%% {wxObject, State} | {wxObject, State, Timeout} | %% ignore | {stop, Reason} %% %% Asynchronous window event handling:
%% handle_event(#wx{}, State) should return
%% {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State} %% %% The user module can export the following callback functions: %% %% handle_call(Msg, {From, Tag}, State) should return
%% {reply, Reply, State} | {reply, Reply, State, Timeout} | %% {noreply, State} | {noreply, State, Timeout} | %% {stop, Reason, Reply, State} %% %% handle_cast(Msg, State) should return
%% {noreply, State} | {noreply, State, Timeout} | %% {stop, Reason, State} %% %% If the above are not exported but called, the wx_object process will crash. %% The user module can also export: %% %% Info is message e.g. {'EXIT', P, R}, {nodedown, N}, ...
%% handle_info(Info, State) should return , ...
%% {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State} %% %% If a message is sent to the wx_object process when handle_info is not %% exported, the message will be dropped and ignored. %% %% When stop is returned in one of the functions above with Reason = %% normal | shutdown | Term, terminate(State) is called. It lets the %% user module clean up, it is always called when server terminates or %% when wx_object() in the driver is deleted. If the Parent process %% terminates the Module:terminate/2 function is called.
%% terminate(Reason, State) %% %% %% Example: %% %% ``` %% -module(myDialog). %% -export([new/2, show/1, destroy/1]). %% API %% -export([init/1, handle_call/3, handle_event/2, %% handle_info/2, code_change/3, terminate/2]). %% new/2, showModal/1, destroy/1]). %% Callbacks %% %% %% Client API %% new(Parent, Msg) -> %% wx_object:start(?MODULE, [Parent,Id], []). %% %% show(Dialog) -> %% wx_object:call(Dialog, show_modal). %% %% destroy(Dialog) -> %% wx_object:call(Dialog, destroy). %% %% %% Server Implementation ala gen_server %% init([Parent, Str]) -> %% Dialog = wxDialog:new(Parent, 42, "Testing", []), %% ... %% wxDialog:connect(Dialog, command_button_clicked), %% {Dialog, MyState}. %% %% handle_call(show, _From, State) -> %% wxDialog:show(State#state.win), %% {reply, ok, State}; %% ... %% handle_event(#wx{}, State) -> %% io:format("Users clicked button~n",[]), %% {noreply, State}; %% ... %% ''' -module(wx_object). -include("wxe.hrl"). -include("../include/wx.hrl"). %% API -export([start/3, start/4, start_link/3, start_link/4, stop/1, stop/3, call/2, call/3, cast/2, reply/2, get_pid/1, set_pid/2 ]). %% -export([behaviour_info/1]). -callback init(Args :: term()) -> {#wx_ref{}, State :: term()} | {#wx_ref{}, State :: term(), timeout() | 'hibernate'} | {'stop', Reason :: term()} | 'ignore'. -callback handle_event(Request :: #wx{}, State :: term()) -> {'noreply', NewState :: term()} | {'noreply', NewState :: term(), timeout() | 'hibernate'} | {'stop', Reason :: term(), NewState :: term()}. -callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, State :: term()) -> {'reply', Reply :: term(), NewState :: term()} | {'reply', Reply :: term(), NewState :: term(), timeout() | 'hibernate'} | {'noreply', NewState :: term()} | {'noreply', NewState :: term(), timeout() | 'hibernate'} | {'stop', Reason :: term(), Reply :: term(), NewState :: term()} | {'stop', Reason :: term(), NewState :: term()}. -callback handle_cast(Request :: term(), State :: term()) -> {'noreply', NewState :: term()} | {'noreply', NewState :: term(), timeout() | 'hibernate'} | {'stop', Reason :: term(), NewState :: term()}. -callback handle_info(Info :: timeout() | term(), State :: term()) -> {'noreply', NewState :: term()} | {'noreply', NewState :: term(), timeout() | 'hibernate'} | {'stop', Reason :: term(), NewState :: term()}. -callback handle_sync_event(Request :: #wx{}, Ref :: #wx_ref{}, State :: term()) -> ok. -callback terminate(Reason :: ('normal' | 'shutdown' | {'shutdown', term()} | term()), State :: term()) -> term(). -callback code_change(OldVsn :: (term() | {'down', term()}), State :: term(), Extra :: term()) -> {'ok', NewState :: term()} | {'error', Reason :: term()}. -optional_callbacks( [handle_call/3, handle_cast/2, handle_info/2, handle_sync_event/3, terminate/2, code_change/3]). %% System exports -export([system_continue/3, system_terminate/4, system_code_change/4, format_status/2]). %% Internal exports -export([init_it/6]). -import(error_logger, [format/2]). %%%========================================================================= %%% API %%%========================================================================= %% @hidden %% behaviour_info(callbacks) -> %% [{init,1}, %% {handle_call,3}, %% {handle_info,2}, %% {handle_event,2}, %% {terminate,2}, %% {code_change,3}]; %% behaviour_info(_Other) -> %% undefined. %% ----------------------------------------------------------------- %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. -spec start(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when Mod::atom(), Args::term(), Flag::trace | log | {logfile, string()} | statistics | debug, Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. -spec start(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when Name::{local, atom()}, Mod::atom(), Args::term(), Flag::trace | log | {logfile, string()} | statistics | debug, Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. -spec start_link(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when Mod::atom(), Args::term(), Flag::trace | log | {logfile, string()} | statistics | debug, Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. -spec start_link(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when Name::{local, atom()}, Mod::atom(), Args::term(), Flag::trace | log | {logfile, string()} | statistics | debug, Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). gen_response({ok, Pid}) -> receive {ack, Pid, Ref = #wx_ref{}} -> Ref end; gen_response(Reply) -> Reply. %% @doc Stops a generic wx_object server with reason 'normal'. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the process does not exist, an %% exception is raised. -spec stop(Obj) -> ok when Obj::wx:wx_object()|atom()|pid(). stop(Ref = #wx_ref{state=Pid}) when is_pid(Pid) -> try gen:stop(Pid) catch _:ExitReason -> erlang:error({ExitReason, {?MODULE, stop, [Ref]}}) end; stop(Name) when is_atom(Name) orelse is_pid(Name) -> try gen:stop(Name) catch _:ExitReason -> erlang:error({ExitReason, {?MODULE, stop, [Name]}}) end. %% @doc Stops a generic wx_object server with the given Reason. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the call times out, or if the process %% does not exist, an exception is raised. -spec stop(Obj, Reason, Timeout) -> ok when Obj::wx:wx_object()|atom()|pid(), Reason::term(), Timeout::timeout(). stop(Ref = #wx_ref{state=Pid}, Reason, Timeout) when is_pid(Pid) -> try gen:stop(Pid, Reason, Timeout) catch _:ExitReason -> erlang:error({ExitReason, {?MODULE, stop, [Ref, Reason, Timeout]}}) end; stop(Name, Reason, Timeout) when is_atom(Name) orelse is_pid(Name) -> try gen:stop(Name, Reason, Timeout) catch _:ExitReason -> erlang:error({ExitReason, {?MODULE, stop, [Name, Reason, Timeout]}}) end. %% @doc Make a call to a wx_object server. %% The call waits until it gets a result. %% Invokes handle_call(Request, From, State) in the server -spec call(Obj, Request) -> term() when Obj::wx:wx_object()|atom()|pid(), Request::term(). call(Ref = #wx_ref{state=Pid}, Request) when is_pid(Pid) -> try {ok,Res} = gen:call(Pid, '$gen_call', Request, infinity), Res catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Ref, Request]}}) end; call(Name, Request) when is_atom(Name) orelse is_pid(Name) -> try {ok,Res} = gen:call(Name, '$gen_call', Request, infinity), Res catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Name, Request]}}) end. %% @doc Make a call to a wx_object server with a timeout. %% Invokes handle_call(Request, From, State) in server -spec call(Obj, Request, Timeout) -> term() when Obj::wx:wx_object()|atom()|pid(), Request::term(), Timeout::integer(). call(Ref = #wx_ref{state=Pid}, Request, Timeout) when is_pid(Pid) -> try {ok,Res} = gen:call(Pid, '$gen_call', Request, Timeout), Res catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Ref, Request, Timeout]}}) end; call(Name, Request, Timeout) when is_atom(Name) orelse is_pid(Name) -> try {ok,Res} = gen:call(Name, '$gen_call', Request, Timeout), Res catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. %% @doc Make a cast to a wx_object server. %% Invokes handle_cast(Request, State) in the server -spec cast(Obj, Request) -> ok when Obj::wx:wx_object()|atom()|pid(), Request::term(). cast(#wx_ref{state=Pid}, Request) when is_pid(Pid) -> Pid ! {'$gen_cast',Request}, ok; cast(Name, Request) when is_atom(Name) orelse is_pid(Name) -> Name ! {'$gen_cast',Request}, ok. %% @doc Get the pid of the object handle. -spec get_pid(Obj) -> pid() when Obj::wx:wx_object()|atom()|pid(). get_pid(#wx_ref{state=Pid}) when is_pid(Pid) -> Pid. %% @doc Sets the controlling process of the object handle. -spec set_pid(Obj, pid()) -> wx:wx_object() when Obj::wx:wx_object()|atom()|pid(). set_pid(#wx_ref{}=R, Pid) when is_pid(Pid) -> R#wx_ref{state=Pid}. %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- %% @doc Get the pid of the object handle. -spec reply({pid(), Tag::term()}, Reply::term()) -> pid(). reply({To, Tag}, Reply) -> catch To ! {Tag, Reply}. %%%======================================================================== %%% Gen-callback functions %%%======================================================================== %%% --------------------------------------------------- %%% Initiate the new process. %%% Register the name using the Rfunc function %%% Calls the Mod:init/Args function. %%% Finally an acknowledge is sent to Parent and the main %%% loop is entered. %%% --------------------------------------------------- %% @hidden init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name, Mod, Args, [WxEnv|Options]) -> case WxEnv of undefined -> ok; _ -> wx:set_env(WxEnv) end, put('_wx_object_', {Mod,'_wx_init_'}), Debug = debug_options(Name, Options), case catch Mod:init(Args) of {#wx_ref{} = Ref, State} -> init_it2(Ref, Starter, Parent, Name, State, Mod, infinity, Debug); {#wx_ref{} = Ref, State, Timeout} -> init_it2(Ref, Starter, Parent, Name, State, Mod, Timeout, Debug); {stop, Reason} -> proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> Error = {bad_return_value, Else}, proc_lib:init_ack(Starter, {error, Error}), exit(Error) end. %% @hidden init_it2(Ref, Starter, Parent, Name, State, Mod, Timeout, Debug) -> ok = wxe_util:register_pid(Ref), case ?CLASS_T(Ref#wx_ref.type, wxWindow) of false -> Reason = {Ref, "not a wxWindow subclass"}, proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); true -> proc_lib:init_ack(Starter, {ok, self()}), proc_lib:init_ack(Starter, Ref#wx_ref{state=self()}), loop(Parent, Name, State, Mod, Timeout, Debug) end. %%%======================================================================== %%% Internal functions %%%======================================================================== %%% --------------------------------------------------- %%% The MAIN loop. %%% --------------------------------------------------- %% @hidden loop(Parent, Name, State, Mod, Time, Debug) -> put('_wx_object_', {Mod,State}), Msg = receive Input -> Input after Time -> timeout end, case Msg of {system, From, Req} -> sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [Name, State, Mod, Time]); {'EXIT', Parent, Reason} -> terminate(Reason, Name, Msg, Mod, State, Debug); {'_wxe_destroy_', _Me} -> terminate(wx_deleted, Name, Msg, Mod, State, Debug); _Msg when Debug =:= [] -> handle_msg(Msg, Parent, Name, State, Mod); _Msg -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {in, Msg}), handle_msg(Msg, Parent, Name, State, Mod, Debug1) end. %%% --------------------------------------------------- %%% Message handling functions %%% --------------------------------------------------- %% @hidden dispatch({'$gen_cast', Msg}, Mod, State) -> Mod:handle_cast(Msg, State); dispatch(Msg = #wx{}, Mod, State) -> Mod:handle_event(Msg, State); dispatch(Info, Mod, State) -> Mod:handle_info(Info, State). %% @hidden handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> reply(From, Reply), loop(Parent, Name, NState, Mod, infinity, []); {reply, Reply, NState, Time1} -> reply(From, Reply), loop(Parent, Name, NState, Mod, Time1, []); {noreply, NState} -> loop(Parent, Name, NState, Mod, infinity, []); {noreply, NState, Time1} -> loop(Parent, Name, NState, Mod, Time1, []); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, [])), reply(From, Reply), exit(R); Other -> handle_common_reply(Other, Name, Msg, Mod, State, []) end; handle_msg(Msg, Parent, Name, State, Mod) -> case catch dispatch(Msg, Mod, State) of {'EXIT', {undef, [{Mod, handle_info, [_,_], _}|_]}} -> handle_no_reply({noreply, State}, Parent, Name, Msg, Mod, State, []); Reply -> handle_no_reply(Reply, Parent, Name, Msg, Mod, State, []) end. %% @hidden handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) -> case catch Mod:handle_call(Msg, From, State) of {reply, Reply, NState} -> Debug1 = reply(Name, From, Reply, NState, Debug), loop(Parent, Name, NState, Mod, infinity, Debug1); {reply, Reply, NState, Time1} -> Debug1 = reply(Name, From, Reply, NState, Debug), loop(Parent, Name, NState, Mod, Time1, Debug1); {noreply, NState} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, infinity, Debug1); {noreply, NState, Time1} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, Time1, Debug1); {stop, Reason, Reply, NState} -> {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, NState, Debug)), _ = reply(Name, From, Reply, NState, Debug), exit(R); Other -> handle_common_reply(Other, Name, Msg, Mod, State, Debug) end; handle_msg(Msg, Parent, Name, State, Mod, Debug) -> Reply = (catch dispatch(Msg, Mod, State)), handle_no_reply(Reply, Parent, Name, Msg, Mod, State, Debug). %% @hidden handle_no_reply({noreply, NState}, Parent, Name, _Msg, Mod, _State, []) -> loop(Parent, Name, NState, Mod, infinity, []); handle_no_reply({noreply, NState, Time1}, Parent, Name, _Msg, Mod, _State, []) -> loop(Parent, Name, NState, Mod, Time1, []); handle_no_reply({noreply, NState}, Parent, Name, _Msg, Mod, _State, Debug) -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, infinity, Debug1); handle_no_reply({noreply, NState, Time1}, Parent, Name, _Msg, Mod, _State, Debug) -> Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, {noreply, NState}), loop(Parent, Name, NState, Mod, Time1, Debug1); handle_no_reply(Reply, _Parent, Name, Msg, Mod, State, Debug) -> handle_common_reply(Reply, Name, Msg, Mod, State,Debug). %% @hidden -spec handle_common_reply(_, _, _, _, _, _) -> no_return(). handle_common_reply(Reply, Name, Msg, Mod, State, Debug) -> case Reply of {stop, Reason, NState} -> terminate(Reason, Name, Msg, Mod, NState, Debug); {'EXIT', What} -> terminate(What, Name, Msg, Mod, State, Debug); _ -> terminate({bad_return_value, Reply}, Name, Msg, Mod, State, Debug) end. %% @hidden reply(Name, {To, Tag}, Reply, State, Debug) -> reply({To, Tag}, Reply), sys:handle_debug(Debug, fun print_event/3, Name, {out, Reply, To, State}). %%----------------------------------------------------------------- %% Callback functions for system messages handling. %%----------------------------------------------------------------- %% @hidden system_continue(Parent, Debug, [Name, State, Mod, Time]) -> loop(Parent, Name, State, Mod, Time, Debug). %% @hidden -spec system_terminate(_, _, _, [_]) -> no_return(). system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) -> terminate(Reason, Name, [], Mod, State, Debug). %% @hidden system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) -> case catch Mod:code_change(OldVsn, State, Extra) of {ok, NewState} -> {ok, [Name, NewState, Mod, Time]}; Else -> Else end. %%----------------------------------------------------------------- %% Format debug messages. Print them as the call-back module sees %% them, not as the real erlang messages. Use trace for that. %%----------------------------------------------------------------- print_event(Dev, {in, Msg}, Name) -> case Msg of {'$gen_call', {From, _Tag}, Call} -> io:format(Dev, "*DBG* ~tp got call ~tp from ~w~n", [Name, Call, From]); {'$gen_cast', Cast} -> io:format(Dev, "*DBG* ~tp got cast ~tp~n", [Name, Cast]); _ -> io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg]) end; print_event(Dev, {out, Msg, To, State}, Name) -> io:format(Dev, "*DBG* ~tp sent ~tp to ~w, new state ~tp~n", [Name, Msg, To, State]); print_event(Dev, {noreply, State}, Name) -> io:format(Dev, "*DBG* ~tp new state ~tp~n", [Name, State]); print_event(Dev, Event, Name) -> io:format(Dev, "*DBG* ~tp dbg ~tp~n", [Name, Event]). %%% --------------------------------------------------- %%% Terminate the server. %%% --------------------------------------------------- %% @hidden terminate(Reason, Name, Msg, Mod, State, Debug) -> case try_terminate(Mod, Reason, State) of {'EXIT', R} -> error_info(R, Name, Msg, State, Debug), exit(R); _ -> case Reason of normal -> exit(normal); shutdown -> exit(shutdown); wx_deleted -> exit(normal); _ -> error_info(Reason, Name, Msg, State, Debug), exit(Reason) end end. try_terminate(Mod, Reason, State) -> case erlang:function_exported(Mod, terminate, 2) of true -> catch Mod:terminate(Reason, State); _ -> ok end. %% @hidden error_info(_Reason, application_controller, _Msg, _State, _Debug) -> ok; error_info(Reason, Name, Msg, State, Debug) -> Reason1 = case Reason of {undef,[{M,F,A,L}|MFAs]} -> case code:is_loaded(M) of false -> {'module could not be loaded',[{M,F,A,L}|MFAs]}; _ -> case erlang:function_exported(M, F, length(A)) of true -> Reason; false -> {'function not exported',[{M,F,A,L}|MFAs]} end end; _ -> Reason end, format("** wx object server ~tp terminating \n" "** Last message in was ~tp~n" "** When Server state == ~tp~n" "** Reason for termination == ~n** ~tp~n", [Name, Msg, State, Reason1]), sys:print_log(Debug), ok. %%% --------------------------------------------------- %%% Misc. functions. %%% --------------------------------------------------- %% @hidden opt(Op, [{Op, Value}|_]) -> {ok, Value}; opt(Op, [_|Options]) -> opt(Op, Options); opt(_, []) -> false. %% @hidden debug_options(Name, Opts) -> case opt(debug, Opts) of {ok, Options} -> dbg_opts(Name, Options); _ -> [] end. %% @hidden dbg_opts(Name, Opts) -> case catch sys:debug_options(Opts) of {'EXIT',_} -> format("~tp: ignoring erroneous debug options - ~tp~n", [Name, Opts]), []; Dbg -> Dbg end. %% @hidden %%----------------------------------------------------------------- %% Status information %%----------------------------------------------------------------- format_status(Opt, StatusData) -> [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = StatusData, StatusHdr = "Status for wx object ", Header = if is_pid(Name) -> lists:concat([StatusHdr, pid_to_list(Name)]); is_atom(Name); is_list(Name) -> lists:concat([StatusHdr, Name]); true -> {StatusHdr, Name} end, Log = sys:get_debug(log, Debug, []), Specfic = case erlang:function_exported(Mod, format_status, 2) of true -> case catch Mod:format_status(Opt, [PDict, State]) of {'EXIT', _} -> [{data, [{"State", State}]}]; Else -> Else end; _ -> [{data, [{"State", State}]}] end, [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, {"Logged events", Log}]} | Specfic].