2016-03-13 08:42:57 +01:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
--- An helper module to map userdata __index and __newindex entries to
|
|
|
|
-- lua classes.
|
|
|
|
--
|
|
|
|
-- @author Emmanuel Lepage-Vallee <elv1313@gmail.com>
|
|
|
|
-- @copyright 2016 Emmanuel Lepage-Vallee
|
2018-12-31 23:50:37 +01:00
|
|
|
-- @submodule gears.object
|
2016-03-13 08:42:57 +01:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
2018-12-26 23:09:02 +01:00
|
|
|
local gtable = require("gears.table")
|
2019-10-20 01:49:14 +02:00
|
|
|
local gtimer = nil --require("gears.timer")
|
2018-12-26 23:09:02 +01:00
|
|
|
-- local gdebug = require("gears.debug")
|
2016-03-13 08:42:57 +01:00
|
|
|
local object = {}
|
2018-12-26 23:09:02 +01:00
|
|
|
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
|
2016-03-13 08:42:57 +01:00
|
|
|
|
|
|
|
|
2016-08-13 09:05:31 +02:00
|
|
|
--- Add the missing properties handler to a CAPI object such as client/tag/screen.
|
2016-03-13 08:42:57 +01:00
|
|
|
-- Valid args:
|
|
|
|
--
|
|
|
|
-- * **getter**: A smart getter (handle property getter itself)
|
|
|
|
-- * **getter_fallback**: A dumb getter method (don't handle individual property getter)
|
|
|
|
-- * **getter_class**: A module with individual property getter/setter
|
|
|
|
-- * **getter_prefix**: A special getter prefix (like "get" or "get_" (default))
|
|
|
|
-- * **setter**: A smart setter (handle property setter itself)
|
|
|
|
-- * **setter_fallback**: A dumb setter method (don't handle individual property setter)
|
|
|
|
-- * **setter_class**: A module with individual property getter/setter
|
|
|
|
-- * **setter_prefix**: A special setter prefix (like "set" or "set_" (default))
|
|
|
|
-- * **auto_emit**: Emit "property::___" automatically (default: false). This is
|
|
|
|
-- ignored when setter_fallback is set or a setter is found
|
|
|
|
--
|
|
|
|
-- @param class A standard luaobject derived object
|
|
|
|
-- @tparam[opt={}] table args A set of accessors configuration parameters
|
2018-12-31 23:50:37 +01:00
|
|
|
-- @function gears.object.properties.capi_index_fallback
|
2016-03-13 08:42:57 +01:00
|
|
|
function object.capi_index_fallback(class, args)
|
|
|
|
args = args or {}
|
|
|
|
|
|
|
|
local getter_prefix = args.getter_prefix or "get_"
|
|
|
|
local setter_prefix = args.setter_prefix or "set_"
|
|
|
|
|
|
|
|
local getter = args.getter or function(cobj, prop)
|
|
|
|
-- Look for a getter method
|
|
|
|
if args.getter_class and args.getter_class[getter_prefix..prop] then
|
|
|
|
return args.getter_class[getter_prefix..prop](cobj)
|
2016-03-31 07:20:04 +02:00
|
|
|
elseif args.getter_class and args.getter_class["is_"..prop] then
|
|
|
|
return args.getter_class["is_"..prop](cobj)
|
2016-03-13 08:42:57 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Make sure something like c:a_mutator() works
|
|
|
|
if args.getter_class and args.getter_class[prop] then
|
|
|
|
return args.getter_class[prop]
|
|
|
|
end
|
|
|
|
-- In case there is already a "dumb" getter like `awful.tag.getproperty'
|
|
|
|
if args.getter_fallback then
|
|
|
|
return args.getter_fallback(cobj, prop)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Use the fallback property table
|
2019-10-13 03:01:18 +02:00
|
|
|
assert(prop ~= "_private")
|
|
|
|
return cobj._private[prop]
|
2016-03-13 08:42:57 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
local setter = args.setter or function(cobj, prop, value)
|
|
|
|
-- Look for a setter method
|
|
|
|
if args.setter_class and args.setter_class[setter_prefix..prop] then
|
|
|
|
return args.setter_class[setter_prefix..prop](cobj, value)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- In case there is already a "dumb" setter like `awful.client.property.set'
|
|
|
|
if args.setter_fallback then
|
|
|
|
return args.setter_fallback(cobj, prop, value)
|
|
|
|
end
|
|
|
|
|
2016-06-04 15:56:52 +02:00
|
|
|
-- If a getter exists but not a setter, then the property is read-only
|
|
|
|
if args.getter_class and args.getter_class[getter_prefix..prop] then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2016-03-13 08:42:57 +01:00
|
|
|
-- Use the fallback property table
|
2019-10-13 03:01:18 +02:00
|
|
|
cobj._private[prop] = value
|
2016-03-13 08:42:57 +01:00
|
|
|
|
|
|
|
-- Emit the signal
|
|
|
|
if args.auto_emit then
|
|
|
|
cobj:emit_signal("property::"..prop, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-10-20 01:49:14 +02:00
|
|
|
assert(type(class) ~= "function")
|
|
|
|
|
2016-03-13 08:42:57 +01:00
|
|
|
-- Attach the accessor methods
|
|
|
|
class.set_index_miss_handler(getter)
|
|
|
|
class.set_newindex_miss_handler(setter)
|
|
|
|
end
|
|
|
|
|
2019-10-26 23:51:53 +02:00
|
|
|
-- Convert the capi objects back into awful ones.
|
|
|
|
local function deprecated_to_current(content)
|
|
|
|
local first = content[1]
|
|
|
|
|
|
|
|
local current = first and first._private and
|
|
|
|
first._private._legacy_convert_to or nil
|
|
|
|
|
|
|
|
if not current then return nil end
|
|
|
|
|
|
|
|
local ret = {current}
|
|
|
|
|
|
|
|
for _, o in ipairs(content) do
|
|
|
|
-- If this is false, someone tried to mix things in a
|
|
|
|
-- way that is definitely not intentional.
|
|
|
|
assert(o._private and o._private._legacy_convert_to)
|
|
|
|
|
|
|
|
if o._private._legacy_convert_to ~= current then
|
|
|
|
-- If this is false, someone tried to mix things in a
|
|
|
|
-- way that is definitely not intentional.
|
|
|
|
assert(o._private._legacy_convert_to)
|
|
|
|
|
|
|
|
table.insert(
|
|
|
|
ret, o._private._legacy_convert_to
|
|
|
|
)
|
|
|
|
current = o._private._legacy_convert_to
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return ret
|
|
|
|
end
|
2018-12-26 23:09:02 +01:00
|
|
|
|
|
|
|
-- (private api)
|
|
|
|
-- Many legacy Awesome APIs such as `client:tags()`, `root.buttons()`,
|
|
|
|
-- `client:keys()`, `drawin:geometry()`, etc used functions for both the getter
|
|
|
|
-- and setter. This contrast with just about everything else that came after
|
|
|
|
-- it and is an artifact of an earlier time before we had "good" Lua object
|
|
|
|
-- support.
|
|
|
|
--
|
|
|
|
-- Because both consistency and backward compatibility are important, this
|
|
|
|
-- table wrapper allows to support both the legacy method based accessors
|
|
|
|
-- and key/value based accessors.
|
|
|
|
--
|
|
|
|
-- TO BE USED FOR DEPRECATION ONLY.
|
|
|
|
|
2019-10-20 07:10:08 +02:00
|
|
|
local function copy_object(obj, to_set, name, capi_name, is_object, join_if, set_empty)-- luacheck: no unused
|
2018-12-26 23:09:02 +01:00
|
|
|
local ret = gtable.clone(to_set, false)
|
|
|
|
|
|
|
|
-- .buttons used to be a function taking the result of `gears.table.join`.
|
|
|
|
-- For compatibility, support this, but from now on, it's a property.
|
|
|
|
return setmetatable(ret, {
|
|
|
|
__call = function(_, self, new_objs)
|
|
|
|
--TODO uncomment
|
|
|
|
-- gdebug.deprecate("`"..name.."` is no longer a function, it is a property. "..
|
|
|
|
-- "Remove the `gears.table.join` and use a brace enclosed table",
|
|
|
|
-- {deprecated_in=5}
|
|
|
|
-- )
|
|
|
|
|
2019-10-20 07:10:08 +02:00
|
|
|
if not is_object then
|
|
|
|
new_objs, self = self, obj
|
|
|
|
end
|
2018-12-26 23:09:02 +01:00
|
|
|
|
2019-10-20 07:10:08 +02:00
|
|
|
local has_content = new_objs and next(new_objs)
|
2018-12-26 23:09:02 +01:00
|
|
|
|
2019-10-20 07:10:08 +02:00
|
|
|
-- Setter
|
|
|
|
if new_objs and has_content then
|
2018-12-26 23:09:02 +01:00
|
|
|
local is_formatted = join_if(new_objs)
|
|
|
|
|
|
|
|
-- Because modules may rely on :buttons taking a list of
|
|
|
|
-- `capi.buttons`/`capi.key` and now the user passes a list of
|
|
|
|
-- `awful.button`/`awful.key` convert this.
|
|
|
|
|
|
|
|
local result = is_formatted and
|
|
|
|
new_objs or gtable.join(unpack(new_objs))
|
|
|
|
|
2019-10-26 23:51:53 +02:00
|
|
|
-- First, when possible, get rid of the legacy format and go
|
|
|
|
-- back to the non-deprecated code path.
|
|
|
|
local current = self["set_"..name]
|
|
|
|
and deprecated_to_current(result) or nil
|
|
|
|
|
|
|
|
if current then
|
|
|
|
self["set_"..name](self, current)
|
|
|
|
return capi_name and self[capi_name](self) or self[name]
|
|
|
|
elseif capi_name and is_object then
|
2018-12-26 23:09:02 +01:00
|
|
|
return self[capi_name](self, result)
|
|
|
|
elseif capi_name then
|
|
|
|
return self[capi_name](result)
|
|
|
|
else
|
|
|
|
self._private[name.."_formatted"] = result
|
|
|
|
return self._private[name.."_formatted"]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Getter
|
|
|
|
if capi_name and is_object then
|
|
|
|
return self[capi_name](self)
|
|
|
|
elseif capi_name then
|
|
|
|
return self[capi_name]()
|
|
|
|
else
|
|
|
|
return self._private[name.."_formatted"] or {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
2019-10-20 02:17:31 +02:00
|
|
|
function object._legacy_accessors(obj, name, capi_name, is_object, join_if, set_empty, delay, add_append_name)
|
|
|
|
delay = delay or add_append_name
|
2018-12-26 23:09:02 +01:00
|
|
|
|
|
|
|
-- Some objects have a special "object" property to add more properties, but
|
|
|
|
-- not all.
|
|
|
|
|
|
|
|
local magic_obj = obj.object and obj.object or obj
|
|
|
|
|
|
|
|
magic_obj["get_"..name] = function(self)
|
|
|
|
self = is_object and self or obj
|
|
|
|
|
|
|
|
self._private[name] = self._private[name] or copy_object(
|
|
|
|
obj, {}, name, capi_name, is_object, join_if, set_empty
|
|
|
|
)
|
|
|
|
|
2019-10-26 23:51:53 +02:00
|
|
|
local current = deprecated_to_current(self._private[name])
|
|
|
|
|
|
|
|
return current or self._private[name]
|
2018-12-26 23:09:02 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
magic_obj["set_"..name] = function(self, objs)
|
|
|
|
if (not set_empty) and not next(objs) then return end
|
|
|
|
|
|
|
|
if not is_object then
|
|
|
|
objs, self = self, obj
|
|
|
|
end
|
2019-10-26 23:17:48 +02:00
|
|
|
|
|
|
|
-- When using lua expressions like `false and true and my_objects`,
|
|
|
|
-- it is possible the code ends up as a boolean rather than `nil`
|
|
|
|
-- the resulting type is sometime counter intuitive and different
|
|
|
|
-- from similar languages such as JavaScript. Be forgiving and correct
|
|
|
|
-- course.
|
|
|
|
if objs == false then
|
|
|
|
objs = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Sometime, setting `nil` might be volontary since the user might
|
|
|
|
-- expect it will act as "clear". The correct thing would be to set
|
|
|
|
-- `{}`, but allow it nevertheless.
|
|
|
|
|
|
|
|
if objs == nil then
|
|
|
|
objs = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(self)
|
2018-12-26 23:09:02 +01:00
|
|
|
|
|
|
|
-- When called from a declarative property list, "buttons" will be set
|
|
|
|
-- using the result of gears.table.join, detect this
|
|
|
|
local is_formatted = join_if(objs)
|
|
|
|
|
|
|
|
-- if is_formatted then
|
|
|
|
--TODO uncomment
|
|
|
|
-- gdebug.deprecate("Remove the `gears.table.join` and replace it with braces",
|
|
|
|
-- {deprecated_in=5}
|
|
|
|
-- )
|
|
|
|
-- end
|
|
|
|
|
|
|
|
|
|
|
|
--TODO v6 Use the original directly and drop this legacy copy
|
2019-10-20 01:49:14 +02:00
|
|
|
local function apply()
|
|
|
|
local result = is_formatted and objs
|
|
|
|
or gtable.join(unpack(objs))
|
|
|
|
|
|
|
|
if is_object and capi_name then
|
|
|
|
self[capi_name](self, result)
|
|
|
|
elseif capi_name then
|
|
|
|
obj[capi_name](result)
|
|
|
|
else
|
|
|
|
self._private[name.."_formatted"] = result
|
|
|
|
end
|
|
|
|
end
|
2018-12-26 23:09:02 +01:00
|
|
|
|
2019-10-20 01:49:14 +02:00
|
|
|
-- Some properties, like keys, are expensive to set, schedule them
|
|
|
|
-- instead.
|
|
|
|
if not delay then
|
|
|
|
apply()
|
2018-12-26 23:09:02 +01:00
|
|
|
else
|
2019-10-20 01:49:14 +02:00
|
|
|
if not self._private["_delayed_"..name] then
|
|
|
|
gtimer = gtimer or require("gears.timer")
|
|
|
|
gtimer.delayed_call(function()
|
|
|
|
self._private["_delayed_"..name]()
|
|
|
|
self._private["_delayed_"..name] = nil
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
self._private["_delayed_"..name] = apply
|
2018-12-26 23:09:02 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
self._private[name] = copy_object(
|
|
|
|
obj, objs, name, capi_name, is_object, join_if, set_empty
|
|
|
|
)
|
|
|
|
end
|
2019-10-20 02:17:31 +02:00
|
|
|
|
|
|
|
if add_append_name then
|
|
|
|
magic_obj["append_"..add_append_name] = function(self, obj2)
|
|
|
|
self._private[name] = self._private[name]
|
|
|
|
or magic_obj["get_"..name](self, nil)
|
|
|
|
|
|
|
|
table.insert(self._private[name], obj2)
|
|
|
|
|
|
|
|
magic_obj["set_"..name](self, self._private[name])
|
|
|
|
end
|
|
|
|
|
|
|
|
magic_obj["remove_"..add_append_name] = function(self, obj2)
|
|
|
|
self._private[name] = self._private[name]
|
|
|
|
or magic_obj["get_"..name](self, nil)
|
|
|
|
|
|
|
|
for k, v in ipairs(self._private[name]) do
|
|
|
|
if v == obj2 then
|
|
|
|
table.remove(self._private[name], k)
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
magic_obj["set_"..name](self, self._private[name])
|
|
|
|
end
|
|
|
|
end
|
2018-12-26 23:09:02 +01:00
|
|
|
end
|
|
|
|
|
2016-03-13 08:42:57 +01:00
|
|
|
return setmetatable( object, {__call = function(_,...) object.capi_index_fallback(...) end})
|
|
|
|
|
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|