Mercurial > wow > reaction
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 |