diff --git a/README.md b/README.md index a99cbc1c..24824f33 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ Several years of development have passed for wxWidgets and Ruby respectively, improving code quality, adding new classes and new language features. In 2022 I finally found the time and the inspiration to pick up this project with the idea of reviving it to build some applications I had in mind. -wxRuby 3 intents to provide Ruby interfaces for all relevant (!) wxWidget +wxRuby 3 intends to provide Ruby interfaces for all relevant (!) wxWidget classes of the latest version 3.2 and beyond. Building on the experiences of the previous wxRuby (2) developments as well as the wxPython Phoenix project it is expected to provide a better and more diff --git a/ext/wxruby3/include/wxruby-ComboPopup.h b/ext/wxruby3/include/wxruby-ComboPopup.h new file mode 100644 index 00000000..1e86a4e7 --- /dev/null +++ b/ext/wxruby3/include/wxruby-ComboPopup.h @@ -0,0 +1,777 @@ +// Copyright (c) 2023 M.J.N. Corino, The Netherlands +// +// This software is released under the MIT license. + +/* + * WxRuby3 WxRubyComboPopup class + */ + +#ifndef _WXRUBY_COMBO_POPUP_H +#define _WXRUBY_COMBO_POPUP_H + +#include +#include + +class WxRubyComboPopup : public wxComboPopup +{ +private: + static WxRuby_ID init_ID; + static WxRuby_ID lazy_create_ID; + static WxRuby_ID create_ID; + static WxRuby_ID destroy_popup_ID; + static WxRuby_ID find_item_ID; + static WxRuby_ID get_adjusted_size_ID; + static WxRuby_ID get_control_ID; + static WxRuby_ID set_string_value_ID; + static WxRuby_ID get_string_value_ID; + static WxRuby_ID on_combo_double_click_ID; + static WxRuby_ID on_combo_key_event_ID; + static WxRuby_ID on_combo_char_event_ID; + static WxRuby_ID on_dismiss_ID; + static WxRuby_ID on_popup_ID; + static WxRuby_ID paint_combo_control_ID; + + static std::map combo_popup_map; + + VALUE rb_combo_popup_; + + class Exception : public Swig::DirectorException + { + public: + Exception(VALUE error, const char *hdr, const char *msg ="") + : Swig::DirectorException(error, hdr, msg) + {} + }; + +public: + static void GC_mark_combo_popups() + { + for (auto pair : combo_popup_map) + { + rb_gc_mark(pair.second); + } + } + + WxRubyComboPopup(VALUE rb_cp) + : wxComboPopup() + , rb_combo_popup_(rb_cp) + { + combo_popup_map[this] = rb_cp; // register + } + + virtual ~WxRubyComboPopup() + { + if (!NIL_P(rb_combo_popup_)) + { + combo_popup_map.erase(this); //deregister + // unlink + rb_iv_set(rb_combo_popup_, "@_wx_combo_popup_proxy", Qnil); + rb_combo_popup_ = Qnil; + } + } + + VALUE GetRubyComboPopup() const + { + return rb_combo_popup_; + } + + virtual void Init() override + { + wxRuby_Funcall(rb_combo_popup_, init_ID(), 0); + } + + virtual bool LazyCreate() override + { + VALUE rc = wxRuby_Funcall(rb_combo_popup_, lazy_create_ID(), 0); + return rc == Qtrue ? true : false; + } + + virtual bool Create(wxWindow* parent) override + { + VALUE rb_parent = wxRuby_WrapWxObjectInRuby(parent); + VALUE rc = wxRuby_Funcall(rb_combo_popup_, create_ID(), 1, rb_parent); + return rc == Qtrue ? true : false; + } + + virtual void DestroyPopup() override + { + wxRuby_Funcall(rb_combo_popup_, destroy_popup_ID(), 0); + delete this; + } + + virtual bool FindItem(const wxString& item, wxString* trueItem=nullptr) override + { + VALUE rc = wxRuby_Funcall(rb_combo_popup_, find_item_ID(), 2, WXSTR_TO_RSTR(item), trueItem ? Qtrue : Qfalse); + if (TYPE(rc) == T_STRING && trueItem) + { + *trueItem = RSTR_TO_WXSTR(rc); + return true; + } + return (rc == Qfalse || NIL_P(rc)) ? false : true; + } + + virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight) override + { + VALUE rc = wxRuby_Funcall(rb_combo_popup_, get_adjusted_size_ID(), + 3, INT2NUM(minWidth), INT2NUM(prefHeight), INT2NUM(maxHeight)); + if (TYPE(rc) == T_DATA) + { + void* ptr; + SWIG_ConvertPtr(rc, &ptr, SWIGTYPE_p_wxSize, 0); + return *reinterpret_cast(ptr); + } + else if (TYPE(rc) == T_ARRAY && RARRAY_LEN(rc) == 2) + { + return wxSize(NUM2INT(rb_ary_entry(rc, 0)), NUM2INT(rb_ary_entry(rc, 1))); + } + else + { + throw Exception(rb_eTypeError, "Return type error: ", + "expected Wx::Size or Array(Integer,Integer) from #get_adjusted_size"); + } + } + + virtual wxWindow *GetControl() override + { + VALUE rc = wxRuby_Funcall(rb_combo_popup_, get_control_ID(), 0); + void *ptr; + int res = SWIG_ConvertPtr(rc, &ptr, SWIGTYPE_p_wxWindow, 0); + if (!SWIG_IsOK(res)) + { + throw Exception(rb_eTypeError, "Return type error: ", + "expected Wx::Window from #get_control"); + } + return reinterpret_cast(ptr); + } + + virtual void SetStringValue(const wxString& value) override + { + wxRuby_Funcall(rb_combo_popup_, set_string_value_ID(), 1, WXSTR_TO_RSTR(value)); + } + + virtual wxString GetStringValue() const override + { + VALUE rc = wxRuby_Funcall(rb_combo_popup_, get_string_value_ID(), 0); + return RSTR_TO_WXSTR(rc); + } + + virtual void OnComboKeyEvent(wxKeyEvent& event) override + { +#if __WXRB_DEBUG__ + wxRuby_Funcall(rb_combo_popup_, on_combo_key_event_ID(), 1, wxRuby_WrapWxEventInRuby(nullptr, &event)); +#else + wxRuby_Funcall(rb_combo_popup_, on_combo_key_event_ID(), 1, wxRuby_WrapWxEventInRuby(&event)); +#endif + } + + virtual void OnComboCharEvent(wxKeyEvent& event) override + { +#if __WXRB_DEBUG__ + wxRuby_Funcall(rb_combo_popup_, on_combo_char_event_ID(), 1, wxRuby_WrapWxEventInRuby(nullptr, &event)); +#else + wxRuby_Funcall(rb_combo_popup_, on_combo_char_event_ID(), 1, wxRuby_WrapWxEventInRuby(&event)); +#endif + } + + virtual void OnComboDoubleClick() override + { + wxRuby_Funcall(rb_combo_popup_, on_combo_double_click_ID(), 0); + } + + virtual void OnPopup() override + { + wxRuby_Funcall(rb_combo_popup_, on_popup_ID(), 0); + } + + virtual void OnDismiss() override + { + wxRuby_Funcall(rb_combo_popup_, on_dismiss_ID(), 0); + } + + virtual void PaintComboControl(wxDC& dc, const wxRect& rect) override + { + wxRuby_Funcall(rb_combo_popup_, paint_combo_control_ID(), 2, + SWIG_NewPointerObj(SWIG_as_voidptr(&dc), SWIGTYPE_p_wxDC, 0), + SWIG_NewPointerObj(new wxRect(rect), SWIGTYPE_p_wxRect, SWIG_POINTER_OWN)); + } + +}; + +WxRuby_ID WxRubyComboPopup::init_ID("init"); +WxRuby_ID WxRubyComboPopup::lazy_create_ID("lazy_create"); +WxRuby_ID WxRubyComboPopup::create_ID("create"); +WxRuby_ID WxRubyComboPopup::destroy_popup_ID("destroy_popup"); +WxRuby_ID WxRubyComboPopup::find_item_ID("find_item"); +WxRuby_ID WxRubyComboPopup::get_adjusted_size_ID("get_adjusted_size"); +WxRuby_ID WxRubyComboPopup::get_control_ID("get_control"); +WxRuby_ID WxRubyComboPopup::set_string_value_ID("set_string_value"); +WxRuby_ID WxRubyComboPopup::get_string_value_ID("get_string_value"); +WxRuby_ID WxRubyComboPopup::on_combo_double_click_ID("on_combo_double_click"); +WxRuby_ID WxRubyComboPopup::on_combo_key_event_ID("on_combo_key_event"); +WxRuby_ID WxRubyComboPopup::on_combo_char_event_ID("on_combo_char_event"); +WxRuby_ID WxRubyComboPopup::on_dismiss_ID("on_dismiss"); +WxRuby_ID WxRubyComboPopup::on_popup_ID("on_popup"); +WxRuby_ID WxRubyComboPopup::paint_combo_control_ID("paint_combo_control"); + +std::map WxRubyComboPopup::combo_popup_map; + +// Wrapper methods for module Wx::ComboPopup + +static VALUE wx_combo_popup_get_combo_ctrl(int argc, VALUE *argv, VALUE self) +{ + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + VALUE rb_cp_proxy = rb_iv_get(self, "@_wx_combo_popup_proxy"); + if (!NIL_P(rb_cp_proxy)) + { + wxComboPopup* cpp = nullptr; + Data_Get_Struct(rb_cp_proxy, wxComboPopup, cpp); + if (cpp) + { + try { + wxComboCtrl* combo = cpp->GetComboCtrl(); + return wxRuby_WrapWxObjectInRuby(combo); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + } + } + return Qnil; +} + +// Wrapper methods for class Wx::ComboPopupWx + +static VALUE combo_popup_wx_get_combo_ctrl(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + wxComboCtrl* combo = cpp->GetComboCtrl(); + return wxRuby_WrapWxObjectInRuby(combo); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_lazy_create(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + return cpp->LazyCreate() ? Qtrue : Qfalse; + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_create(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 1 || argc > 1) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 1)", argc); + } + + void *ptr; + int res = SWIG_ConvertPtr(argv[0], &ptr, SWIGTYPE_p_wxWindow, 0); + if (!SWIG_IsOK(res)) { + rb_raise(rb_eArgError, "Expected Wx::Window for 1"); + return Qnil; + } + wxWindow *parent = reinterpret_cast< wxWindow * >(ptr); + if (!parent) + { + rb_raise(rb_eArgError, + "Window parent argument must not be nil"); + } + + try { + return cpp->Create(parent) ? Qtrue : Qfalse; + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_find_item(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 1 || argc > 2) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 2)", argc); + } + + wxString item = RSTR_TO_WXSTR(argv[0]); + bool f_trueItem = (argc<2 || (argv[1] == Qfalse || argv[1] == Qnil)) ? false : true; + wxString trueItem; + try { + bool rc = cpp->FindItem(item, f_trueItem ? &trueItem : nullptr); + return rc ? (f_trueItem ? WXSTR_TO_RSTR(trueItem) : Qtrue) : Qfalse; + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_get_adjusted_size(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 3 || argc > 3) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 3)", argc); + } + + try { + wxSize sz = cpp->GetAdjustedSize(NUM2INT(argv[0]), NUM2INT(argv[1]), NUM2INT(argv[2])); + return SWIG_NewPointerObj(new wxSize(sz), SWIGTYPE_p_wxSize, SWIG_POINTER_OWN); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_get_control(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + wxWindow* control = cpp->GetControl(); + return wxRuby_WrapWxObjectInRuby(control); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_set_string_value(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 1 || argc > 1) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 1)", argc); + } + + try { + wxString val = RSTR_TO_WXSTR(argv[0]); + cpp->SetStringValue(val); + return Qnil; + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_get_string_value(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + wxString val = cpp->GetStringValue(); + return WXSTR_TO_RSTR(val); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } +} + +static VALUE combo_popup_wx_on_combo_double_click(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + cpp->OnComboDoubleClick(); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +static VALUE get_key_event_class() +{ + static VALUE key_event_klass = Qnil; + if (NIL_P(key_event_klass)) + { + key_event_klass = rb_eval_string("Wx::KeyEvent"); + } + return key_event_klass; +} + +static VALUE combo_popup_wx_on_combo_key_event(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 1 || argc > 1) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 1)", argc); + } + + if (!rb_obj_is_kind_of(argv[0], get_key_event_class())) + { + rb_raise(rb_eTypeError, "Expected Wx::KeyEvent for 1"); + } + + wxEvent* evt = reinterpret_cast (DATA_PTR(argv[0])); + if (evt == nullptr) + { + rb_raise(rb_eTypeError, "Invalid null reference for Wx::KeyEvent"); + } + + try { + cpp->OnComboKeyEvent(*dynamic_cast (evt)); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +static VALUE combo_popup_wx_on_combo_char_event(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 1 || argc > 1) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 1)", argc); + } + + if (!rb_obj_is_kind_of(argv[0], get_key_event_class())) + { + rb_raise(rb_eTypeError, "Expected Wx::KeyEvent for 1"); + } + + wxEvent* evt = reinterpret_cast (DATA_PTR(argv[0])); + if (evt == nullptr) + { + rb_raise(rb_eTypeError, "Invalid null reference for Wx::KeyEvent"); + } + + try { + cpp->OnComboCharEvent(*dynamic_cast (evt)); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +static VALUE combo_popup_wx_on_dismiss(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + cpp->OnDismiss(); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +static VALUE combo_popup_wx_on_popup(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 0 || argc > 0) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 0)", argc); + } + + try { + cpp->OnPopup(); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +static VALUE combo_popup_wx_paint_combo_control(int argc, VALUE *argv, VALUE self) +{ + wxComboPopup *cpp; + Data_Get_Struct(self, wxComboPopup, cpp); + + if (argc < 2 || argc > 2) + { + rb_raise(rb_eArgError, "wrong # of arguments (%d for 2)", argc); + } + + void *ptr; + int res = SWIG_ConvertPtr(argv[0], &ptr, SWIGTYPE_p_wxDC, 0); + if (!SWIG_IsOK(res)) { + rb_raise(rb_eArgError, "Expected Wx::DC for 1"); + return Qnil; + } + if (!ptr) { + rb_raise(rb_eArgError, "Invalid null reference for Wx::DC"); + return Qnil; + } + wxDC *dc = reinterpret_cast< wxDC * >(ptr); + res = SWIG_ConvertPtr(argv[1], &ptr, SWIGTYPE_p_wxRect, 0); + if (!SWIG_IsOK(res)) { + rb_raise(rb_eArgError, "Expected Wx::Rect for 2"); + return Qnil; + } + if (!ptr) { + rb_raise(rb_eArgError, "Invalid null reference for Wx::Rect"); + return Qnil; + } + wxRect* rect = reinterpret_cast< wxRect * >(ptr); + try { + cpp->PaintComboControl(*dc, *rect); + } + catch (const Swig::DirectorException& swigex) { + if (rb_obj_is_kind_of(swigex.getError(), rb_eException)) + { + rb_exc_raise(swigex.getError()); + } + else + { + rb_exc_raise(rb_exc_new_cstr(swigex.getError(), swigex.what())); + } + } + catch (const std::exception& ex) { + rb_raise(rb_eRuntimeError, "Unexpected C++ exception: %s", ex.what()); + } + catch (...) { + rb_raise(rb_eRuntimeError, "Unexpected UNKNOWN exception"); + } + return Qnil; +} + +#endif /* _WXRUBY_COMBO_POPUP_H */ diff --git a/lib/wx/core/combo_ctrl.rb b/lib/wx/core/combo_ctrl.rb new file mode 100644 index 00000000..8ab91901 --- /dev/null +++ b/lib/wx/core/combo_ctrl.rb @@ -0,0 +1,171 @@ +# Copyright (c) 2023 M.J.N. Corino, The Netherlands +# +# This software is released under the MIT license. + + +module Wx + + module ComboPopup + + def self.included(mod) + unless mod == Wx::ComboPopupWx + mod.class_eval { include Wx::ComboPopup::Methods } + end + end + + module Methods + # Returns pointer to the associated parent {Wx::ComboCtrl}. + # @return [Wx::ComboCtrl] + # def get_combo_ctrl; end + + # The including class must implement this to initialize its internal variables. + # + # This method is called immediately after construction finishes. m_combo member variable has been initialized before the call. + # @return [void] + def init + end + + # The including class may implement this to return true if it wants to delay call to {Wx::ComboPopup#create} until the popup is shown for the first time. + # + # It is more efficient, but on the other hand it is often more convenient to have the control created immediately. + # + #
+ # Remark: + #

Base implementation returns false. + #

+ #
+ # @return [Boolean] + def lazy_create + false + end + + # The including class must implement this to create the popup control. + # + # true if the call succeeded, false otherwise. + # @param parent [Wx::Window] + # @return [Boolean] + def create(parent) + false + end + + # You only need to implement this member function if you create your popup class in non-standard way. + # + # The default implementation can handle both multiple-inherited popup control (as seen in {Wx::ComboCtrl} samples) and one allocated separately in heap. + # If you do completely re-implement this function, make sure it calls Destroy() for the popup control and also deletes this object (usually as the last thing). + # @return [void] + def destroy_popup + end + + # Implement to customize matching of value string to an item container entry. + # + #
+ # Remark: + #

Default implementation always return true and does not alter trueItem. + #

+ #
+ # @param item [String] String entered, usually by user or from SetValue() call. + # @param trueItem [Boolean] if true the true item string should be returned in case matching but different + # @return [Boolean, String] Returns true if a match is found or false if not. If trueItem == true and item matches an entry, but the entry's string representation is not exactly the same (case mismatch, for example), then the true item string should be returned as the match result. + def find_item(item, trueItem=false) + true + end + + # The including class may implement this to return adjusted size for the popup control, according to the variables given. + # + #
+ # Remark: + #

This function is called each time popup is about to be shown. + #

+ #
+ # @param minWidth [Integer] Preferred minimum width. + # @param prefHeight [Integer] Preferred height. May be -1 to indicate no preference. + # @param maxHeight [Integer] Max height for window, as limited by screen size. + # @return [Wx::Size] + def get_adjusted_size(minWidth, prefHeight, maxHeight) + Wx::Size.new(minWidth, prefHeight) + end + + # The including class must implement this to return pointer to the associated control created in {Wx::ComboPopup#create}. + # @return [Wx::Window] + def get_control + end + + # The including class must implement this to receive string value changes from {Wx::ComboCtrl}. + # @param value [String] + # @return [void] + def set_string_value(value) + end + + # The including class must implement this to return string representation of the value. + # @return [String] + def get_string_value + nil + end + + # The including class may implement this to do something when the parent {Wx::ComboCtrl} gets double-clicked. + # @return [void] + def on_combo_double_click + end + + # The including class may implement this to receive key down events from the parent {Wx::ComboCtrl}. + # + # Events not handled should be skipped, as usual. + # @param event [Wx::KeyEvent] + # @return [void] + def on_combo_key_event(event) + event.skip + end + + # The including class may implement this to receive char events from the parent {Wx::ComboCtrl}. + # + # Events not handled should be skipped, as usual. + # @param event [Wx::KeyEvent] + # @return [void] + def on_combo_char_event(event) + event.skip + end + + # The including class may implement this to do special processing when popup is hidden. + # @return [void] + def on_dismiss + end + + # The including class may implement this to do special processing when popup is shown. + # @return [void] + def on_popup + end + + # The including class may implement this to paint the parent {Wx::ComboCtrl}. + # This is called to custom paint in the combo control itself (ie. not the popup). + # + # Default implementation draws value as string. + # @param dc [Wx::DC] + # @param rect [Wx::Rect] + # @return [void] + def paint_combo_control(dc, rect) + combo = get_combo_ctrl + if combo.get_window_style.allbits?(Wx::CB_READONLY) # ie. no textctrl + combo.prepare_background(dc, rect,0) + + dc.draw_text(combo.get_value, + rect.x + combo.get_margin_left, + (rect.height-dc.get_char_height)/2 + rect.y) + end + end + + end + + end + + class ComboPopupWx + + include ComboPopup + + # this method has not been wrapped as a default popup control will always already have been + # initialized before returned from #get_combo_control + # just do nothing here (or should we raise an exception?) + def init; end + + end + +end diff --git a/lib/wx/doc/comboctrl.rb b/lib/wx/doc/comboctrl.rb index 153b902d..af21567c 100644 --- a/lib/wx/doc/comboctrl.rb +++ b/lib/wx/doc/comboctrl.rb @@ -7,11 +7,136 @@ module Wx - class OwnerDrawnComboBox < ComboCtrl + # In order to use a custom popup with {Wx::ComboCtrl}, a class must include {Wx::ComboPopup}. + # + # For more information on how to use it, see {Wx::ComboCtrl Setting Custom Popup for Wx::ComboCtrl}. + module ComboPopup - alias :get_item_data :get_client_object + # Returns pointer to the associated parent {Wx::ComboCtrl}. + # @return [Wx::ComboCtrl] + def get_combo_ctrl; end - alias :set_item_data :set_client_object + # The including class must implement this to initialize its internal variables. + # + # This method is called immediately after construction finishes. m_combo member variable has been initialized before the call. + # @return [void] + def init; end + + # The including class may implement this to return true if it wants to delay call to {Wx::ComboPopup#create} until the popup is shown for the first time. + # + # It is more efficient, but on the other hand it is often more convenient to have the control created immediately. + # + #
+ # Remark: + #

Base implementation returns false. + #

+ #
+ # @return [Boolean] + def lazy_create; end + + # The including class must implement this to create the popup control. + # + # true if the call succeeded, false otherwise. + # @param parent [Wx::Window] + # @return [Boolean] + def create(parent) end + + # You only need to implement this member function if you create your popup class in non-standard way. + # + # The default implementation can handle both multiple-inherited popup control (as seen in {Wx::ComboCtrl} samples) and one allocated separately in heap. + # If you do completely re-implement this function, make sure it calls Destroy() for the popup control and also deletes this object (usually as the last thing). + # @return [void] + def destroy_popup; end + + # Implement to customize matching of value string to an item container entry. + # + #
+ # Remark: + #

Default implementation always return true and does not alter trueItem. + #

+ #
+ # @param item [String] String entered, usually by user or from SetValue() call. + # @param trueItem [Boolean] if true the true item string should be returned in case matching but different + # @return [Boolean, String] Returns true if a match is found or false if not. If trueItem == true and item matches an entry, but the entry's string representation is not exactly the same (case mismatch, for example), then the true item string should be returned as the match result. + def find_item(item, trueItem=false) end + + # The including class may implement this to return adjusted size for the popup control, according to the variables given. + # + #
+ # Remark: + #

This function is called each time popup is about to be shown. + #

+ #
+ # @param minWidth [Integer] Preferred minimum width. + # @param prefHeight [Integer] Preferred height. May be -1 to indicate no preference. + # @param maxHeight [Integer] Max height for window, as limited by screen size. + # @return [Wx::Size] + def get_adjusted_size(minWidth, prefHeight, maxHeight) end + + # The including class must implement this to return pointer to the associated control created in {Wx::ComboPopup#create}. + # @return [Wx::Window] + def get_control; end + + # The including class must implement this to receive string value changes from {Wx::ComboCtrl}. + # @param value [String] + # @return [void] + def set_string_value(value) end + + # The including class must implement this to return string representation of the value. + # @return [String] + def get_string_value; end + + # The including class may implement this to do something when the parent {Wx::ComboCtrl} gets double-clicked. + # @return [void] + def on_combo_double_click; end + + # The including class may implement this to receive key down events from the parent {Wx::ComboCtrl}. + # + # Events not handled should be skipped, as usual. + # @param event [Wx::KeyEvent] + # @return [void] + def on_combo_key_event(event) end + + # The including class may implement this to receive char events from the parent {Wx::ComboCtrl}. + # + # Events not handled should be skipped, as usual. + # @param event [Wx::KeyEvent] + # @return [void] + def on_combo_char_event(event) end + + # The including class may implement this to do special processing when popup is hidden. + # @return [void] + def on_dismiss; end + + # The including class may implement this to do special processing when popup is shown. + # @return [void] + def on_popup; end + + # The including class may implement this to paint the parent {Wx::ComboCtrl}. + # This is called to custom paint in the combo control itself (ie. not the popup). + # + # Default implementation draws value as string. + # @param dc [Wx::DC] + # @param rect [Wx::Rect] + # @return [void] + def paint_combo_control(dc, rect) end + + end + + # A Ruby interface class for default comboctrl popup classes used by {Wx::OwnerDrawnComboBox} and + # {Wx::RichTextStyleListBox}. + # + # If no custom popup control has been installed with {Wx::ComboCtrl#SetPopupControl} an instance of this + # class will be returned when {Wx::ComboCtrl#GetPopupControl} is called for either of the widgets mentioned + # above. + #
+ # Remark: + #

This is an abstract class that cannot be derived from. + #

+ #
+ class ComboPopupWx + + include ComboPopup end diff --git a/lib/wx/doc/owner_drawn_combobox.rb b/lib/wx/doc/owner_drawn_combobox.rb index 8a79dc41..7cfe8b45 100644 --- a/lib/wx/doc/owner_drawn_combobox.rb +++ b/lib/wx/doc/owner_drawn_combobox.rb @@ -7,7 +7,11 @@ module Wx - class OwnerDrawnComboBox + class OwnerDrawnComboBox < ComboCtrl + + alias :get_item_data :get_client_object + + alias :set_item_data :set_client_object # Returns the label of the selected item or an empty string if no item is selected. # diff --git a/rakelib/lib/core/include/funcall.inc b/rakelib/lib/core/include/funcall.inc index 3581e35e..66fba1a0 100644 --- a/rakelib/lib/core/include/funcall.inc +++ b/rakelib/lib/core/include/funcall.inc @@ -22,7 +22,8 @@ namespace Swig DirectorRubyException(VALUE error, VALUE rcvr, ID fn_id) : DirectorException(Qnil) { - VALUE msg = rb_sprintf("Caught exception in SWIG director method for %s#%s", rb_class2name(CLASS_OF(rcvr)), rb_id2name(fn_id)); + VALUE msg = rb_sprintf("Caught exception in SWIG director method for %s#%s : ", rb_class2name(CLASS_OF(rcvr)), rb_id2name(fn_id)); + rb_str_append(msg, rb_funcall(error, rb_intern("message"), 0)); this->swig_msg = StringValuePtr(msg); swig_error = rb_exc_new_str(rb_eRuntimeError, msg); VALUE bt = rb_funcall(error, rb_intern("backtrace"), 0); diff --git a/rakelib/lib/director/comboctrl.rb b/rakelib/lib/director/comboctrl.rb index f2716f6c..7937b54a 100644 --- a/rakelib/lib/director/comboctrl.rb +++ b/rakelib/lib/director/comboctrl.rb @@ -14,9 +14,10 @@ class Director class ComboCtrl < Window + include Typemap::ComboPopup + def setup super - spec.items << 'wxComboPopup' # mixin TextEntry spec.include_mixin 'wxComboCtrl', { 'Wx::TextEntry' => 'wxTextEntryBase' } spec.override_inheritance_chain('wxComboCtrl', @@ -29,8 +30,108 @@ def setup wxComboCtrl::DoSetPopupControl wxComboCtrl::DoShowPopup ] - # turn wxComboPopup into a mixin module - spec.make_mixin 'wxComboPopup' + spec.add_header_code <<~__HEREDOC + #include "wxruby-ComboPopup.h" + + static VALUE g_rb_mWxComboPopup = Qnil; + static VALUE g_rb_cComboPopupWx = Qnil; + + WXRUBY_EXPORT wxComboPopup* wxRuby_ComboPopupFromRuby(VALUE popup) + { + if (!NIL_P(popup) && !rb_obj_is_kind_of(popup, g_rb_mWxComboPopup)) + { + rb_raise(rb_eArgError, "Expected a Wx::ComboPopup or nil for 1"); + return nullptr; + } + + wxComboPopup* cpp = nullptr; + if (!NIL_P(popup)) + { + VALUE rb_cp_proxy = rb_iv_get(popup, "@_wx_combo_popup_proxy"); + if (NIL_P(rb_cp_proxy)) + { + cpp = new WxRubyComboPopup(popup); + rb_cp_proxy = Data_Wrap_Struct(rb_cObject, 0, 0, cpp); + rb_iv_set(popup, "@_wx_combo_popup_proxy", rb_cp_proxy); + } + else + { + Data_Get_Struct(rb_cp_proxy, wxComboPopup, cpp); + } + } + return cpp; + } + + WXRUBY_EXPORT VALUE wxRuby_ComboPopupToRuby(wxComboPopup* cpp) + { + VALUE rb_cpp = Qnil; + if (cpp) + { + WxRubyComboPopup *wxrb_cpp = dynamic_cast (cpp); + if (wxrb_cpp) + { + rb_cpp = wxrb_cpp->GetRubyComboPopup(); + } + else + { + // in this case we're probably working for a wxOwnerDrawnComboBox or wxRichTextListbox + // with default popup control which is a C++ implemented class without any Ruby linkage. + // wrap this in the Wx::ComboPopupWx class to provide a Ruby interface + rb_cpp = Data_Wrap_Struct(g_rb_cComboPopupWx, 0, 0, cpp); // do not own or track + } + } + return rb_cpp; + } + + static void wxRuby_markComboPopups() + { + WxRubyComboPopup::GC_mark_combo_popups(); + } + __HEREDOC + # ignore these + spec.ignore 'wxComboCtrl::SetPopupControl', + 'wxComboCtrl::GetPopupControl', + ignore_doc: false + # for GetPopupControl docs only + spec.map 'wxComboPopup*' => 'Wx::ComboPopup', swig: false do + map_out code: '' + end + # and replace + spec.add_extend_code 'wxComboCtrl', <<~__HEREDOC + void SetPopupControl(VALUE popup) + { + wxComboPopup* cpp = wxRuby_ComboPopupFromRuby(popup); + $self->SetPopupControl(cpp); + } + + VALUE GetPopupControl() + { + return wxRuby_ComboPopupToRuby($self->GetPopupControl()); + } + __HEREDOC + spec.add_init_code <<~__HEREDOC + wxRuby_AppendMarker(wxRuby_markComboPopups); + + g_rb_mWxComboPopup = rb_define_module_under(mWxCore, "ComboPopup"); + rb_define_method(g_rb_mWxComboPopup, "get_combo_ctrl", VALUEFUNC(wx_combo_popup_get_combo_ctrl), -1); + + g_rb_cComboPopupWx = rb_define_class_under(mWxCore, "ComboPopupWx", rb_cObject); + rb_undef_alloc_func(g_rb_cComboPopupWx); + rb_define_method(g_rb_cComboPopupWx, "lazy_create", VALUEFUNC(combo_popup_wx_lazy_create), -1); + rb_define_method(g_rb_cComboPopupWx, "create", VALUEFUNC(combo_popup_wx_create), -1); + rb_define_method(g_rb_cComboPopupWx, "get_combo_ctrl", VALUEFUNC(combo_popup_wx_get_combo_ctrl), -1); + rb_define_method(g_rb_cComboPopupWx, "find_item", VALUEFUNC(combo_popup_wx_find_item), -1); + rb_define_method(g_rb_cComboPopupWx, "get_adjusted_size", VALUEFUNC(combo_popup_wx_get_adjusted_size), -1); + rb_define_method(g_rb_cComboPopupWx, "get_control", VALUEFUNC(combo_popup_wx_get_control), -1); + rb_define_method(g_rb_cComboPopupWx, "set_string_value", VALUEFUNC(combo_popup_wx_set_string_value), -1); + rb_define_method(g_rb_cComboPopupWx, "get_string_value", VALUEFUNC(combo_popup_wx_get_string_value), -1); + rb_define_method(g_rb_cComboPopupWx, "on_combo_double_click", VALUEFUNC(combo_popup_wx_on_combo_double_click), -1); + rb_define_method(g_rb_cComboPopupWx, "on_combo_key_event", VALUEFUNC(combo_popup_wx_on_combo_key_event), -1); + rb_define_method(g_rb_cComboPopupWx, "on_combo_char_event", VALUEFUNC(combo_popup_wx_on_combo_char_event), -1); + rb_define_method(g_rb_cComboPopupWx, "on_dismiss", VALUEFUNC(combo_popup_wx_on_dismiss), -1); + rb_define_method(g_rb_cComboPopupWx, "on_popup", VALUEFUNC(combo_popup_wx_on_popup), -1); + rb_define_method(g_rb_cComboPopupWx, "paint_combo_control", VALUEFUNC(combo_popup_wx_paint_combo_control), -1); + __HEREDOC end end # class ComboCtrl diff --git a/rakelib/lib/director/gdicommon.rb b/rakelib/lib/director/gdicommon.rb index 32bada8b..6af930f8 100644 --- a/rakelib/lib/director/gdicommon.rb +++ b/rakelib/lib/director/gdicommon.rb @@ -49,6 +49,12 @@ def setup 'wxRect::Intersect(const wxRect &)', 'wxRect::Union(const wxRect &)' ] + if Config.instance.wx_version >= '3.3.0' + # ignore these as they are supposed to specify unary minus but confuse + # SWIG + spec.ignore 'wxPoint::operator-(const wxPoint&)', + 'wxRealPoint::operator-(const wxRealPoint&)' + end spec.regard 'wxRect::Offset', regard_doc: false # overrule common wxPoint mapping for wxRect ctor to fix ctor ambiguities here wrt wxSize spec.map 'const wxPoint& topLeft', 'const wxPoint& bottomRight', as: 'Wx::Point' do diff --git a/rakelib/lib/director/num_validator.rb b/rakelib/lib/director/num_validator.rb index 7f01a755..bc147f93 100644 --- a/rakelib/lib/director/num_validator.rb +++ b/rakelib/lib/director/num_validator.rb @@ -383,13 +383,11 @@ class WXFloatValidator : public wxFloatingPointValidator, public wxRubyV # hardcoded interface declarations spec.add_interface_code <<~__HEREDOC // Bit masks used for numeric validator styles. - enum wxNumValidatorStyle - { - wxNUM_VAL_DEFAULT = 0x0, - wxNUM_VAL_THOUSANDS_SEPARATOR = 0x1, - wxNUM_VAL_ZERO_AS_BLANK = 0x2, - wxNUM_VAL_NO_TRAILING_ZEROES = 0x4 - }; + enum wxNumValidatorStyle; + %constant int NumValidatorStyle_wxNUM_VAL_DEFAULT = 0x0; + %constant int NumValidatorStyle_wxNUM_VAL_THOUSANDS_SEPARATOR = 0x1; + %constant int NumValidatorStyle_wxNUM_VAL_ZERO_AS_BLANK = 0x2; + %constant int NumValidatorStyle_wxNUM_VAL_NO_TRAILING_ZEROES = 0x4; %alias WXIntegerValidator::GetMin "min"; %alias WXIntegerValidator::SetMin "min="; diff --git a/rakelib/lib/director/owner_drawn_combobox.rb b/rakelib/lib/director/owner_drawn_combobox.rb index ab145538..78232431 100644 --- a/rakelib/lib/director/owner_drawn_combobox.rb +++ b/rakelib/lib/director/owner_drawn_combobox.rb @@ -15,6 +15,7 @@ class Director class OwnerDrawnComboBox < Window include Typemap::ClientData + include Typemap::ComboPopup def setup super diff --git a/rakelib/lib/director/pgproperty.rb b/rakelib/lib/director/pgproperty.rb index ed229ff8..3393cbc0 100644 --- a/rakelib/lib/director/pgproperty.rb +++ b/rakelib/lib/director/pgproperty.rb @@ -113,7 +113,11 @@ def setup # do not think this useful for wxRuby (Also; caused GC problems) spec.ignore 'wxPGProperty::GetCellRenderer' # obsolete - spec.ignore %w[wxPGProperty::AddChild wxPGProperty::GetValueString] + if Config.instance.wx_version < '3.3.0' + spec.ignore %w[wxPGProperty::AddChild wxPGProperty::GetValueString] + else + spec.ignore 'wxPGProperty::AddChild' + end # not of use in Ruby spec.ignore(%w[wxPGProperty::GetClientData wxPGProperty::SetClientData]) # only keep the const version diff --git a/rakelib/lib/director/richtext_style_listbox.rb b/rakelib/lib/director/richtext_style_listbox.rb index d78914d3..5647477f 100644 --- a/rakelib/lib/director/richtext_style_listbox.rb +++ b/rakelib/lib/director/richtext_style_listbox.rb @@ -15,6 +15,7 @@ class Director class RichTextStyleListBox < Window include Typemap::RichText + include Typemap::ComboPopup def setup super @@ -28,6 +29,10 @@ def setup wxWindow wxEvtHandler wxObject]) + # missing from docs; required so proxy calls correct override + spec.extend_interface 'wxRichTextStyleComboCtrl', + 'virtual void DoSetPopupControl(wxComboPopup* popup)', + visibility: 'protected' end end diff --git a/rakelib/lib/generate/doc/combo_ctrl.yaml b/rakelib/lib/generate/doc/combo_ctrl.yaml new file mode 100644 index 00000000..49d69582 --- /dev/null +++ b/rakelib/lib/generate/doc/combo_ctrl.yaml @@ -0,0 +1,135 @@ +--- +:wxComboCtrl: + :detail: + :pre: + :programlisting: + - :pattern: !ruby/regexp /wxDECLARE_EVENT_TABLE/ + :replace: | + + ```ruby + class ListViewComboPopup < Wx::ListView + + include Wx::ComboPopup + + # Allow only default ctor + def initialize + # call default control ctor; need to call Wx::ListView#create later + super + end + + # Initialize member variables + def init + @value = -1 + end + + # Create popup control + def create(parent) + # need to finish creating the list view here + # as calling super here would just call Wx::ComboPopup#create and not Wx::ListView#create + # we need to use Ruby magic + wx_lv_create = (Wx::ListView.instance_method :create).bind(self) + wx_lv_create.call(parent, 1, [0,0], Wx::DEFAULT_SIZE) + evt_motion :on_mouse_move + evt_left_up :on_mouse_click + end + + # Return pointer to the created control + def get_control + self + end + + def lv_find_item(*args) + unless @wx_lv_find_item + @wx_lv_find_item = (Wx::ListView.instance_method :find_item).bind(self) + end + @wx_lv_find_item.call(*args) + end + protected :lv_find_item + + # Translate string into a list selection + def set_string_value(s) + n = lv_find_item(-1, s) + if n >= 0 && n < get_item_count + select(n) + @value = n + end + end + + # Get list selection as a string + def get_string_value + return get_item_text(@value) if @value >= 0 + '' + end + + # Do mouse hot-tracking (which is typical in list popups) + def on_mouse_move(event) + # Move selection to cursor ... + end + + # On mouse left up, set the value and close the popup + def on_mouse_click(_event) + @value = get_first_selected + + # Send event as well ... + + dismiss + end + + end + ``` + - :pattern: !ruby/regexp /wxComboCtrl/ + :replace: | + + ```ruby + comboCtrl = Wx::ComboCtrl.new(self, Wx::ID_ANY, '') + + popupCtrl = ListViewComboPopup.new + + # It is important to call #set_popup_control as soon as possible + comboCtrl.set_popup_control(popupCtrl) + + # Populate using Wx::ListView methods + popupCtrl.insert_item((popupCtrl.item_count, 'First Item') + popupCtrl.insert_item((popupCtrl.item_count, 'Second Item') + popupCtrl.insert_item((popupCtrl.item_count, 'Third Item') + ``` +:wxComboCtrl.SetMainControl: + :detail: + :pre: + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: | + + ```ruby + # Create the combo control using its default ctor. + combo = Wx::ComboCtrl.new + + # Create the custom main control using its default ctor too. + main = SomeWindow.new + + # Set the custom main control before creating the combo. + combo.set_main_control(main) + + # And only create it now: Wx::TextCtrl won't be unnecessarily + # created because the combo already has a main window. + combo.create(panel, Wx::ID_ANY, '') + + # Finally create the main window itself, now that its parent was + # created. + main.create(combo, ...) + ``` +:wxComboCtrl.SetTextCtrlStyle: + :detail: + :pre: + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: | + + ```ruby + comboCtrl = Wx::ComboCtrl.new + + # Let's make the text right-aligned + comboCtrl.set_text_ctrl_style(Wx::TE_RIGHT) + + comboCtrl.create(parent, Wx::ID_ANY, '') + ``` diff --git a/rakelib/lib/generate/doc/file_dialog_customize_hook.yaml b/rakelib/lib/generate/doc/file_dialog_customize_hook.yaml new file mode 100644 index 00000000..ade75901 --- /dev/null +++ b/rakelib/lib/generate/doc/file_dialog_customize_hook.yaml @@ -0,0 +1,62 @@ +--- +:wxFileDialogCustomizeHook: + :detail: + :pre: + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: | + + ```ruby + class EncryptHook < Wx::FileDialogCustomizeHook + + attr_reader :encrypt + + # Override to add custom controls using the provided customizer object. + def add_custom_controls(customizer) + # Suppose we can encrypt files when saving them. + @checkbox = customizer.add_check_box('Encrypt') + + # While @checkbox is not a Wx::CheckBox, it looks almost like one + # and, in particular, we can bind to custom control events as usual. + @checkbox.evt_checkbox(Wx::ID_ANY) do |event| + # We can also call Wx::Window-like functions on them. + @button.enable(event.checked?) + end + + # The encryption parameters can be edited in a dedicated dialog. + @button = customizer.add_button('Parameters...') + @button.evt_button(Wx::ID_ANY) do |event| + # ... show the encryption parameters dialog here ... + end + end + + # Override to save the values of the custom controls. + def transfer_data_from_custom_controls + # Save the checkbox value, as we won't be able to use it any more + # once this function returns. + @encrypt = @checkbox.get_value + end + + end + + # ... + + def some_method + Wx.FileDialog(nil, 'Save document', '', 'file.my', + 'My files (*.my)|*.my', + Wx::FD_SAVE | Wx::FD_OVERWRITE_PROMPT) do |dialog| + + # This object may be destroyed before the dialog, but must remain + # alive until #show_modal returns. + customize_hook = EncryptHook.new + dialog.set_customize_hook(custom_hook) + + if dialog.show_modal == Wx::ID_OK + if customize_hook.encrypt + # ... save with encryption ... + else + # ... save without encryption ... + end + end + end + ``` diff --git a/rakelib/lib/generate/doc/file_system.yaml b/rakelib/lib/generate/doc/file_system.yaml new file mode 100644 index 00000000..5f302c2c --- /dev/null +++ b/rakelib/lib/generate/doc/file_system.yaml @@ -0,0 +1,28 @@ +--- +:wxFileSystemHandler.CanOpen: + :detail: + :pre: + :para: + - :pattern: !ruby/regexp /Example:/ + :subst: '' + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: '' +:wxFileSystemHandler.GetMimeTypeFromExt: + :detail: + :pre: + :para: + - :pattern: !ruby/regexp /Example:/ + :subst: '' + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: '' +:wxMemoryFSHandler: + :detail: + :pre: + :para: + - :pattern: !ruby/regexp /Example:/ + :subst: '' + :programlisting: + - :pattern: !ruby/regexp /.*/ + :replace: '' diff --git a/rakelib/lib/generate/interface.rb b/rakelib/lib/generate/interface.rb index 2bf68ebd..b058e8a4 100644 --- a/rakelib/lib/generate/interface.rb +++ b/rakelib/lib/generate/interface.rb @@ -547,10 +547,18 @@ def gen_enums(fout) if Extractor::EnumDef === item && !item.ignored && !item.items.all? {|e| e.ignored } fout.puts fout.puts "// from enum #{item.is_anonymous ? '' : item.name}" - fout.puts "enum #{item.name};" unless item.is_anonymous - item.items.each do |e| - unless e.ignored - fout.puts "%constant int #{e.name} = #{e.fqn};" + if item.is_anonymous + item.items.each do |e| + unless e.ignored + fout.puts "%constant int #{e.name} = #{e.fqn};" + end + end + else + fout.puts "enum #{item.name};" + item.items.each do |e| + unless e.ignored + fout.puts "%constant int #{item.name}_#{e.name} = #{e.fqn};" + end end end end diff --git a/rakelib/lib/swig_runner.rb b/rakelib/lib/swig_runner.rb index 1426c772..32c2838d 100644 --- a/rakelib/lib/swig_runner.rb +++ b/rakelib/lib/swig_runner.rb @@ -262,7 +262,7 @@ def collect_enumerators def_items.each do |item| case item when Extractor::EnumDef - item.items.each { |e| enumerators[rb_wx_name(e.name)] = item } if item.is_type + item.items.each { |e| enumerators["#{rb_wx_name(item.name)}_#{e.name}"] = item } if item.is_type when Extractor::ClassDef item.items.select { |itm| Extractor::EnumDef === itm }.each do |enum| enum.items.each { |e| enumerators[rb_wx_name(e.name)] = enum } if enum.is_type @@ -334,6 +334,7 @@ def run fix_enum = true enum_item = enum_table[md[2]] enum_name = rb_wx_name(enum_item.name) + enumerator_name = rb_wx_name(md[2].sub(/\A#{enum_name}_/, '')) enum_id = enum_item.scope.empty? ? enum_name : "#{rb_wx_name(enum_item.scope)}::#{enum_name}" enum_var = enum_id.gsub('::', '_') line = [ @@ -343,7 +344,7 @@ def run # add enum class constant to current module (use unscoped name) " rb_define_const(#{md[1]}, \"#{enum_name}\", cWx#{enum_var}); // Inserted by fixmodule.rb", # create enumerator value const under new enum class - " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{md[2]}\"#{md[3]} // Updated by fixmodule.rb" + " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{enumerator_name}\"#{md[3]} // Updated by fixmodule.rb" ].join("\n") end else @@ -352,13 +353,15 @@ def run # of the same enum? if enum_item && enum_table[md[2]] == enum_item enum_name = rb_wx_name(enum_item.name) + enumerator_name = rb_wx_name(md[2].sub(/\A#{enum_name}_/, '')) enum_id = enum_item.scope.empty? ? enum_name : "#{rb_wx_name(enum_item.scope)}::#{enum_name}" enum_var = enum_id.gsub('::', '_') # create enumerator value const under new enum class - line = " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{md[2]}\"#{md[3]} // Updated by fixmodule.rb" + line = " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{enumerator_name}\"#{md[3]} // Updated by fixmodule.rb" else # we found the start of another enum enum_item = enum_table[md[2]] enum_name = rb_wx_name(enum_item.name) + enumerator_name = rb_wx_name(md[2].sub(/\A#{enum_name}_/, '')) enum_id = enum_item.scope.empty? ? enum_name : "#{rb_wx_name(enum_item.scope)}::#{enum_name}" enum_var = enum_id.gsub('::', '_') line = [ @@ -368,7 +371,7 @@ def run # add enum class constant to current module (use unscoped name) " rb_define_const(#{md[1]}, \"#{enum_name}\", cWx#{enum_var}); // Inserted by fixmodule.rb", # create enumerator value const under new enum class - " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{md[2]}\"#{md[3]} // Updated by fixmodule.rb" + " wxRuby_AddEnumValue(cWx#{enum_var}, \"#{enumerator_name}\"#{md[3]} // Updated by fixmodule.rb" ].join("\n") end else # end of enum def diff --git a/rakelib/lib/typemap/combo_popup.rb b/rakelib/lib/typemap/combo_popup.rb new file mode 100644 index 00000000..ac4a21dc --- /dev/null +++ b/rakelib/lib/typemap/combo_popup.rb @@ -0,0 +1,42 @@ +# Copyright (c) 2023 M.J.N. Corino, The Netherlands +# +# This software is released under the MIT license. + +### +# wxRuby3 wxComboPopup typemap definition +### + +require_relative '../core/mapping' + +module WXRuby3 + + module Typemap + + module ComboPopup + + include Typemap::Module + + define do + + # for DoSetPopupControl + map 'wxComboPopup* popup' => 'Wx::ComboPopup,nil' do + + add_header_code <<~__CODE + #include + + WXRUBY_EXPORT wxComboPopup* wxRuby_ComboPopupFromRuby(VALUE popup); + WXRUBY_EXPORT VALUE wxRuby_ComboPopupToRuby(wxComboPopup* popup); + __CODE + + map_in code: '$1 = wxRuby_ComboPopupFromRuby($input);' + + map_directorin code: '$input = wxRuby_ComboPopupToRuby($1);' + end + + end + + end + + end + +end diff --git a/tests/test_combo_ctrl.rb b/tests/test_combo_ctrl.rb new file mode 100644 index 00000000..d18f55e6 --- /dev/null +++ b/tests/test_combo_ctrl.rb @@ -0,0 +1,196 @@ +# Copyright (c) 2023 M.J.N. Corino, The Netherlands +# +# This software is released under the MIT license. + +require_relative './lib/wxframe_runner' +require_relative './lib/text_entry_tests' + +class ComboCtrlCtrlTests < WxRuby::Test::GUITests + + include TextEntryTests + + class LVComboPopup < Wx::ListView + + include Wx::ComboPopup + + def initialize + # call default control ctor; need to call Wx::ListView#create later + super + end + + def init + @value = -1 + end + + def create(parent) + # need to finish creating the list view here + # as calling super here would just call Wx::ComboPopup#create and not Wx::ListView#create + # we need to use Ruby magic + wx_lv_create = (Wx::ListView.instance_method :create).bind(self) + wx_lv_create.call(parent, 1, [0,0], Wx::DEFAULT_SIZE) + evt_motion :on_mouse_move + evt_left_up :on_mouse_click + end + + # Return pointer to the created control + def get_control + self + end + + def lv_find_item(*args) + unless @wx_lv_find_item + @wx_lv_find_item = (Wx::ListView.instance_method :find_item).bind(self) + end + @wx_lv_find_item.call(*args) + end + protected :lv_find_item + + # Translate string into a list selection + def set_string_value(s) + n = lv_find_item(-1, s) + if n >= 0 && n < get_item_count + select(n) + @value = n + end + end + + # Get list selection as a string + def get_string_value + return get_item_text(@value) if @value >= 0 + '' + end + + # Do mouse hot-tracking (which is typical in list popups) + def on_mouse_move(event) + # Move selection to cursor ... + end + + # On mouse left up, set the value and close the popup + def on_mouse_click(_event) + @value = get_first_selected + + # Send event as well ... + + dismiss + end + + end + + def setup + super + @combo = Wx::ComboCtrl.new(frame_win, name: 'ComboCtrl') + @combo.set_popup_control(LVComboPopup.new) + end + + def cleanup + @combo.destroy + super + end + + attr_reader :combo + alias :text_entry :combo + + def fill_list(list) + list.insert_item(0, 'This is the first item') + list.insert_item(1, 'This is the second item') + list.insert_item(2, 'This is the third item') + list.insert_item(3, 'This is the fourth item') + end + + def test_popup + assert_equal('', combo.get_value) + + assert_kind_of(Wx::ComboPopup, combo.get_popup_control) + assert_kind_of(Wx::ListView, combo.get_popup_control) + assert_kind_of(Wx::ListView, combo.get_popup_control.get_control) + + assert_nothing_raised { fill_list(combo.get_popup_control) } + combo.popup + + combo.set_value_by_user('This is the second item') + + assert_equal('This is the second item', combo.get_popup_control.get_string_value) + + combo.dismiss + end + +end + +class OwnerDrawnCBTests < WxRuby::Test::GUITests + + include TextEntryTests + + class TestODComboBox < Wx::OwnerDrawnComboBox + + def on_draw_item(dc, rect, item, _flags) + return if item == Wx::NOT_FOUND + + dc.set_text_foreground(Wx::BLACK) + dc.draw_text(get_string(item), + rect.x + 3, + rect.y + ((rect.height - dc.char_height)/2)) + end + + def on_draw_background(dc, rect, item, flags) + # If item is selected or even, or we are painting the + # combo control itself, use the default rendering. + if flags.anybits?(Wx::ODCB_PAINTING_CONTROL|Wx::ODCB_PAINTING_SELECTED) || (item & 1) == 0 + super(dc,rect,item,flags) + return + end + + # Otherwise, draw every other background with different colour. + bgCol = Wx::Colour.new(240,240,250) + dc.set_brush(Wx::Brush.new(bgCol)) + dc.set_pen(Wx::Pen.new(bgCol)) + dc.draw_rectangle(rect) + end + + def on_measure_item(_item) + 48 + end + + def on_measure_item_width(_item) + -1 # default - will be measured from text width + end + + end + + def setup + super + @combo = TestODComboBox.new(frame_win, name: 'ODComboBox') + end + + def cleanup + @combo.destroy + super + end + + attr_reader :combo + alias :text_entry :combo + + def fill_list(list) + list.append('This is the first item') + list.append('This is the second item') + list.append('This is the third item') + list.append('This is the fourth item') + end + + def test_popup + assert_equal('', combo.get_value) + + assert_kind_of(Wx::ComboPopup, combo.get_popup_control) + assert_kind_of(Wx::ComboPopupWx, combo.get_popup_control) + assert_kind_of(Wx::VListBox, combo.get_popup_control.get_control) + + assert_nothing_raised { fill_list(combo) } + combo.popup + + combo.set_value_by_user('This is the third item') + + assert_equal('This is the third item', combo.get_popup_control.get_string_value) + + combo.dismiss + end + +end