# frozen_string_literal: true require 'gir_ffi/builders/object_builder' require 'gir_ffi/g_type' module GirFFI module Builders # Implements the creation of GObject subclasses from Ruby. class UserDefinedBuilder < ObjectBuilder def initialize(info) @info = info end def setup_class setup_layout register_type setup_constants setup_property_accessors setup_constructor TypeBuilder::CACHE[@gtype] = klass end def target_gtype @gtype ||= klass.gtype end private def register_type @gtype = GObject.type_register_static(parent_gtype.to_i, info.g_name, gobject_type_info, 0) included_interfaces.each do |interface| ifinfo = gobject_interface_info interface GObject.type_add_interface_static @gtype, interface.gtype, ifinfo end end def parent_info @info.parent end def parent_gtype @info.parent_gtype end def interface_gtypes included_interfaces.map { |interface| GType.new(interface.gtype) } end def included_interfaces klass.included_modules - Object.included_modules end def klass @klass ||= @info.described_class end def gobject_type_info GObject::TypeInfo.new.tap do |type_info| type_info.class_size = class_size type_info.instance_size = instance_size type_info.class_init = class_init_proc end end def gobject_interface_info(interface) GObject::InterfaceInfo.new.tap do |interface_info| interface_info.interface_init = interface_init_proc(interface) end end def class_init_proc proc do |type_class_or_ptr, _data| object_class_ptr = type_class_or_ptr.to_ptr setup_properties object_class_ptr setup_vfuncs object_class_ptr end end def interface_init_proc(interface) proc do |interface_or_ptr, _data| interface_ptr = interface_or_ptr.to_ptr setup_interface_vfuncs interface, interface_ptr end end def instance_size size = parent_gtype.instance_size alignment = struct_class.alignment properties.each do |prop| type_size = FFI.type_size(prop.ffi_type) size += [type_size, alignment].max end size end def class_size parent_gtype.class_size + interface_gtypes.map(&:class_size).inject(0, :+) end def setup_properties(object_class_ptr) object_class = GObject::ObjectClass.wrap object_class_ptr object_class.get_property = property_getter object_class.set_property = property_setter properties.each_with_index do |property, index| object_class.install_property index + 1, property.param_spec end end def property_getter proc do |object, _property_id, value, pspec| value.set_value object.send(pspec.accessor_name) end end def property_setter proc do |object, _property_id, value, pspec| object.send("#{pspec.accessor_name}=", value.get_value) end end def setup_vfuncs(object_class_ptr) super_class_struct = superclass.gir_ffi_builder.object_class_struct::Struct.new(object_class_ptr) info.vfunc_implementations.each do |impl| setup_vfunc super_class_struct, impl end end def setup_interface_vfuncs(interface, interface_ptr) interface_builder = interface.gir_ffi_builder interface_struct = interface_builder.interface_struct::Struct.new(interface_ptr) interface_info = interface_builder.info info.vfunc_implementations.each do |impl| setup_interface_vfunc interface_info, interface_struct, impl end end def setup_vfunc(super_class_struct, impl) vfunc_name = impl.name vfunc_info = parent_info.find_vfunc vfunc_name.to_s if vfunc_info install_vfunc super_class_struct, vfunc_name, vfunc_info, impl.implementation end end def setup_interface_vfunc(interface_info, interface_struct, impl) vfunc_name = impl.name vfunc_info = interface_info.find_vfunc vfunc_name.to_s if vfunc_info install_vfunc interface_struct, vfunc_name, vfunc_info, impl.implementation end end def install_vfunc(container_struct, vfunc_name, vfunc_info, implementation) vfunc = VFuncBuilder.new(vfunc_info).build_class container_struct[vfunc_name] = vfunc.from implementation end def properties info.properties end def layout_specification parent_spec = [:parent, superclass::Struct] offset = parent_gtype.instance_size alignment = superclass::Struct.alignment fields_spec = properties.flat_map do |param_info| field_name = param_info.accessor_name.to_sym ffi_type = param_info.ffi_type type_size = FFI.type_size(ffi_type) spec = [field_name, ffi_type, offset] offset += [type_size, alignment].max spec end parent_spec + fields_spec end # TODO: Move this to its own file. # TODO: See if this or FieldTypeInfo can be merged with with # UserDefinedPropertyInfo. class UserDefinedPropertyFieldInfo # Field info for user-defined property class FieldTypeInfo include InfoExt::ITypeInfo def initialize(property_info) @property_info = property_info end def tag @property_info.type_tag end def pointer? @property_info.pointer_type? end def interface_type @property_info.interface_type_tag if tag == :interface end def hidden_struct_type? false end def interface_class Builder.build_by_gtype @property_info.value_type if tag == :interface end def interface_class_name interface_class.name if tag == :interface end end def initialize(property_info, container, offset) @property_info = property_info @container = container @offset = offset end attr_reader :container, :offset def name @property_info.accessor_name end def field_type @field_type ||= FieldTypeInfo.new @property_info end def related_array_length_field nil end def writable? @property_info.writable? end end def setup_property_accessors offset = parent_gtype.instance_size alignment = struct_class.alignment properties.each do |param_info| field_info = UserDefinedPropertyFieldInfo.new(param_info, info, offset) type_size = FFI.type_size(param_info.ffi_type) offset += [type_size, alignment].max FieldBuilder.new(field_info, klass).build end end def method_introspection_data(_method) nil end def setup_constructor code = <<-CODE def initialize ptr = GObject::Lib.g_object_newv #{@gtype}, 0, nil store_pointer(ptr) end CODE klass.class_eval code end end end end