diff State.lua @ 90:7cabc8ac6c16

Updates for wow 3.0 - TOC update - updated changed APIs/frame names - rewrote state code per new SecureHandlers API - cleaned up Bar, ActionButton code - removed AceLibrary/Dewdrop, menu from bar right-click - fixed various small bugs Updated WowAce external locations Updated README.html
author Flick <flickerstreak@gmail.com>
date Wed, 15 Oct 2008 16:29:41 +0000
parents 1ad208c25618
children c2504a8b996c
line wrap: on
line diff
--- a/State.lua	Mon Oct 13 23:32:33 2008 +0000
+++ b/State.lua	Wed Oct 15 16:29:41 2008 +0000
@@ -7,8 +7,9 @@
 local ReAction = ReAction
 local L = ReAction.L
 local _G = _G
+local format = string.format
 local InCombatLockdown = InCombatLockdown
-local format = string.format
+local RegisterStateDriver = RegisterStateDriver
 
 ReAction:UpdateRevision("$Revision$")
 
@@ -53,10 +54,205 @@
 end
 
 
-local InitRules, ApplyStates, SetProperty, GetProperty, RegisterProperty
+local InitRules, ApplyStates, CleanupStates, SetProperty, GetProperty, RegisterProperty, ShowAll
 
 -- PRIVATE --
 do
+
+  -- the field names must match the field names of the options table, below
+  -- the field values are secure snippets
+  local properties = { 
+    hide = 
+      [[
+        local h = hide and hide[state] and not showAll
+        if h ~= hidden then
+          if h then
+            self:Hide()
+          else
+            self:Show()
+          end
+          hidden = h
+        end
+      ]],
+
+    --keybindState  TODO: broken
+
+      -- the anchoring is handled in a special handler
+    anchorEnable = true,
+    --anchorFrame = true,  TODO: broken
+    anchorPoint = true,
+    anchorRelPoint = true,
+    anchorX = true,
+    anchorY = true,
+    enableScale = true,
+    scale = true,
+  }
+
+
+  --
+  -- Secure Handler Snippets
+  --
+  local SetHandlerData, SetStateDriver, SetStateKeybind, RefreshState
+  do
+    local stateHandler_propInit = 
+    [[
+      propfuncs = table.new()
+      local proplist = self:GetAttribute("prop-func-list")
+      for s in string.gmatch(proplist, "(%w+)") do
+        table.insert(propfuncs, s)
+      end
+    ]]
+
+    local onStateHandler = 
+    -- function _onstate-reaction( self, stateid, newstate )
+    [[
+      print("received state",newstate,"on bar",self:GetName())
+      set_state = newstate or set_state
+
+      local oldState = state
+      state = state_override or set_state or state
+
+      for i = 1, #propfuncs do
+        print("running state func",propfuncs[i])
+        control:RunAttribute("func-"..propfuncs[i])
+      end
+      
+      if anchorEnable and anchorEnable[state] ~= anchorstate then
+        anchorstate = anchorEnable[state]
+        control:RunAttribute("func-doanchor")
+      end
+
+      control:ChildUpdate()
+    ]]
+
+    local anchorHandler = 
+    -- function func-doanchor( self )
+    [[
+      -- TODO
+      if anchorstate then
+        -- TODO: get anchor data from state tables
+      else
+        -- TODO: get anchor data from defaults
+      end
+    ]]
+
+    local onClickHandler = 
+    -- function OnClick( self, button, down )
+    [[
+      if state_override == button then
+        state_override = nil -- toggle
+      else
+        state_override = button
+      end
+    ]] .. onStateHandler
+
+    local weak         = { __mode = "k" }
+    local statedrivers = setmetatable( { }, weak )
+    local keybinds     = setmetatable( { }, weak )
+
+    -- Construct a lua assignment as a code string and execute it within the header
+    -- frame's sandbox. 'value' must be a string, boolean, number, or nil. If called
+    -- with four arguments, then it treats 'varname' as an existing global table and
+    -- sets a key-value pair. For a slight efficiency boost, pass the values in as
+    -- attributes and fetch them as attributes from the snippet code, to leverage snippet
+    -- caching.
+    function SetHandlerData( bar, varname, value, key )
+      local f = bar:GetFrame()
+      f:SetAttribute("data-varname",varname)
+      f:SetAttribute("data-value",  value)
+      f:SetAttribute("data-key",    key)
+      f:Execute(
+        [[
+          local name  = self:GetAttribute("data-varname")
+          local value = self:GetAttribute("data-value")
+          local key   = self:GetAttribute("data-key")
+          if name then
+            if key then
+              if not _G[name] then
+                _G[name] = table.new()
+              end
+              _G[name][key] = value
+            else
+              _G[name] = value
+            end
+          end
+        ]])
+    end
+
+    function SetDefaultAnchor( bar )
+      local point, frame, relPoint, x, y = bar:GetAnchor()
+      SetHandlerData(bar, "defaultAnchor", point, "point")
+      SetHandlerData(bar, "defaultAnchor", relPoint, "relPoint")
+      SetHandlerData(bar, "defaultAnchor", x, "x")
+      SetHandlerData(bar, "defaultAnchor", y, "y")
+
+      if frame then
+        local f = bar:GetFrame()
+        f:SetFrameRef("defaultAnchor", f)
+        f:Execute(
+          [[
+            defaultAnchor.frame = self:GetAttribute("frameref-defaultAnchor")
+          ]])
+      end
+    end
+
+    function RefreshState( bar )
+      SetDefaultAnchor(bar)
+      bar:GetFrame():Execute([[control:RunAttribute("reaction-refresh")]])
+    end
+
+    function SetStateDriver( bar, rule )
+      local f = bar:GetFrame()
+
+      local props = { }
+      for p, h in pairs(properties) do
+        if type(h) == "string" then
+          table.insert(props,p)
+          f:SetAttribute("func-"..p, h)
+        end
+      end
+      f:SetAttribute("prop-func-list", table.concat(props," "))
+      f:Execute(stateHandler_propInit)
+      f:SetAttribute("reaction-refresh", onStateHandler)
+      f:SetAttribute("func-doanchor", anchorHandler)
+      
+      if rule and #rule > 0 then
+        f:SetAttribute( "_onstate-reaction", onStateHandler )
+        RegisterStateDriver(f, "reaction", rule)
+        statedrivers[bar] = rule
+      elseif statedrivers[bar] then
+        UnregisterStateDriver(f, "reaction")
+        f:SetAttribute( "_onstate-reaction", nil )
+        statedrivers[bar] = nil
+      end
+    end
+
+    function SetStateKeybind( bar, key, state )
+      local f = bar:GetFrame()
+
+      local kb = keybinds[bar]
+      if kb == nil then
+        if key == nil then
+          -- nothing to do
+          return
+        end
+        kb = { }
+        keybinds[bar] = kb
+      end
+
+      -- clear the old binding, if any
+      if kb[state] then
+        SetOverrideBinding(f, false, kb[state], nil)
+      end
+      kb[state] = key
+
+      if key then
+        f:SetAttribute("_onclick", onClickHandler)
+        SetOverrideBindingClick(f, false, key, state, nil) -- state name is the virtual mouse button
+      end
+    end
+  end
+
   -- As far as I can tell the macro clauses are NOT locale-specific.
   local ruleformats = { 
     stealth       = "stealth",
@@ -113,146 +309,10 @@
     ruleformats.moonkin   = format("form:%d",moonkin)
   end
 
-
-  -- state property functions
-  local ofskeys = {
-    anchorPoint = "point",
-    anchorRelPoint = "relpoint",
-    anchorX = "x",
-    anchorY = "y" 
-  }
-
-  local barofsidx = {
-    anchorPoint = 1,
-    anchorRelPoint = 3,
-    anchorX = 4,
-    anchorY = 5
-  }
-
-  local function UpdatePartialAnchor(bar, states, ckey)
-    local map = { }
-    local bc = bar.config
-    for state, c in pairs(states) do
-      if c.enableAnchor then
-        map[state] = c[ckey]
-      end
-    end
-    local ofskey = ofskeys[ckey]
-    local default = select(barofsidx[ckey], bar:GetAnchor())
-    bar:SetStateAttribute(format("headofs%s",ofskeys[ckey]), map, default)
-  end
-  
-  -- the table key name for each function maps to the name of the config element
-  local propertyFuncs = { 
-    hide = function( bar, states )
-      local hs = { }
-      for state, config in pairs(states) do
-        if config.hide then
-          table.insert(hs, state)
-        end
-      end
-      bar:GetButtonFrame():SetAttribute("hidestates", table.concat(hs,","))
-    end,
-
-    keybindstate = function( bar, states )
-      local map = { }
-      for state, config in pairs(states) do
-        local kbset = config.keybindstate and state
-        map[state] = kbset
-        for button in bar:IterateButtons() do
-          -- TODO: inform children they should maintain multiple binding sets
-          -- ?? button:UpdateBindingSet(kbset)
-        end
-      end
-      bar:SetStateAttribute("statebindings", map, true) -- apply to button frame, bindings only work for direct children
-    end,
-
-    enableAnchor = function( bar, states )
-      for ckey in pairs(ofskeys) do
-        UpdatePartialAnchor(bar, states, ckey)
-      end
-    end,
-
-    enableScale = function( bar, states )
-      local map = { }
-      for state, c in pairs(states) do
-        if c.enableScale then
-          map[state] = c.scale
-        end
-      end
-      bar:SetStateAttribute("headscale", map, 1.0)
-    end,
-  }
-
-  -- generate some table entries
-  propertyFuncs.scale = propertyFuncs.enableScale
-  for ckey in pairs(ofskeys) do
-    propertyFuncs[ckey] = function( bar, states )
-      UpdatePartialAnchor(bar, states, ckey)
-    end
-  end
-
-
-  function GetProperty( bar, state, propname )
-    return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname)
-  end
-
-  function SetProperty( bar, state, propname, value )
-    local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
-    tbuild(states, state)[propname] = value
-    local f = propertyFuncs[propname]
-    if f then
-      f(bar, states)
-    end
-  end
-
-  function RegisterProperty( propname, f )
-    propertyFuncs[propname] = f
-    for bar in ReAction:IterateBars() do
-      local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
-      if states then
-        f(bar,states)
-      end
-    end
-  end
-
-
-
-  --
-  -- Build a state-transition spec string and statemap to be passed to
-  -- Bar:SetStateDriver().
-  --
-  -- The statemap building is complex: keybound states override all 
-  -- other transitions, so must remain in their current state, but must
-  -- also remember other transitions that happen while they're stuck there
-  -- so that when the binding is toggled off it can return to the proper state
-  --
-  local function BuildStateMap(states)
+  local function BuildRule(states)
     local rules = { }
-    local statemap = { }
-    local keybinds = { }
     local default
 
-    -- first grab all the keybind override states
-    -- and construct an override template
-    local override
-    do
-      local overrides = { }
-      for name, state in pairs(states) do
-        local type = tfetch(state, "rule", "type")
-        if type == "keybind" then
-          -- use the state-stack to remember the current transition
-          -- use $s as a marker for a later call to gsub()
-          table.insert(overrides, format("%s:$s set() %s", name, name))
-        end
-      end
-      if #overrides > 0 then
-        table.insert(overrides, "") -- for a trailing ';'
-      end
-      override = table.concat(overrides, ";") or ""
-    end
-
-    -- now iterate the rules in order
     for idx, state in ipairs(fieldsort(states, "rule", "order")) do
       local c = states[state].rule
       local type = c.type
@@ -284,43 +344,83 @@
           end
         end
       end
-      
-      -- use a different virtual button for the actual keybind transition,
-      -- to implement a toggle. You have to clear it regardless of the type
-      -- (which is usually a no-op) to unbind state transitions when switching
-      -- transition types.
-      local bindbutton = format("%s_binding",state)
-      if type == "keybind" then
-        keybinds[bindbutton] = c.keybind or false
-        statemap[bindbutton] = format("%s:pop();*:set(%s)", state, state)
-      else
-        keybinds[bindbutton] = false
-      end
-
-      -- construct the statemap. gsub() the state name into the override template.
-      statemap[state] = format("%s%s", override:gsub("%$s",state), state)
     end
     -- make sure that the default, if any, is last
     if default then
       table.insert(rules, default)
     end
-    return table.concat(rules,";"), statemap, keybinds
+    return table.concat(rules,";")
+  end
+
+  local function BuildKeybinds( bar, states )
+    for name, state in pairs(states) do
+      local type = tfetch(state, "rule", "type")
+      if type == "keybind" then
+        local key = tfetch(state, "rule", "keybind")
+        SetStateKeybind(bar, key, name)
+      else
+        SetStateKeybind(bar, nil, name) -- this clears an existing keybind
+      end
+    end
+  end
+
+  function GetProperty( bar, state, propname )
+    return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname)
+  end
+
+  function SetProperty( bar, state, propname, value )
+    local s = tbuild(module.db.profile.bars, bar:GetName(), "states", state)
+    s[propname] = value
+    SetHandlerData(bar, propname, value, state)
+    RefreshState(bar)
+  end
+
+  function RegisterProperty( propname, snippet )
+    properties[propname] = snippet or true
+    print("registered property",propname)
+    for _, bar in ReAction:IterateBars() do
+      local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
+      if states then
+        for name, s in pairs(states) do
+          SetHandlerData(bar, propname, s[propname], name)
+        end
+        SetStateDriver(bar, BuildRule(states))
+        RefreshState(bar)
+      end
+    end
+  end
+
+  function UnregisterProperty( propname )
+    properties[propname] = nil
+    for _, bar in ReAction:IterateBars() do
+      SetHandlerData(bar, propname, nil)
+      SetStateDriver(bar, BuildRule(states))
+      RefreshState(bar)
+    end
   end
 
   function ApplyStates( bar )
     local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
     if states then
-      local rule, statemap, keybinds = BuildStateMap(states)
-      bar:SetStateDriver("reaction", rule, statemap)
-      for state, key in pairs(keybinds) do
-        bar:SetAttributeBinding(state, key, "state-reaction", state)
+      for propname in pairs(properties) do
+        for name, s in pairs(states) do
+          SetHandlerData(bar, propname, s[propname], name)
+        end
       end
-      for k, f in pairs(propertyFuncs) do
-        f(bar, states)
-      end
+      BuildKeybinds(bar, states)
+      SetStateDriver(bar, BuildRule(states))
+      RefreshState(bar)
     end
   end
 
+  function CleanupStates( bar )
+    SetStateDriver(bar, nil)
+  end
+
+  function ShowAll( bar, show )
+    SetHandlerData(bar, "showAll", show)
+    RefreshState(bar)
+  end
 end
 
 
@@ -342,6 +442,7 @@
   ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")
 
   ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar")
+  ReAction.RegisterCallback(self, "OnDestroyBar")
   ReAction.RegisterCallback(self, "OnRefreshBar")
   ReAction.RegisterCallback(self, "OnEraseBar")
   ReAction.RegisterCallback(self, "OnRenameBar")
@@ -351,7 +452,7 @@
 function module:PLAYER_AURAS_CHANGED()
   self:UnregisterEvent("PLAYER_AURAS_CHANGED")
   -- on login the number of stances is 0 until this event fires during the init sequence.
-  -- however if you reload just the UI the number of stances is correct immediately
+  -- however if you just reload the UI the number of stances is correct immediately
   -- and this event won't fire until you gain/lose buffs/debuffs, at which point you might
   -- be in combat.
   if not InCombatLockdown() then
@@ -369,6 +470,10 @@
   end
 end
 
+function module:OnDestroyBar(event, bar, name)
+  CleanupStates(bar)
+end
+
 function module:OnEraseBar(event, bar, name)
   self.db.profile.bars[name] = nil
 end
@@ -379,7 +484,11 @@
 end
 
 function module:OnConfigModeChanged(event, mode)
-  -- nothing to do (yet)
+  for name, bar in ReAction:IterateBars() do
+    if self.db.profile.bars[name] then
+      ShowAll(bar, mode)
+    end
+  end
 end
 
 
@@ -505,30 +614,40 @@
           set  = "SetProp",
           get  = "GetProp",
         },
-        keybindstate = {
+        --[[ BROKEN
+        keybindState = {
           name  = L["Override Keybinds"],
           desc  = L["Set this state to maintain its own set of keybinds which override the defaults when active"],
           order = 91,
           type  = "toggle",
           set   = "SetProp",
           get   = "GetProp",
-        },
+        }, ]]
         position = {
           name  = L["Position"],
           order = 92,
           type  = "group",
           inline = true,
           args = {
-            enableAnchor = {
+            anchorEnable = {
               name  = L["Set New Position"],
               order = 1,
               type  = "toggle",
               set   = "SetProp",
               get   = "GetProp",
             },
+            --[[ TODO: broken
+            anchorFrame = {
+              name   = L["Anchor Frame"],
+              order  = 2,
+              type   = "select",
+              values = "GetAnchorFrames",
+              set    = ???
+              get    = ???
+            },   ]]
             anchorPoint = {
               name  = L["Point"],
-              order = 2,
+              order = 3,
               type  = "select",
               values = pointTable,
               set   = "SetAnchorPointProp",
@@ -538,7 +657,7 @@
             },
             anchorRelPoint = {
               name  = L["Relative Point"],
-              order = 3,
+              order = 4,
               type  = "select",
               values = pointTable,
               set   = "SetAnchorPointProp",
@@ -548,7 +667,7 @@
             },
             anchorX = {
               name  = L["X Offset"],
-              order = 4,
+              order = 5,
               type  = "range",
               min   = -100,
               max   = 100,
@@ -560,7 +679,7 @@
             },
             anchorY = {
               name  = L["Y Offset"],
-              order = 5,
+              order = 6,
               type  = "range",
               min   = -100,
               max   = 100,
@@ -680,10 +799,35 @@
     },
   }
 
-  local StateHandler = { }
+  local handlers = { }
+  local meta = {
+    __index = function(self, key)
+      for _, h in pairs(handlers) do
+        if h[key] then
+          return h[key]
+        end
+      end
+    end,
+  }
+  local StateHandler = setmetatable({ }, meta)
+  local proto        = { __index = StateHandler }
+
+  function RegisterPropertyOptions( field, options, handler )
+    stateOptions.properties.plugins[field] = options
+    handlers[field] = handler
+  end
+
+  function UnregisterPropertyOptions( field )
+    stateOptions.properties.plugins[field] = nil
+    handlers[field] = nil
+  end
 
   function StateHandler:New( bar, opts )
-    local self = setmetatable({ bar = bar }, { __index = StateHandler })
+    local self = setmetatable(
+      { 
+        bar = bar 
+      }, 
+      proto )
 
     function self:GetName()
       return opts.name
@@ -700,10 +844,9 @@
     -- get reference to states table: even if the bar
     -- name changes the states table ref won't
     self.states = tbuild(module.db.profile.bars, bar:GetName(), "states")
+    self.state  = tbuild(self.states, opts.name)
 
-    tbuild(self.states, opts.name)
-
-    opts.order = self:GetRule("order")
+    opts.order = self:GetRuleField("order")
     if opts.order == nil then
       -- add after the highest
       opts.order = 100
@@ -713,7 +856,7 @@
           opts.order = x + 1
         end
       end
-      self:SetRule("order",opts.order)
+      self:SetRuleField("order",opts.order)
     end
 
     return self
@@ -721,12 +864,12 @@
 
   -- helper methods
 
-  function StateHandler:SetRule( key, value, ... )
-    tbuild(self.states, self:GetName(), "rule", ...)[key] = value
+  function StateHandler:SetRuleField( key, value, ... )
+    tbuild(self.state, "rule", ...)[key] = value
   end
 
-  function StateHandler:GetRule( ... )
-    return tfetch(self.states, self:GetName(), "rule", ...)
+  function StateHandler:GetRuleField( ... )
+    return tfetch(self.state, "rule", ...)
   end
 
   function StateHandler:FixAll( setkey )
@@ -735,7 +878,7 @@
     -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set,
     -- it will be retained.
     local notified = false
-    if self:GetRule("type") == "all" then
+    if self:GetRuleField("type") == "all" then
       for _, c in ipairs(rules) do
         local rule, hidden, fields = unpack(c)
         local once = false
@@ -748,9 +891,9 @@
         end
         for idx, field in ipairs(fields) do
           local key = next(field)
-          if self:GetRule("values",key) then
+          if self:GetRuleField("values",key) then
             if once and key ~= setkey then
-              self:SetRule(key,false,"values")
+              self:SetRuleField(key,false,"values")
               if not setkey and not notified then
                 ReAction:UserError(L["Warning: one or more incompatible rules were turned off"])
                 notified = true
@@ -858,37 +1001,37 @@
   end
 
   function StateHandler:SetType(info, value)
-    self:SetRule("type", value)
+    self:SetRuleField("type", value)
     self:FixAll()
     ApplyStates(self.bar)
   end
 
   function StateHandler:GetType()
-    return self:GetRule("type")
+    return self:GetRuleField("type")
   end
 
   function StateHandler:GetClearAllDisabled()
-    local t = self:GetRule("type")
+    local t = self:GetRuleField("type")
     return not( t == "any" or t == "all" or t == "custom")
   end
 
   function StateHandler:ClearAllConditions()
-    local t = self:GetRule("type")
+    local t = self:GetRuleField("type")
     if t == "custom" then
-      self:SetRule("custom","")
+      self:SetRuleField("custom","")
     elseif t == "any" or t == "all" then
-      self:SetRule("values", {})
+      self:SetRuleField("values", {})
     end
     ApplyStates(self.bar)
   end
 
   function StateHandler:GetConditionsDisabled()
-    local t = self:GetRule("type")
+    local t = self:GetRuleField("type")
     return not( t == "any" or t == "all")
   end
 
   function StateHandler:SetCondition(info, key, value)
-    self:SetRule(ruleMap[key], value or nil, "values")
+    self:SetRuleField(ruleMap[key], value or nil, "values")
     if value then
       self:FixAll(ruleMap[key])
     end
@@ -896,20 +1039,20 @@
   end
 
   function StateHandler:GetCondition(info, key)
-    return self:GetRule("values", ruleMap[key]) or false
+    return self:GetRuleField("values", ruleMap[key]) or false
   end
 
   function StateHandler:GetCustomDisabled()
-    return self:GetRule("type") ~= "custom"
+    return self:GetRuleField("type") ~= "custom"
   end
 
   function StateHandler:SetCustomRule(info, value)
-    self:SetRule("custom",value)
+    self:SetRuleField("custom",value)
     ApplyStates(self.bar)
   end
 
   function StateHandler:GetCustomRule()
-    return self:GetRule("custom") or ""
+    return self:GetRuleField("custom") or ""
   end
 
   function StateHandler:ValidateCustomRule(info, value)
@@ -929,18 +1072,18 @@
   end
 
   function StateHandler:GetKeybindDisabled()
-    return self:GetRule("type") ~= "keybind"
+    return self:GetRuleField("type") ~= "keybind"
   end
 
   function StateHandler:GetKeybind()
-    return self:GetRule("keybind")
+    return self:GetRuleField("keybind")
   end
 
   function StateHandler:SetKeybind(info, value)
     if value and #value == 0 then
       value = nil
     end
-    self:SetRule("keybind",value)
+    self:SetRuleField("keybind",value)
     ApplyStates(self.bar)
   end
 
@@ -957,17 +1100,6 @@
     return opts
   end
 
-
-  function RegisterPropertyOptions( field, options, handler )
-    stateOptions.properties.plugins[field] = options
-    if handler then
-      for k,v in pairs(handler) do
-        StateHandler[k] = v
-      end
-    end
-  end
-
-
   function module:GetBarOptions(bar)
     local private = { }
     local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
@@ -1033,23 +1165,23 @@
 
 -- Module API --
 
--- Pass in a property field-name, an implementation function, a static options table, and an 
+-- Pass in a property field-name, an implementation secure snippet, a static options table, and an 
 -- optional options handler method-table
 --
--- propertyImplFunc prototype:
---   propertyImplFunc( bar, stateTable )
---     where stateTable is a { ["statename"] = { state config } } table.
---
 -- The options table is static, i.e. not bar-specific and should only reference handler method
 -- strings (either existing ones or those added via optHandler). The existing options are ordered
 -- 90-99. Order #1 is reserved for the heading.
 --
--- The contents of optHandler, if provided, will be added to the existing StateHandler metatable.
+-- The contents of optHandler, if provided, will be added to the existing StateHandler options metatable.
 -- See above, for existing API. In particular see the properties set up in the New method: self.bar,
 -- self.states, and self:GetName(), and the generic property handlers self:GetProp() and self:SetProp().
 --
-function module:RegisterStateProperty( field, propertyImplFunc, options, optHandler )
-  RegisterProperty(field, propertyImplFunc)
+function module:RegisterStateProperty( field, snippetHandler, options, optHandler )
+  RegisterProperty(field, snippetHandler)
   RegisterPropertyOptions(field, options, optHandler)
 end
 
+function module:UnregisterStateProperty( field )
+  UnregisterProperty(field)
+  UnregisterPropertyOptions(field)
+end