Skip to content

Commit

Permalink
Merge pull request #210 from mcorino/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
mcorino committed Oct 31, 2023
2 parents b5a05ff + 56d826b commit c657b15
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 342 deletions.
338 changes: 338 additions & 0 deletions ext/wxruby3/include/wxRubyApp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
// Copyright (c) 2023 M.J.N. Corino, The Netherlands
//
// This software is released under the MIT license.

#include <memory>

/*
* WxRuby3 App class
*/

class wxRubyApp : public wxApp
{
private:
bool is_running_ = false;
VALUE self_ = Qnil;
public:
static wxRubyApp* GetInstance () { return dynamic_cast<wxRubyApp*> (wxApp::GetInstance()); }

virtual ~wxRubyApp()
{
#ifdef __WXTRACE__
std::wcout << "~wxRubyApp" << std::endl;
#endif
// unlink
if (this->self_ != Qnil)
{
DATA_PTR(this->self_) = 0;
}
this->self_ = Qnil;
}

// special event handler for destruction of windows which is done
// automatically by wxWidgets. Tag the object as having been destroyed
// by WxWidgets.
void OnWindowDestroy(wxWindowDestroyEvent &event)
{
wxObject* wx_obj = event.GetEventObject();
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "<= OnWindowDestroy [" << wx_obj << "]" << std::endl;
#endif
GC_SetWindowDeleted((void *)wx_obj);
event.Skip();
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "=> OnWindowDestroy [" << wx_obj << "]" << std::endl;
#endif
}

bool IsRunning() const { return this->is_running_; }

// When ruby's garbage collection runs, if the app is still active, it
// cycles through all currently known SWIG objects and calls this
// function on each to preserve still active Wx::Windows, and also
// pending Wx::Events which have been queued from the Ruby side (the
// only sort of events that will be in the tracking hash.
static void markIterate(void* ptr, VALUE rb_obj)
{
// Check if it's a valid object (sometimes SWIG doesn't return what we're
// expecting), a descendant of Wx::Window (but not a Dialog), and if it has not yet been
// deleted by WxWidgets; if so, mark it.
if ( TYPE(rb_obj) == T_DATA )
{
if ( rb_obj_is_kind_of(rb_obj, wxRuby_GetWindowClass()) )
{
rb_gc_mark(rb_obj);
}
else if (rb_obj_is_kind_of(rb_obj, wxRuby_GetDefaultEventClass()) )
rb_gc_mark(rb_obj);
}
else if (TYPE(rb_obj) == T_ARRAY )
{
VALUE proc = rb_ary_entry(rb_obj, 0);
if (rb_obj_is_kind_of(proc, rb_cProc) || rb_obj_is_kind_of(proc, rb_cMethod))
{
// keep the async call alive
rb_gc_mark(rb_obj);
}
}
}

// Implements GC protection across wxRuby. Always called because
// Wx::THE_APP is a constant so always checked in GC mark phase.
static void mark_wxRubyApp(void *ptr)
{

#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "=== Starting App GC mark phase" << std::endl;
#endif

// If the App has ended, the ruby object will have been unlinked from
// the C++ one; this implies that all Windows have already been destroyed
// so there is no point trying to mark them, and doing so may cause
// errors.
if ( !wxRubyApp::GetInstance() || !wxRubyApp::GetInstance()->IsRunning() )
{
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "=== App not started yet or has ended, skipping mark phase" << std::endl;
#endif
return;
}

// Mark any active (tracked) log target
wxLog* curLog = wxLog::GetActiveTarget();
VALUE rb_cur_log = wxRuby_FindTracking(curLog);
if (!NIL_P(rb_cur_log))
{
rb_gc_mark(rb_cur_log);
}

// Mark evt handler procs associated with live windows - see
// classes/EvtHandler.i
wxRuby_MarkProtectedEvtHandlerProcs();

// run registered markers
for (wxVector<WXRBMarkFunction>::iterator it = WXRuby_Mark_List.begin();
it != WXRuby_Mark_List.end() ;++it)
{
(*it) ();
}

// To do the main marking, primarily of Windows, iterate over SWIG's
// list of tracked objects
wxRuby_IterateTracking(&wxRubyApp::markIterate);

#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "=== App GC mark phase completed" << std::endl;
#endif
}

// This is the method run when main_loop is called in Ruby
// wxEntry calls the C++ App::OnInit method
int main_loop()
{
int rc = 0;

// There should ever only be only a single App instance running
if (rb_const_defined(mWxCore, rb_intern("THE_APP")))
{
rb_raise(rb_eRuntimeError, "There is already another App instance running");
return -1;
}

// Set self reference and global THE_APP constant
this->self_ = SWIG_RubyInstanceFor(this);
rb_define_const(mWxCore, "THE_APP", this->self_);
// Also cache the Ruby App reference on the stack here as after
// wxEntry returns the C++ App instance will have been destroyed
// and we cannot reference it (or it's members) anymore
VALUE the_app = this->self_;

this->Connect(wxEVT_DESTROY,
wxWindowDestroyEventHandler(wxRubyApp::OnWindowDestroy));

#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "Calling wxEntry, this=" << this << std::endl;
#endif

// collect ruby app name and arguments array
VALUE rb_args = rb_get_argv();
int argc = 1 + RARRAY_LEN(rb_args);
std::unique_ptr<char*[]> argv_safe = std::make_unique<char*[]> (argc);
VALUE sval = rb_gv_get("$0");
argv_safe[0] = StringValuePtr(sval);
for (int i=0; i<RARRAY_LEN(rb_args) ;++i)
{
sval = rb_ary_entry(rb_args, i);
argv_safe[1+i] = StringValuePtr(sval);
}
// there is no need to copy the strings as we only need them until
// wxEntry returns

#ifdef __WXMSW__
wxApp::m_nCmdShow = SW_NORMAL;
#endif
rc = wxEntry(argc, argv_safe.get());

/*
At this point the C++ wxRubyApp instance has been destroyed so take care NOT to reference
it or any of it's members anymore but only unroll the callstack.
*/

#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "returned from wxEntry..." << std::endl;
#endif
rb_gc_start();
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "survived gc" << std::endl;
#endif

rb_const_remove(mWxCore, rb_intern("THE_APP"));

VALUE exc = rb_iv_get(the_app, "@exception");
if (!NIL_P(exc))
{
rb_exc_raise(exc);
}
return rc;
}

// This method initializes the stock objects (Pens, Brushes, Fonts)
// before yielding to ruby by calling the App's on_init method.
// Note that as of wxWidget 2.8, the stock fonts in particular cannot
// be initialized any earlier than this without crashing
bool OnInit() override
{
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "OnInit..." << std::endl;
#endif

if (!wxApp::OnInit())
return false;

// Signal that we've started
this->is_running_ = true;
// Set up the GDI objects
Init_wxRubyStockObjects();
// Get the ruby representation of the App object, and call the
// ruby on_init method to set up the initial window state
bool ex_caught = false;
VALUE result = wxRuby_Funcall(ex_caught, this->self_, rb_intern("on_ruby_init"), 0, 0);

if (ex_caught)
{
#ifdef __WXRB_DEBUG__
wxRuby_PrintException(result);
#endif
rb_iv_set(this->self_, "@exception", result);
result = Qfalse; // exit app
}

// If on_init return any (ruby) true value, signal to wxWidgets to
// enter the main event loop by returning true, else return false
// which will make wxWidgets exit.
if ( result == Qfalse || result == Qnil )
{
this->is_running_ = false;
return false;
}
else
{
return true;
}
}

int OnExit() override
{
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "OnExit..." << std::endl;
#endif

// Get the ruby representation of the App object, and call the
// ruby on_exit method (if any) for application level cleanup
ID on_exit_id = rb_intern("on_exit");
if (rb_funcall(this->self_, rb_intern("respond_to?"), 1, ID2SYM(on_exit_id)) == Qtrue)
{
bool ex_caught = false;
VALUE rc = wxRuby_Funcall(ex_caught, this->self_, on_exit_id, 0, 0);
if (ex_caught)
{
#ifdef __WXRB_DEBUG__
wxRuby_PrintException(rc);
#endif
rb_iv_set(this->self_, "@exception", rc);
}
}

// perform wxRuby cleanup
_wxRuby_Cleanup();

// execute base wxWidgets functionality
return this->wxApp::OnExit();
}

// actually implemented in ruby in classes/app.rb
virtual void OnAssertFailure(const wxChar *file, int line, const wxChar *func, const wxChar *cond, const wxChar *msg) override
{
if (rb_during_gc() || NIL_P(this->self_))
{
std::wcerr << file << "(" << line << "): ASSERT " << cond
<< (NIL_P(this->self_) ? " fired without THE_APP in " : " fired during GC phase in ")
<< func << "() with message [" << msg << "]" << std::endl;
}
else
{
VALUE obj0 = Qnil ;
VALUE obj1 = Qnil ;
VALUE obj2 = Qnil ;
VALUE obj3 = Qnil ;
VALUE obj4 = Qnil ;

obj0 = rb_str_new2((const char *)wxString(file).utf8_str());
obj1 = INT2NUM(line);
obj2 = rb_str_new2((const char *)wxString(func).utf8_str());
obj3 = rb_str_new2((const char *)wxString(cond).utf8_str());
obj4 = rb_str_new2((const char *)wxString(msg).utf8_str());
(void)wxRuby_Funcall(this->self_, rb_intern("on_assert_failure"), 5,obj0,obj1,obj2,obj3,obj4);
}
}

void _wxRuby_Cleanup()
{
#ifdef __WXRB_DEBUG__
if (wxRuby_TraceLevel()>0)
std::wcout << "wxRuby_Cleanup..." << std::endl;
#endif
// Note in a global variable that the App has ended, so that we
// can skip any GC marking later
this->is_running_ = false;

// if a Ruby implemented logger has been installed clean that up
// before we exit, otherwise let wxWidgets handle things
wxLog *oldlog = wxLog::GetActiveTarget();
if (wxRuby_FindTracking(oldlog) != Qnil)
{
oldlog = wxLog::SetActiveTarget(new wxLogStderr);
}
else
{
oldlog = 0;
}
SetTopWindow(0);
if ( oldlog )
{
SWIG_RubyUnlinkObjects(oldlog);
SWIG_RubyRemoveTracking(oldlog);
delete oldlog;
}
}
};
4 changes: 2 additions & 2 deletions lib/wx/core/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def is_main_loop_running
# WxRuby. Such messages usually indicate that the API is being used
# incorrectly; the file/line reference points to the place in the
# WxWidgets source code where the assertion was made.
define_method(:on_assert_failure) do | file, line, func, condition, message |
warn "Wx WARNING: #{message} (#{func}@#{file}:#{line})"
def on_assert_failure(file, line, func, condition, message)
warn %Q{Wx WARNING: ASSERT #{condition} fired with "#{message}" at (#{func}@#{file}:#{line})}
end

# For use in development only, of no practical use in production code.
Expand Down
2 changes: 1 addition & 1 deletion lib/wx/core/collapsible_pane.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

module Wx

if Wx.has_feature?(:USE_COLLPANE) && (!Wx.has_feature?(:WXGTK20) || Wx.has_feature?(:WXUNIVERSAL))
if Wx.has_feature?(:USE_COLLPANE)

GenericCollapsiblePane = CollapsiblePane

Expand Down
12 changes: 9 additions & 3 deletions rakelib/lib/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -615,9 +615,15 @@ def explicit_excluded_modules
def _retrieve_features(wxwidgets_setup_h)
features = {}

File.read(wxwidgets_setup_h).scan(/^\s*#define\s+(wx\w+|__\w+__)\s+([01])/) do | define |
features[$1] = $2.to_i.zero? ? false : true
end if is_configured? && wxwidgets_setup_h
if is_configured? && wxwidgets_setup_h
File.read(wxwidgets_setup_h).scan(/^\s*#define\s+(wx\w+|__\w+__)\s+([01])/) do | define |
features[$1] = $2.to_i.zero? ? false : true
end
# make sure correct platform defines are included as well which will not be the case always
# (for example __WXMSW__ and __WXOSX__ are not in setup.h)
features['__WXMSW__'] = true if features['__GNUWIN32__']
features['__WXOSX__'] = true if features['__DARWIN__']
end

features
end
Expand Down
2 changes: 1 addition & 1 deletion rakelib/lib/config/mingw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def ldflags(target)
end

def debug_command(*args)
args.unshift(FileUtils::RUBY)
args.unshift(nix_path(FileUtils::RUBY))
args.unshift('--args')
args.unshift('gdb')
args.join(' ')
Expand Down
Loading

0 comments on commit c657b15

Please sign in to comment.