node-gtk
Version:
GNOME Gtk+ bindings for NodeJS
180 lines (139 loc) • 4.43 kB
JavaScript
/*
* register-class.js
*/
const snakeCase = require('lodash.snakecase')
const internal = require('./native.js')
const module_ = require('./module.js')
const { GI } = require('./bootstrap.js')
const GObject = module_.require('GObject')
module.exports = registerClass
/**
* Create a new GObject type
* @param {Class} klass - The class to register
* @param {string} [klass.GTypeName] - The name of the GType (klass.name by default)
*/
function registerClass(klass) {
const parent = Object.getPrototypeOf(klass.prototype).constructor
if (!(klass.prototype instanceof GObject.Object))
throw new Error(`Invalid base class (${parent.name})`)
const name = createGTypeName(klass)
const gtype = GObject.typeFromName(name)
const parentName = getGTypeName(parent)
const parentGtype = GObject.typeFromName(parentName)
if (gtype !== GObject.TYPE_INVALID)
throw new Error(`GType name already registerd: ${name}`)
if (parentGtype === GObject.TYPE_INVALID)
throw new Error(`Parent class not registered: ${parent.name}`)
// Register the class with the type system
const klassGtype = internal.RegisterClass(name, klass, parentName, parent)
// Setup our class as the native ones are done
klass.prototype.__gtype__ = klassGtype
// Setup virtual functions
setupVirtualFunctions(klass, klassGtype, parentGtype)
}
// Helpers
function setupVirtualFunctions(klass, klassGtype, parentGtype) {
const parentInfo = findInfoByGtype(parentGtype)
if (!parentInfo)
throw new Error(`Could not find GIR data in inheritance chain`)
Object.getOwnPropertyNames(klass.prototype).forEach(key => {
if (key === 'constructor')
return
if (typeof klass.prototype[key] !== 'function')
return
const nativeName = snakeCase(key)
const vfuncInfo = findVFunc(klassGtype, parentInfo, nativeName)
if (!vfuncInfo)
return
internal.RegisterVFunc(
vfuncInfo,
klassGtype,
nativeName,
klass.prototype[key]
)
})
}
function findVFunc(gtype, parentInfo, name) {
let vfuncInfo = findVFuncOnParents(parentInfo, name)
if (!vfuncInfo) {
vfuncInfo = findVFuncOnInterfaces(gtype, name)
}
return vfuncInfo
}
function findVFuncOnParents(info, name) {
let parent = info
/* Since it isn't possible to override a vfunc on
* an interface without reimplementing it, we don't need
* to search the parent types when looking for a vfunc. */
let [vfunc, _] =
GI.object_info_find_vfunc_using_interfaces(parent, name, null)
if (vfunc) {
return vfunc
}
while (parent) {
vfunc = GI.object_info_find_vfunc(info, name)
if (vfunc) {
return vfunc
}
/* HACK: object_info_find_vfunc sometimes fail, so we also search for
* the matching entry manually. */
const n = GI.object_info_get_n_vfuncs(parent)
for (let i = 0; i < n; i++) {
const vfunc = GI.object_info_get_vfunc(parent, i)
const currentName = GI.BaseInfo_get_name.call(vfunc)
if (currentName === name) {
return vfunc
}
}
parent = GI.object_info_get_parent(parent)
}
return null
}
function findVFuncOnInterfaces(gtype, name) {
const interfaces = GObject.typeInterfaces(gtype);
for (i = 0; i < interfaces.length; i++) {
const interfaceInfo = findInfoByGtype(interfaces[i])
/* The interface doesn't have to exist, it could be private
* or dynamic. */
if (interfaceInfo) {
const vfunc =
GI.interface_info_find_vfunc(interfaceInfo, name);
if (vfunc)
return vfunc
}
}
return null
}
function findInfoByGtype(gtype) {
let current = gtype
while (current) {
const info = GI.Repository_find_by_gtype.call(GI.Repository_get_default(), current)
if (info)
return info
current = GObject.typeParent(current)
}
return null
}
function getGTypeName(klass) {
const name =
klass.hasOwnProperty('GTypeName') ? klass.GTypeName : klass.name
if (name) {
const sanitized = sanitizeGType(name);
if (sanitized !== name)
throw new Error(`GTypeName value is invalid: ${name}`)
return sanitized
}
return undefined
}
let nextId = 1
function createGTypeName(klass) {
const name = getGTypeName(klass)
if (name)
return name
const newName = `Anonymous${nextId++}`
klass.name = newName
return sanitizeGType(newName)
}
function sanitizeGType(s) {
return s.replace(/[^a-z0-9+_-]/gi, '_');
}