diff Editor.lua @ 257:920d17851a93 stable

Merge 1.1 beta 4 to stable
author Flick
date Tue, 12 Apr 2011 16:06:31 -0700
parents f255cd69e890
children d7a7ef367faf
line wrap: on
line diff
--- a/Editor.lua	Thu Nov 18 13:11:08 2010 -0800
+++ b/Editor.lua	Tue Apr 12 16:06:31 2011 -0700
@@ -3,12 +3,17 @@
 local L = ReAction.L
 local _G = _G
 local wipe = wipe
+local format = string.format
+local InCombatLockdown = InCombatLockdown
+local tfetch = addonTable.tfetch
+local tbuild = addonTable.tbuild
 
 local AceConfigReg = LibStub("AceConfigRegistry-3.0")
 local AceConfigDialog = LibStub("AceConfigDialog-3.0")
 
 
 local pointTable = {
+  NONE        = " ",
   CENTER      = L["Center"], 
   LEFT        = L["Left"],
   RIGHT       = L["Right"],
@@ -20,7 +25,9 @@
   BOTTOMRIGHT = L["Bottom Right"],
 }
 
-local Editor = { }
+local Editor = { 
+  buttonHandlers = { }
+}
 
 function Editor:New()
   -- create new self
@@ -90,12 +97,12 @@
             name = L["Button Type"],
             get  = function() return self.tmp.barType or ReAction:GetDefaultBarType() or "" end,
             set  = function(info, val) 
-                     local c = ReAction:GetBarTypeConfig(val)
+                     local c = ReAction:GetDefaultBarConfig(val)
                      self.tmp.barType = val 
-                     self.tmp.barSize = c.defaultButtonSize or self.tmp.barSize
-                     self.tmp.barRows = c.defaultBarRows or self.tmp.barRows
-                     self.tmp.barCols = c.defaultBarCols or self.tmp.barCols
-                     self.tmp.barSpacing = c.defaultBarSpacing or self.tmp.barSpacing
+                     self.tmp.barSize = c.btnWidth or self.tmp.barSize
+                     self.tmp.barRows = c.btnRows or self.tmp.barRows
+                     self.tmp.barCols = c.btnColumns or self.tmp.barCols
+                     self.tmp.barSpacing = c.spacing or self.tmp.barSpacing
                    end,
             values = "GetBarTypes",
             order = 3,
@@ -173,12 +180,9 @@
 
   ReAction.RegisterCallback(self,"OnCreateBar")
   ReAction.RegisterCallback(self,"OnDestroyBar")
-  ReAction.RegisterCallback(self,"OnEraseBar")
   ReAction.RegisterCallback(self,"OnRenameBar")
 
-  for name, bar in ReAction:IterateBars() do
-    self:CreateBarTree(bar)
-  end
+  self:RefreshBarOptions()
 
   return self
 end
@@ -203,160 +207,170 @@
   AceConfigReg:NotifyChange(self.configID)
 end
 
-function Editor:CreateBarTree(bar)
+function Editor:UpdateBarOptions(bar)
   local name = bar:GetName()
-  -- AceConfig doesn't allow spaces, etc, in arg key names, and they must be
-  -- unique strings. So generate a unique key (it can be whatever) for the bar
+  local key  = self.barOptMap[name]
   local args = self.options.args
-  local key
-  local i = 1
-  repeat
-    key = ("bar%s"):format(i)
-    i = i+1
-  until args[key] == nil
-  self.barOptMap[name] = key
-  args[key] = { 
-    type = "group",
-    name = name,
-    childGroups = "tab",
-    order = i+100,
-    args = {
-      general = {
-        type = "group",
-        name = L["General"],
-        order = 1,
-        args = {
-          name = {
-            type = "input",
-            name = L["Rename Bar"],
-            get  = function() return bar:GetName() end,
-            set  = function(info, value) return ReAction:RenameBar(bar, value) end,
-            order = 1,
-          },
-          delete = {
-            type = "execute",
-            name = L["Delete Bar"],
-            desc = function() return bar:GetName() end,
-            confirm = true,
-            func = function() ReAction:EraseBar(bar) end,
-            order = 2
-          },
-          anchor = {
-            type = "group",
-            name = L["Anchor"],
-            inline = true,
-            args = {
-              frame = {
-                type = "input",
-                name = L["Frame"],
-                desc = L["The frame that the bar is anchored to"],
-                get  = function() local _, f = bar:GetAnchor(); return f end,
-                set  = function(info, val) bar:SetAnchor(nil,val) end,
-                validate = function(info, name) 
-                    if name then
-                      local f = ReAction:GetBar(name)
-                      if f then
-                        return true
-                      else
-                        f = _G[name]
-                        if f and type(f) == "table" and f.IsObjectType and f:IsObjectType("Frame") then
-                          local _, explicit = f:IsProtected()
-                          return explicit
+
+  if not key then
+    -- AceConfig doesn't allow spaces, etc, in arg key names, and they must be
+    -- unique strings. So generate a unique key (it can be whatever) for the bar
+    local i = 1
+    repeat
+      key = ("bar%s"):format(i)
+      i = i+1
+    until args[key] == nil
+    self.barOptMap[name] = key
+
+    args[key] = { 
+      type = "group",
+      name = name,
+      childGroups = "tab",
+      order = i+100,
+      args = {
+        general = {
+          type = "group",
+          name = L["General"],
+          order = 1,
+          args = {
+            name = {
+              type = "input",
+              name = L["Rename Bar"],
+              get  = function() return bar:GetName() end,
+              set  = function(info, value) return ReAction:RenameBar(bar, value) end,
+              order = 1,
+            },
+            delete = {
+              type = "execute",
+              name = L["Delete Bar"],
+              desc = function() return bar:GetName() end,
+              confirm = true,
+              func = function() ReAction:EraseBar(bar) end,
+              order = 2
+            },
+            anchor = {
+              type = "group",
+              name = L["Anchor"],
+              inline = true,
+              args = {
+                frame = {
+                  type = "input",
+                  name = L["Frame"],
+                  desc = L["The frame that the bar is anchored to"],
+                  get  = function() local _, f = bar:GetAnchor(); return f end,
+                  set  = function(info, val) bar:SetAnchor(nil,val) end,
+                  validate = function(info, name) 
+                      if name then
+                        local f = ReAction:GetBar(name)
+                        if f then
+                          return true
+                        else
+                          f = _G[name]
+                          if f and type(f) == "table" and f.IsObjectType and f:IsObjectType("Frame") then
+                            local _, explicit = f:IsProtected()
+                            return explicit
+                          end
                         end
                       end
-                    end
-                    return false
-                  end,
-                width = "double",
-                order = 1
+                      return false
+                    end,
+                  width = "double",
+                  order = 1
+                },
+                point = {
+                  type = "select",
+                  name = L["Point"],
+                  desc = L["Anchor point on the bar frame"],
+                  style = "dropdown",
+                  get  = function() return bar:GetAnchor() end,
+                  set  = function(info, val) bar:SetAnchor(val) end,
+                  values = pointTable,
+                  order = 2,
+                },
+                relativePoint = {
+                  type = "select",
+                  name = L["Relative Point"],
+                  desc = L["Anchor point on the target frame"],
+                  style = "dropdown",
+                  get  = function() local p,f,r = bar:GetAnchor(); return r end,
+                  set  = function(info, val) bar:SetAnchor(nil,nil,val) end,
+                  values = pointTable,
+                  order = 3,
+                },
+                x = {
+                  type = "input",
+                  pattern = "\-?%d+",
+                  name = L["X offset"],
+                  get = function() local p,f,r,x = bar:GetAnchor(); return ("%d"):format(x) end,
+                  set = function(info,val) bar:SetAnchor(nil,nil,nil,val) end,
+                  order = 4
+                },
+                y = {
+                  type = "input",
+                  pattern = "\-?%d+",
+                  name = L["Y offset"],
+                  get = function() local p,f,r,x,y = bar:GetAnchor(); return ("%d"):format(y) end,
+                  set = function(info,val) bar:SetAnchor(nil,nil,nil,nil,val) end,
+                  order = 5
+                },
               },
-              point = {
-                type = "select",
-                name = L["Point"],
-                desc = L["Anchor point on the bar frame"],
-                style = "dropdown",
-                get  = function() return bar:GetAnchor() end,
-                set  = function(info, val) bar:SetAnchor(val) end,
-                values = pointTable,
-                order = 2,
-              },
-              relativePoint = {
-                type = "select",
-                name = L["Relative Point"],
-                desc = L["Anchor point on the target frame"],
-                style = "dropdown",
-                get  = function() local p,f,r = bar:GetAnchor(); return r end,
-                set  = function(info, val) bar:SetAnchor(nil,nil,val) end,
-                values = pointTable,
-                order = 3,
-              },
-              x = {
-                type = "input",
-                pattern = "\-?%d+",
-                name = L["X offset"],
-                get = function() local p,f,r,x = bar:GetAnchor(); return ("%d"):format(x) end,
-                set = function(info,val) bar:SetAnchor(nil,nil,nil,val) end,
-                order = 4
-              },
-              y = {
-                type = "input",
-                pattern = "\-?%d+",
-                name = L["Y offset"],
-                get = function() local p,f,r,x,y = bar:GetAnchor(); return ("%d"):format(y) end,
-                set = function(info,val) bar:SetAnchor(nil,nil,nil,nil,val) end,
-                order = 5
-              },
+              order = 3
             },
-            order = 3
-          },
-          alpha = {
-            type = "range",
-            name = L["Transparency"],
-            get  = function() return bar:GetAlpha() end,
-            set  = function(info, val) bar:SetAlpha(val) end,
-            min = 0, 
-            max = 1,
-            isPercent = true,
-            step = 0.01,
-            bigStep = 0.05,
-            order = 4,
+            alpha = {
+              type = "range",
+              name = L["Transparency"],
+              get  = function() return bar:GetAlpha() end,
+              set  = function(info, val) bar:SetAlpha(val) end,
+              min = 0, 
+              max = 1,
+              isPercent = true,
+              step = 0.01,
+              bigStep = 0.05,
+              order = 4,
+            },
           },
         },
-      },
+        buttonOpts = self:CreateButtonOptions(bar),
+        stateOpts  = self:CreateStateOptions(bar)
+      }
     }
-  }
-  self:RefreshBarOptions()
+  end
+
+end
+
+function Editor:CreateButtonOptions(bar)
+  local buttonClass = bar:GetButtonClass()
+  local classID = buttonClass:GetButtonTypeID()
+  local handler = self.buttonHandlers[classID]
+
+  if handler then
+    local h = handler:New(bar)
+    return h:GetOptions()
+  end
 end
 
 function Editor:RefreshBarOptions()
   for name, key in pairs(self.barOptMap) do
-    local bar = ReAction:GetBar(name)
-    if bar and key and self.options.args[key] then
-      self.options.args[key].plugins = self:GenerateBarOptionsTable(bar)
+    if not ReAction:GetBar(name) then
+      self.barOptMap[name] = nil
+      self.options.args[key] = nil
     end
   end
-  AceConfigReg:NotifyChange(self.configID)
+  for name, bar in ReAction:IterateBars() do
+    self:UpdateBarOptions(bar)
+  end
+  self:Refresh()
 end
 
 function Editor:OnCreateBar(evt, bar)
-  if not self.tmp.creating then
-    -- a bit of hack to work around OnCreateBar event handler ordering
-    self:CreateBarTree(bar)
-  end
+  self:UpdateBarOptions(bar)
+  self:Refresh()
 end
 
 function Editor:OnDestroyBar(evt, bar, name)
   local key = self.barOptMap[name]
   if key then
-    self.options.args[key] = nil
-  end
-  self:Refresh()
-end
-
-function Editor:OnEraseBar(evt, name)
-  local key = self.barOptMap[name]
-  self.barOptMap[name] = nil
-  if key then
+    self.barOptMap[name] = nil
     self.options.args[key] = nil
     self:Refresh()
   end
@@ -364,8 +378,8 @@
 
 function Editor:OnRenameBar(evt, bar, oldname, newname)
   local key = self.barOptMap[oldname]
-  self.barOptMap[oldname], self.barOptMap[newname] = nil, key
   if key then
+    self.barOptMap[oldname], self.barOptMap[newname] = nil, key
     self.options.args[key].name = newname
     self:Refresh()
   end
@@ -379,42 +393,1300 @@
 
 function Editor:CreateBar()
   if self.tmp.barName and self.tmp.barName ~= "" then
-    self.tmp.creating = true
     local bar = ReAction:CreateBar(self.tmp.barName, self.tmp.barType or ReAction:GetDefaultBarType(), self.tmp.barRows, self.tmp.barCols, self.tmp.barSize, self.tmp.barSpacing)
     if bar then
-      self:CreateBarTree(bar)
       AceConfigDialog:SelectGroup(self.configID, self.barOptMap[self.tmp.barName])
       self.tmp.barName = nil
     end
-    self.tmp.creating = false
   end
 end
 
-function Editor:GenerateBarOptionsTable( bar )
-  local opts = { }
-  if not ReAction.barOptionGenerators then
-    return
+-------------------------------
+---- Action button handler ----
+-------------------------------
+
+do
+  local ActionHandler = {
+    buttonClass = ReAction.Button.Action,
+    options = {
+      hideEmpty = {
+        name = L["Hide Empty Buttons"],
+        order = 1,
+        type = "toggle",
+        width = "double",
+        get  = "GetHideEmpty",
+        set  = "SetHideEmpty",
+      },
+      lockButtons = {
+        name = L["Lock Buttons"],
+        desc = L["Prevents picking up/dragging actions (use SHIFT to override this behavior)"],
+        order = 2,
+        type = "toggle",
+        get = "GetLockButtons",
+        set = "SetLockButtons",
+      },
+      lockOnlyCombat = {
+        name = L["Only in Combat"],
+        desc = L["Only lock the buttons when in combat"],
+        order = 3,
+        type = "toggle",
+        disabled = "LockButtonsCombatDisabled",
+        get = "GetLockButtonsCombat",
+        set = "SetLockButtonsCombat",
+      },
+      pages = {
+        name  = L["# Pages"],
+        desc  = L["Use the Dynamic State tab to specify page transitions"],
+        order = 4,
+        type  = "range",
+        min   = 1,
+        max   = 10,
+        step  = 1,
+        get   = "GetNumPages",
+        set   = "SetNumPages",
+      },
+      mindcontrol = {
+        name = L["Mind Control Support"],
+        desc = L["When possessing a target (e.g. via Mind Control), map the first 12 buttons of this bar to the possessed target's actions."],
+        order = 5,
+        type = "toggle",
+        width = "double",
+        set = "SetMindControl",
+        get = "GetMindControl",
+      },
+      vehicle = {
+        name = L["Vehicle Support"],
+        desc = L["When on a vehicle, map the first 6 buttons of this bar to the vehicle actions. The vehicle-exit button is mapped to the 7th button. Pitch controls are not supported."],
+        order = 6,
+        type = "toggle",
+        width = "double",
+        get = "GetVehicle",
+        set = "SetVehicle",
+      },
+      actions = {
+        name   = L["Edit Action IDs"],
+        order  = 7,
+        type   = "group",
+        inline = true,
+        args   = {
+          method = {
+            name   = L["Assign"],
+            order  = 1,
+            type   = "select",
+            width  = "double",
+            values = { [0] = L["Choose Method..."],
+                       [1] = L["Individually"],
+                       [2] = L["All at Once"], },
+            get    = "GetActionEditMethod",
+            set    = "SetActionEditMethod",
+          },
+          rowSelect = {
+            name   = L["Row"],
+            desc   = L["Rows are numbered top to bottom"],
+            order  = 2,
+            type   = "select",
+            width  = "half",
+            hidden = "IsButtonSelectHidden",
+            values = "GetRowList",
+            get    = "GetSelectedRow",
+            set    = "SetSelectedRow",
+          },
+          colSelect = {
+            name   = L["Col"],
+            desc   = L["Columns are numbered left to right"],
+            order  = 3,
+            type   = "select",
+            width  = "half",
+            hidden = "IsButtonSelectHidden",
+            values = "GetColumnList",
+            get    = "GetSelectedColumn",
+            set    = "SetSelectedColumn",
+          },
+          pageSelect = {
+            name   = L["Page"],
+            order  = 4,
+            type   = "select",
+            width  = "half",
+            hidden = "IsPageSelectHidden",
+            values = "GetPageList",
+            get    = "GetSelectedPage",
+            set    = "SetSelectedPage",
+          },
+          single = {
+            name   = L["Action ID"],
+            usage  = L["Specify ID 1-120"],
+            order  = 5,
+            type   = "input",
+            width  = "half",
+            hidden = "IsButtonSelectHidden",
+            get    = "GetActionID",
+            set    = "SetActionID",
+            validate = "ValidateActionID",
+          },
+          multi = {
+            name   = L["ID List"],
+            usage  = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"],
+            order  = 6,
+            type   = "input",
+            multiline = true,
+            width  = "double",
+            hidden = "IsMultiIDHidden",
+            get    = "GetMultiID",
+            set    = "SetMultiID",
+            validate = "ValidateMultiID",
+          },
+        },
+      },
+    }
+  }
+
+  Editor.buttonHandlers[ActionHandler.buttonClass:GetButtonTypeID()] = ActionHandler
+
+  local meta = { __index = ActionHandler }
+
+  function ActionHandler:New( bar )
+    return setmetatable(
+      {
+        bar = bar,
+        config = bar:GetConfig(),
+      }, 
+      meta)
   end
 
-  for module, func in pairs(ReAction.barOptionGenerators) do
-    local success, r
-    if type(func) == "string" then
-      success, r = pcall(module[func], module, bar)
-    else
-      success, r = pcall(func, bar)
-    end
-    if success then
-      if r then
-        opts[module:GetName()] = { [module:GetName()] = r }
+  function ActionHandler:Refresh()
+    self.buttonClass:SetupBar(self.bar)
+  end
+
+  function ActionHandler:UpdateButtonLock()
+    self.buttonClass:SetButtonLock(self.bar, self.config.lockButtons, self.config.lockButtonsCombat)
+  end
+
+  function ActionHandler:GetLastButton()
+    return self.bar:GetButton(self.bar:GetNumButtons())
+  end
+
+    -- options handlers
+  function ActionHandler:GetOptions()
+    return {
+      type = "group",
+      name = L["Action Buttons"],
+      handler = self,
+      order = 2,
+      args = self.options
+    }
+  end
+
+  function ActionHandler:SetHideEmpty(info, value)
+    if value ~= self.config.hideEmpty then
+      self.config.hideEmpty = value
+      for _, b in self.bar:IterateButtons() do
+        b:ShowGrid(not value)
       end
-    else
-      geterrorhandler()(r)
     end
   end
-  return opts
+
+  function ActionHandler:GetHideEmpty()
+    return self.config.hideEmpty
+  end
+
+  function ActionHandler:GetLockButtons()
+    return self.config.lockButtons
+  end
+
+  function ActionHandler:SetLockButtons(info, value)
+    self.config.lockButtons = value
+    self:UpdateButtonLock()
+  end
+
+  function ActionHandler:GetLockButtonsCombat()
+    return self.config.lockButtonsCombat
+  end
+
+  function ActionHandler:SetLockButtonsCombat(info, value)
+    self.config.lockButtonsCombat = value
+    self:UpdateButtonLock()
+  end
+
+  function ActionHandler:LockButtonsCombatDisabled()
+    return not self.config.lockButtons
+  end
+
+  function ActionHandler:GetNumPages()
+    return self.config.nPages
+  end
+
+  function ActionHandler:SetNumPages(info, value)
+    self.config.nPages = value
+    self:Refresh()
+  end
+
+  function ActionHandler:GetMindControl()
+    return self.config.mindcontrol
+  end
+
+  function ActionHandler:SetMindControl(info, value)
+    self.config.mindcontrol = value
+    self:Refresh()
+  end
+
+  function ActionHandler:GetVehicle()
+    return self.config.vehicle
+  end
+
+  function ActionHandler:SetVehicle(info, value)
+    self.config.vehicle = value
+    self:Refresh()
+  end
+
+  function ActionHandler:GetActionEditMethod()
+    return self.editMethod or 0
+  end
+
+  function ActionHandler:SetActionEditMethod(info, value)
+    self.editMethod = value
+  end
+
+  function ActionHandler:IsButtonSelectHidden()
+    return self.editMethod ~= 1
+  end
+
+  function ActionHandler:GetRowList()
+    local r,c = self.bar:GetButtonGrid()
+    if self.rowList == nil or #self.rowList ~= r then
+      local list = { }
+      for i = 1, r do
+        table.insert(list,i)
+      end
+      self.rowList = list
+    end
+    return self.rowList
+  end
+
+  function ActionHandler:GetSelectedRow()
+    local r, c = self.bar:GetButtonGrid()
+    local row = self.selectedRow or 1
+    if row > r then
+      row = 1
+    end
+    self.selectedRow = row
+    return row
+  end
+
+  function ActionHandler:SetSelectedRow(info, value)
+    self.selectedRow = value
+  end
+
+  function ActionHandler:GetColumnList()
+    local r,c = self.bar:GetButtonGrid()
+    if self.columnList == nil or #self.columnList ~= c then
+      local list = { }
+      for i = 1, c do
+        table.insert(list,i)
+      end
+      self.columnList = list
+    end
+    return self.columnList
+  end
+
+  function ActionHandler:GetSelectedColumn()
+    local r, c = self.bar:GetButtonGrid()
+    local col = self.selectedColumn or 1
+    if col > c then
+      col = 1
+    end
+    self.selectedColumn = col
+    return col
+  end
+
+  function ActionHandler:SetSelectedColumn(info, value)
+    self.selectedColumn = value
+  end
+
+  function ActionHandler:IsPageSelectHidden()
+    return self.editMethod ~= 1 or (self.config.nPages or 1) < 2
+  end
+
+  function ActionHandler:GetPageList()
+    local n = self.config.nPages or 1
+    if self.pageList == nil or #self.pageList ~= n then
+      local p = { }
+      for i = 1, n do
+        table.insert(p,i)
+      end
+      self.pageList = p
+    end
+    return self.pageList
+  end
+
+  function ActionHandler:GetSelectedPage()
+    local p = self.selectedPage or 1
+    if p > (self.config.nPages or 1) then
+      p = 1
+    end
+    self.selectedPage = p
+    return p
+  end
+
+  function ActionHandler:SetSelectedPage(info, value)
+    self.selectedPage = value
+  end
+
+  function ActionHandler:GetActionID()
+    local row = self.selectedRow or 1
+    local col = self.selectedColumn or 1
+    local r, c = self.bar:GetButtonGrid()
+    local n = (row-1) * c + col
+    local btn = self.bar:GetButton(n)
+    if btn then
+      return tostring(btn:GetActionID(self.selectedPage or 1))
+    end
+  end
+
+  function ActionHandler:SetActionID(info, value)
+    local row = self.selectedRow or 1
+    local col = self.selectedColumn or 1
+    local r, c = self.bar:GetButtonGrid()
+    local n = (row-1) * c + col
+    local btn = self.bar:GetButton(n)
+    if btn then
+      btn:SetActionID(tonumber(value), self.selectedPage or 1)
+    end
+  end
+
+  function ActionHandler:ValidateActionID(info, value)
+    value = tonumber(value)
+    if value == nil or value < 1 or value > 120 then
+      return L["Specify ID 1-120"]
+    end
+    return true
+  end
+
+  function ActionHandler:IsMultiIDHidden()
+    return self.editMethod ~= 2
+  end
+
+  function ActionHandler:GetMultiID()
+    local p = { }
+    for i = 1, self.config.nPages or 1 do
+      local b = { }
+      for _, btn in self.bar:IterateButtons() do
+        table.insert(b, btn:GetActionID(i))
+      end
+      table.insert(p, table.concat(b,","))
+    end
+    return table.concat(p,";\n")
+  end
+
+
+  local function ParseMultiID(nBtns, nPages, s)
+    if s:match("[^%d%s,;]") then
+      return nil
+    end
+    local p = { }
+    for list in s:gmatch("[^;]+") do
+      local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns))
+      local ids = { list:match(pattern) }
+      if #ids ~= nBtns then
+        return nil
+      end
+      table.insert(p,ids)
+    end
+    if #p ~= nPages then
+      return nil
+    end
+    return p
+  end
+
+  function ActionHandler:SetMultiID(info, value)
+    local p = ParseMultiID(self.bar:GetNumButtons(), self.config.nPages or 1, value)
+    for page, b in ipairs(p) do
+      for button, id in ipairs(b) do
+        self.bar:GetButton(button):SetActionID(id, page)
+      end
+    end
+  end
+
+  function ActionHandler:ValidateMultiID(info, value)
+    local bad = L["Invalid action ID list string"]
+    if value == nil or ParseMultiID(self.bar:GetNumButtons(), self.config.nPages or 1, value) == nil then
+      return bad
+    end
+    return true
+  end
 end
 
 
+----------------------------------
+---- PetAction button handler ----
+----------------------------------
+
+do
+  local PetHandler = { 
+    buttonClass = ReAction.Button.PetAction,
+  }
+
+  Editor.buttonHandlers[PetHandler.buttonClass:GetButtonTypeID()] = PetHandler
+
+  local meta = { __index = PetHandler }
+
+  function PetHandler:New(bar)
+    return setmetatable(
+      {
+        bar = bar,
+        config = bar.config
+      }, meta)
+  end
+
+  function PetHandler:GetLockButtons()
+    return self.config.lockButtons
+  end
+
+  function PetHandler:SetLockButtons(info, value)
+    self.config.lockButtons = value
+    self.buttonClass:UpdateButtonLock(self.bar)
+  end
+
+  function PetHandler:GetLockButtonsCombat()
+    return self.config.lockButtonsCombat
+  end
+
+  function PetHandler:SetLockButtonsCombat(info, value)
+    self.config.lockButtonsCombat = value
+    self.buttonClass:UpdateButtonLock(self.bar)
+  end
+
+  function PetHandler:LockButtonsCombatDisabled()
+    return not self.config.lockButtons
+  end
+
+  function PetHandler:GetOptions()
+    return {
+      type = "group",
+      name = L["Pet Buttons"],
+      handler = self,
+      order = 2,
+      args = {
+        lockButtons = {
+          name = L["Lock Buttons"],
+          desc = L["Prevents picking up/dragging actions (use SHIFT to override this behavior)"],
+          order = 2,
+          type = "toggle",
+          get = "GetLockButtons",
+          set = "SetLockButtons",
+        },
+        lockOnlyCombat = {
+          name = L["Only in Combat"],
+          desc = L["Only lock the buttons when in combat"],
+          order = 3,
+          type = "toggle",
+          disabled = "LockButtonsCombatDisabled",
+          get = "GetLockButtonsCombat",
+          set = "SetLockButtonsCombat",
+        },
+      }
+    }
+  end
+end
+
+
+-------------------------------------
+---- Vehicle Exit button handler ----
+-------------------------------------
+
+do
+  local VExitHandler = { 
+    buttonClass = ReAction.Button.VehicleExit,
+  }
+
+  Editor.buttonHandlers[VExitHandler.buttonClass:GetButtonTypeID()] = VExitHandler
+
+  local meta = { __index = VExitHandler }
+
+  function VExitHandler:New(bar)
+    return setmetatable(
+      {
+        bar = bar,
+      }, meta)
+  end
+
+  function VExitHandler:GetConfig()
+    return self.bar:GetConfig()
+  end
+
+  function VExitHandler:GetPassengerOnly()
+    return not self:GetConfig().withControls
+  end
+
+  function VExitHandler:SetPassengerOnly(info, value)
+    self:GetConfig().withControls = not value
+    self.buttonClass:UpdateRegistration(self.bar)
+  end
+
+
+  function VExitHandler:GetOptions()
+    return {
+      type = "group",
+      name = L["Exit Vehicle"],
+      handler = self,
+      args = {
+        passengerOnly = {
+          name = L["Show only when passenger"],
+          desc = L["Only show the button when riding as a passenger in a vehicle (no vehicle controls)"],
+          order = 2,
+          width = "double",
+          type = "toggle",
+          get = "GetPassengerOnly",
+          set = "SetPassengerOnly",
+        },
+      }
+    }
+  end
+end
+
+
+------------------------------
+--- Dynamic State options ----
+------------------------------
+do
+  local ApplyStates   = ReAction.Bar.ApplyStates
+  local CleanupStates = ReAction.Bar.CleanupStates
+  local SetProperty   = ReAction.Bar.SetStateProperty
+  local GetProperty   = ReAction.Bar.GetStateProperty
+
+  -- pre-sorted by the order they should appear in
+  local rules = {
+    --  rule       fields
+    { "stance",  { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } },
+    { "form",    { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {tree = L["Tree of Life"]}, {moonkin = L["Moonkin Form"]} } },
+    { "stealth", { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]}, {shadowdance = L["Shadow Dance"]} } },
+    { "shadow",  { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } },
+    { "demon",   { {demon = L["Demon Form"]}, {nodemon = L["No Demon Form"]} } },
+    { "pet",     { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } },
+    { "target",  { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } },
+    { "focus",   { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } },
+    { "possess", { {possess = L["Mind Control"]} } },
+    { "vehicle", { {vehicle = L["In a Vehicle"]} } },
+    { "group",   { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } },
+    { "combat",  { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } },
+  }
+
+  local ruleSelect = { }
+  local ruleMap    = { }
+  local optionMap  = setmetatable({},{__mode="k"})
+
+
+  -- unpack rules table into ruleSelect and ruleMap
+  for _, c in ipairs(rules) do
+    local rule, fields = unpack(c)
+    for _, field in ipairs(fields) do
+      local key, label = next(field)
+      table.insert(ruleSelect, label)
+      table.insert(ruleMap, key)
+    end
+  end
+
+  local stateOptions = {
+    ordering = {
+      name = L["Info"],
+      order = 1,
+      type = "group",
+      args = {
+        delete = {
+          name = L["Delete this State"],
+          order = -1,
+          type = "execute",
+          func = "DeleteState",
+        },
+        rename = {
+          name = L["Name"],
+          order = 1,
+          type = "input",
+          get  = "GetName",
+          set  = "SetStateName",
+          pattern = "^%w*$",
+          usage = L["State names must be alphanumeric without spaces"],
+        },
+        ordering = {
+          name = L["Evaluation Order"],
+          desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"],
+          order = 2,
+          type = "group",
+          inline = true,
+          args = {
+            up = {
+              name  = L["Up"],
+              order = 1,
+              type  = "execute",
+              width = "half",
+              func  = "MoveStateUp",
+            },
+            down = {
+              name  = L["Down"],
+              order = 2,
+              type  = "execute",
+              width = "half",
+              func  = "MoveStateDown",
+            }
+          }
+        }
+      }
+    },
+    properties = {
+      name = L["Properties"],
+      order = 2,
+      type = "group",
+      args = { 
+        desc = {
+          name = L["Set the properties for the bar when in this state"],
+          order = 1,
+          type = "description"
+        },
+        page = {
+          name     = L["Show Page #"],
+          order    = 11,
+          type     = "select",
+          width    = "half",
+          disabled = "IsPageDisabled",
+          hidden   = "IsPageHidden",
+          values   = "GetPageValues",
+          set      = "SetProp",
+          get      = "GetPage",
+        },
+        hide = {
+          name = L["Hide Bar"],
+          order = 90,
+          type = "toggle",
+          set  = "SetProp",
+          get  = "GetProp",
+        },
+        --[[ 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 = {
+            anchorEnable = {
+              name  = L["Reposition"],
+              order = 1,
+              type  = "toggle",
+              set   = "SetProp",
+              get   = "GetProp",
+            },
+            anchorFrame = {
+              name   = L["Anchor Frame"],
+              order  = 2,
+              type   = "select",
+              values = "GetAnchorFrames",
+              set    = "SetAnchorFrame",
+              get    = "GetAnchorFrame",
+              disabled = "GetAnchorDisabled",
+              hidden = "GetAnchorDisabled",
+            },
+            anchorPoint = {
+              name  = L["Point"],
+              order = 3,
+              type  = "select",
+              values = pointTable,
+              set   = "SetAnchorPointProp",
+              get   = "GetAnchorPointProp",
+              disabled = "GetAnchorDisabled",
+              hidden = "GetAnchorDisabled",
+            },
+            anchorRelPoint = {
+              name  = L["Relative Point"],
+              order = 4,
+              type  = "select",
+              values = pointTable,
+              set   = "SetAnchorPointProp",
+              get   = "GetAnchorPointProp",
+              disabled = "GetAnchorDisabled",
+              hidden = "GetAnchorDisabled",
+            },
+            anchorX = {
+              name  = L["X Offset"],
+              order = 5,
+              type  = "range",
+              min   = -100,
+              max   = 100,
+              step  = 1,
+              set   = "SetProp",
+              get   = "GetProp",
+              disabled = "GetAnchorDisabled",
+              hidden = "GetAnchorDisabled",
+            },
+            anchorY = {
+              name  = L["Y Offset"],
+              order = 6,
+              type  = "range",
+              min   = -100,
+              max   = 100,
+              step  = 1,
+              set   = "SetProp",
+              get   = "GetProp",
+              disabled = "GetAnchorDisabled",
+              hidden = "GetAnchorDisabled",
+            },
+          },
+        },
+        scale = {
+          name  = L["Scale"],
+          order = 93,
+          type  = "group",
+          inline = true,
+          args = {
+            enableScale = {
+              name  = L["Set New Scale"],
+              order = 1,
+              type  = "toggle",
+              set   = "SetProp",
+              get   = "GetProp",
+            },
+            scale = {
+              name  = L["Scale"],
+              order = 2,
+              type  = "range",
+              min   = 0.25,
+              max   = 2.5,
+              step  = 0.05,
+              isPercent = true,
+              set   = "SetProp",
+              get   = "GetScale",
+              disabled = "GetScaleDisabled",
+              hidden = "GetScaleDisabled",
+            },
+          },
+        },
+        alpha = {
+          name  = L["Transparency"],
+          order = 94,
+          type  = "group",
+          inline = true,
+          args = {
+            enableAlpha = {
+              name  = L["Set Transparency"],
+              order = 1,
+              type  = "toggle",
+              set   = "SetProp",
+              get   = "GetProp",
+            },
+            alpha = {
+              name  = L["Transparency"],
+              order = 2,
+              type  = "range",
+              min   = 0,
+              max   = 1,
+              step  = 0.01,
+              bigStep = 0.05,
+              isPercent = true,
+              set   = "SetProp",
+              get   = "GetAlpha",
+              disabled = "GetAlphaDisabled",
+              hidden = "GetAlphaDisabled",
+            },
+          },
+        },
+      },
+      plugins = { }
+    },
+    rules = {
+      name   = L["Rule"],
+      order  = 3,
+      type   = "group",
+      args   = {
+        mode = {
+          name   = L["Select this state"],
+          order  = 2,
+          type   = "select",
+          style  = "radio",
+          values = { 
+            default = L["by default"], 
+            any = L["when ANY of these"], 
+            all = L["when ALL of these"], 
+            custom = L["via custom rule"],
+            keybind = L["via keybinding"],
+          },
+          set    = "SetType",
+          get    = "GetType",
+        },
+        clear = {
+          name     = L["Clear All"],
+          order    = 3,
+          type     = "execute",
+          hidden   = "GetClearAllDisabled",
+          disabled = "GetClearAllDisabled",
+          func     = "ClearAllConditions",
+        },
+        inputs = {
+          name     = L["Conditions"],
+          order    = 4,
+          type     = "multiselect",
+          hidden   = "GetConditionsDisabled",
+          disabled = "GetConditionsDisabled",
+          values   = ruleSelect,
+          set      = "SetCondition",
+          get      = "GetCondition",
+        },
+        custom = {
+          name = L["Custom Rule"],
+          order = 5,
+          type = "input",
+          multiline = true,
+          hidden = "GetCustomDisabled",
+          disabled = "GetCustomDisabled",
+          desc = L["Syntax like macro rules: see preset rules for examples"],
+          set  = "SetCustomRule",
+          get  = "GetCustomRule",
+          validate = "ValidateCustomRule",
+        },
+        keybind = {
+          name = L["Keybinding"],
+          order = 6,
+          inline = true,
+          hidden = "GetKeybindDisabled",
+          disabled = "GetKeybindDisabled",
+          type = "group",
+          args = {
+            desc = {
+              name = L["Invoking a state keybind toggles an override of all other transition rules."],
+              order = 1,
+              type = "description",
+            },
+            keybind = {
+              name = L["State Hotkey"],
+              desc = L["Define an override toggle keybind"],
+              order = 2,
+              type = "keybinding",
+              set  = "SetKeybind",
+              get  = "GetKeybind",
+            },
+          },
+        },
+      },
+    },
+  }
+
+  local StateHandler = { }
+  local meta         = { __index = StateHandler }
+
+  function StateHandler:New( bar, opts )
+    local self = setmetatable(
+      { 
+        bar = bar 
+      }, 
+      meta )
+
+    function self:GetName()
+      return opts.name
+    end
+
+    function self:SetName(name)
+      opts.name = name
+    end
+
+    function self:GetOrder()
+      return opts.order
+    end
+
+    -- get reference to states table: even if the bar
+    -- name changes the states table ref won't
+    self.states = tbuild(bar:GetConfig(), "states")
+    self.state  = tbuild(self.states, opts.name)
+
+    opts.order = self:GetRuleField("order")
+    if opts.order == nil then
+      -- add after the highest
+      opts.order = 100
+      for _, state in pairs(self.states) do
+        local x = tonumber(tfetch(state, "rule", "order"))
+        if x and x >= opts.order then
+          opts.order = x + 1
+        end
+      end
+      self:SetRuleField("order",opts.order)
+    end
+
+    return self
+  end
+
+  -- helper methods
+
+  function StateHandler:SetRuleField( key, value, ... )
+    tbuild(self.state, "rule", ...)[key] = value
+  end
+
+  function StateHandler:GetRuleField( ... )
+    return tfetch(self.state, "rule", ...)
+  end
+
+  function StateHandler:FixAll( setkey )
+    -- if multiple selections in the same group are chosen when 'all' is selected,
+    -- keep only one of them. If changing the mode, the first in the fields list will 
+    -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set,
+    -- it will be retained.
+    local notified = false
+    if self:GetRuleField("type") == "all" then
+      for _, c in ipairs(rules) do
+        local rule, fields = unpack(c)
+        local once = false
+        if setkey then
+          for idx, field in ipairs(fields) do
+            if next(field) == setkey then
+              once = true
+            end
+          end
+        end
+        for idx, field in ipairs(fields) do
+          local key = next(field)
+          if self:GetRuleField("values",key) then
+            if once and key ~= setkey then
+              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
+              end
+            end
+            once = true
+          end
+        end
+      end
+    end
+  end
+
+  function StateHandler:GetNeighbors()
+    local before, after
+    for k, v in pairs(self.states) do
+      local o = tonumber(tfetch(v, "rule", "order"))
+      if o and k ~= self:GetName() then
+        local obefore = tfetch(self.states,before,"rule","order")
+        local oafter  = tfetch(self.states,after,"rule","order")
+        if o < self:GetOrder() and (not obefore or obefore < o) then
+          before = k
+        end
+        if o > self:GetOrder() and (not oafter or oafter > o) then
+          after = k
+        end
+      end
+    end
+    return before, after
+  end
+
+  function StateHandler:SwapOrder( a, b )
+    -- do options table
+    local args = optionMap[self.bar].args
+    args[a].order, args[b].order = args[b].order, args[a].order
+    -- do profile
+    a = tbuild(self.states, a, "rule")
+    b = tbuild(self.states, b, "rule")
+    a.order, b.order = b.order, a.order
+  end
+
+  -- handler methods 
+
+  function StateHandler:GetProp( info )
+    -- gets property of the same name as the options arg
+    return GetProperty(self.bar, self:GetName(), info[#info])
+  end
+
+  function StateHandler:SetProp( info, value )
+    -- sets property of the same name as the options arg
+    SetProperty(self.bar, self:GetName(), info[#info], value)
+  end
+
+  function StateHandler:DeleteState()
+    if self.states[self:GetName()] then
+      self.states[self:GetName()] = nil
+      ApplyStates(self.bar)
+    end
+    optionMap[self.bar].args[self:GetName()] = nil
+  end
+
+  function StateHandler:SetStateName(info, value)
+    -- check for existing state name
+    if self.states[value] then
+      ReAction:UserError(format(L["State named '%s' already exists"],value))
+      return
+    end
+    local args = optionMap[self.bar].args
+    local name = self:GetName()
+    self.states[value], args[value], self.states[name], args[name] = self.states[name], args[name], nil, nil
+    self:SetName(value)
+    ApplyStates(self.bar)
+    ReAction:ShowEditor(self.bar, moduleID, value)
+    end
+
+  function StateHandler:MoveStateUp()
+    local before, after = self:GetNeighbors()
+    if before then
+      self:SwapOrder(before, self:GetName())
+      ApplyStates(self.bar)
+    end
+  end
+
+  function StateHandler:MoveStateDown()
+    local before, after = self:GetNeighbors()
+    if after then
+      self:SwapOrder(self:GetName(), after)
+      ApplyStates(self.bar)
+    end
+  end
+
+  function StateHandler:GetAnchorDisabled()
+    return not GetProperty(self.bar, self:GetName(), "anchorEnable")
+  end
+
+  function StateHandler:IsPageDisabled()
+    local n = self.bar:GetConfig().nPages or 1
+    return not (n > 1)
+  end
+
+  function StateHandler:IsPageHidden()
+    return not self.bar:GetConfig().nPages
+  end
+
+  function StateHandler:GetPageValues()
+    if not self._pagevalues then
+      self._pagevalues = { }
+    end
+    local n = self.bar:GetConfig().nPages
+      -- cache the results
+    if self._npages ~= n then
+      self._npages = n
+      wipe(self._pagevalues)
+      for i = 1, n do
+        self._pagevalues["page"..i] = i
+      end
+    end
+    return self._pagevalues
+  end
+
+  function StateHandler:GetPage(info)
+    return self:GetProp(info) or 1
+  end
+
+  function StateHandler:GetAnchorFrames(info)
+    self._anchorframes = self._anchorframes or { }
+    table.wipe(self._anchorframes)
+
+    table.insert(self._anchorframes, "UIParent")
+    for name, bar in ReAction:IterateBars() do
+      table.insert(self._anchorframes, bar:GetFrame():GetName())
+    end
+    return self._anchorframes
+  end
+
+  function StateHandler:GetAnchorFrame(info)
+    local value = self:GetProp(info)
+    for k,v in pairs(self._anchorframes) do
+      if v == value then
+        return k
+      end
+    end
+  end
+
+  function StateHandler:SetAnchorFrame(info, value)
+    local f = _G[self._anchorframes[value]]
+    if f then
+      self.bar:SetFrameRef("anchor-"..self:GetName(), f)
+      self:SetProp(info, f:GetName())
+    end
+  end
+
+  function StateHandler:SetAnchorPointProp(info, value)
+    self:SetProp(info, value ~= "NONE" and value or nil)
+  end
+
+  function StateHandler:GetAnchorPointProp(info)
+    return self:GetProp(info) or "NONE"
+  end
+
+  function StateHandler:GetScale(info)
+    return self:GetProp(info) or 1.0
+  end
+
+  function StateHandler:GetScaleDisabled()
+    return not GetProperty(self.bar, self:GetName(), "enableScale")
+  end
+
+  function StateHandler:GetAlpha(info)
+    return self:GetProp(info) or 1.0
+  end
+
+  function StateHandler:GetAlphaDisabled()
+    return not GetProperty(self.bar, self:GetName(), "enableAlpha")
+  end
+
+  function StateHandler:SetType(info, value)
+    self:SetRuleField("type", value)
+    self:FixAll()
+    ApplyStates(self.bar)
+  end
+
+  function StateHandler:GetType()
+    return self:GetRuleField("type")
+  end
+
+  function StateHandler:GetClearAllDisabled()
+    local t = self:GetRuleField("type")
+    return not( t == "any" or t == "all" or t == "custom")
+  end
+
+  function StateHandler:ClearAllConditions()
+    local t = self:GetRuleField("type")
+    if t == "custom" then
+      self:SetRuleField("custom","")
+    elseif t == "any" or t == "all" then
+      self:SetRuleField("values", {})
+    end
+    ApplyStates(self.bar)
+  end
+
+  function StateHandler:GetConditionsDisabled()
+    local t = self:GetRuleField("type")
+    return not( t == "any" or t == "all")
+  end
+
+  function StateHandler:SetCondition(info, key, value)
+    self:SetRuleField(ruleMap[key], value or nil, "values")
+    if value then
+      self:FixAll(ruleMap[key])
+    end
+    ApplyStates(self.bar)
+  end
+
+  function StateHandler:GetCondition(info, key)
+    return self:GetRuleField("values", ruleMap[key]) or false
+  end
+
+  function StateHandler:GetCustomDisabled()
+    return self:GetRuleField("type") ~= "custom"
+  end
+
+  function StateHandler:SetCustomRule(info, value)
+    self:SetRuleField("custom",value)
+    ApplyStates(self.bar)
+  end
+
+  function StateHandler:GetCustomRule()
+    return self:GetRuleField("custom") or ""
+  end
+
+  function StateHandler:ValidateCustomRule(info, value)
+    local s = value:gsub("%s","") -- remove all spaces
+    -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler
+    repeat
+      if s == "" then
+        return true
+      end
+      local c, r = s:match("(%b[])(.*)")
+      if c == nil and s and #s > 0 then
+        return format(L["Invalid custom rule '%s': each clause must appear within [brackets]"],value or "")
+      end
+      s = r
+    until c == nil
+    return true
+  end
+
+  function StateHandler:GetKeybindDisabled()
+    return self:GetRuleField("type") ~= "keybind"
+  end
+
+  function StateHandler:GetKeybind()
+    return self:GetRuleField("keybind")
+  end
+
+  function StateHandler:SetKeybind(info, value)
+    if value and #value == 0 then
+      value = nil
+    end
+    self:SetRuleField("keybind",value)
+    ApplyStates(self.bar)
+  end
+
+  local function CreateStateOptions(bar, name)
+    local opts = { 
+      type = "group",
+      name = name,
+      childGroups = "tab",
+      args = stateOptions
+    }
+
+    opts.handler = StateHandler:New(bar,opts)
+
+    return opts
+  end
+
+  function Editor:CreateStateOptions(bar)
+    local private = { }
+    local states = tbuild(bar:GetConfig(), "states")
+    local options = {
+      name = L["Dynamic State"],
+      type = "group",
+      order = -1,
+      childGroups = "tree",
+      disabled = InCombatLockdown,
+      args = {
+        __desc__ = {
+          name = L["States are evaluated in the order they are listed"],
+          order = 1,
+          type = "description",
+        },
+        __new__ = {
+          name = L["New State..."],
+          order = 2,
+          type = "group",
+          args = {
+            name = {
+              name = L["State Name"],
+              desc = L["Set a name for the new state"],
+              order = 1,
+              type = "input",
+              get = function() return private.newstatename or "" end,
+              set = function(info,value) private.newstatename = value end,
+              pattern = "^%w*$",
+              usage = L["State names must be alphanumeric without spaces"],
+            },
+            create = {
+              name = L["Create State"],
+              order = 2,
+              type = "execute",
+              func = function ()
+                  local name = private.newstatename
+                  if states[name] then
+                    ReAction:UserError(format(L["State named '%s' already exists"],name))
+                  else
+                    -- TODO: select default state options and pass as final argument
+                    states[name] = { }
+                    optionMap[bar].args[name] = CreateStateOptions(bar,name)
+                    ReAction:ShowEditor(bar, moduleID, name)
+                    private.newstatename = ""
+                  end
+                end,
+              disabled = function()
+                  local name = private.newstatename or ""
+                  return #name == 0 or name:find("%W")
+                end,
+            }
+          }
+        }
+      }
+    }
+    for name, config in pairs(states) do
+      options.args[name] = CreateStateOptions(bar,name)
+    end
+    optionMap[bar] = options
+    return options
+  end
+end
+
 
 ---- Export to ReAction ----
 function ReAction:ShowEditor(bar, ...)
@@ -439,22 +1711,3 @@
   end
 end
 
-function ReAction:RegisterBarOptionGenerator( module, func )
-  if not module or type(module) ~= "table" then -- doesn't need to be a proper module, strictly
-    error("ReAction:RegisterBarOptionGenerator() : Invalid module")
-  end
-  if type(func) == "string" then
-    if not module[func] then
-      error(("ReAction:RegisterBarOptionGenerator() : Invalid method '%s'"):format(func))
-    end
-  elseif func and type(func) ~= "function" then
-    error("ReAction:RegisterBarOptionGenerator() : Invalid function")
-  end
-  self.barOptionGenerators = self.barOptionGenerators or { }
-  self.barOptionGenerators[module] = func
-
-  if self.editor then
-    self.editor:RefreshBarOptions()
-  end
-end
-