File: GObject-Object.lua

package info (click to toggle)
lua-lgi 0.9.2-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,388 kB
  • sloc: ansic: 5,082; makefile: 169; sh: 31
file content (376 lines) | stat: -rw-r--r-- 12,671 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
------------------------------------------------------------------------------
--
--  lgi GObject.Object handling.
--
--  Copyright (c) 2010-2014 Pavel Holejsovsky
--  Licensed under the MIT license:
--  http://www.opensource.org/licenses/mit-license.php
--
------------------------------------------------------------------------------

local pairs, select, setmetatable, error, type
   = pairs, select, setmetatable, error, type

local core = require 'lgi.core'
local gi = core.gi
local repo = core.repo
local ffi = require 'lgi.ffi'
local ti = ffi.types

local Value = repo.GObject.Value
local Type = repo.GObject.Type
local Closure = repo.GObject.Closure

local TypeClass = repo.GObject.TypeClass
local Object = repo.GObject.Object

-- Add overrides for GObject.TypeClass
TypeClass._free = gi.GObject.resolve.g_type_class_unref
local type_class_ref = core.callable.new {
   addr = gi.GObject.resolve.g_type_class_ref,
   ret = ti.ptr, ti.GType
}
function TypeClass:_new()
   local ptr = type_class_ref(self._gtype)
   return core.record.new(self, ptr, true)
end

-- Object constructor, 'param' contains table _with properties/signals
-- to initialize.
local parameter_repo = repo.GObject.Parameter
-- Before GLib 2.54, g_object_newv() was annotated with [rename-to g_object_new].
-- Starting from GLib 2.54, g_object_new_with_properties() has this annotation.
-- We always want g_object_newv().
local object_new = gi.GObject.Object.methods.newv or gi.GObject.Object.methods.new
if object_new then
   object_new = core.callable.new(object_new)
else
   -- Unfortunately, older GI (<1.30) does not export g_object_newv()
   -- in the typelib, so we have to workaround here with manually
   -- implemented C version.
   object_new = core.object.new
end

-- Generic construction method.
function Object:_construct(gtype, param, owns)
   local object
   if type(param) == 'userdata' then
      -- Wrap existing GObject instance in the lgi proxy.
      object = core.object.new(param, owns)
      gtype = object._gtype
   end

   -- Check that gtype fits.
   if not Type.is_a(gtype, self._gtype) then
      error(("`%s' is not subtype of `%s'"):format(
	       Type.name(gtype), self._name), 3)
   end

   -- In 'wrap' mode, just return the created object.
   if object then return object end

   -- Process 'args' table, separate properties from other fields.
   local parameters, others, safe = {}, {}, {}
   for name, arg in pairs(param or {}) do
      if type(name) == 'string' then
	 local argtype = self[name]
	 if gi.isinfo(argtype) and argtype.is_property then
	    local parameter = core.record.new(parameter_repo)
	    name = argtype.name
	    local value = parameter.value

	    -- Store the name string in some safe Lua place ('safe'
	    -- table), because param is GParameter, which contains
	    -- only non-owning pointer to the string, and it could be
	    -- Lua-GC'ed while still referenced by GParameter
	    -- instance.
	    safe[#safe + 1] = name
	    safe[#safe + 1] = value

	    parameter.name = name
	    local gtype = Type.from_typeinfo(argtype.typeinfo)
	    Value.init(value, gtype)
	    local marshaller = Value.find_marshaller(gtype, argtype.typeinfo)
	    marshaller(value, nil, arg)
	    parameters[#parameters + 1] = parameter
	 else
	    others[name] = arg
	 end
      end
   end

   -- Create the object.
   object = object_new(gtype, parameters)

   -- Perform initialization on interfaces.
   if next(self._implements) then
      local inited
      for _, initname in ipairs { '_init1', '_init2' } do
	 for _, interface in pairs(self._implements) do
	    local init = interface[initname]
	    if init then
	       local ok, err = init(object)
	       if not ok then return nil, err end
	       if ok == '_initskip' then
		  -- This initializer does not apply, continue looking
		  -- for others.
	       else
		  inited = true
		  break;
	       end
	    end
	 end
	 if inited then break end
      end
   end

   -- Attach arguments previously filtered out from creation.
   for name, value in pairs(others) do
      if type(name) == 'string' then object[name] = value end
   end

   -- In case that type has _container_add() method, use it to process
   -- array part of the args.
   local add = self._container_add
   if add and param then
      for i = 1, #param do add(object, param[i]) end
   end
   return object
end

function Object:_new(...)
   -- Invoke object's construct method which does the work.
   return self:_construct(self._gtype, ...)
end

-- Override normal 'new' constructor, to allow creating objects with
-- specified GType.
function Object.new(gtype, params, owns)
   -- Find proper repo instance for gtype.
   local gtype_walker = gtype
   while true do
      local self = core.repotype(gtype_walker)
      if self then
	 -- We have repo instance, use it to construct the object.
	 return self:_construct(gtype, params, owns)
      end
      gtype_walker = Type.parent(gtype_walker)
      if not gtype_walker then
	 error(("`%s': cannot create object, type not found"):format(gtype), 2)
      end
   end
end

-- Prepare callbacks for get_property and set_property
local get_property_guard, get_property_addr = core.marshal.callback(
   gi.GObject.ObjectClass.fields.get_property.typeinfo.interface,
   function(self, prop_id, value, pspec)
      local name = pspec.name:gsub('%-', '_')
      local prop_get = core.object.query(self, 'repo')._property_get[name]
      if prop_get then
	 value.value = prop_get(self)
      else
	 value.value = self.priv[name]
      end
end)

local set_property_guard, set_property_addr = core.marshal.callback(
   gi.GObject.ObjectClass.fields.get_property.typeinfo.interface,
   function(self, prop_id, value, pspec)
      local name = pspec.name:gsub('%-', '_')
      local prop_set = core.object.query(self, 'repo')._property_set[name]
      if prop_set then
	 prop_set(self, value.value)
      else
	 self.priv[name] = value.value
      end
end)

if not core.guards then core.guards = {} end
core.guards.get_property = get_property_guard
core.guards.set_property = set_property_guard

-- _class_init method on the Object will install all properties
-- accumulated in _property table.  It will be called automatically on
-- derived classes during class initialization routine.
function Object:_class_init(class)
   if next(self._property) then
      -- First install get/set_property overrides, unless already present.
      if not self._override.get_property then
	 class.get_property = get_property_addr
      end
      if not self._override.set_property then
	 class.set_property = set_property_addr
      end

      -- Install properties.
      local prop_id = 0
      for name, pspec in pairs(self._property) do
	 prop_id = prop_id + 1
	 class:install_property(prop_id, pspec)
      end
   end
end

-- Initially unowned creation is similar to normal GObject creation,
-- but we have to ref_sink newly created object.
local InitiallyUnowned = repo.GObject.InitiallyUnowned
function InitiallyUnowned:_construct(...)
   local object = Object._construct(self, ...)
   return Object.ref_sink(object)
end

-- Reading 'class' yields real instance of the object class.
Object._attribute = { _type = {} }
function Object._attribute._type:get()
   return core.object.query(self, 'repo')
end

-- Custom _element implementation, checks dynamically inherited
-- interfaces and dynamic properties.
local inherited_element = Object._element
function Object:_element(object, name)
   local element, category = inherited_element(self, object, name)
   if element then return element, category end

   -- Everything else works only if we have object instance.
   if not object then return nil end

   -- List all interfaces implemented by this object and try whether
   -- they can handle specified _element request.
   local interfaces = Type.interfaces(object._gtype)
   for i = 1, #interfaces do
      local info = gi[core.gtype(interfaces[i])]
      local iface = info and repo[info.namespace][info.name]
      if iface then element, category = iface:_element(object, name, self) end
      if element then return element, category end
   end

   -- Element not found in the repo (typelib), try whether dynamic
   -- property of the specified name exists.
   local property = Object._class.find_property(
      object._class, name:gsub('_', '-'))
   if property then return property, '_property' end
end

-- Sets/gets property using specified marshaller attributes.
local function marshal_property(obj, name, flags, gtype, marshaller, ...)
   -- Check access rights of the property.
   local mode = select('#', ...) > 0 and 'WRITABLE' or 'READABLE'
   if not flags[mode] then
      error(("%s: `%s' not %s"):format(core.object.query(obj, 'repo')._name,
				       name, core.downcase(mode)))
   end
   local value = Value(gtype)
   if mode == 'WRITABLE' then
      marshaller(value, nil, ...)
      Object.set_property(obj, name, value)
   else
      Object.get_property(obj, name, value)
      return marshaller(value)
   end
end

-- Property accessor.
function Object:_access_property(object, prop, ...)
   if gi.isinfo(prop) then
      -- GI-based property
      local typeinfo = prop.typeinfo
      local gtype = Type.from_typeinfo(typeinfo)
      local marshaller = Value.find_marshaller(gtype, typeinfo,
					       prop.transfer)
      return marshal_property(object, prop.name,
			      repo.GObject.ParamFlags[prop.flags],
			      gtype, marshaller, ...)
   else
      -- pspec-based property
      return marshal_property(object, prop.name, prop.flags, prop.value_type,
			      Value.find_marshaller(prop.value_type), ...)
   end
end

local quark_from_string = repo.GLib.quark_from_string
local signal_lookup = repo.GObject.signal_lookup
local signal_connect_closure_by_id = repo.GObject.signal_connect_closure_by_id
local signal_emitv = repo.GObject.signal_emitv
-- Connects signal to specified object instance.
local function connect_signal(obj, gtype, name, closure, detail, after)
   return signal_connect_closure_by_id(
      obj, signal_lookup(name, gtype),
      detail and quark_from_string(detail) or 0,
      closure, after or false)
end
-- Emits signal on specified object instance.
local function emit_signal(obj, gtype, info, detail, ...)
   -- Compile callable info.
   local call_info = Closure.CallInfo.new(info)

   -- Marshal input arguments.
   local retval, params, marshalling_params = call_info:pre_call(obj, ...)

   -- Invoke the signal.
   signal_emitv(params, signal_lookup(info.name, gtype),
		detail and quark_from_string(detail) or 0, retval)

   -- Unmarshal results.
   return call_info:post_call(params, retval, marshalling_params)
end

-- Signal accessor.
function Object:_access_signal(object, info, ...)
   local gtype = self._gtype
   if select('#', ...) > 0 then
      -- Assignment means 'connect signal without detail'.
      connect_signal(object, gtype, info.name, Closure((...), info))
   else
      -- Reading yields table with signal operations.
      local mt = {}
      local pad = setmetatable({}, mt)
      function pad:connect(target, detail, after)
	 return connect_signal(object, gtype, info.name,
			       Closure(target, info), detail, after)
      end
      function pad:emit(...)
	 return emit_signal(object, gtype, info, nil, ...)
      end
      function mt:__call(_, ...)
	 return emit_signal(object, gtype, info, nil, ...)
      end

      -- If signal supports details, add metatable implementing
      -- __newindex for connecting in the 'on_signal['detail'] =
      -- handler' form.
      if not info.is_signal or info.flags.detailed then
	 function pad:emit(detail, ...)
	    return emit_signal(object, gtype, info, detail, ...)
	 end
	 function mt:__newindex(detail, target)
	    connect_signal(object, gtype, info.name, Closure(target, info),
			   detail)
	 end
      end

      -- Return created signal pad.
      return pad
   end
end

-- GOI<1.30 does not export 'Object.on_notify' signal from the
-- typelib.  Work-around this problem by implementing custom on_notify
-- attribute.
if not gi.GObject.Object.signals.notify then
   local notify_info = gi.GObject.ObjectClass.fields.notify.typeinfo.interface
   function Object._attribute.on_notify(object, ...)
      local repotable = core.object.query(object, 'repo')
      return Object._access_signal(repotable, object, notify_info, ...)
   end
end

-- Bind property implementation.  For some strange reason, GoI<1.30
-- exports it only on GInitiallyUnowned and not on GObject.  Oh
-- well...
for _, name in pairs { 'bind_property', 'bind_property_full' } do
   if not Object[name] then
      Object._method[name] = InitiallyUnowned[name]
   end
end