Mercurial > wow > reaction
changeset 64:2000f4f4c6af
Redesigned state interface. The only thing missing now is the actual state properties.
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Wed, 28 May 2008 00:20:04 +0000 |
parents | 768be7eb22a0 |
children | 5ea65ec7d162 |
files | locale/enUS.lua modules/ReAction_State/ReAction_State.lua |
diffstat | 2 files changed, 513 insertions(+), 543 deletions(-) [+] |
line wrap: on
line diff
--- a/locale/enUS.lua Thu May 22 22:02:08 2008 +0000 +++ b/locale/enUS.lua Wed May 28 00:20:04 2008 +0000 @@ -101,78 +101,58 @@ "Use the mouse to arrange and resize the bars on screen. Tooltips on bars indicate additional functionality.", -- modules/ReAction_State -"Dynamic State", -"Hide Bar", -"Hide the bar while in this state", -"Select State (%s):", -"Key Binding", -"Delete this State", -"Select Rule Type", -"Delete this Rule", -"Show Rule String", -"Toggles display of the raw rule string", -"Rule String", -"Warrior Stance", +"State named '%s' already exists", "Battle Stance", "Defensive Stance", "Berserker Stance", -"Druid Form", -"Normal", -"Bear", -"Cat", +"Caster Form", +"Bear Form", +"Cat Form", "Tree/Moonkin", -"Caster", "Stealth", +"No Stealth", "Shadowform", -"Pet", +"No Shadowform", +"With Pet", "Without Pet", -"With Pet", -"Target", -"No Target", "Hostile Target", "Friendly Target", -"Default", -"Focus", -"No Focus", +"No Target", "Hostile Focus", "Friendly Focus", -"In Group", +"No Focus", +"Raid", +"Party", "Solo", -"Party", -"Raid", -"Key Press", -"On Key Press", -"Combat", "In Combat", "Out of Combat", -"Custom", -"Rule", -"Syntax like macro conditions: see preset rules for examples", -"States", +"Warning: one or more incompatible rules were turned off", +"Properties", +"Delete this State", +"Name", +"Evaluation Order", +"State transitions are evaluated in the order listed:\nMove a state up or down to change the order", +"Up", +"Down", +"Rules", +"Select this state", +"by default", +"when ANY of these", +"when ALL of these", +"via custom rule", +"Clear All", +"Custom Rule", +"Syntax like macro rules: see preset rules for examples", +"Invalid custom rule '%s': each clause must appear within [brackets]", +"Dynamic State", +"States are evaluated in the order they are listed", "New State...", +"State Name", "Set a name for the new state", -"State Name", +"State names must be alphanumeric without spaces", "Create State", -"Options", -"Transition Rules", -"New Rule...", -"Rule Name", -"Set a name for the new transition rule", -"Create Rule", -"Presets", -"Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults.", -"Select Preset", -"Load", -"Delete", -"Save As...", "State named '%s' already exists", -"Rule named '%s' already exists", -"State names must be alphanumeric without spaces", -"Rule names must be alphanumeric without spaces", -"Invalid custom rule '%s': Rule cannot end with ';'", -"Invalid custom rule '%s': Expressions must be separated by ';'", -"Invalid custom rule '%s': Each expression must have a state", -"Invalid custom rule '%s': '%s' is not a state", + }) do L[string] = true
--- a/modules/ReAction_State/ReAction_State.lua Thu May 22 22:02:08 2008 +0000 +++ b/modules/ReAction_State/ReAction_State.lua Wed May 28 00:20:04 2008 +0000 @@ -20,49 +20,43 @@ { profile = { bars = { }, - presets = { } } } ) - self.states = { } - self.options = setmetatable({},{__mode="k"}) + + ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") + + ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") + ReAction.RegisterCallback(self, "OnRefreshBar") + ReAction.RegisterCallback(self, "OnEraseBar") + ReAction.RegisterCallback(self, "OnRenameBar") + ReAction.RegisterCallback(self, "OnConfigModeChanged") end - - --- ReAction module interface -function module:ApplyToBar(bar) - self:RefreshBar(bar) -end - -function module:RefreshBar(bar) - local c = self.db.profile.bars[bar:GetName()] +function module:OnRefreshBar(event, bar, name) + local c = self.db.profile.bars[name] if c then - --self:BuildStates(bar) - --self:BuildRules(bar) + self:UpdateStates(bar) end end -function module:RemoveFromBar(bar) +function module:OnEraseBar(event, bar, name) + self.db.profile.bars[name] = nil end -function module:EraseBarConfig(barName) - self.db.profile.bars[barName] = nil -end - -function module:RenameBarConfig(oldname, newname) +function module:OnRenameBar(event, bar, oldname, newname) local b = self.db.profile.bars bars[newname], bars[oldname] = bars[oldname], nil end -function module:ApplyConfigMode(mode,bars) - -- swap out hidestates +function module:OnConfigModeChanged(event, mode) + -- TODO: unregister all state drivers (temporarily) and hidestates end --- Private -- +-- Utility -- -- traverse a table tree by key list and fetch the result or first nil local function tfetch(t, ...) @@ -82,9 +76,158 @@ return t end +-- PRIVATE -- +local BuildRuleString, ApplyStates +do + local forms = { } + for i = 1, GetNumShapeshiftForms() do + local icon, name = GetShapeshiftFormInfo(i) + forms[name] = i; + end + -- TODO: need to find out if form name is localized, it probably is + local dStance = forms["Defensive Stance"] or 2 + local zStance = forms["Berserker Stance"] or 3 + local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 + local cForm = forms["Cat Form"] or 3 + local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 + local aForm = forms["Aquatic Form"] or 2 + local trForm = forms["Travel Form"] or 4 + local fForm = forms["Flight Form"] or forms["Swift Flight Form"] or 6 -local rules -local BuildRuleString + -- TODO: do the macro conditional strings need to be localized? + -- they're not contained in GlobalStrings.lua, but the parsing is done + -- via the C function SecureCmdOptionParse(), so it's not clear + local ruleformats = { + battle = "stance:1", + defensive = ("stance:%d"):format(dStance), + berserker = ("stance:%d"):format(zStance), + caster = ("form:0/%d/%d/%d"):format(aForm, trForm, fForm), + bear = ("form:%d"):format(bForm), + cat = ("form:%d"):format(cForm), + treeOrMoonkin = ("form:%d"):format(tForm), + stealth = "stealth", + nostealth = "nostealth", + shadowform = "form:1", + noshadowform = "noform", + pet = "pet", + nopet = "nopet", + harm = "target=target,harm", + help = "target=target,help", + notarget = "target=target,noexists", + focusharm = "target=focus,harm", + focushelp = "target=focus,help", + nofocus = "target=focus,noexists", + raid = "group:raid", + party = "group:party", + solo = "nogroup", + combat = "combat", + nocombat = "nocombat", + } + + function BuildRuleString(states) + local s = "" + local default + local sorted = { } + for name in pairs(states) do + table.insert(sorted,name) + end + table.sort(sorted, function(lhs, rhs) + local olhs = tfetch(states[lhs],"rule","order") or 0 + local orhs = tfetch(states[rhs],"rule","order") or 0 + return olhs < orhs + end) + for idx, name in ipairs(sorted) do + local state = states[name] + local semi = #s > 0 and "; " or "" + local mode = tfetch(state,"rule","type") + if mode == "default" then + default = name + elseif mode == "custom" then + if state.rule.custom then + -- strip out all spaces from the custom rule + s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name) + end + elseif mode == "any" then + if state.rule.values then + local clause = "" + for key, value in pairs(state.rule.values) do + clause = ("%s[%s]"):format(clause,ruleformats[key]) + end + if #clause > 0 then + s = ("%s%s%s %s"):format(s, semi, clause, name) + end + end + elseif mode == "all" then + if state.rule.values then + local clause = "" + for key, value in pairs(state.rule.values) do + clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key]) + end + if #clause > 0 then + s = ("%s%s[%s] %s"):format(s, semi, clause, name) + end + end + end + end + if default then + s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default) + end + return s + end + + local drivers = setmetatable({},{__mode="k"}) + + function ApplyStates( bar ) + local states = tfetch(module.db.profile.bars, bar:GetName(), "states") + if states then + local frame = bar:GetFrame() + local string = BuildRuleString(states) + ReAction:Print("'"..string.."'") + if string and #string > 0 then + -- register a handler to set the value of attribute "state-reaction" + -- in response to events as per the rule string + RegisterStateDriver(frame, "reaction", string) + drivers[bar] = true + -- register a trivial map for each "statemap-reaction-XXX" to set 'state' to 'XXX' + for state in pairs(states) do + frame:SetAttribute(("statemap-reaction-%s"):format(state), state) + end + elseif drivers[bar] then + UnregisterStateDriver(frame, "reaction") + drivers[bar] = nil + end + end + end +end + + + +-- API -- + +function module:UpdateStates( bar ) + ApplyStates(bar) +end + +function module:CreateState( bar, name ) + local states = tbuild(self.db.profile.bars, bar:GetName(), "states") + if states[name] then + ReAction:UserError(L["State named '%s' already exists"]:format(name)) + else + states[name] = { } + end +end + +function module:DeleteState( bar, name ) + local states = tfetch(self.db.profile.bars, bar:GetName(), "states") + if states[name] then + states[name] = nil + ApplyStates(bar) + end +end + +-- Options -- + +local CreateBarOptions do local function ClassCheck(...) for i = 1, select('#',...) do @@ -96,516 +239,359 @@ return true end - -- The structure of this table is important: each row is designed to be unpack()ed - -- into variables as defined in the header comment row. - -- The 'fields' subtable (index 4) structure is also important: its subtables - -- are ordered to match appearances of '%s' (string-substitutions) in the corresponding - -- rules[] format-string entry. Each subtable's single element's key is the storage key used - -- in the user db, and the name of the group-element table in the config tree, so it too - -- is important. - -- - -- While this allows for very compact code and data storage, the scheme is admittedly obtuse, - -- so I document it here for my own sanity. - -- - rules = { - -- rule name hidden fields - { "stance", L["Warrior Stance"], ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, - { "form", L["Druid Form"], ClassCheck("DRUID"), { {bear = L["Bear"]}, {cat = L["Cat"]}, {treeOrMoonkin = L["Tree/Moonkin"]}, {caster = L["Normal"]}, } }, - { "stealth", L["Stealth"], ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {normal = L["Normal"]} } }, - { "shadow", L["Shadowform"], ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {normal = L["Normal"]} } }, - { "pet", L["Pet"], ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, - { "target", L["Target"], false, { {hostile = L["Hostile Target"]}, {friendly = L["Friendly Target"]}, {none = L["No Target"]} } }, - { "focus", L["Focus"], false, { {hostile = L["Hostile Focus"]}, {friendly = L["Friendly Focus"]}, {none = L["No Focus"]} } }, - { "group", L["In Group"], false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, - { "key", L["Key Press"], false, { {state = L["On Key Press"]}, {keybinding = false} } }, -- keybinding has no state-selector, it's implemented elsewhere - { "combat", L["Combat"], false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, - { "custom", L["Custom"], false, { {rule = false} } }, -- custom has no state-selector, it's implemented elsewhere + -- pre-sorted by the order they should appear in + local rules = { + -- rule hidden fields + { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } }, + { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } }, + { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } }, + { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } }, + { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } }, + { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } }, + { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } }, + { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } }, + { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } }, } - do - local forms = { } - for i = 1, GetNumShapeshiftForms() do - local icon, name = GetShapeshiftFormInfo(i) - -- TODO: need to find out if name is localized, it probably is - forms[name] = i; - end - local dStance = forms["Defensive Stance"] or 2 - local zStance = forms["Berserker Stance"] or 3 - local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 - local cForm = forms["Cat Form"] or 3 - local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 + local ruleSelect = { } + local ruleMap = { } + local optionMap = setmetatable({},{__mode="k"}) - -- TODO: do the macro conditional strings need to be localized? - local ruleformats = { - stance = ("[stance:1] %%s; [stance:%d] %%s; [stance:%d] %%s"):format(dStance,zStance), - form = ("[form:%d] %%s; [form:%d] %%s; [form:%d] %%s; %%s"):format(bForm,cForm,tForm), - stealth = "[stealth] %s; %s", - shadow = "[stance:1] %s; %s", - pet = "[pet] %s; %s", - target = "[harm] %s; [help] %s; %s", - focus = "[target=focus,harm] %s; [target=focus,help] %s; %s", - group = "[group:raid] %s; [group] %s; %s", - combat = "[combat] %s; %s", - custom = "%s", - } - - local fieldmap = { } - for i = 1, #rules do - fieldmap[rules[i][1]] = rules[i][4] - end - - local _scratch = { } - function BuildRuleString(bar, name) - local rule, data = module:GetRule(bar,name) - if not rule then return "" end - local fields = fieldmap[rule] - for i = 1, #fields do - _scratch[i] = data[next(fields[i])] -- TODO: insert default state here + -- unpack rules table into ruleSelect and ruleMap + for _, c in ipairs(rules) do + local rule, hidden, fields = unpack(c) + if not hidden then + for _, field in ipairs(fields) do + local key, label = next(field) + table.insert(ruleSelect, label) + table.insert(ruleMap, key) end - for i = #fields+1, #_scratch do - _scratch[i] = nil - end - local success, value = pcall(string.format, ruleformats[rule], unpack(_scratch)) - return success and value or "<error>" end end - -end - - - --- API -- - -function module:BuildStates( bar ) - local c = tfetch(self.db.profile.bars, bar:GetName(), "states") - if c then - for name, s in pairs(c) do - -- TODO: new state here - end - end -end - -function module:CreateState( bar, name ) - local c = tbuild(self.db.profile.bars, bar:GetName(), "states") - if c[name] then - ReAction:UserError(L["State named '%s' already exists"]:format(name)) - else - c[name] = { } - -- TODO: new state here - end -end - -function module:DeleteState( bar, name ) - local c = tfetch(self.db.profile.bars, bar:GetName(), "states") - if c[name] then - -- TODO: delete state - c[name] = nil - end -end - -function module:BuildRules( bar ) - for bar, c in pairs(self.db.profile.bars) do - if c.rules then - for name, t in pairs(c.rules) do - local rule, config = next(t) - self:SetRule(bar, name, rule, config) - end - end - end -end - -function module:CreateRule( bar, name, rule, config ) - local c = tbuild(self.db.profile.bars, bar:GetName(), "rules") - if c[name] then - ReAction:UserError(L["Rule named '%s' already exists"]:format(name)) - else - tbuild(self.db.profile.bars, bar:GetName(), "rules", name) - if rule then - self:SetRule(bar,name,rule,config) - end - end -end - -function module:DeleteRule( bar, name ) - local c = tfetch(self.db.profile.bars, bar:GetName(), "rules") - if c[name] then - local f = bar:GetFrame() - -- TODO: delete rule - c[name] = nil - end -end - -function module:UpdateRule( bar, name ) - local rule, c = self:GetRule(bar,name) - -- TODO: remove all relevant outdated attributes - -- TODO: set new attributes -end - -function module:GetRule(bar, name) - local c = tfetch(self.db.profile.bars, bar:GetName(), "rules", name) - if c then - return next(c) -- returns key, value (= rulename, configtable) - end -end - -function module:SetRule(bar, name, rule, config) - tbuild(self.db.profile.bars, bar:GetName(), "rules")[name] = { [rule] = (config or {}) } - self:UpdateRule(bar,name) -end - - --- options -- -local CreateBarOptions -do - local function GetRuleConfig(bar, name, rule, field) - return tfetch(module.db.profile.bars, bar:GetName(), "rules", name, rule, field) - end - - local function SetRuleConfig( bar, name, rule, field, value ) - tbuild(module.db.profile.bars, bar:GetName(), "rules", name, rule)[field] = value - end - local function CreateStateOptions(bar, name) - return { - type = "group", - name = name, - args = { - -- show/hide would go here - -- page # would go here - -- anchoring would go here - __delete__ = { - type = "execute", - name = L["Delete this State"], - func = function(info) - module:DeleteState(bar,name) - module.options[bar].args.states.args[name] = nil - end, - order = -1 - }, - } - } - end - - - -- display rule string setting is shared between all rule opts and is transient - -- (mostly used for debugging) - local display = { show = false } - - local function CreateRuleOptions(bar, name) - local function get(info) - local rule = info[#info-1] - local field = info[#info] - return GetRuleConfig(bar,name,rule,field) or "" - end - - local function set(info, value) - local rule = info[#info-1] - local field = info[#info] - SetRuleConfig(bar,name,rule,field,value) - module:UpdateRule(bar,name) - end - local opts = { type = "group", name = name, - childGroups = "inline", - args = { - __select__ = { - type = "select", - name = L["Select Rule Type"], - get = function(info) return module:GetRule(bar,name) or "" end, - set = function(info,value) module:SetRule(bar,name,value) end, -- TODO: get default rule config and pass as final value - values = function() - local v = { } - for i = 1, #rules do - local rule, name, hidden = unpack(rules[i]) - if not hidden then - v[rule] = name - end - end - return v - end, - order = 1, - }, - __delete__ = { - type = "execute", - name = L["Delete this Rule"], - func = function(info) - module:DeleteRule(bar,name) - module.options[bar].args.rules.args[name] = nil - end, - order = -3 - }, - -- - -- rule selection groups will be inserted here - -- - __show__ = { - type = "toggle", - name = L["Show Rule String"], - desc = L["Toggles display of the raw rule string"], - get = function() return display.show end, - set = function(info,value) display.show = value end, - order = -2 - }, - __rule__ = { - type = "input", - name = L["Rule String"], - disabled = true, - multiline = true, - width = "double", - hidden = function() return not display.show end, - get = function() return BuildRuleString(bar,name) end, - set = function() end, - order = -1 - } - } + childGroups = "tab", } - -- unpack rules table - for i = 1, #rules do - local rule, label, _, fields = unpack(rules[i]) - local hidden = function() - return module:GetRule(bar,name) ~= rule - end - opts.args[rule] = { - type = "group", - name = label, - hidden = hidden, - disabled = hidden, - inline = true, - args = { } - } - for j = 1, #fields do - local field, label = next(fields[j]) -- extract from table of single key-value pair - if field and label then - opts.args[rule].args[field] = { - type = "select", - name = L["Select State (%s):"]:format(label), - values = function () - local states = tfetch(module.db.profile.bars,bar:GetName(),"states") - local v = { } - if states then - for k in pairs(states) do - v[k] = k - end - end - return v - end, - set = set, - get = get, - order = 100 + j - } + local states = tbuild(module.db.profile.bars, bar:GetName(), "states") + + local function put( key, value, ... ) + tbuild(states, opts.name, "rule", ...)[key] = value + end + + local function fetch( ... ) + return tfetch(states, opts.name, "rule", ...) + end + + local function 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 + for _, c in ipairs(rules) do + local rule, hidden, fields = unpack(c) + local found = false + for key in ipairs(fields) do + if fetch("values",key) then + if (found or setkey) and key ~= setkey then + put(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 + found = true + end end end end - -- set up special entry for keybinding - opts.args.key.args.binding = { - type = "keybinding", - name = L["Key Binding"], - get = get, - set = set, - order = -1 + local function getNeighbors() + local before, after + for k, v in pairs(states) do + local o = tonumber(tfetch(v, "rule", "order")) + if o and k ~= opts.name then + local obefore = tfetch(states,before,"rule","order") + local oafter = tfetch(states,after,"rule","order") + if o < opts.order and (not obefore or obefore < o) then + before = k + end + if o > opts.order and (not oafter or oafter > o) then + after = k + end + end + end + return before, after + end + + local function swapOrder( a, b ) + -- do options table + local args = optionMap[bar].args + args[a].order, args[b].order = args[b].order, args[a].order + -- do profile + a = tbuild(states, a, "rule") + b = tbuild(states, b, "rule") + a.order, b.order = b.order, a.order + end + + local function update() + module:UpdateStates(bar) + end + + + opts.order = fetch("order") + if opts.order == nil then + -- add after the highest + opts.order = 100 + for _, state in pairs(states) do + local x = tonumber(tfetch(state, "rule", "order")) + if x and x >= opts.order then + opts.order = x + 1 + end + end + put("order",opts.order) + end + + opts.args = { + properties = { + type = "group", + name = L["Properties"], + order = 1, + args = { + delete = { + type = "execute", + name = L["Delete this State"], + func = function(info) + module:DeleteState(bar,opts.name) + optionMap[bar].args[opts.name] = nil + end, + order = -1 + }, + rename = { + type = "input", + name = L["Name"], + order = 1, + get = function() return opts.name end, + set = function(info, value) + -- check for existing state name + if states[value] then + L["State named '%s' already exists"]:format(value) + end + local args = optionMap[bar].args + states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil + opts.name = value + update() + end, + pattern = "^%w*$", + usage = L["State names must be alphanumeric without spaces"], + }, + ordering = { + type = "group", + inline = true, + 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, + args = { + up = { + type = "execute", + name = L["Up"], + width = "half", + order = 1, + func = function() + local before, after = getNeighbors() + if before then + swapOrder(before, opts.name) + update() + end + end, + }, + down = { + type = "execute", + name = L["Down"], + width = "half", + order = 2, + func = function() + local before, after = getNeighbors() + if after then + ReAction:Print(opts.name, after) + swapOrder(opts.name, after) + update() + end + end, + } + } + }, + -- keybinding for show-this-state would go here + -- show/hide would go here + -- page # would go here + -- anchoring would go here + } + }, + rules = { + type = "group", + name = L["Rules"], + order = 2, + args = { + mode = { + type = "select", + style = "radio", + name = L["Select this state"], + values = { + default = L["by default"], + any = L["when ANY of these"], + all = L["when ALL of these"], + custom = L["via custom rule"] + }, + set = function( info, value ) + put("type", value) + fixall() + update() + end, + get = function( info ) + return fetch("type") + end, + order = 2 + }, + clear = { + type = "execute", + name = L["Clear All"], + func = function() + local type = fetch("type") + if type == "custom" then + put("custom","") + elseif type == "any" or type == "all" then + put("values", {}) + end + update() + end, + order = 3 + }, + inputs = { + type = "multiselect", + name = L["Rules"], + hidden = function() + return fetch("type") == "custom" + end, + disabled = function() + return fetch("type") == "default" + end, + values = ruleSelect, + set = function(info, key, value ) + put(ruleMap[key], value or nil, "values") + if value then + fixall(ruleMap[key]) + end + update() + end, + get = function(info, key) + return fetch("values", ruleMap[key]) or false + end, + order = 4 + }, + custom = { + type = "input", + multiline = true, + hidden = function() + return fetch("type") ~= "custom" + end, + disabled = function() + return fetch("type") == "default" + end, + name = L["Custom Rule"], + desc = L["Syntax like macro rules: see preset rules for examples"], + set = function(info, value) + put("custom",value) + update() + end, + get = function(info) + return fetch("custom") or "" + end, + validate = function (info, rule) + local s = rule: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 L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule) + end + s = r + until c == nil + return true + end, + order = 5, + } + } + } } - - -- set up special entry for custom - opts.args.custom.args.rule = { - type = "input", - name = L["Rule"], - desc = L["Syntax like macro conditions: see preset rules for examples"], - get = get, - set = set, - validate = function (info, rule) - local s = rule:gsub("%s","") -- remove all spaces - if s:match(";$") then -- can't end with semicolon - return L["Invalid custom rule '%s': Rule cannot end with ';'"]:format(rule) - end - if s == "" then - return true - end - -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler - repeat - repeat - local c, r = s:match("(%b[])(.*)") - if r then s = r end - until c == nil - local state, s = s:match("(%w+)(.*)") - if not state then - return L["Invalid custom rule '%s': Each expression must have a state"]:format(rule) - end - if not tfetch(module.db.profile.bars,bar:GetName(),"states",state) then - return L["Invalid custom rule '%s': '%s' is not a state"]:format(rule,state) - end - if s:match("^[^;]") then - return L["Invalid custom rule '%s': Expressions must be separated by ';'"]:format(rule) - end - until #s == 0 - return true - end, - multiline = true, - order = 1, - } - return opts end + CreateBarOptions = function(bar) local private = { } local options = { type = "group", name = L["Dynamic State"], - childGroups = "tab", + childGroups = "tree", disabled = InCombatLockdown, args = { - states = { + __desc__ = { + type = "description", + name = L["States are evaluated in the order they are listed"], + order = 1 + }, + __new__ = { type = "group", - name = L["States"], - childGroups = "tree", - order = 1, + name = L["New State..."], + order = 2, args = { - __new__ = { - type = "group", - name = L["New State..."], - order = 1, - args = { - name = { - type = "input", - name = L["State Name"], - desc = L["Set a name for the new state"], - 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"], - order = 1 - }, - create = { - type = "execute", - name = L["Create State"], - func = function () - local name = private.newstatename - module:CreateState(bar,name) -- TODO: select default state options and pass as final argument - module.options[bar].args.states.args[name] = CreateStateOptions(bar,name) - private.newstatename = "" - end, - disabled = function() - local name = private.newstatename or "" - return #name == 0 or name:find("%W") - end, - order = 2, - } - } + name = { + type = "input", + name = L["State Name"], + desc = L["Set a name for the new state"], + 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"], + order = 1 + }, + create = { + type = "execute", + name = L["Create State"], + func = function () + local name = private.newstatename + module:CreateState(bar,name) -- TODO: select default state options and pass as final argument + optionMap[bar].args[name] = CreateStateOptions(bar,name) + private.newstatename = "" + end, + disabled = function() + local name = private.newstatename or "" + return #name == 0 or name:find("%W") + end, + order = 2, } } - }, - rules = { - type = "group", - name = L["Transition Rules"], - childGroups = "tree", - order = 2, - args = { - __new__ = { - type = "group", - name = L["New Rule..."], - order = 1, - args = { - name = { - type = "input", - name = L["Rule Name"], - desc = L["Set a name for the new transition rule"], - get = function() return private.newtransname or "" end, - set = function(info,value) private.newtransname = value end, - pattern = "^%w*$", - usage = L["Rule names must be alphanumeric without spaces"], - order = 1 - }, - create = { - type = "execute", - name = L["Create Rule"], - func = function () - local name = private.newtransname - module:CreateRule(bar,name) -- TODO: select default rule and add as final argument - module.options[bar].args.rules.args[name] = CreateRuleOptions(bar,name) - private.newtransname = "" - end, - disabled = function () - local name = private.newtransname or "" - return #name == 0 or name:find("%W") - end, - order = 2, - }, - } - } - } - }, - presets = { - type = "group", - name = L["Presets"], - order = 3, - args = { - desc = { - type = "description", - name = L["Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults."], - order = 1, - }, - select = { - type = "select", - name = L["Select Preset"], - set = function(info,value) end, - get = function() return "" end, - values = function() return { } end, - order = 2, - }, - load = { - type = "execute", - name = L["Load"], - func = function() end, - width = "half", - order = 3, - }, - delete = { - type = "execute", - name = L["Delete"], - disabled = function() return false end, - func = function() end, - width = "half", - order = 4, - }, - hdr = { - type = "header", - name = " ", - order = 5, - }, - save = { - type = "input", - name = L["Save As..."], - get = function() return "" end, - set = function(info,name) end, - order = 6, - }, - }, - }, + } } } local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then for name, config in pairs(states) do - options.args.states.args[name] = CreateStateOptions(bar,name) + options.args[name] = CreateStateOptions(bar,name) end end - local rules = tfetch(module.db.profile.bars, bar:GetName(), "rules") - if rules then - for name, config in pairs(rules) do - options.args.rules.args[name] = CreateRuleOptions(bar,name) - end - end + optionMap[bar] = options return options end end function module:GetBarOptions(bar) - if not self.options[bar] then - self.options[bar] = CreateBarOptions(bar) - end - return self.options[bar] + return CreateBarOptions(bar) end