comparison modules/ReAction_State/ReAction_State.lua @ 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 f9cdb920470a
children 5ea65ec7d162
comparison
equal deleted inserted replaced
63:768be7eb22a0 64:2000f4f4c6af
18 function module:OnInitialize() 18 function module:OnInitialize()
19 self.db = ReAction.db:RegisterNamespace( moduleID, 19 self.db = ReAction.db:RegisterNamespace( moduleID,
20 { 20 {
21 profile = { 21 profile = {
22 bars = { }, 22 bars = { },
23 presets = { }
24 } 23 }
25 } 24 }
26 ) 25 )
27 self.states = { } 26
28 self.options = setmetatable({},{__mode="k"}) 27 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")
29 end 28
30 29 ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar")
31 30 ReAction.RegisterCallback(self, "OnRefreshBar")
32 31 ReAction.RegisterCallback(self, "OnEraseBar")
33 -- ReAction module interface 32 ReAction.RegisterCallback(self, "OnRenameBar")
34 function module:ApplyToBar(bar) 33 ReAction.RegisterCallback(self, "OnConfigModeChanged")
35 self:RefreshBar(bar) 34 end
36 end 35
37 36 function module:OnRefreshBar(event, bar, name)
38 function module:RefreshBar(bar) 37 local c = self.db.profile.bars[name]
39 local c = self.db.profile.bars[bar:GetName()]
40 if c then 38 if c then
41 --self:BuildStates(bar) 39 self:UpdateStates(bar)
42 --self:BuildRules(bar) 40 end
43 end 41 end
44 end 42
45 43 function module:OnEraseBar(event, bar, name)
46 function module:RemoveFromBar(bar) 44 self.db.profile.bars[name] = nil
47 end 45 end
48 46
49 function module:EraseBarConfig(barName) 47 function module:OnRenameBar(event, bar, oldname, newname)
50 self.db.profile.bars[barName] = nil
51 end
52
53 function module:RenameBarConfig(oldname, newname)
54 local b = self.db.profile.bars 48 local b = self.db.profile.bars
55 bars[newname], bars[oldname] = bars[oldname], nil 49 bars[newname], bars[oldname] = bars[oldname], nil
56 end 50 end
57 51
58 function module:ApplyConfigMode(mode,bars) 52 function module:OnConfigModeChanged(event, mode)
59 -- swap out hidestates 53 -- TODO: unregister all state drivers (temporarily) and hidestates
60 end 54 end
61 55
62 56
63 57
64 58
65 -- Private -- 59 -- Utility --
66 60
67 -- traverse a table tree by key list and fetch the result or first nil 61 -- traverse a table tree by key list and fetch the result or first nil
68 local function tfetch(t, ...) 62 local function tfetch(t, ...)
69 for i = 1, select('#', ...) do 63 for i = 1, select('#', ...) do
70 t = t and t[select(i, ...)] 64 t = t and t[select(i, ...)]
80 t = t[key] 74 t = t[key]
81 end 75 end
82 return t 76 return t
83 end 77 end
84 78
85 79 -- PRIVATE --
86 local rules 80 local BuildRuleString, ApplyStates
87 local BuildRuleString 81 do
82 local forms = { }
83 for i = 1, GetNumShapeshiftForms() do
84 local icon, name = GetShapeshiftFormInfo(i)
85 forms[name] = i;
86 end
87 -- TODO: need to find out if form name is localized, it probably is
88 local dStance = forms["Defensive Stance"] or 2
89 local zStance = forms["Berserker Stance"] or 3
90 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1
91 local cForm = forms["Cat Form"] or 3
92 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5
93 local aForm = forms["Aquatic Form"] or 2
94 local trForm = forms["Travel Form"] or 4
95 local fForm = forms["Flight Form"] or forms["Swift Flight Form"] or 6
96
97 -- TODO: do the macro conditional strings need to be localized?
98 -- they're not contained in GlobalStrings.lua, but the parsing is done
99 -- via the C function SecureCmdOptionParse(), so it's not clear
100 local ruleformats = {
101 battle = "stance:1",
102 defensive = ("stance:%d"):format(dStance),
103 berserker = ("stance:%d"):format(zStance),
104 caster = ("form:0/%d/%d/%d"):format(aForm, trForm, fForm),
105 bear = ("form:%d"):format(bForm),
106 cat = ("form:%d"):format(cForm),
107 treeOrMoonkin = ("form:%d"):format(tForm),
108 stealth = "stealth",
109 nostealth = "nostealth",
110 shadowform = "form:1",
111 noshadowform = "noform",
112 pet = "pet",
113 nopet = "nopet",
114 harm = "target=target,harm",
115 help = "target=target,help",
116 notarget = "target=target,noexists",
117 focusharm = "target=focus,harm",
118 focushelp = "target=focus,help",
119 nofocus = "target=focus,noexists",
120 raid = "group:raid",
121 party = "group:party",
122 solo = "nogroup",
123 combat = "combat",
124 nocombat = "nocombat",
125 }
126
127 function BuildRuleString(states)
128 local s = ""
129 local default
130 local sorted = { }
131 for name in pairs(states) do
132 table.insert(sorted,name)
133 end
134 table.sort(sorted, function(lhs, rhs)
135 local olhs = tfetch(states[lhs],"rule","order") or 0
136 local orhs = tfetch(states[rhs],"rule","order") or 0
137 return olhs < orhs
138 end)
139 for idx, name in ipairs(sorted) do
140 local state = states[name]
141 local semi = #s > 0 and "; " or ""
142 local mode = tfetch(state,"rule","type")
143 if mode == "default" then
144 default = name
145 elseif mode == "custom" then
146 if state.rule.custom then
147 -- strip out all spaces from the custom rule
148 s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name)
149 end
150 elseif mode == "any" then
151 if state.rule.values then
152 local clause = ""
153 for key, value in pairs(state.rule.values) do
154 clause = ("%s[%s]"):format(clause,ruleformats[key])
155 end
156 if #clause > 0 then
157 s = ("%s%s%s %s"):format(s, semi, clause, name)
158 end
159 end
160 elseif mode == "all" then
161 if state.rule.values then
162 local clause = ""
163 for key, value in pairs(state.rule.values) do
164 clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key])
165 end
166 if #clause > 0 then
167 s = ("%s%s[%s] %s"):format(s, semi, clause, name)
168 end
169 end
170 end
171 end
172 if default then
173 s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default)
174 end
175 return s
176 end
177
178 local drivers = setmetatable({},{__mode="k"})
179
180 function ApplyStates( bar )
181 local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
182 if states then
183 local frame = bar:GetFrame()
184 local string = BuildRuleString(states)
185 ReAction:Print("'"..string.."'")
186 if string and #string > 0 then
187 -- register a handler to set the value of attribute "state-reaction"
188 -- in response to events as per the rule string
189 RegisterStateDriver(frame, "reaction", string)
190 drivers[bar] = true
191 -- register a trivial map for each "statemap-reaction-XXX" to set 'state' to 'XXX'
192 for state in pairs(states) do
193 frame:SetAttribute(("statemap-reaction-%s"):format(state), state)
194 end
195 elseif drivers[bar] then
196 UnregisterStateDriver(frame, "reaction")
197 drivers[bar] = nil
198 end
199 end
200 end
201 end
202
203
204
205 -- API --
206
207 function module:UpdateStates( bar )
208 ApplyStates(bar)
209 end
210
211 function module:CreateState( bar, name )
212 local states = tbuild(self.db.profile.bars, bar:GetName(), "states")
213 if states[name] then
214 ReAction:UserError(L["State named '%s' already exists"]:format(name))
215 else
216 states[name] = { }
217 end
218 end
219
220 function module:DeleteState( bar, name )
221 local states = tfetch(self.db.profile.bars, bar:GetName(), "states")
222 if states[name] then
223 states[name] = nil
224 ApplyStates(bar)
225 end
226 end
227
228 -- Options --
229
230 local CreateBarOptions
88 do 231 do
89 local function ClassCheck(...) 232 local function ClassCheck(...)
90 for i = 1, select('#',...) do 233 for i = 1, select('#',...) do
91 local _, c = UnitClass("player") 234 local _, c = UnitClass("player")
92 if c == select(i,...) then 235 if c == select(i,...) then
94 end 237 end
95 end 238 end
96 return true 239 return true
97 end 240 end
98 241
99 -- The structure of this table is important: each row is designed to be unpack()ed 242 -- pre-sorted by the order they should appear in
100 -- into variables as defined in the header comment row. 243 local rules = {
101 -- The 'fields' subtable (index 4) structure is also important: its subtables 244 -- rule hidden fields
102 -- are ordered to match appearances of '%s' (string-substitutions) in the corresponding 245 { "stance", ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } },
103 -- rules[] format-string entry. Each subtable's single element's key is the storage key used 246 { "form", ClassCheck("DRUID"), { {caster = L["Caster Form"]}, {bear = L["Bear Form"]}, {cat = L["Cat Form"]}, {treeOrMoonkin = L["Tree/Moonkin"]} } },
104 -- in the user db, and the name of the group-element table in the config tree, so it too 247 { "stealth", ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {nostealth = L["No Stealth"]} } },
105 -- is important. 248 { "shadow", ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {noshadowform = L["No Shadowform"]} } },
106 -- 249 { "pet", ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } },
107 -- While this allows for very compact code and data storage, the scheme is admittedly obtuse, 250 { "target", false, { {harm = L["Hostile Target"]}, {help = L["Friendly Target"]}, {notarget = L["No Target"]} } },
108 -- so I document it here for my own sanity. 251 { "focus", false, { {focusharm = L["Hostile Focus"]}, {focushelp = L["Friendly Focus"]}, {nofocus = L["No Focus"]} } },
109 -- 252 { "group", false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } },
110 rules = { 253 { "combat", false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } },
111 -- rule name hidden fields
112 { "stance", L["Warrior Stance"], ClassCheck("WARRIOR"), { {battle = L["Battle Stance"]}, {defensive = L["Defensive Stance"]}, {berserker = L["Berserker Stance"]} } },
113 { "form", L["Druid Form"], ClassCheck("DRUID"), { {bear = L["Bear"]}, {cat = L["Cat"]}, {treeOrMoonkin = L["Tree/Moonkin"]}, {caster = L["Normal"]}, } },
114 { "stealth", L["Stealth"], ClassCheck("ROGUE","DRUID"), { {stealth = L["Stealth"]}, {normal = L["Normal"]} } },
115 { "shadow", L["Shadowform"], ClassCheck("PRIEST"), { {shadowform = L["Shadowform"]}, {normal = L["Normal"]} } },
116 { "pet", L["Pet"], ClassCheck("HUNTER","WARLOCK"), { {pet = L["With Pet"]}, {nopet = L["Without Pet"]} } },
117 { "target", L["Target"], false, { {hostile = L["Hostile Target"]}, {friendly = L["Friendly Target"]}, {none = L["No Target"]} } },
118 { "focus", L["Focus"], false, { {hostile = L["Hostile Focus"]}, {friendly = L["Friendly Focus"]}, {none = L["No Focus"]} } },
119 { "group", L["In Group"], false, { {raid = L["Raid"]}, {party = L["Party"]}, {solo = L["Solo"]} } },
120 { "key", L["Key Press"], false, { {state = L["On Key Press"]}, {keybinding = false} } }, -- keybinding has no state-selector, it's implemented elsewhere
121 { "combat", L["Combat"], false, { {combat = L["In Combat"]}, {nocombat = L["Out of Combat"]} } },
122 { "custom", L["Custom"], false, { {rule = false} } }, -- custom has no state-selector, it's implemented elsewhere
123 } 254 }
124 255
125 do 256 local ruleSelect = { }
126 local forms = { } 257 local ruleMap = { }
127 for i = 1, GetNumShapeshiftForms() do 258 local optionMap = setmetatable({},{__mode="k"})
128 local icon, name = GetShapeshiftFormInfo(i) 259
129 -- TODO: need to find out if name is localized, it probably is 260 -- unpack rules table into ruleSelect and ruleMap
130 forms[name] = i; 261 for _, c in ipairs(rules) do
131 end 262 local rule, hidden, fields = unpack(c)
132 local dStance = forms["Defensive Stance"] or 2 263 if not hidden then
133 local zStance = forms["Berserker Stance"] or 3 264 for _, field in ipairs(fields) do
134 local bForm = forms["Dire Bear Form"] or forms["Bear Form"] or 1 265 local key, label = next(field)
135 local cForm = forms["Cat Form"] or 3 266 table.insert(ruleSelect, label)
136 local tForm = forms["Tree of Life"] or forms["Moonkin Form"] or 5 267 table.insert(ruleMap, key)
137 268 end
138 -- TODO: do the macro conditional strings need to be localized? 269 end
139 local ruleformats = {
140 stance = ("[stance:1] %%s; [stance:%d] %%s; [stance:%d] %%s"):format(dStance,zStance),
141 form = ("[form:%d] %%s; [form:%d] %%s; [form:%d] %%s; %%s"):format(bForm,cForm,tForm),
142 stealth = "[stealth] %s; %s",
143 shadow = "[stance:1] %s; %s",
144 pet = "[pet] %s; %s",
145 target = "[harm] %s; [help] %s; %s",
146 focus = "[target=focus,harm] %s; [target=focus,help] %s; %s",
147 group = "[group:raid] %s; [group] %s; %s",
148 combat = "[combat] %s; %s",
149 custom = "%s",
150 }
151
152 local fieldmap = { }
153 for i = 1, #rules do
154 fieldmap[rules[i][1]] = rules[i][4]
155 end
156
157 local _scratch = { }
158 function BuildRuleString(bar, name)
159 local rule, data = module:GetRule(bar,name)
160 if not rule then return "" end
161 local fields = fieldmap[rule]
162 for i = 1, #fields do
163 _scratch[i] = data[next(fields[i])] -- TODO: insert default state here
164 end
165 for i = #fields+1, #_scratch do
166 _scratch[i] = nil
167 end
168 local success, value = pcall(string.format, ruleformats[rule], unpack(_scratch))
169 return success and value or "<error>"
170 end
171 end
172
173
174 end
175
176
177
178 -- API --
179
180 function module:BuildStates( bar )
181 local c = tfetch(self.db.profile.bars, bar:GetName(), "states")
182 if c then
183 for name, s in pairs(c) do
184 -- TODO: new state here
185 end
186 end
187 end
188
189 function module:CreateState( bar, name )
190 local c = tbuild(self.db.profile.bars, bar:GetName(), "states")
191 if c[name] then
192 ReAction:UserError(L["State named '%s' already exists"]:format(name))
193 else
194 c[name] = { }
195 -- TODO: new state here
196 end
197 end
198
199 function module:DeleteState( bar, name )
200 local c = tfetch(self.db.profile.bars, bar:GetName(), "states")
201 if c[name] then
202 -- TODO: delete state
203 c[name] = nil
204 end
205 end
206
207 function module:BuildRules( bar )
208 for bar, c in pairs(self.db.profile.bars) do
209 if c.rules then
210 for name, t in pairs(c.rules) do
211 local rule, config = next(t)
212 self:SetRule(bar, name, rule, config)
213 end
214 end
215 end
216 end
217
218 function module:CreateRule( bar, name, rule, config )
219 local c = tbuild(self.db.profile.bars, bar:GetName(), "rules")
220 if c[name] then
221 ReAction:UserError(L["Rule named '%s' already exists"]:format(name))
222 else
223 tbuild(self.db.profile.bars, bar:GetName(), "rules", name)
224 if rule then
225 self:SetRule(bar,name,rule,config)
226 end
227 end
228 end
229
230 function module:DeleteRule( bar, name )
231 local c = tfetch(self.db.profile.bars, bar:GetName(), "rules")
232 if c[name] then
233 local f = bar:GetFrame()
234 -- TODO: delete rule
235 c[name] = nil
236 end
237 end
238
239 function module:UpdateRule( bar, name )
240 local rule, c = self:GetRule(bar,name)
241 -- TODO: remove all relevant outdated attributes
242 -- TODO: set new attributes
243 end
244
245 function module:GetRule(bar, name)
246 local c = tfetch(self.db.profile.bars, bar:GetName(), "rules", name)
247 if c then
248 return next(c) -- returns key, value (= rulename, configtable)
249 end
250 end
251
252 function module:SetRule(bar, name, rule, config)
253 tbuild(self.db.profile.bars, bar:GetName(), "rules")[name] = { [rule] = (config or {}) }
254 self:UpdateRule(bar,name)
255 end
256
257
258 -- options --
259 local CreateBarOptions
260 do
261 local function GetRuleConfig(bar, name, rule, field)
262 return tfetch(module.db.profile.bars, bar:GetName(), "rules", name, rule, field)
263 end
264
265 local function SetRuleConfig( bar, name, rule, field, value )
266 tbuild(module.db.profile.bars, bar:GetName(), "rules", name, rule)[field] = value
267 end 270 end
268 271
269 local function CreateStateOptions(bar, name) 272 local function CreateStateOptions(bar, name)
270 return {
271 type = "group",
272 name = name,
273 args = {
274 -- show/hide would go here
275 -- page # would go here
276 -- anchoring would go here
277 __delete__ = {
278 type = "execute",
279 name = L["Delete this State"],
280 func = function(info)
281 module:DeleteState(bar,name)
282 module.options[bar].args.states.args[name] = nil
283 end,
284 order = -1
285 },
286 }
287 }
288 end
289
290
291 -- display rule string setting is shared between all rule opts and is transient
292 -- (mostly used for debugging)
293 local display = { show = false }
294
295 local function CreateRuleOptions(bar, name)
296 local function get(info)
297 local rule = info[#info-1]
298 local field = info[#info]
299 return GetRuleConfig(bar,name,rule,field) or ""
300 end
301
302 local function set(info, value)
303 local rule = info[#info-1]
304 local field = info[#info]
305 SetRuleConfig(bar,name,rule,field,value)
306 module:UpdateRule(bar,name)
307 end
308
309 local opts = { 273 local opts = {
310 type = "group", 274 type = "group",
311 name = name, 275 name = name,
312 childGroups = "inline", 276 childGroups = "tab",
313 args = { 277 }
314 __select__ = { 278
315 type = "select", 279 local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
316 name = L["Select Rule Type"], 280
317 get = function(info) return module:GetRule(bar,name) or "" end, 281 local function put( key, value, ... )
318 set = function(info,value) module:SetRule(bar,name,value) end, -- TODO: get default rule config and pass as final value 282 tbuild(states, opts.name, "rule", ...)[key] = value
319 values = function() 283 end
320 local v = { } 284
321 for i = 1, #rules do 285 local function fetch( ... )
322 local rule, name, hidden = unpack(rules[i]) 286 return tfetch(states, opts.name, "rule", ...)
323 if not hidden then 287 end
324 v[rule] = name 288
325 end 289 local function fixall(setkey)
290 -- if multiple selections in the same group are chosen when 'all' is selected,
291 -- keep only one of them. If changing the mode, the first in the fields list will
292 -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set,
293 -- it will be retained.
294 local notified = false
295 for _, c in ipairs(rules) do
296 local rule, hidden, fields = unpack(c)
297 local found = false
298 for key in ipairs(fields) do
299 if fetch("values",key) then
300 if (found or setkey) and key ~= setkey then
301 put(key,false,"values")
302 if not setkey and not notified then
303 ReAction:UserError(L["Warning: one or more incompatible rules were turned off"])
304 notified = true
326 end 305 end
327 return v 306 end
328 end, 307 found = true
329 order = 1, 308 end
330 }, 309 end
331 __delete__ = { 310 end
332 type = "execute", 311 end
333 name = L["Delete this Rule"], 312
334 func = function(info) 313 local function getNeighbors()
335 module:DeleteRule(bar,name) 314 local before, after
336 module.options[bar].args.rules.args[name] = nil 315 for k, v in pairs(states) do
337 end, 316 local o = tonumber(tfetch(v, "rule", "order"))
338 order = -3 317 if o and k ~= opts.name then
339 }, 318 local obefore = tfetch(states,before,"rule","order")
340 -- 319 local oafter = tfetch(states,after,"rule","order")
341 -- rule selection groups will be inserted here 320 if o < opts.order and (not obefore or obefore < o) then
342 -- 321 before = k
343 __show__ = { 322 end
344 type = "toggle", 323 if o > opts.order and (not oafter or oafter > o) then
345 name = L["Show Rule String"], 324 after = k
346 desc = L["Toggles display of the raw rule string"], 325 end
347 get = function() return display.show end, 326 end
348 set = function(info,value) display.show = value end, 327 end
349 order = -2 328 return before, after
350 }, 329 end
351 __rule__ = { 330
352 type = "input", 331 local function swapOrder( a, b )
353 name = L["Rule String"], 332 -- do options table
354 disabled = true, 333 local args = optionMap[bar].args
355 multiline = true, 334 args[a].order, args[b].order = args[b].order, args[a].order
356 width = "double", 335 -- do profile
357 hidden = function() return not display.show end, 336 a = tbuild(states, a, "rule")
358 get = function() return BuildRuleString(bar,name) end, 337 b = tbuild(states, b, "rule")
359 set = function() end, 338 a.order, b.order = b.order, a.order
360 order = -1 339 end
340
341 local function update()
342 module:UpdateStates(bar)
343 end
344
345
346 opts.order = fetch("order")
347 if opts.order == nil then
348 -- add after the highest
349 opts.order = 100
350 for _, state in pairs(states) do
351 local x = tonumber(tfetch(state, "rule", "order"))
352 if x and x >= opts.order then
353 opts.order = x + 1
354 end
355 end
356 put("order",opts.order)
357 end
358
359 opts.args = {
360 properties = {
361 type = "group",
362 name = L["Properties"],
363 order = 1,
364 args = {
365 delete = {
366 type = "execute",
367 name = L["Delete this State"],
368 func = function(info)
369 module:DeleteState(bar,opts.name)
370 optionMap[bar].args[opts.name] = nil
371 end,
372 order = -1
373 },
374 rename = {
375 type = "input",
376 name = L["Name"],
377 order = 1,
378 get = function() return opts.name end,
379 set = function(info, value)
380 -- check for existing state name
381 if states[value] then
382 L["State named '%s' already exists"]:format(value)
383 end
384 local args = optionMap[bar].args
385 states[value], args[value], states[opts.name], args[opts.name] = states[opts.name], args[opts.name], nil, nil
386 opts.name = value
387 update()
388 end,
389 pattern = "^%w*$",
390 usage = L["State names must be alphanumeric without spaces"],
391 },
392 ordering = {
393 type = "group",
394 inline = true,
395 name = L["Evaluation Order"],
396 desc = L["State transitions are evaluated in the order listed:\nMove a state up or down to change the order"],
397 order = 2,
398 args = {
399 up = {
400 type = "execute",
401 name = L["Up"],
402 width = "half",
403 order = 1,
404 func = function()
405 local before, after = getNeighbors()
406 if before then
407 swapOrder(before, opts.name)
408 update()
409 end
410 end,
411 },
412 down = {
413 type = "execute",
414 name = L["Down"],
415 width = "half",
416 order = 2,
417 func = function()
418 local before, after = getNeighbors()
419 if after then
420 ReAction:Print(opts.name, after)
421 swapOrder(opts.name, after)
422 update()
423 end
424 end,
425 }
426 }
427 },
428 -- keybinding for show-this-state would go here
429 -- show/hide would go here
430 -- page # would go here
431 -- anchoring would go here
432 }
433 },
434 rules = {
435 type = "group",
436 name = L["Rules"],
437 order = 2,
438 args = {
439 mode = {
440 type = "select",
441 style = "radio",
442 name = L["Select this state"],
443 values = {
444 default = L["by default"],
445 any = L["when ANY of these"],
446 all = L["when ALL of these"],
447 custom = L["via custom rule"]
448 },
449 set = function( info, value )
450 put("type", value)
451 fixall()
452 update()
453 end,
454 get = function( info )
455 return fetch("type")
456 end,
457 order = 2
458 },
459 clear = {
460 type = "execute",
461 name = L["Clear All"],
462 func = function()
463 local type = fetch("type")
464 if type == "custom" then
465 put("custom","")
466 elseif type == "any" or type == "all" then
467 put("values", {})
468 end
469 update()
470 end,
471 order = 3
472 },
473 inputs = {
474 type = "multiselect",
475 name = L["Rules"],
476 hidden = function()
477 return fetch("type") == "custom"
478 end,
479 disabled = function()
480 return fetch("type") == "default"
481 end,
482 values = ruleSelect,
483 set = function(info, key, value )
484 put(ruleMap[key], value or nil, "values")
485 if value then
486 fixall(ruleMap[key])
487 end
488 update()
489 end,
490 get = function(info, key)
491 return fetch("values", ruleMap[key]) or false
492 end,
493 order = 4
494 },
495 custom = {
496 type = "input",
497 multiline = true,
498 hidden = function()
499 return fetch("type") ~= "custom"
500 end,
501 disabled = function()
502 return fetch("type") == "default"
503 end,
504 name = L["Custom Rule"],
505 desc = L["Syntax like macro rules: see preset rules for examples"],
506 set = function(info, value)
507 put("custom",value)
508 update()
509 end,
510 get = function(info)
511 return fetch("custom") or ""
512 end,
513 validate = function (info, rule)
514 local s = rule:gsub("%s","") -- remove all spaces
515 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler
516 repeat
517 if s == "" then
518 return true
519 end
520 local c, r = s:match("(%b[])(.*)")
521 if c == nil and s and #s > 0 then
522 return L["Invalid custom rule '%s': each clause must appear within [brackets]"]:format(rule)
523 end
524 s = r
525 until c == nil
526 return true
527 end,
528 order = 5,
529 }
361 } 530 }
362 } 531 }
363 } 532 }
364
365 -- unpack rules table
366 for i = 1, #rules do
367 local rule, label, _, fields = unpack(rules[i])
368 local hidden = function()
369 return module:GetRule(bar,name) ~= rule
370 end
371 opts.args[rule] = {
372 type = "group",
373 name = label,
374 hidden = hidden,
375 disabled = hidden,
376 inline = true,
377 args = { }
378 }
379 for j = 1, #fields do
380 local field, label = next(fields[j]) -- extract from table of single key-value pair
381 if field and label then
382 opts.args[rule].args[field] = {
383 type = "select",
384 name = L["Select State (%s):"]:format(label),
385 values = function ()
386 local states = tfetch(module.db.profile.bars,bar:GetName(),"states")
387 local v = { }
388 if states then
389 for k in pairs(states) do
390 v[k] = k
391 end
392 end
393 return v
394 end,
395 set = set,
396 get = get,
397 order = 100 + j
398 }
399 end
400 end
401 end
402
403 -- set up special entry for keybinding
404 opts.args.key.args.binding = {
405 type = "keybinding",
406 name = L["Key Binding"],
407 get = get,
408 set = set,
409 order = -1
410 }
411
412 -- set up special entry for custom
413 opts.args.custom.args.rule = {
414 type = "input",
415 name = L["Rule"],
416 desc = L["Syntax like macro conditions: see preset rules for examples"],
417 get = get,
418 set = set,
419 validate = function (info, rule)
420 local s = rule:gsub("%s","") -- remove all spaces
421 if s:match(";$") then -- can't end with semicolon
422 return L["Invalid custom rule '%s': Rule cannot end with ';'"]:format(rule)
423 end
424 if s == "" then
425 return true
426 end
427 -- unfortunately %b and captures don't support the '+' notation, or this would be considerably simpler
428 repeat
429 repeat
430 local c, r = s:match("(%b[])(.*)")
431 if r then s = r end
432 until c == nil
433 local state, s = s:match("(%w+)(.*)")
434 if not state then
435 return L["Invalid custom rule '%s': Each expression must have a state"]:format(rule)
436 end
437 if not tfetch(module.db.profile.bars,bar:GetName(),"states",state) then
438 return L["Invalid custom rule '%s': '%s' is not a state"]:format(rule,state)
439 end
440 if s:match("^[^;]") then
441 return L["Invalid custom rule '%s': Expressions must be separated by ';'"]:format(rule)
442 end
443 until #s == 0
444 return true
445 end,
446 multiline = true,
447 order = 1,
448 }
449
450 return opts 533 return opts
451 end 534 end
535
452 536
453 CreateBarOptions = function(bar) 537 CreateBarOptions = function(bar)
454 local private = { } 538 local private = { }
455 local options = { 539 local options = {
456 type = "group", 540 type = "group",
457 name = L["Dynamic State"], 541 name = L["Dynamic State"],
458 childGroups = "tab", 542 childGroups = "tree",
459 disabled = InCombatLockdown, 543 disabled = InCombatLockdown,
460 args = { 544 args = {
461 states = { 545 __desc__ = {
546 type = "description",
547 name = L["States are evaluated in the order they are listed"],
548 order = 1
549 },
550 __new__ = {
462 type = "group", 551 type = "group",
463 name = L["States"], 552 name = L["New State..."],
464 childGroups = "tree", 553 order = 2,
465 order = 1,
466 args = { 554 args = {
467 __new__ = { 555 name = {
468 type = "group", 556 type = "input",
469 name = L["New State..."], 557 name = L["State Name"],
470 order = 1, 558 desc = L["Set a name for the new state"],
471 args = { 559 get = function() return private.newstatename or "" end,
472 name = { 560 set = function(info,value) private.newstatename = value end,
473 type = "input", 561 pattern = "^%w*$",
474 name = L["State Name"], 562 usage = L["State names must be alphanumeric without spaces"],
475 desc = L["Set a name for the new state"], 563 order = 1
476 get = function() return private.newstatename or "" end, 564 },
477 set = function(info,value) private.newstatename = value end, 565 create = {
478 pattern = "^%w*$", 566 type = "execute",
479 usage = L["State names must be alphanumeric without spaces"], 567 name = L["Create State"],
480 order = 1 568 func = function ()
481 }, 569 local name = private.newstatename
482 create = { 570 module:CreateState(bar,name) -- TODO: select default state options and pass as final argument
483 type = "execute", 571 optionMap[bar].args[name] = CreateStateOptions(bar,name)
484 name = L["Create State"], 572 private.newstatename = ""
485 func = function () 573 end,
486 local name = private.newstatename 574 disabled = function()
487 module:CreateState(bar,name) -- TODO: select default state options and pass as final argument 575 local name = private.newstatename or ""
488 module.options[bar].args.states.args[name] = CreateStateOptions(bar,name) 576 return #name == 0 or name:find("%W")
489 private.newstatename = "" 577 end,
490 end, 578 order = 2,
491 disabled = function()
492 local name = private.newstatename or ""
493 return #name == 0 or name:find("%W")
494 end,
495 order = 2,
496 }
497 }
498 } 579 }
499 } 580 }
500 }, 581 }
501 rules = {
502 type = "group",
503 name = L["Transition Rules"],
504 childGroups = "tree",
505 order = 2,
506 args = {
507 __new__ = {
508 type = "group",
509 name = L["New Rule..."],
510 order = 1,
511 args = {
512 name = {
513 type = "input",
514 name = L["Rule Name"],
515 desc = L["Set a name for the new transition rule"],
516 get = function() return private.newtransname or "" end,
517 set = function(info,value) private.newtransname = value end,
518 pattern = "^%w*$",
519 usage = L["Rule names must be alphanumeric without spaces"],
520 order = 1
521 },
522 create = {
523 type = "execute",
524 name = L["Create Rule"],
525 func = function ()
526 local name = private.newtransname
527 module:CreateRule(bar,name) -- TODO: select default rule and add as final argument
528 module.options[bar].args.rules.args[name] = CreateRuleOptions(bar,name)
529 private.newtransname = ""
530 end,
531 disabled = function ()
532 local name = private.newtransname or ""
533 return #name == 0 or name:find("%W")
534 end,
535 order = 2,
536 },
537 }
538 }
539 }
540 },
541 presets = {
542 type = "group",
543 name = L["Presets"],
544 order = 3,
545 args = {
546 desc = {
547 type = "description",
548 name = L["Presets are canned sets of states and transitions. You can create your own presets to add to ReAction's built in defaults."],
549 order = 1,
550 },
551 select = {
552 type = "select",
553 name = L["Select Preset"],
554 set = function(info,value) end,
555 get = function() return "" end,
556 values = function() return { } end,
557 order = 2,
558 },
559 load = {
560 type = "execute",
561 name = L["Load"],
562 func = function() end,
563 width = "half",
564 order = 3,
565 },
566 delete = {
567 type = "execute",
568 name = L["Delete"],
569 disabled = function() return false end,
570 func = function() end,
571 width = "half",
572 order = 4,
573 },
574 hdr = {
575 type = "header",
576 name = " ",
577 order = 5,
578 },
579 save = {
580 type = "input",
581 name = L["Save As..."],
582 get = function() return "" end,
583 set = function(info,name) end,
584 order = 6,
585 },
586 },
587 },
588 } 582 }
589 } 583 }
590 local states = tfetch(module.db.profile.bars, bar:GetName(), "states") 584 local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
591 if states then 585 if states then
592 for name, config in pairs(states) do 586 for name, config in pairs(states) do
593 options.args.states.args[name] = CreateStateOptions(bar,name) 587 options.args[name] = CreateStateOptions(bar,name)
594 end 588 end
595 end 589 end
596 local rules = tfetch(module.db.profile.bars, bar:GetName(), "rules") 590 optionMap[bar] = options
597 if rules then
598 for name, config in pairs(rules) do
599 options.args.rules.args[name] = CreateRuleOptions(bar,name)
600 end
601 end
602 return options 591 return options
603 end 592 end
604 end 593 end
605 594
606 function module:GetBarOptions(bar) 595 function module:GetBarOptions(bar)
607 if not self.options[bar] then 596 return CreateBarOptions(bar)
608 self.options[bar] = CreateBarOptions(bar) 597 end
609 end
610 return self.options[bar]
611 end