WIP: Allowing extension (mixin) classes

Needed to represent QIODeviceBase as a second base class for
QIOdevice, QFile etc. Additional base classes are supported but
in a rather limited way. They only contribute constants like enums.
No methods can be provided this way (a limitation of the method
enumeration scheme which only supports one base classe)
This commit is contained in:
Matthias Koefferlein 2021-12-07 23:24:08 +01:00
parent 6f9fa7a4a2
commit 9c0e94e63c
20 changed files with 471 additions and 143 deletions

View File

@ -807,6 +807,13 @@ include "QVector4D", [ "<QVector4D>", "<QVector2D>", "<QMatrix4x4>" ]
include "QAction", [ "<QAction>", "<QActionGroup>", "<QGraphicsWidget>", "<QMenu>" ]
include "QCursor", [ "<QCursor>", "<QScreen>", "<QBitmap>" ]
include "QGraphicsItem", [ "<QGraphicsItem>", "<QGraphicsTransform>", "<QGraphicsScene>", "<QStyleOptionGraphicsItem>", "<QGraphicsEffect>", "<QGraphicsWidget>", "<QGraphicsSceneContextMenuEvent>", "<QGraphicsSceneDragDropEvent>", "<QGraphicsSceneHoverEvent>", "<QGraphicsSceneMouseEvent>", "<QGraphicsSceneWheelEvent>", "<QPainter>" ]
include "QGraphicsObject", [ "<QGraphicsObject>", "<QChildEvent>", "<QCursor>", "<QEvent>", "<QFocusEvent>", "<QGraphicsEffect>",
"<QGraphicsItem>", "<QGraphicsItemGroup>", "<QGraphicsScene>", "<QGraphicsSceneContextMenuEvent>", "<QGraphicsSceneDragDropEvent>",
"<QGraphicsSceneHoverEvent>", "<QGraphicsSceneMouseEvent>", "<QGraphicsSceneWheelEvent>", "<QGraphicsWidget>",
"<QInputMethodEvent>", "<QKeyEvent>", "<QMetaMethod>", "<QObject>", "<QPainter>", "<QPainterPath>", "<QPointF>",
"<QPolygonF>", "<QRectF>", "<QRegion>", "<QSize>", "<QStyleOptionGraphicsItem>", "<QThread>",
"<QTimerEvent>", "<QTransform>", "<QWidget>",
"<QGraphicsTransform>" ]
include "QGraphicsScene", [ "<QGraphicsScene>", "<QGraphicsView>", "<QGraphicsItem>", "<QGraphicsWidget>", "<QGraphicsEllipseItem>", "<QGraphicsLineItem>", "<QGraphicsPathItem>", "<QGraphicsPixmapItem>", "<QGraphicsPolygonItem>", "<QGraphicsRectItem>", "<QGraphicsSimpleTextItem>", "<QGraphicsTextItem>", "<QGraphicsProxyWidget>", "<QGraphicsItemGroup>", "<QStyle>", "<QGraphicsSceneContextMenuEvent>", "<QGraphicsSceneDragDropEvent>", "<QGraphicsSceneHelpEvent>", "<QGraphicsSceneMouseEvent>", "<QGraphicsSceneWheelEvent>" ]
include "QGuiApplication", [ "<QGuiApplication>", "<QScreen>", "<QSessionManager>", "<QClipboard>", "<QWindow>", "<QStyleHints>", "<QFont>", "<QPalette>" ]
include "QApplication", [ "<QApplication>", "<QSessionManager>", "<QStyle>", "<QWindow>", "<QScreen>", "<QFont>", "<QFontMetrics>", "<QWidget>" ]

View File

@ -265,7 +265,7 @@ class CPPStruct
cls = self.id.to_s
# collect enums from inner classes
# collect used enums from inner classes
(self.body_decl || []).each do |bd|
decl = nil
if bd.is_a?(CPPStructDeclaration) && bd.visibility == :public && bd.struct.body_decl && bd.myself != "" && !conf.is_class_dropped?(cls, bd.myself)
@ -273,6 +273,14 @@ class CPPStruct
end
end
# collect used enums from base classes
(self.base_classes || []).each do |bc|
bc_obj = self.parent.resolve_qid(bc.class_id)
if bc_obj.is_a?(CPPStructDeclaration) && bc_obj.visibility == :public && bc_obj.struct.body_decl && bc_obj.myself != "" && !conf.is_class_dropped?(cls, bc_obj.myself)
bc_obj.struct.collect_used_enums(map, conf)
end
end
methods = {}
self.collect_all_methods(methods, conf)
@ -315,7 +323,7 @@ class CPPStruct
end
def collect_methods(map)
def collect_methods(map, weak = false)
mmap = {}
@ -355,6 +363,7 @@ class CPPStruct
end
# take non-duplicates (by call signature) for the map
# weak ones do not redefine methods
mmap.each do |mn,decls|
seen = {}
@ -362,7 +371,9 @@ class CPPStruct
s = d.call_sig
if !seen[s]
seen[s] = true
(map[mn] ||= []) << d
if !weak || !map[mn]
(map[mn] ||= []) << d
end
end
end
@ -1907,6 +1918,10 @@ END
base_cls = base_classes[0] && base_classes[0].class_id.to_s
base_clsn = base_cls && make_cls_name(base_cls)
# as we only support single base classes (a tribute to Ruby), we treat all other base classes as
# mixins
mixin_base_classes = base_classes[1..] || []
methods_by_name = {}
all_methods_by_name = {}
enum_decls_by_name = {}
@ -1918,8 +1933,8 @@ END
end
mmap = {}
struct.collect_methods(methods_by_name)
struct.collect_all_methods(all_methods_by_name, conf)
struct.collect_methods(methods_by_name)
struct.collect_enum_decls(enum_decls_by_name) { |bd| self.is_enum_used?(bd) || conf.is_enum_included?(cls, bd.myself) }
# if one method is abstract, omit ctors for example
@ -2365,35 +2380,35 @@ END
base_classes.each do |bc|
bc_name = bc.class_id.to_s
bc_name = bc.class_id.to_s
ofile.puts("// base class cast for #{bc_name}")
ofile.puts("")
ofile.puts("static void _init_f_#{clsn}_as_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<#{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<#{bc_name} *> ((#{bc_name} *)(#{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
ofile.puts("// base class cast for #{bc_name}")
ofile.puts("")
ofile.puts("static void _init_f_#{clsn}_as_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<#{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<#{bc_name} *> ((#{bc_name} *)(#{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
mdecl_bcc << "new qt_gsi::GenericMethod (\"as#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\", false, &_init_f_#{clsn}_as_#{bc_name}, &_call_f_#{clsn}_as_#{bc_name});"
mdecl_bcc << "new qt_gsi::GenericMethod (\"as#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\", false, &_init_f_#{clsn}_as_#{bc_name}, &_call_f_#{clsn}_as_#{bc_name});"
ofile.puts("static void _init_f_#{clsn}_as_const_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<const #{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_const_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<const #{bc_name} *> ((const #{bc_name} *)(const #{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _init_f_#{clsn}_as_const_#{bc_name} (qt_gsi::GenericMethod *decl)")
ofile.puts("{")
ofile.puts(" decl->set_return<const #{bc_name} *> ();")
ofile.puts("}")
ofile.puts("")
ofile.puts("static void _call_f_#{clsn}_as_const_#{bc_name} (const qt_gsi::GenericMethod *, void *cls, gsi::SerialArgs &, gsi::SerialArgs &ret) ")
ofile.puts("{")
ofile.puts(" ret.write<const #{bc_name} *> ((const #{bc_name} *)(const #{cls} *)cls);")
ofile.puts("}")
ofile.puts("")
mdecl_bcc << "new qt_gsi::GenericMethod (\"asConst#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\\n\\nUse this version if you have a const reference.\", true, &_init_f_#{clsn}_as_const_#{bc_name}, &_call_f_#{clsn}_as_const_#{bc_name});"
mdecl_bcc << "new qt_gsi::GenericMethod (\"asConst#{bc_name}\", \"@brief Delivers the base class interface #{bc_name} of #{cls}\\nClass #{cls} is derived from multiple base classes. This method delivers the #{bc_name} base class aspect.\\n\\nUse this version if you have a const reference.\", true, &_init_f_#{clsn}_as_const_#{bc_name}, &_call_f_#{clsn}_as_const_#{bc_name});"
end
@ -2474,6 +2489,23 @@ END
end
# Produce the mixin base classes
if ! mixin_base_classes.empty?
ofile.puts("// Additional base classes")
ofile.puts("")
mixin_base_classes.each do |bc|
bc_name = bc.class_id.to_s
ofile.puts("gsi::Class<#{bc_name}> &qtdecl_#{bc_name} ();")
end
ofile.puts("")
end
mixin_base_classes.each do |bc|
bc_name = bc.class_id.to_s
ofile.puts("gsi::ClassExt<#{cls}> base_class_#{bc_name}_in_#{clsn} (qtdecl_#{bc_name} ());")
end
ofile.puts("")
ofile.puts("GSI_#{modn.upcase}_PUBLIC gsi::Class<#{cls}> &qtdecl_#{clsn} () { return decl_#{clsn}; }")
ofile.puts("")
@ -3213,7 +3245,7 @@ bp.read(input_file)
puts("Collecting used enums ..")
l = bp.prod_list(conf)
l.each_with_index do |decl_obj,i|
puts "#{decl_obj.myself}: #{i+1}/#{l.size}"
decl_obj.myself && puts("#{decl_obj.myself}: #{i+1}/#{l.size}")
bp.collect_used_enums(conf, decl_obj)
end

View File

@ -196,7 +196,7 @@ module QualifiedNameResolver
@id2obj && @id2obj[id]
end
def resolve_qid(qid)
def resolve_qid(qid, stop = nil, include_other = true)
qid.is_a?(CPPQualifiedId) || raise("Argument of resolve_qid must be a CPPQualifiedId object")
@ -206,22 +206,30 @@ module QualifiedNameResolver
while root.parent
root = root.parent
end
obj = root.resolve_qid(qid)
obj = root.resolve_qid(qid, nil, false)
else
obj = id2obj(qid.parts[0].id)
if obj && qid.parts.size > 1
# The part may be a typedef: resolve it in that case before we proceed
while obj && obj.is_a?(CPPTypedef)
obj = obj.type.concrete.is_a?(CPPQualifiedId) && self.resolve_qid(obj.type.concrete)
obj = obj.type.concrete.is_a?(CPPQualifiedId) && self.resolve_qid(obj.type.concrete, stop, include_other)
end
if obj
qid_new = qid.dup
qid_new.parts = qid.parts[1 .. -1]
obj = obj.respond_to?(:resolve_qid) && obj.resolve_qid(qid_new)
obj = obj.respond_to?(:resolve_qid) && obj.resolve_qid(qid_new, stop, include_other)
end
end
if ! obj && self.parent
obj = self.parent.resolve_qid(qid)
if ! obj && include_other
# try base classes
self.other_children.each do |bc|
if bc != self && bc.respond_to?(:resolve_qid)
(obj = bc.resolve_qid(qid, self, false)) && break
end
end
end
if ! obj && self.parent && self.parent != stop
obj = self.parent.resolve_qid(qid, stop, include_other)
end
end
@ -281,6 +289,10 @@ class CPPDeclaration
[]
end
def other_children
[]
end
def myself
self.type.name
end
@ -295,6 +307,10 @@ class CPPEnumDeclaration
[]
end
def other_children
[]
end
def myself
# exclude forward declarations
self.enum.specs && self.enum.name.to_s
@ -310,6 +326,10 @@ class CPPEnumSpec
[]
end
def other_children
[]
end
def myself
self.name.to_s
end
@ -353,6 +373,10 @@ class CPPTypedef
[]
end
def other_children
[]
end
end
class CPPStructDeclaration
@ -395,7 +419,7 @@ class CPPStructDeclaration
# The parent may be null for template base classes which are
# forward-declared .. we're not interested in this case.
if self.parent
bc_obj = self.parent.resolve_qid(bc.class_id)
bc_obj = self.parent.resolve_qid(bc.class_id, nil, false)
# NOTE: it may look strange to test whether the base class is the class itself but
# since we do a half-hearted job of resolving template variants, this may happen
# if we derive a template specialization from another one (specifically
@ -524,6 +548,10 @@ class CPPModule
end
def other_children
[]
end
def remove(d)
self.decls.delete(d)
end

View File

@ -316,7 +316,7 @@ public:
* This feature is not quite useful usually and is reserved for special use cases
* such as including enums into a declaration namespace.
*/
ClassExt (const ClassBase &import, const std::string &name, const std::string &doc = std::string ())
ClassExt (const ClassBase &import, const std::string &name = std::string (), const std::string &doc = std::string ())
: ClassBase (doc, Methods ()), mp_declaration (&import)
{
set_name (name);

View File

@ -1664,5 +1664,32 @@ static gsi::Class<GFactory_P> decl_gfactory (decl_gfactory_base, "", "GFactory",
gsi::factory_callback ("f", &GFactory_P::f, &GFactory_P::f_cb)
);
static gsi::Class<B1> decl_b1 ("", "B1",
gsi::method ("get1", &B1::get1) +
gsi::method ("set1", &B1::set1) +
gsi::constant ("C1", 42)
);
static gsi::Class<B2> decl_b2 ("", "B2",
gsi::constant ("C2", 17)
);
static gsi::Class<B3> decl_b3 ("", "B3",
gsi::constant ("C3", -1)
);
gsi::EnumIn<B3, B3::E> enum_in_b3 ("", "E",
gsi::enum_const ("E3A", B3::E3A) +
gsi::enum_const ("E3B", B3::E3B) +
gsi::enum_const ("E3C", B3::E3C)
);
// 3 base classes
static gsi::Class<BB> decl_bb (decl_b1, "", "BB",
gsi::method ("d3", &BB::d3)
);
gsi::ClassExt<BB> b2_in_bb (decl_b2);
gsi::ClassExt<BB> b3_in_bb (decl_b3);
}

View File

@ -1397,6 +1397,37 @@ private:
int m_tag;
};
class B1
{
public:
B1 () : m_value (0) {}
int get1 () { return m_value; }
void set1 (int v) { m_value = v; }
private:
int m_value;
};
class B2
{
public:
B2 () {}
};
class B3
{
public:
B3 () {}
enum E { E3A = 100, E3B = 101, E3C = 102 };
};
class BB
: public B1, public B2, public B3
{
public:
int d3 (B3::E a, B3::E b) { return b - a; }
};
}
#endif

View File

@ -846,6 +846,11 @@ qt_gsi::QtNativeClass<QIODevice> decl_QIODevice (qtdecl_QObject (), "QtCore", "Q
methods_QIODevice (),
"@qt\n@brief Binding of QIODevice");
// Additional base classes
gsi::Class<QIODeviceBase> &qtdecl_QIODeviceBase ();
gsi::ClassExt<QIODevice> base_class_QIODeviceBase_in_QIODevice (qtdecl_QIODeviceBase ());
GSI_QTCORE_PUBLIC gsi::Class<QIODevice> &qtdecl_QIODevice () { return decl_QIODevice; }

View File

@ -776,6 +776,19 @@ gsi::Class<QXmlContentHandler> &qtdecl_QXmlContentHandler ();
gsi::Class<QXmlDefaultHandler> decl_QXmlDefaultHandler (qtdecl_QXmlContentHandler (), "QtCore5Compat", "QXmlDefaultHandler_Native",
methods_QXmlDefaultHandler (),
"@hide\n@alias QXmlDefaultHandler");
// Additional base classes
gsi::Class<QXmlErrorHandler> &qtdecl_QXmlErrorHandler ();
gsi::Class<QXmlDTDHandler> &qtdecl_QXmlDTDHandler ();
gsi::Class<QXmlEntityResolver> &qtdecl_QXmlEntityResolver ();
gsi::Class<QXmlLexicalHandler> &qtdecl_QXmlLexicalHandler ();
gsi::Class<QXmlDeclHandler> &qtdecl_QXmlDeclHandler ();
gsi::ClassExt<QXmlDefaultHandler> base_class_QXmlErrorHandler_in_QXmlDefaultHandler (qtdecl_QXmlErrorHandler ());
gsi::ClassExt<QXmlDefaultHandler> base_class_QXmlDTDHandler_in_QXmlDefaultHandler (qtdecl_QXmlDTDHandler ());
gsi::ClassExt<QXmlDefaultHandler> base_class_QXmlEntityResolver_in_QXmlDefaultHandler (qtdecl_QXmlEntityResolver ());
gsi::ClassExt<QXmlDefaultHandler> base_class_QXmlLexicalHandler_in_QXmlDefaultHandler (qtdecl_QXmlLexicalHandler ());
gsi::ClassExt<QXmlDefaultHandler> base_class_QXmlDeclHandler_in_QXmlDefaultHandler (qtdecl_QXmlDeclHandler ());
GSI_QTCORE5COMPAT_PUBLIC gsi::Class<QXmlDefaultHandler> &qtdecl_QXmlDefaultHandler () { return decl_QXmlDefaultHandler; }

View File

@ -313,6 +313,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QOffscreenSurface> decl_QOffscreenSurface (qtdecl_QObject (), "QtGui", "QOffscreenSurface_Native",
methods_QOffscreenSurface (),
"@hide\n@alias QOffscreenSurface");
// Additional base classes
gsi::Class<QSurface> &qtdecl_QSurface ();
gsi::ClassExt<QOffscreenSurface> base_class_QSurface_in_QOffscreenSurface (qtdecl_QSurface ());
GSI_QTGUI_PUBLIC gsi::Class<QOffscreenSurface> &qtdecl_QOffscreenSurface () { return decl_QOffscreenSurface; }

View File

@ -297,6 +297,11 @@ gsi::Class<QWindow> &qtdecl_QWindow ();
qt_gsi::QtNativeClass<QPaintDeviceWindow> decl_QPaintDeviceWindow (qtdecl_QWindow (), "QtGui", "QPaintDeviceWindow_Native",
methods_QPaintDeviceWindow (),
"@hide\n@alias QPaintDeviceWindow");
// Additional base classes
gsi::Class<QPaintDevice> &qtdecl_QPaintDevice ();
gsi::ClassExt<QPaintDeviceWindow> base_class_QPaintDevice_in_QPaintDeviceWindow (qtdecl_QPaintDevice ());
GSI_QTGUI_PUBLIC gsi::Class<QPaintDeviceWindow> &qtdecl_QPaintDeviceWindow () { return decl_QPaintDeviceWindow; }

View File

@ -382,6 +382,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QPdfWriter> decl_QPdfWriter (qtdecl_QObject (), "QtGui", "QPdfWriter_Native",
methods_QPdfWriter (),
"@hide\n@alias QPdfWriter");
// Additional base classes
gsi::Class<QPagedPaintDevice> &qtdecl_QPagedPaintDevice ();
gsi::ClassExt<QPdfWriter> base_class_QPagedPaintDevice_in_QPdfWriter (qtdecl_QPagedPaintDevice ());
GSI_QTGUI_PUBLIC gsi::Class<QPdfWriter> &qtdecl_QPdfWriter () { return decl_QPdfWriter; }

View File

@ -2206,6 +2206,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QWindow> decl_QWindow (qtdecl_QObject (), "QtGui", "QWindow_Native",
methods_QWindow (),
"@hide\n@alias QWindow");
// Additional base classes
gsi::Class<QSurface> &qtdecl_QSurface ();
gsi::ClassExt<QWindow> base_class_QSurface_in_QWindow (qtdecl_QSurface ());
GSI_QTGUI_PUBLIC gsi::Class<QWindow> &qtdecl_QWindow () { return decl_QWindow; }

View File

@ -450,6 +450,11 @@ gsi::Class<QAccessibleWidget> decl_QAccessibleWidget (qtdecl_QAccessibleObject (
methods_QAccessibleWidget (),
"@qt\n@brief Binding of QAccessibleWidget");
// Additional base classes
gsi::Class<QAccessibleActionInterface> &qtdecl_QAccessibleActionInterface ();
gsi::ClassExt<QAccessibleWidget> base_class_QAccessibleActionInterface_in_QAccessibleWidget (qtdecl_QAccessibleActionInterface ());
GSI_QTWIDGETS_PUBLIC gsi::Class<QAccessibleWidget> &qtdecl_QAccessibleWidget () { return decl_QAccessibleWidget; }

View File

@ -58,6 +58,7 @@
#include <QTimerEvent>
#include <QTransform>
#include <QWidget>
#include <QGraphicsTransform>
#include "gsiQt.h"
#include "gsiQtWidgetsCommon.h"
#include <memory>
@ -242,6 +243,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QGraphicsObject> decl_QGraphicsObject (qtdecl_QObject (), "QtWidgets", "QGraphicsObject_Native",
methods_QGraphicsObject (),
"@hide\n@alias QGraphicsObject");
// Additional base classes
gsi::Class<QGraphicsItem> &qtdecl_QGraphicsItem ();
gsi::ClassExt<QGraphicsObject> base_class_QGraphicsItem_in_QGraphicsObject (qtdecl_QGraphicsItem ());
GSI_QTWIDGETS_PUBLIC gsi::Class<QGraphicsObject> &qtdecl_QGraphicsObject () { return decl_QGraphicsObject; }

View File

@ -1374,6 +1374,11 @@ gsi::Class<QGraphicsObject> &qtdecl_QGraphicsObject ();
qt_gsi::QtNativeClass<QGraphicsWidget> decl_QGraphicsWidget (qtdecl_QGraphicsObject (), "QtWidgets", "QGraphicsWidget_Native",
methods_QGraphicsWidget (),
"@hide\n@alias QGraphicsWidget");
// Additional base classes
gsi::Class<QGraphicsLayoutItem> &qtdecl_QGraphicsLayoutItem ();
gsi::ClassExt<QGraphicsWidget> base_class_QGraphicsLayoutItem_in_QGraphicsWidget (qtdecl_QGraphicsLayoutItem ());
GSI_QTWIDGETS_PUBLIC gsi::Class<QGraphicsWidget> &qtdecl_QGraphicsWidget () { return decl_QGraphicsWidget; }

View File

@ -1013,6 +1013,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QLayout> decl_QLayout (qtdecl_QObject (), "QtWidgets", "QLayout_Native",
methods_QLayout (),
"@hide\n@alias QLayout");
// Additional base classes
gsi::Class<QLayoutItem> &qtdecl_QLayoutItem ();
gsi::ClassExt<QLayout> base_class_QLayoutItem_in_QLayout (qtdecl_QLayoutItem ());
GSI_QTWIDGETS_PUBLIC gsi::Class<QLayout> &qtdecl_QLayout () { return decl_QLayout; }

View File

@ -4981,6 +4981,11 @@ gsi::Class<QObject> &qtdecl_QObject ();
qt_gsi::QtNativeClass<QWidget> decl_QWidget (qtdecl_QObject (), "QtWidgets", "QWidget_Native",
methods_QWidget (),
"@hide\n@alias QWidget");
// Additional base classes
gsi::Class<QPaintDevice> &qtdecl_QPaintDevice ();
gsi::ClassExt<QWidget> base_class_QPaintDevice_in_QWidget (qtdecl_QPaintDevice ());
GSI_QTWIDGETS_PUBLIC gsi::Class<QWidget> &qtdecl_QWidget () { return decl_QWidget; }

View File

@ -2384,6 +2384,10 @@ PythonModule::make_classes (const char *mod_name)
PYASignal::make_class (module);
std::list<const gsi::ClassBase *> sorted_classes = gsi::ClassBase::classes_in_definition_order (mod_name);
// ... @@@ find all extensions for a class ...
// ... @@@ reverse this order ...
for (std::list<const gsi::ClassBase *>::const_iterator c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if (mod_name && (*c)->module () != mod_name) {
@ -2398,6 +2402,7 @@ PythonModule::make_classes (const char *mod_name)
// duplication of enums into child classes). In this case we create a constant inside the
// target class.
if ((*c)->declaration () != *c) {
// ... @@@ continue if named ...
tl_assert ((*c)->parent () != 0); // top-level classes should be merged
PyTypeObject *parent_type = PythonClassClientData::py_type (*(*c)->parent ()->declaration ());
PyTypeObject *type = PythonClassClientData::py_type (*(*c)->declaration ());
@ -2414,6 +2419,7 @@ PythonModule::make_classes (const char *mod_name)
m_classes.push_back (*c);
PythonRef bases;
// ... @@@ collect bases (from this base and extensions) ...
if ((*c)->base () != 0) {
bases = PythonRef (PyTuple_New (1));
PyTypeObject *pt = PythonClassClientData::py_type (*(*c)->base ());
@ -2465,6 +2471,8 @@ PythonModule::make_classes (const char *mod_name)
PyModule_AddObject (module, (*c)->name ().c_str (), (PyObject *) type);
}
// ... @@@ add the named extensions as attributes (see above) ...
// Build the attributes now ...
MethodTable *mt = MethodTable::method_table_by_class (*c);

View File

@ -1111,7 +1111,7 @@ method_adaptor (int mid, int argc, VALUE *argv, VALUE self, bool ctor)
}
} catch (tl::CancelException) {
} catch (tl::CancelException &) {
// break encountered
}
@ -1521,52 +1521,112 @@ rba_add_path (const std::string &path)
}
}
static void
rba_init (RubyInterpreterPrivateData *d)
namespace
{
VALUE module = rb_define_module ("RBA");
// initialize the locked object vault as a fast replacement for rb_gc_register_address/rb_gc_unregister_address.
rba::make_locked_object_vault (module);
class RubyClassGenerator
{
public:
RubyClassGenerator (VALUE module)
: m_module (module)
{
// .. nothing yet ..
}
// save all constants for later (we cannot declare them while we are still producing classes
// because of the enum representative classes and enum constants are important)
std::vector <RubyConstDescriptor> constants;
// needs to be called before for each extension before the classes are made
void register_extension (const gsi::ClassBase *cls)
{
if (cls->name ().empty ()) {
std::list<const gsi::ClassBase *> sorted_classes = gsi::ClassBase::classes_in_definition_order ();
for (std::list<const gsi::ClassBase *>::const_iterator c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
// got an extension
tl_assert (cls->parent ());
m_extensions_for [cls->parent ()->declaration ()].push_back (cls->declaration ());
m_extensions.insert (cls->declaration ());
// we might encounter a child class which is a reference to a top-level class (e.g.
// duplication of enums into child classes). In this case we create a constant inside the
// target class.
if ((*c)->declaration () != *c) {
tl_assert ((*c)->parent () != 0); // top-level classes should be merged
rb_define_const (ruby_cls ((*c)->parent ()->declaration ()), (*c)->name ().c_str (), ruby_cls ((*c)->declaration ()));
continue;
} else {
m_links_for [cls->parent ()->declaration ()].push_back (std::make_pair (cls->name (), cls->declaration ()));
}
}
VALUE make_class (const gsi::ClassBase *cls)
{
if (is_registered (cls)) {
return ruby_cls (cls);
}
bool is_extension = (m_extensions.find (cls) != m_extensions.end ());
bool base_class_is_extension = (m_extensions.find (cls->base ()) != m_extensions.end ());
VALUE super = rb_cObject;
if ((*c)->base () != 0) {
tl_assert (is_registered ((*c)->base ()));
super = ruby_cls ((*c)->base ());
if (cls->base () != 0) {
super = make_class (cls->base ());
}
VALUE klass;
if ((*c)->parent ()) {
tl_assert (is_registered ((*c)->parent ()->declaration ()));
VALUE parent_class = ruby_cls ((*c)->parent ()->declaration ());
klass = rb_define_class_under (parent_class, (*c)->name ().c_str (), super);
if (is_extension) {
if (tl::verbosity () >= 20) {
tl::log << tl::to_string (tr ("Registering class as Ruby module:) ")) << cls->name ();
}
if (cls->base () && ! base_class_is_extension) {
tl::warn << tl::to_string (tr ("Base class of mixin module ignored: ")) << cls->name ();
}
if (cls->parent ()) {
VALUE parent_class = make_class (cls->parent ()->declaration ());
klass = rb_define_module_under (parent_class, cls->name ().c_str ());
} else {
klass = rb_define_module_under (m_module, cls->name ().c_str ());
}
} else {
klass = rb_define_class_under (module, (*c)->name ().c_str (), super);
VALUE direct_super = base_class_is_extension ? rb_cObject : super;
if (cls->parent ()) {
VALUE parent_class = make_class (cls->parent ()->declaration ());
klass = rb_define_class_under (parent_class, cls->name ().c_str (), direct_super);
} else {
klass = rb_define_class_under (m_module, cls->name ().c_str (), direct_super);
}
rb_define_alloc_func (klass, alloc_proxy);
}
register_class (klass, *c);
// if the base class is an extension (mixin), we cannot use it as superclass because it's a module
if (cls->base () != 0 && base_class_is_extension) {
rb_include_module (klass, super);
}
rb_define_alloc_func (klass, alloc_proxy);
register_class (klass, cls);
MethodTable *mt = MethodTable::method_table_by_class (*c, true /*force init*/);
// mix-in unnamed extensions
for (gsi::ClassBase::method_iterator m = (*c)->begin_methods (); m != (*c)->end_methods (); ++m) {
auto exts = m_extensions_for.find (cls);
if (exts != m_extensions_for.end ()) {
for (auto ie = exts->second.begin (); ie != exts->second.end (); ++ie) {
VALUE ext_module = make_class (*ie);
rb_include_module (klass, ext_module);
rb_extend_object (klass, ext_module);
}
}
// add named extensions
auto links = m_links_for.find (cls);
if (links != m_links_for.end ()) {
for (auto il = links->second.begin (); il != links->second.end (); ++il) {
VALUE linked_class = make_class (il->second);
rb_define_const (klass, il->first.c_str (), linked_class);
}
}
MethodTable *mt = MethodTable::method_table_by_class (cls, true /*force init*/);
for (auto m = (cls)->begin_methods (); m != (cls)->end_methods (); ++m) {
if (! (*m)->is_callback ()) {
@ -1582,7 +1642,7 @@ rba_init (RubyInterpreterPrivateData *d)
if (! drop_method) {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
for (auto syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (syn->is_predicate) {
mt->add_method (syn->name, *m);
mt->add_method (syn->name + "?", *m);
@ -1597,17 +1657,17 @@ rba_init (RubyInterpreterPrivateData *d)
} else {
for (gsi::MethodBase::synonym_iterator syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
for (auto syn = (*m)->begin_synonyms (); syn != (*m)->end_synonyms (); ++syn) {
if (isupper (syn->name [0]) && (*m)->begin_arguments () == (*m)->end_arguments ()) {
// Static const methods are constants.
// Methods without arguments which start with a capital letter are treated as constants
// for backward compatibility
constants.push_back (RubyConstDescriptor ());
constants.back ().klass = klass;
constants.back ().meth = *m;
constants.back ().name = (*m)->begin_synonyms ()->name;
m_constants.push_back (RubyConstDescriptor ());
m_constants.back ().klass = klass;
m_constants.back ().meth = *m;
m_constants.back ().name = (*m)->begin_synonyms ()->name;
} else if ((*m)->ret_type ().type () == gsi::T_object && (*m)->ret_type ().pass_obj () && syn->name == "new") {
@ -1640,96 +1700,150 @@ rba_init (RubyInterpreterPrivateData *d)
// clean up the method table
mt->finish ();
// Hint: we need to do static methods before the non-static ones because
// rb_define_module_function creates an private instance method.
// If we do the non-static methods afterwards we will make it a public once again.
// The order of the names will be "name(non-static), name(static), ..." because
// the static flag is the second member of the key (string, bool) pair.
for (size_t mid = mt->bottom_mid (); mid < mt->top_mid (); ++mid) {
// NOTE: extensions can't carry methods - this is due to the method numbering scheme
// which can only handle direct base classes. So only constants are carried forward.
if (! is_extension) {
if (mt->is_static (mid)) {
// Hint: we need to do static methods before the non-static ones because
// rb_define_module_function creates an private instance method.
// If we do the non-static methods afterwards we will make it a public once again.
// The order of the names will be "name(non-static), name(static), ..." because
// the static flag is the second member of the key (string, bool) pair.
for (size_t mid = mt->bottom_mid (); mid < mt->top_mid (); ++mid) {
tl_assert (mid < size_t (sizeof (method_adaptors) / sizeof (method_adaptors [0])));
if (mt->is_static (mid)) {
tl_assert (mid < size_t (sizeof (method_adaptors) / sizeof (method_adaptors [0])));
/* Note: Ruby does not support static protected functions, hence we have them (i.e. QThread::usleep).
* Do we silently create public ones from them:
if (mt->is_protected (mid)) {
tl::warn << "static '" << mt->name (mid) << "' method cannot be protected in class " << c->name ();
}
*/
rb_define_module_function (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
/* Note: Ruby does not support static protected functions, hence we have them (i.e. QThread::usleep).
* Do we silently create public ones from them:
if (mt->is_protected (mid)) {
tl::warn << "static '" << mt->name (mid) << "' method cannot be protected in class " << c->name ();
}
*/
rb_define_module_function (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
}
for (size_t mid = mt->bottom_mid (); mid < mt->top_mid (); ++mid) {
if (mt->is_ctor (mid)) {
tl_assert (mid < size_t (sizeof (method_adaptors_ctor) / sizeof (method_adaptors_ctor [0])));
if (! mt->is_protected (mid)) {
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
} else {
// a protected constructor needs to be provided in both protected and non-protected mode
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
rb_define_protected_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
}
} else if (! mt->is_static (mid)) {
tl_assert (mid < size_t (sizeof (method_adaptors) / sizeof (method_adaptors [0])));
if (! mt->is_protected (mid)) {
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
} else {
rb_define_protected_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
}
}
if (mt->is_signal (mid)) {
// We alias the signal name to an assignment, so the following can be done:
// x = object with signal "signal"
// x.signal = proc
// this will basically map to
// x.signal(proc)
// which will make proc the only receiver for the signal
rb_define_alias (klass, (mt->name (mid) + "=").c_str (), mt->name (mid).c_str ());
}
if (mt->name (mid) == "to_s") {
#if HAVE_RUBY_VERSION_CODE>=20000 && defined(GSI_ALIAS_INSPECT)
// Ruby 2.x does no longer alias "inspect" to "to_s" automatically, so we have to do this:
rb_define_alias (klass, "inspect", "to_s");
#endif
} else if (mt->name (mid) == "==") {
rb_define_alias (klass, "eql?", "==");
}
}
}
for (size_t mid = mt->bottom_mid (); mid < mt->top_mid (); ++mid) {
return klass;
}
if (mt->is_ctor (mid)) {
void make_constants ()
{
for (auto c = m_constants.begin (); c != m_constants.end (); ++c) {
tl_assert (mid < size_t (sizeof (method_adaptors_ctor) / sizeof (method_adaptors_ctor [0])));
try {
if (! mt->is_protected (mid)) {
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
} else {
// a protected constructor needs to be provided in both protected and non-protected mode
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
rb_define_protected_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors_ctor[mid], -1);
}
gsi::SerialArgs retlist (c->meth->retsize ());
gsi::SerialArgs arglist (c->meth->argsize ());
c->meth->call (0, arglist, retlist);
tl::Heap heap;
VALUE ret = pop_arg (c->meth->ret_type (), 0, retlist, heap);
rb_define_const (c->klass, c->name.c_str (), ret);
} else if (! mt->is_static (mid)) {
tl_assert (mid < size_t (sizeof (method_adaptors) / sizeof (method_adaptors [0])));
if (! mt->is_protected (mid)) {
rb_define_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
} else {
rb_define_protected_method (klass, mt->name (mid).c_str (), (ruby_func) method_adaptors[mid], -1);
}
}
if (mt->is_signal (mid)) {
// We alias the signal name to an assignment, so the following can be done:
// x = object with signal "signal"
// x.signal = proc
// this will basically map to
// x.signal(proc)
// which will make proc the only receiver for the signal
rb_define_alias (klass, (mt->name (mid) + "=").c_str (), mt->name (mid).c_str ());
}
if (mt->name (mid) == "to_s") {
#if HAVE_RUBY_VERSION_CODE>=20000 && defined(GSI_ALIAS_INSPECT)
// Ruby 2.x does no longer alias "inspect" to "to_s" automatically, so we have to do this:
rb_define_alias (klass, "inspect", "to_s");
#endif
} else if (mt->name (mid) == "==") {
rb_define_alias (klass, "eql?", "==");
} catch (tl::Exception &ex) {
tl::warn << "Got exception '" << ex.msg () << "' while defining constant " << c->name;
}
}
}
private:
VALUE m_module;
std::vector <RubyConstDescriptor> m_constants;
std::map<const gsi::ClassBase *, std::vector<const gsi::ClassBase *> > m_extensions_for;
std::set<const gsi::ClassBase *> m_extensions;
std::map<const gsi::ClassBase *, std::vector<std::pair<std::string, const gsi::ClassBase *> > > m_links_for;
};
}
static void
rba_init (RubyInterpreterPrivateData *d)
{
VALUE module = rb_define_module ("RBA");
// initialize the locked object vault as a fast replacement for rb_gc_register_address/rb_gc_unregister_address.
rba::make_locked_object_vault (module);
// save all constants for later (we cannot declare them while we are still producing classes
// because of the enum representative classes and enum constants are important)
std::vector <RubyConstDescriptor> constants;
std::list<const gsi::ClassBase *> sorted_classes = gsi::ClassBase::classes_in_definition_order ();
RubyClassGenerator gen (module);
// first pass: register the extensions
for (auto c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if ((*c)->declaration () != *c) {
gen.register_extension (*c);
}
}
// second pass: make the classes
for (auto c = sorted_classes.begin (); c != sorted_classes.end (); ++c) {
if ((*c)->declaration () == *c) {
gen.make_class (*c);
}
}
// now make the constants
for (std::vector <RubyConstDescriptor>::const_iterator c = constants.begin (); c != constants.end (); ++c) {
try {
gsi::SerialArgs retlist (c->meth->retsize ());
gsi::SerialArgs arglist (c->meth->argsize ());
c->meth->call (0, arglist, retlist);
tl::Heap heap;
VALUE ret = pop_arg (c->meth->ret_type (), 0, retlist, heap);
rb_define_const (c->klass, c->name.c_str (), ret);
} catch (tl::Exception &ex) {
tl::warn << "Got exception '" << ex.msg () << "' while defining constant " << c->name;
}
}
gen.make_constants ();
// define a signal representative class RBASignal
SignalHandler::define_class (module, "RBASignal");

View File

@ -3012,6 +3012,23 @@ class Basic_TestClass < TestBase
end
# Tests multi-base mixins (only constants and enums available)
def test_multiBaseMixins
bb = RBA::BB::new # base classes B1,B2,B3
bb.set1(17) # B1
assert_equal(bb.get1, 17) # B1
bb.set1(21) # B1
assert_equal(bb.get1, 21) # B1
assert_equal(RBA::BB::C2, 17) # B2
assert_equal(RBA::BB::C3, -1) # B3
assert_equal(RBA::BB::E::E3B.to_i, 101) # B3
assert_equal(bb.d3(RBA::BB::E::E3C, RBA::BB::E::E3A), -2) # BB with B3 enums
assert_equal(bb.d3(RBA::BB::E::E3A, RBA::BB::E::E3C), 2) # BB with B3 enums
end
# Custom factory implemented in Ruby
def test_80