Merge pull request #913 from Elv13/gear_obj_props
Gear.object properties
This commit is contained in:
commit
c1d3f291fe
|
@ -1,4 +1,9 @@
|
||||||
---------------------------------------------------------------------------
|
---------------------------------------------------------------------------
|
||||||
|
-- The object oriented programming base class used by various Awesome
|
||||||
|
-- widgets and components.
|
||||||
|
--
|
||||||
|
-- It provide basic observer pattern, signaling and dynamic properties.
|
||||||
|
--
|
||||||
-- @author Uli Schlachter
|
-- @author Uli Schlachter
|
||||||
-- @copyright 2010 Uli Schlachter
|
-- @copyright 2010 Uli Schlachter
|
||||||
-- @release @AWESOME_VERSION@
|
-- @release @AWESOME_VERSION@
|
||||||
|
@ -21,10 +26,10 @@ local function check(obj)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Find a given signal
|
--- Find a given signal
|
||||||
-- @param obj The object to search in
|
-- @tparam table obj The object to search in
|
||||||
-- @param name The signal to find
|
-- @tparam string name The signal to find
|
||||||
-- @param error_msg Error message for if the signal is not found
|
-- @tparam string error_msg Error message for if the signal is not found
|
||||||
-- @return The signal table
|
-- @treturn table The signal table
|
||||||
local function find_signal(obj, name, error_msg)
|
local function find_signal(obj, name, error_msg)
|
||||||
check(obj)
|
check(obj)
|
||||||
if not obj._signals[name] then
|
if not obj._signals[name] then
|
||||||
|
@ -34,7 +39,9 @@ local function find_signal(obj, name, error_msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Add a signal to an object. All signals must be added before they can be used.
|
--- Add a signal to an object. All signals must be added before they can be used.
|
||||||
-- @param name The name of the new signal.
|
--
|
||||||
|
--@DOC_text_gears_object_signal_EXAMPLE@
|
||||||
|
-- @tparam string name The name of the new signal.
|
||||||
function object:add_signal(name)
|
function object:add_signal(name)
|
||||||
check(self)
|
check(self)
|
||||||
assert(type(name) == "string", "name must be a string, got: " .. type(name))
|
assert(type(name) == "string", "name must be a string, got: " .. type(name))
|
||||||
|
@ -46,9 +53,10 @@ function object:add_signal(name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Connect to a signal
|
--- Connect to a signal.
|
||||||
-- @param name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @param func The callback to call when the signal is emitted
|
-- @tparam function func The callback to call when the signal is emitted
|
||||||
|
-- @see add_signal
|
||||||
function object:connect_signal(name, func)
|
function object:connect_signal(name, func)
|
||||||
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
|
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
|
||||||
local sig = find_signal(self, name, "connect to")
|
local sig = find_signal(self, name, "connect to")
|
||||||
|
@ -88,8 +96,8 @@ end
|
||||||
|
|
||||||
--- Connect to a signal weakly. This allows the callback function to be garbage
|
--- Connect to a signal weakly. This allows the callback function to be garbage
|
||||||
-- collected and automatically disconnects the signal when that happens.
|
-- collected and automatically disconnects the signal when that happens.
|
||||||
-- @param name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @param func The callback to call when the signal is emitted
|
-- @tparam function func The callback to call when the signal is emitted
|
||||||
function object:weak_connect_signal(name, func)
|
function object:weak_connect_signal(name, func)
|
||||||
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
|
assert(type(func) == "function", "callback must be a function, got: " .. type(func))
|
||||||
local sig = find_signal(self, name, "connect to")
|
local sig = find_signal(self, name, "connect to")
|
||||||
|
@ -97,18 +105,19 @@ function object:weak_connect_signal(name, func)
|
||||||
sig.weak[func] = make_the_gc_obey(func)
|
sig.weak[func] = make_the_gc_obey(func)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Disonnect to a signal
|
--- Disonnect to a signal.
|
||||||
-- @param name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @param func The callback that should be disconnected
|
-- @tparam function func The callback that should be disconnected
|
||||||
|
-- @see add_signal
|
||||||
function object:disconnect_signal(name, func)
|
function object:disconnect_signal(name, func)
|
||||||
local sig = find_signal(self, name, "disconnect from")
|
local sig = find_signal(self, name, "disconnect from")
|
||||||
sig.weak[func] = nil
|
sig.weak[func] = nil
|
||||||
sig.strong[func] = nil
|
sig.strong[func] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Emit a signal
|
--- Emit a signal.
|
||||||
--
|
--
|
||||||
-- @param name The name of the signal
|
-- @tparam string name The name of the signal
|
||||||
-- @param ... Extra arguments for the callback functions. Each connected
|
-- @param ... Extra arguments for the callback functions. Each connected
|
||||||
-- function receives the object as first argument and then any extra arguments
|
-- function receives the object as first argument and then any extra arguments
|
||||||
-- that are given to emit_signal()
|
-- that are given to emit_signal()
|
||||||
|
@ -122,11 +131,59 @@ function object:emit_signal(name, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Returns a new object. You can call :emit_signal(), :disconnect_signal,
|
local function get_miss(self, key)
|
||||||
-- :connect_signal() and :add_signal() on the resulting object.
|
local class = rawget(self, "_class")
|
||||||
local function new()
|
|
||||||
|
if rawget(self, "get_"..key) then
|
||||||
|
return rawget(self, "get_"..key)(self)
|
||||||
|
elseif class and class["get_"..key] then
|
||||||
|
return class["get_"..key](self)
|
||||||
|
elseif class then
|
||||||
|
return class[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local function set_miss(self, key, value)
|
||||||
|
local class = rawget(self, "_class")
|
||||||
|
|
||||||
|
if rawget(self, "set_"..key) then
|
||||||
|
return rawget(self, "set_"..key)(self, value)
|
||||||
|
elseif class and class["set_"..key] then
|
||||||
|
return class["set_"..key](self, value)
|
||||||
|
elseif rawget(self, "_enable_auto_signals") then
|
||||||
|
local changed = class[key] ~= value
|
||||||
|
class[key] = value
|
||||||
|
|
||||||
|
if changed then
|
||||||
|
self:emit_signal("property::"..key, value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return rawset(self, key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Returns a new object. You can call `:emit_signal()`, `:disconnect_signal()`,
|
||||||
|
-- `:connect_signal()` and `:add_signal()` on the resulting object.
|
||||||
|
--
|
||||||
|
-- Note that `args.enable_auto_signals` is only supported when
|
||||||
|
-- `args.enable_properties` is true.
|
||||||
|
--
|
||||||
|
--@DOC_text_gears_object_properties_EXAMPLE@
|
||||||
|
-- @tparam[opt={}] table args The arguments
|
||||||
|
-- @tparam[opt=false] boolean args.enable_properties Automatically call getters and setters
|
||||||
|
-- @tparam[opt=false] boolean args.enable_auto_signals Generate "property::xxxx" signals
|
||||||
|
-- when an unknown property is set.
|
||||||
|
-- @tparam[opt=nil] table args.class
|
||||||
|
-- @treturn table A new object
|
||||||
|
-- @function gears.object
|
||||||
|
local function new(args)
|
||||||
|
args = args or {}
|
||||||
local ret = {}
|
local ret = {}
|
||||||
|
|
||||||
|
-- Automatic signals cannot work without both miss handlers.
|
||||||
|
assert(not (args.enable_auto_signals and args.enable_properties ~= true))
|
||||||
|
|
||||||
-- Copy all our global functions to our new object
|
-- Copy all our global functions to our new object
|
||||||
for k, v in pairs(object) do
|
for k, v in pairs(object) do
|
||||||
if type(v) == "function" then
|
if type(v) == "function" then
|
||||||
|
@ -136,7 +193,27 @@ local function new()
|
||||||
|
|
||||||
ret._signals = {}
|
ret._signals = {}
|
||||||
|
|
||||||
return ret
|
local mt = {}
|
||||||
|
|
||||||
|
-- Look for methods in another table
|
||||||
|
ret._class = args.class
|
||||||
|
ret._enable_auto_signals = args.enable_auto_signals
|
||||||
|
|
||||||
|
-- To catch all changes, a proxy is required
|
||||||
|
if args.enable_auto_signals then
|
||||||
|
ret._class = ret._class and setmetatable({}, {__index = args.class}) or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.enable_properties then
|
||||||
|
-- Check got existing get_xxxx and set_xxxx
|
||||||
|
mt.__index = get_miss
|
||||||
|
mt.__newindex = set_miss
|
||||||
|
elseif args.class then
|
||||||
|
-- Use the class table a miss handler
|
||||||
|
mt.__index = ret._class
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(ret, mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
function object.mt.__call(_, ...)
|
function object.mt.__call(_, ...)
|
||||||
|
|
|
@ -161,6 +161,64 @@ describe("gears.object", function()
|
||||||
assert.is_true(finalized)
|
assert.is_true(finalized)
|
||||||
obj:emit_signal("signal")
|
obj:emit_signal("signal")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("dynamic property disabled", function()
|
||||||
|
local class = {}
|
||||||
|
function class:get_foo() return "bar" end
|
||||||
|
function class:set_foo() end
|
||||||
|
|
||||||
|
local obj2 = object{class=class}
|
||||||
|
|
||||||
|
obj2.foo = 42
|
||||||
|
|
||||||
|
assert.is_true(obj2.foo == 42)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("dynamic property disabled", function()
|
||||||
|
local class = {}
|
||||||
|
function class:get_foo() return "bar" end
|
||||||
|
function class:set_foo() end
|
||||||
|
|
||||||
|
local obj2 = object{class=class, enable_properties = true}
|
||||||
|
|
||||||
|
obj2.foo = 42
|
||||||
|
|
||||||
|
assert.is_true(obj2.foo == "bar")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("auto emit disabled", function()
|
||||||
|
local got_it = false
|
||||||
|
obj:add_signal("property::foo")
|
||||||
|
obj:connect_signal("property::foo", function() got_it=true end)
|
||||||
|
|
||||||
|
obj.foo = 42
|
||||||
|
|
||||||
|
assert.is_false(got_it)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("auto emit enabled", function()
|
||||||
|
local got_it = false
|
||||||
|
local obj2 = object{enable_auto_signals=true, enable_properties=true}
|
||||||
|
obj2:add_signal("property::foo")
|
||||||
|
obj2:connect_signal("property::foo", function() got_it=true end)
|
||||||
|
|
||||||
|
obj2.foo = 42
|
||||||
|
|
||||||
|
assert.is_true(got_it)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("auto emit enabled", function()
|
||||||
|
assert.has.errors(function()
|
||||||
|
local obj2 = object{enable_auto_signals=true, enable_properties=true}
|
||||||
|
obj2.foo = "bar"
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("auto emit without dynamic properties", function()
|
||||||
|
assert.has.errors(function()
|
||||||
|
object{enable_auto_signals=true, enable_properties=false}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
local gears = require("gears") --DOC_HIDE
|
||||||
|
|
||||||
|
-- Create a class for this object. It will be used as a backup source for
|
||||||
|
-- methods and acessors. It is also possible to set them diretly on the
|
||||||
|
-- object.
|
||||||
|
local class = {}
|
||||||
|
|
||||||
|
function class:get_foo()
|
||||||
|
print("In get foo", self._foo or "bar")
|
||||||
|
return self._foo or "bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
function class:set_foo(value)
|
||||||
|
print("In set foo", value)
|
||||||
|
|
||||||
|
-- In case it is necessary to bypass the object property system, use
|
||||||
|
-- `rawset`
|
||||||
|
rawset(self, "_foo", value)
|
||||||
|
|
||||||
|
-- When using custom accessors, the signals need to be handled manually
|
||||||
|
self:emit_signal("property::foo", value)
|
||||||
|
end
|
||||||
|
|
||||||
|
function class:method(a, b, c)
|
||||||
|
print("In a mathod", a, b, c)
|
||||||
|
end
|
||||||
|
|
||||||
|
local o = gears.object {
|
||||||
|
class = class,
|
||||||
|
enable_properties = true,
|
||||||
|
enable_auto_signals = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
o:add_signal "property::foo"
|
||||||
|
|
||||||
|
print(o.foo)
|
||||||
|
|
||||||
|
o.foo = 42
|
||||||
|
|
||||||
|
print(o.foo)
|
||||||
|
|
||||||
|
o:method(1, 2, 3)
|
||||||
|
|
||||||
|
-- Random properties can also be added, the signal will be emited automatically.
|
||||||
|
o:add_signal "property::something"
|
||||||
|
|
||||||
|
o:connect_signal("property::something", function(obj, value)
|
||||||
|
print("In the connection handler!", obj, value)
|
||||||
|
end)
|
||||||
|
|
||||||
|
print(o.something)
|
||||||
|
|
||||||
|
o.something = "a cow"
|
||||||
|
|
||||||
|
print(o.something)
|
|
@ -0,0 +1,27 @@
|
||||||
|
local gears = require("gears") --DOC_HIDE
|
||||||
|
|
||||||
|
local o = gears.object{}
|
||||||
|
|
||||||
|
-- Add a new signals to the object. This is used to catch typos
|
||||||
|
o:add_signal "my_signal"
|
||||||
|
|
||||||
|
-- Function can be attached to signals
|
||||||
|
local function slot(obj, a, b, c)
|
||||||
|
print("In slot", obj, a, b, c)
|
||||||
|
end
|
||||||
|
|
||||||
|
o:connect_signal("my_signal", slot)
|
||||||
|
|
||||||
|
-- Emit can be done without argument. In that case, the object will be
|
||||||
|
-- implicitly added as an argument.
|
||||||
|
o:emit_signal "my_signal"
|
||||||
|
|
||||||
|
-- It is also possible to add as many random arguments are required.
|
||||||
|
o:emit_signal("my_signal", "foo", "bar", 42)
|
||||||
|
|
||||||
|
-- Finally, to allow the object to be garbage collected (the memory freed), it
|
||||||
|
-- is necessary to disconnect the signal or use `weak_connect_signal`
|
||||||
|
o:disconnect_signal("my_signal", slot)
|
||||||
|
|
||||||
|
-- This time, the `slot` wont be called as it is no longer connected.
|
||||||
|
o:emit_signal "my_signal"
|
|
@ -0,0 +1,19 @@
|
||||||
|
local file_path, _, luacovpath = ...
|
||||||
|
|
||||||
|
-- Set the global shims
|
||||||
|
-- luacheck: globals awesome root tag screen client mouse drawin
|
||||||
|
awesome = require( "awesome" )
|
||||||
|
root = require( "root" )
|
||||||
|
tag = require( "tag" )
|
||||||
|
screen = require( "screen" )
|
||||||
|
client = require( "client" )
|
||||||
|
mouse = require( "mouse" )
|
||||||
|
drawin = require( "drawin" )
|
||||||
|
|
||||||
|
-- If luacov is available, use it. Else, do nothing.
|
||||||
|
pcall(function()
|
||||||
|
require("luacov.runner")(luacovpath)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Execute the test
|
||||||
|
loadfile(file_path)()
|
Loading…
Reference in New Issue