diff options
Diffstat (limited to 'lib/wx')
-rw-r--r-- | lib/wx/c_src/wxe_impl.cpp | 32 | ||||
-rw-r--r-- | lib/wx/c_src/wxe_impl.h | 2 | ||||
-rw-r--r-- | lib/wx/examples/demo/demo.erl | 3 | ||||
-rw-r--r-- | lib/wx/src/wx_object.erl | 49 | ||||
-rw-r--r-- | lib/wx/src/wxe_master.erl | 10 | ||||
-rw-r--r-- | lib/wx/test/Makefile | 1 | ||||
-rw-r--r-- | lib/wx/test/wx_basic_SUITE.erl | 155 | ||||
-rw-r--r-- | lib/wx/test/wx_obj_test.erl | 5 | ||||
-rw-r--r-- | lib/wx/test/wx_oc_object.erl | 44 |
9 files changed, 274 insertions, 27 deletions
diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp index 0d2da5d4a7..90cd35455a 100644 --- a/lib/wx/c_src/wxe_impl.cpp +++ b/lib/wx/c_src/wxe_impl.cpp @@ -128,7 +128,7 @@ bool WxeApp::OnInit() delayed_cleanup = new wxList; wxe_ps_init2(); - // wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); // Hmm printpreview doesn't work in 2.9 with this + wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); Connect(wxID_ANY, wxEVT_IDLE, (wxObjectEventFunction) (wxEventFunction) &WxeApp::idle); Connect(CREATE_PORT, wxeEVT_META_COMMAND,(wxObjectEventFunction) (wxEventFunction) &WxeApp::newMemEnv); @@ -200,7 +200,8 @@ void WxeApp::OnAssertFailure(const wxChar *file, int line, const wxChar *cfunc, // Called by wx thread void WxeApp::idle(wxIdleEvent& event) { event.Skip(true); - dispatch_cmds(); + if(dispatch_cmds()) + event.RequestMore(); } /* ************************************************************ @@ -233,14 +234,15 @@ void handle_event_callback(ErlDrvPort port, ErlDrvTermData process) } } -void WxeApp::dispatch_cmds() +int WxeApp::dispatch_cmds() { + int more = 0; if(wxe_status != WXE_INITIATED) - return; + return more; recurse_level++; // fprintf(stderr, "\r\ndispatch_normal %d\r\n", recurse_level);fflush(stderr); wxe_queue->cb_start = 0; - dispatch(wxe_queue); + more = dispatch(wxe_queue); // fprintf(stderr, "\r\ndispatch_done %d\r\n", recurse_level);fflush(stderr); recurse_level--; @@ -262,12 +264,14 @@ void WxeApp::dispatch_cmds() delete event; } } + return more; } int WxeApp::dispatch(wxeFifo * batch) { int ping = 0; int blevel = 0; + int wait = 0; // Let event handling generate events sometime wxeCommand *event; erl_drv_mutex_lock(wxe_batch_locker_m); while(true) { @@ -275,10 +279,10 @@ int WxeApp::dispatch(wxeFifo * batch) erl_drv_mutex_unlock(wxe_batch_locker_m); switch(event->op) { case WXE_BATCH_END: - {--blevel; } + if(blevel>0) blevel--; break; case WXE_BATCH_BEGIN: - {blevel++; } + blevel++; break; case WXE_DEBUG_PING: // When in debugger we don't want to hang waiting for a BATCH_END @@ -293,7 +297,7 @@ int WxeApp::dispatch(wxeFifo * batch) memcpy(cb_buff, event->buffer, event->len); } event->Delete(); - return blevel; + return 1; default: if(event->op < OPENGL_START) { // fprintf(stderr, " c %d (%d) \r\n", event->op, blevel); @@ -307,13 +311,15 @@ int WxeApp::dispatch(wxeFifo * batch) erl_drv_mutex_lock(wxe_batch_locker_m); batch->Cleanup(); } - if(blevel <= 0) { + if(blevel <= 0 || wait > 3) { erl_drv_mutex_unlock(wxe_batch_locker_m); - return blevel; + if(blevel > 0) return 1; // We are still in a batch but we can let wx check for events + else return 0; } // sleep until something happens - //fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__, batch->m_n, blevel);fflush(stderr); + // fprintf(stderr, "%s:%d sleep %d %d %d\r\n", __FILE__, __LINE__, batch->m_n, blevel, wait);fflush(stderr); wxe_needs_signal = 1; + wait += 1; while(batch->m_n == 0) { erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); } @@ -666,7 +672,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) { throw wxe_badarg(index); } void * temp = memenv->ref2ptr[index]; - if((index < memenv->next) && ((index == 0) || (temp > NULL))) + if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL))) return temp; else { throw wxe_badarg(index); @@ -678,7 +684,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) { if(!memenv) throw wxe_badarg(index); void * temp = memenv->ref2ptr[index]; - if((index < memenv->next) && ((index == 0) || (temp > NULL))) { + if((index < memenv->next) && ((index == 0) || (temp != (void *) NULL))) { ptrMap::iterator it; it = ptr2ref.find(temp); if(it != ptr2ref.end()) { diff --git a/lib/wx/c_src/wxe_impl.h b/lib/wx/c_src/wxe_impl.h index 57dac997ab..68f5deb336 100644 --- a/lib/wx/c_src/wxe_impl.h +++ b/lib/wx/c_src/wxe_impl.h @@ -73,7 +73,7 @@ public: void wxe_dispatch(wxeCommand& event); void idle(wxIdleEvent& event); - void dispatch_cmds(); + int dispatch_cmds(); void dummy_close(wxEvent& Ev); bool sendevent(wxEvent *event); diff --git a/lib/wx/examples/demo/demo.erl b/lib/wx/examples/demo/demo.erl index 8b7412017a..0258202a67 100644 --- a/lib/wx/examples/demo/demo.erl +++ b/lib/wx/examples/demo/demo.erl @@ -243,6 +243,9 @@ handle_event(#wx{id = Id, %% If you are going to printout mainly text it is easier if %% you generate HTML code and use a wxHtmlEasyPrint %% instead of using DCs + + %% Printpreview doesn't work in >2.9 without this + wxIdleEvent:setMode(?wxIDLE_PROCESS_ALL), Module = "ex_" ++ wxListBox:getStringSelection(State#state.selector) ++ ".erl", HEP = wxHtmlEasyPrinting:new([{name, "Print"}, {parentWindow, State#state.win}]), diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl index 40170b6eb1..1907e3c725 100644 --- a/lib/wx/src/wx_object.erl +++ b/lib/wx/src/wx_object.erl @@ -39,19 +39,31 @@ %% {wxObject, State} | {wxObject, State, Timeout} | %% ignore | {stop, Reason} %% +%% Asynchronous window event handling: <br/> +%% handle_event(#wx{}, State) should return <br/> +%% {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 <br/> %% {reply, Reply, State} | {reply, Reply, State, Timeout} | %% {noreply, State} | {noreply, State, Timeout} | %% {stop, Reason, Reply, State} %% -%% Asynchronous window event handling: <br/> -%% handle_event(#wx{}, State) should return <br/> -%% {noreply, State} | {noreply, State, Timeout} | {stop, Reason, State} +%% handle_cast(Msg, State) should return <br/> +%% {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}, ... <br/> %% handle_info(Info, State) should return , ... <br/> %% {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 @@ -135,6 +147,8 @@ {'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()) -> @@ -143,6 +157,9 @@ 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, @@ -426,6 +443,7 @@ 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 @@ -447,8 +465,12 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) -> Other -> handle_common_reply(Other, Name, Msg, Mod, State, []) end; handle_msg(Msg, Parent, Name, State, Mod) -> - Reply = (catch dispatch(Msg, Mod, State)), - handle_no_reply(Reply, Parent, Name, Msg, Mod, State, []). + 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) -> @@ -528,8 +550,8 @@ system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) -> %% @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 + {ok, NewState} -> {ok, [Name, NewState, Mod, Time]}; + Else -> Else end. %%----------------------------------------------------------------- @@ -560,7 +582,7 @@ print_event(Dev, Event, Name) -> %%% --------------------------------------------------- %% @hidden terminate(Reason, Name, Msg, Mod, State, Debug) -> - case catch Mod:terminate(Reason, State) of + case try_terminate(Mod, Reason, State) of {'EXIT', R} -> error_info(R, Name, Msg, State, Debug), exit(R); @@ -577,6 +599,15 @@ terminate(Reason, Name, Msg, Mod, 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; diff --git a/lib/wx/src/wxe_master.erl b/lib/wx/src/wxe_master.erl index e17a3327ac..913bf4d41b 100644 --- a/lib/wx/src/wxe_master.erl +++ b/lib/wx/src/wxe_master.erl @@ -82,8 +82,14 @@ init_port(SilentStart) -> %% Initalizes the opengl library %%-------------------------------------------------------------------- init_opengl() -> - GLLib = wxe_util:wxgl_dl(), - wxe_util:call(?WXE_INIT_OPENGL, <<(list_to_binary(GLLib))/binary, 0:8>>). + case get(wx_init_opengl) of + true -> {ok, "already initialized"}; + _ -> + GLLib = wxe_util:wxgl_dl(), + Res = wxe_util:call(?WXE_INIT_OPENGL, <<(list_to_binary(GLLib))/binary, 0:8>>), + element(1, Res) =:= ok andalso put(wx_init_opengl, true), + Res + end. %%-------------------------------------------------------------------- %% Fetch early messages, hack to get start up args on mac diff --git a/lib/wx/test/Makefile b/lib/wx/test/Makefile index 9a78307be1..965db228fb 100644 --- a/lib/wx/test/Makefile +++ b/lib/wx/test/Makefile @@ -29,6 +29,7 @@ APPDIR = $(shell dirname $(PWD)) ERL_COMPILE_FLAGS = -pa $(APPDIR)/ebin Mods = wxt wx_test_lib wx_obj_test \ + wx_oc_object \ wx_app_SUITE \ wx_basic_SUITE \ wx_event_SUITE \ diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index 6a2528780e..d0ec0b1f26 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -49,10 +49,13 @@ suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [silent_start, create_window, several_apps, wx_api, wx_misc, - data_types, wx_object]. + data_types, wx_object, {group, undef_callbacks}, + undef_in_handle_info, undef_in_terminate]. groups() -> - []. + [{undef_callbacks, [], + [undef_handle_event, undef_handle_call, undef_handle_cast, undef_handle_info, + undef_code_change, undef_terminate1, undef_terminate2]}]. init_per_group(_GroupName, Config) -> Config. @@ -426,6 +429,154 @@ wx_object(Config) -> catch wx:destroy(), ok. +%% Test that the server crashes correctly if the handle_event callback is +%% not exported in the callback module +undef_handle_event(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_handle_event(_Config) -> + wx:new(), + {_, _, _, Pid} = wx_object:start(wx_oc_object, [], []), + MRef = monitor(process, Pid), + %% Mock a call to handle_event + Pid ! {wx, a, b, c, d}, + ok = receive + {'DOWN', MRef, process, Pid, + {undef, [{wx_oc_object, handle_event, _, _}|_]}} -> + ok + after 5000 -> + ct:fail(should_crash) + end. + +%% Test that the server crashes correctly if the handle_call callback is +%% not exported in the callback module +undef_handle_call(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_handle_call(_Config) -> + wx:new(), + Frame = wx_object:start(wx_oc_object, [], []), + try + wx_object:call(Frame, call_msg), + ct:fail(should_crash) + catch error:{{undef, [{wx_oc_object,handle_call, _, _}|_]}, + {wx_object,call,_}} -> + ok + end. + +%% Test that the server crashes correctly if the handle_cast callback is +%% not exported in the callback module +undef_handle_cast(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_handle_cast(_Config) -> + wx:new(), + {_, _, _, Pid} = Frame = wx_object:start(wx_oc_object, [], []), + MRef = monitor(process, Pid), + wx_object:cast(Frame, cast_msg), + ok = receive + {'DOWN', MRef, process, Pid, + {undef, [{wx_oc_object, handle_cast, _, _}|_]}} -> + ok + after 5000 -> + ct:fail(should_crash) + end. + +%% Test the default implementation of handle_info if the callback module +%% does not export it +undef_handle_info(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_handle_info(_Config) -> + wx:new(), + {_, _, _, Pid} = wx_object:start(wx_oc_object, [], []), + MRef = monitor(process, Pid), + Pid ! test, + receive + {'DOWN', MRef, process, Pid, _} -> + ct:fail(should_not_crash) + after 500 -> + ok + end, + ok = wx_object:stop(Pid). + +%% Test the server crashes correctly if called and the code_change callback is +%% not exported in the callback module +undef_code_change(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_code_change(_Config) -> + wx:new(), + {_, _, _, Pid} = wx_object:start(wx_oc_object, [], []), + sys:suspend(Pid), + sys:replace_state(Pid, fun([P, S, M, T]) -> [P, {new, S}, M, T] end), + {error, {'EXIT', {undef, [{wx_oc_object,code_change, [_, _, _], _}|_]}}} + = sys:change_code(Pid, wx_oc_object, old_vsn, []), + ok = sys:resume(Pid), + ok = wx_object:stop(Pid). + +%% Test the default implementation of terminate if the callback module +%% does not export it +undef_terminate1(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_terminate1(_Config) -> + ok = terminate([], normal). + +%% Test the default implementation of terminate if the callback module +%% does not export it +undef_terminate2(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_terminate2(_Config) -> + ok = terminate([{error, test}, infinity], {error, test}). + +terminate(ArgsTl, Reason) -> + wx:new(), + {_, _, _, Pid} = wx_object:start(wx_oc_object, [], []), + MRef = monitor(process, Pid), + ok = apply(wx_object, stop, [Pid|ArgsTl]), + receive + {'DOWN', MRef, process, Pid, Reason} -> + ok + after 1000 -> + ct:fail(failed) + end. + +%% Test that the server crashes correctly if the handle_info callback is +%% calling an undefined function +undef_in_handle_info(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_in_handle_info(_Config) -> + wx:new(), + Init = ui_init_fun(), + {_, _, _, Pid} = wx_object:start(wx_obj_test, + [{parent, self()}, {init, Init}], []), + unlink(Pid), + MRef = monitor(process, Pid), + Pid ! {call_undef_fun, {wx_obj_test, handle_info}}, + receive + {'DOWN', MRef, process, Pid, + {undef, [{wx_obj_test, handle_info, _, _}|_]}} -> + ok + after 1000 -> + ct:fail(failed) + end, + ok. + +%% Test that the server crashes correctly if the terminate callback is +%% calling an undefined function +undef_in_terminate(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); +undef_in_terminate(_Config) -> + wx:new(), + Init = ui_init_fun(), + Frame = wx_object:start(wx_obj_test, + [{parent, self()}, {init, Init}, + {terminate, {wx_obj_test, terminate}}], []), + try + wx_object:stop(Frame), + ct:fail(should_crash) + catch error:{{undef, [{wx_obj_test, terminate, _, _}|_]}, _} -> + ok + end. + +ui_init_fun() -> + Init = fun() -> + Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame = wx_object:set_pid(Frame0, self()), + Sz = wxBoxSizer:new(?wxHORIZONTAL), + Panel = wxPanel:new(Frame), + wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxWindow:show(Frame), + {Frame, {Frame, Panel}} + end, + Init. + check_events(Msgs) -> check_events(Msgs, 0,0). diff --git a/lib/wx/test/wx_obj_test.erl b/lib/wx/test/wx_obj_test.erl index 23142e28b2..1b0a9e9091 100644 --- a/lib/wx/test/wx_obj_test.erl +++ b/lib/wx/test/wx_obj_test.erl @@ -79,6 +79,9 @@ handle_cast(What, State = #state{parent=Pid}) -> Pid ! {cast, What}, {noreply, State}. +handle_info({call_undef_fun, {Mod, Fun}}, State) -> + Mod:Fun(), + {noreply, State}; handle_info(What, State = #state{parent=Pid}) -> Pid ! {info, What}, {noreply, State}. @@ -87,6 +90,8 @@ terminate(What, #state{parent=Pid, opts=Opts, user_state=US}) -> case proplists:get_value(terminate, Opts) of undefined -> ok; + {Mod, Fun} -> + Mod:Fun(); Terminate -> Terminate(US) end, diff --git a/lib/wx/test/wx_oc_object.erl b/lib/wx/test/wx_oc_object.erl new file mode 100644 index 0000000000..3924202410 --- /dev/null +++ b/lib/wx/test/wx_oc_object.erl @@ -0,0 +1,44 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%% +-module(wx_oc_object). +-include_lib("wx/include/wx.hrl"). + +-behaviour(wx_object). + +%% gen_server callbacks +-export([init/1]). + +-record(state, {}). + +init([]) -> + Init = fun() -> + Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame = wx_object:set_pid(Frame0, self()), + Sz = wxBoxSizer:new(?wxHORIZONTAL), + Panel = wxPanel:new(Frame), + wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), + wxWindow:show(Frame), + {Frame, {Frame, Panel}} + end, + {Obj, _UserState} = Init(), + {Obj, #state{}}; +init([Init]) -> + {Obj, _UserState} = Init(), + {Obj, #state{}}. |