File: GObject-Value.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 (216 lines) | stat: -rw-r--r-- 7,313 bytes parent folder | download | duplicates (5)
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
------------------------------------------------------------------------------
--
--  LGI GObject.Value support.
--
--  Copyright (c) 2010, 2011 Pavel Holejsovsky
--  Licensed under the MIT license:
--  http://www.opensource.org/licenses/mit-license.php
--
------------------------------------------------------------------------------

local assert, pairs, select, type, tostring, error =
   assert, pairs, select, type, tostring, error
local lgi = require 'lgi'
local core = require 'lgi.core'
local repo = core.repo
local ffi = require 'lgi.ffi'
local gi = core.gi
local Type = repo.GObject.Type

-- Value is constructible from any kind of source Lua value, and the
-- type of the value can be hinted by type name.
local Value = repo.GObject.Value

local log = lgi.log.domain('Lgi')

-- Workaround for incorrect annotations - g_value_set_xxx are missing
-- (allow-none) annotations in glib < 2.30.
for _, name in pairs { 'set_object', 'set_variant', 'set_string' } do
   if not gi.GObject.Value.methods[name].args[1].optional then
      log.message("g_value_%s() is missing (allow-none)", name)
      local setter = Value[name]
      Value._method[name] =
      function(value, val)
	 if not val then Value.reset(value) else setter(value, val) end
      end
   end
end

-- Workaround for incorrect annotations - g_value_get_variant is missing
-- (transfer-none).
local _ = Value.get_variant
Value.get_variant = core.callable.new {
   addr = gi.GObject.resolve.g_value_get_variant,
   name = 'GObject.Value.get_variant',
   ret = repo.GLib.Variant, Value,
}

-- Do not allow direct access to fields.
local value_field_gtype = Value._field.g_type
Value._field = nil

-- Register _uninit function, to avoid memory leaks from values which
-- are inline-allocated by core.record.new.
Value._uninit = core.record.value_unset
Value._copy = core.record.value_copy

-- 'type' property controls gtype of the property.
Value._attribute = { gtype = {} }
function Value._attribute.gtype.get(value)
   return core.record.field(value, value_field_gtype)
end
function Value._attribute.gtype.set(value, newtype)
   local gtype = core.record.field(value, value_field_gtype)
   if gtype then
      if newtype then
	 -- Try converting old value to new one.
	 local dest = core.record.new(Value)
	 Value.init(dest, newtype)
	 if not Value.transform(value, dest) then
	    error(("GObject.Value: cannot convert `%s' to `%s'"):format(
		     gtype, core.record.field(dest, value_field_gtype)))
	 end
	 Value.unset(value)
	 Value.init(value, newtype)
	 Value.copy(dest, value)
      else
	 Value.unset(value)
      end
   elseif newtype then
      -- No value was set and some is requested, so set it.
      Value.init(value, newtype)
   end
end

local value_marshallers = {}
for name, gtype in pairs(Type) do
   name = core.downcase(name)
   local get = Value['get_' .. name]
   local set = Value['set_' .. name]
   if get and set then
      value_marshallers[gtype] =
      function(value, params, ...)
	 return (select('#', ...) > 0 and set or get)(value, ...)
      end
   end
end

-- Interface marshaller is the same as object marshaller.
value_marshallers[Type.INTERFACE] = value_marshallers[Type.OBJECT]

-- Override 'boxed' marshaller, default one marshalls to gpointer
-- instead of target boxed type.
value_marshallers[Type.BOXED] =
function(value, params, ...)
   local repotype = core.repotype(core.record.field(value, value_field_gtype))
   if select('#', ...) > 0 then
      Value.set_boxed(value, core.record.query((...), 'addr', repotype))
   else
      return core.record.new(repotype, Value.get_boxed(value))
   end
end

-- Override marshallers for enums and bitmaps, marshal them as strings
-- or sets of string flags.
for name, gtype in pairs { ENUM = Type.ENUM, FLAGS = Type.FLAGS } do
   name = core.downcase(name)
   local get = Value._method['get_' .. name]
   local set = Value._method['set_' .. name]
   value_marshallers[gtype] = function(value, params, ...)
      local rtype
      if select('#', ...) > 0 then
	 local param = ...
	 if type(param) ~= 'number' then
	    rtype = core.repotype(core.record.field(value, value_field_gtype))
	    param = rtype(param)
	 end
	 set(value, param)
      else
	 rtype = core.repotype(core.record.field(value, value_field_gtype))
	 return rtype[get(value)]
      end
   end
end

-- Create GStrv marshaller, implement it using typeinfo marshaller
-- with proper null-terminated-array-of-utf8 typeinfo 'stolen' from
-- g_shell_parse_argv().
value_marshallers[Type.STRV] = core.marshal.container(
   gi.GLib.shell_parse_argv.args[3].typeinfo)

-- Finds marshaller closure which can marshal type described either by
-- gtype or typeinfo/transfer combo.
function Value._method.find_marshaller(gtype, typeinfo, transfer)
   -- Check whether we can have marshaller for typeinfo, if the
   -- typeinfo is container.
   local marshaller
   if typeinfo then
      marshaller = core.marshal.container(typeinfo, transfer)
      if marshaller then return marshaller end
   end

   -- Special case for non-gtype records.
   if not gtype and typeinfo and typeinfo.tag == 'interface' then
      -- Workaround for GoI < 1.30; it does not know that GLib structs are
      -- boxed, so it does not assign them GType; moreover it incorrectly
      -- considers GParamSpec as GType-less struct instead of the class.
      local function marshal_record_no_gtype(value, params, ...)
	 -- Check actual gtype of the real value.
	 local gtype = core.record.field(value, value_field_gtype)

	 if Type.is_a(gtype, Type.PARAM) then
	    return value_marshallers[Type.PARAM](value, ...)
	 end

	 -- Find out proper getter/setter method for the value.
	 local get, set
	 if Type.is_a(gtype, Type.BOXED) then
	    get, set = Value.get_boxed, Value.set_boxed
	 else
	    get, set = Value.get_pointer, Value.set_pointer
	 end

	 -- Do GValue<->record transfer.
	 local record_repo = core.repotype(typeinfo.interface)
	 if select('#', ...) > 0 then
	    set(value, core.record.query((...), 'addr', record_repo))
	 else
	    return core.record.new(record_repo, get(value))
	 end
      end
      return marshal_record_no_gtype
   end

   local gt = gtype
   if type(gt) == 'userdata' then gt = Type.name(gt) end

   -- Special marshaller, allowing only 'nil'.
   if not gt then return function() end end

   -- Find marshaller according to gtype of the value.
   while gt do
      -- Check simple and/or fundamental marshallers.
      marshaller = value_marshallers[gt] or core.marshal.fundamental(gt)
      if marshaller then return marshaller end
      gt = Type.parent(gt)
   end
   error(("GValue marshaller for `%s' not found"):format(tostring(gtype)))
end

-- Value 'value' property provides access to GValue's embedded data.
function Value._attribute:value(...)
   local marshaller = Value._method.find_marshaller(
      core.record.field(self, value_field_gtype))
   return marshaller(self, nil, ...)
end

-- Implement custom 'constructor', taking optionally two values (type
-- and value).  The reason why it is overriden is that the order of
-- initialization is important, and standard record intializer cannot
-- enforce the order.
function Value:_new(gtype, value)
   local v = core.record.new(Value)
   if gtype then v.gtype = gtype end
   if value then v.value = value end
   return v
end