comparison ReAction.lua @ 63:768be7eb22a0

Converted several ReAction APIs to event-driven model instead of 'call-method-on-all-modules' model. Cleaned up a number of other architectural issues.
author Flick <flickerstreak@gmail.com>
date Thu, 22 May 2008 22:02:08 +0000
parents 2ee41dcd673f
children 06cd74bdc7da
comparison
equal deleted inserted replaced
62:f9cdb920470a 63:768be7eb22a0
1 -- ReAction.lua 1 --[[
2 -- See modules/ReAction_ModuleTemplate for Module API listing 2 ReAction.lua
3 -- See Bar.lua for Bar object listing 3
4 The ReAction core manages 4 collections:
5 - modules (via AceAddon)
6 - bars
7 - options
8 - bar-type constructors
9
10 and publishes events when those collections change. It also implements a single property, 'config mode',
11 and has a couple convenience methods which drill down to particular modules.
12
13 Most of the "real work" of the addon happens in Bar.lua and the various modules.
14
15 Events (with handler arguments):
16 --------------------------------
17 "OnCreateBar" (bar, name) : after a bar object is created
18 "OnDestroyBar" (bar, name) : before a bar object is destroyed
19 "OnEraseBar" (bar, name) : before a bar config is removed from the profile db
20 "OnRenameBar" (bar, oldname, newname) : after a bar is renamed
21 "OnRefreshBar" (bar, name) : after a bar's state has been updated
22 "OnOptionsRefreshed" () : after the global options tree is refreshed
23 "OnConfigModeChanged" (mode) : after the config mode is changed
24 "OnBarOptionGeneratorRegistered" (module, function) : after an options generator function is registered
25
26 ReAction is also an AceAddon-3.0 and contains an AceDB-3.0, which in turn publish more events.
27 ]]--
28 local version = GetAddOnMetadata("ReAction","Version")
4 29
5 ------ CORE ------ 30 ------ CORE ------
6 local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", 31 local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction",
7 "AceConsole-3.0", 32 "AceConsole-3.0",
8 "AceEvent-3.0" 33 "AceEvent-3.0"
9 ) 34 )
10 ReAction.version = GetAddOnMetadata("ReAction","Version")
11 ReAction.revision = tonumber(("$Revision$"):match("%d+")) 35 ReAction.revision = tonumber(("$Revision$"):match("%d+"))
12 36
13 ------ GLOBALS ------ 37 ------ GLOBALS ------
14 _G["ReAction"] = ReAction 38 _G["ReAction"] = ReAction
15 39
18 local dbprint 42 local dbprint
19 if ReAction.debug then 43 if ReAction.debug then
20 dbprint = function(msg) 44 dbprint = function(msg)
21 DEFAULT_CHAT_FRAME:AddMessage(msg) 45 DEFAULT_CHAT_FRAME:AddMessage(msg)
22 end 46 end
23 --seterrorhandler(dbprint)
24 else 47 else
25 dbprint = function() end 48 dbprint = function() end
26 end 49 end
27 ReAction.dbprint = dbprint 50 ReAction.dbprint = dbprint
28 51
29 ------ LIBRARIES ------ 52 ------ LIBRARIES ------
53 local callbacks = LibStub("CallbackHandler-1.0"):New(ReAction)
30 local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") 54 local L = LibStub("AceLocale-3.0"):GetLocale("ReAction")
31 ReAction.L = L 55 ReAction.L = L
32 56
33 ------ PRIVATE ------ 57 ------ PRIVATE ------
34 local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, SafeCall, CheckMethod, SlashHandler 58 local private = { }
59 local bars = {}
60 local defaultBarConfig = {}
61 local barOptionGenerators = { }
62 local options = {
63 type = "group",
64 name = "ReAction",
65 childGroups = "tab",
66 args = {
67 _desc = {
68 type = "description",
69 name = L["Customizable replacement for Blizzard's Action Bars"],
70 order = 1,
71 },
72 global = {
73 type = "group",
74 name = L["Global Settings"],
75 desc = L["Global configuration settings"],
76 args = {
77 unlock = {
78 type = "toggle",
79 name = L["Unlock Bars"],
80 desc = L["Unlock bars for dragging and resizing with the mouse"],
81 handler = ReAction,
82 get = "GetConfigMode",
83 set = function(info, value) ReAction:SetConfigMode(value) end,
84 disabled = InCombatLockdown,
85 order = 1
86 },
87 },
88 plugins = { },
89 order = 2,
90 },
91 module = {
92 type = "group",
93 childGroups = "select",
94 name = L["Module Settings"],
95 desc = L["Configuration settings for each module"],
96 args = { },
97 plugins = { },
98 order = 3,
99 },
100 },
101 plugins = { }
102 }
103 ReAction.options = options
104
105 local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, CallModuleMethod, SlashHandler
35 do 106 do
36 local pcall = pcall 107 local pcall = pcall
37 local geterrorhandler = geterrorhandler 108 local geterrorhandler = geterrorhandler
38 109 local self = ReAction
39 SelectBar = function(x) 110 local inited = false
111
112 function SelectBar(x)
40 local bar, name 113 local bar, name
41 if type(x) == "string" then 114 if type(x) == "string" then
42 name = x 115 name = x
43 bar = ReAction:GetBar(name) 116 bar = self:GetBar(name)
44 else 117 else
45 for k,v in pairs(ReAction.bars) do 118 for k,v in pairs(bars) do
46 if v == x then 119 if v == x then
47 name = k 120 name = k
48 bar = x 121 bar = x
49 end 122 end
50 end 123 end
51 end 124 end
52 return bar, name 125 return bar, name
53 end 126 end
54 127
55 DestroyBar = function(x) 128 function DestroyBar(x)
56 local bar, name = SelectBar(x) 129 local bar, name = SelectBar(x)
57 if name and bar then 130 if bar and name then
58 ReAction.bars[name] = nil 131 bars[name] = nil
59 ReAction:CallMethodOnAllModules("RemoveFromBar", bar) 132 callbacks:Fire("OnDestroyBar", bar, name)
60 bar:Destroy() 133 bar:Destroy()
61 end 134 end
62 end 135 end
63 136
64 InitializeBars = function () 137 function InitializeBars()
65 if not(ReAction.inited) then 138 if not inited then
66 for name, config in pairs(ReAction.db.profile.bars) do 139 for name, config in pairs(self.db.profile.bars) do
67 if config then 140 if config then
68 ReAction:CreateBar(name, config) 141 self:CreateBar(name, config)
69 end 142 end
70 end 143 end
71 ReAction:CallMethodOnAllBars("ApplyAnchor") -- re-anchor in the case of oddball ordering 144 -- re-anchor in case anchor order does not match init order
72 ReAction.inited = true 145 for name, bar in pairs(bars) do
73 end 146 bar:ApplyAnchor()
74 end 147 end
75 148 inited = true
76 TearDownBars = function() 149 end
77 for name, bar in pairs(ReAction.bars) do 150 end
151
152 function TearDownBars()
153 for name, bar in pairs(bars) do
78 if bar then 154 if bar then
79 ReAction.bars[name] = DestroyBar(bar) 155 bars[name] = DestroyBar(bar)
80 end 156 end
81 end 157 end
82 ReAction.inited = false 158 inited = false
83 end 159 end
84 160
85 DeepCopy = function(x) 161 function DeepCopy(x)
86 if type(x) ~= "table" then 162 if type(x) ~= "table" then
87 return x 163 return x
88 end 164 end
89 local r = {} 165 local r = {}
90 for k,v in pairs(x) do 166 for k,v in pairs(x) do
91 r[k] = DeepCopy(v) 167 r[k] = DeepCopy(v)
92 end 168 end
93 return r 169 return r
94 end 170 end
95 171
96 SafeCall = function(f, ...) 172 function CallModuleMethod(modulename, method, ...)
97 if f then 173 local m = self:GetModule(modulename,true)
98 local success, err = pcall(f,...) 174 if not m then
99 if not success then 175 LoadAddOn(("ReAction_%s"):format(modulename))
100 geterrorhandler()(err) 176 m = self:GetModule(modulename,true)
101 end 177 if m then
102 end 178 dbprint(("succesfully loaded LOD module: %s"):format(modulename))
103 end 179 end
104 180 end
105 CheckMethod = function(m) 181 if m then
106 if type(m) == "function" then 182 if type(m) == "table" and type(m[method]) == "function" then
107 return m 183 m[method](m,...)
108 end 184 else
109 if type(m) ~= "string" then 185 dbprint(("Bad call '%s' to %s module"):format(tostring(method),modulename));
110 error("Invalid method") 186 end
111 end 187 else
112 end 188 self:Print(("Module '%s' not found"):format(tostring(modulename)))
113 189 end
114 SlashHandler = function(option) 190 end
191
192 function SlashHandler(option)
115 if option == "config" then 193 if option == "config" then
116 ReAction:ShowConfig() 194 self:ShowConfig()
117 elseif option == "edit" then 195 elseif option == "edit" then
118 ReAction:ShowEditor() 196 self:ShowEditor()
119 elseif option == "unlock" then 197 elseif option == "unlock" then
120 ReAction:SetConfigMode(true) 198 self:SetConfigMode(true)
121 elseif option == "lock" then 199 elseif option == "lock" then
122 ReAction:SetConfigMode(false) 200 self:SetConfigMode(false)
123 else 201 else
124 ReAction:Print(("%3.1f.%d"):format(ReAction.version,ReAction.revision)) 202 self:Print(("%3.1f.%d"):format(version,self.revision))
125 ReAction:Print("/reaction config") 203 self:Print("/rxn config")
126 ReAction:Print("/reaction edit") 204 self:Print("/rxn edit")
127 ReAction:Print("/reaction lock") 205 self:Print("/rxn lock")
128 ReAction:Print("/reaction unlock") 206 self:Print("/rxn unlock")
129 end 207 end
130 end 208 end
131 end 209 end
132 210
133 211
142 } 220 }
143 -- default profile is character-specific 221 -- default profile is character-specific
144 ) 222 )
145 self.db.RegisterCallback(self,"OnProfileChanged") 223 self.db.RegisterCallback(self,"OnProfileChanged")
146 self.db.RegisterCallback(self,"OnProfileReset","OnProfileChanged") 224 self.db.RegisterCallback(self,"OnProfileReset","OnProfileChanged")
147 self.callbacks = LibStub("CallbackHandler-1.0"):New(self) 225
226 options.args.profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
227
148 self:RegisterChatCommand("reaction", SlashHandler) 228 self:RegisterChatCommand("reaction", SlashHandler)
149 self:RegisterChatCommand("rxn", SlashHandler) 229 self:RegisterChatCommand("rxn", SlashHandler)
150 self:RegisterEvent("PLAYER_REGEN_DISABLED") 230 self:RegisterEvent("PLAYER_REGEN_DISABLED")
151
152 self.bars = {}
153 self.defaultBarConfig = {}
154
155 self.options = {
156 type = "group",
157 name = "ReAction",
158 childGroups = "tab",
159 args = {
160 _desc = {
161 type = "description",
162 name = L["Customizable replacement for Blizzard's Action Bars"],
163 order = 1,
164 },
165 global = {
166 type = "group",
167 name = L["Global Settings"],
168 desc = L["Global configuration settings"],
169 args = {
170 unlock = {
171 type = "toggle",
172 handler = module,
173 name = L["Unlock Bars"],
174 desc = L["Unlock bars for dragging and resizing with the mouse"],
175 get = function() return self.configMode end,
176 set = function(info, value) self:SetConfigMode(value) end,
177 disabled = InCombatLockdown,
178 order = 1
179 },
180 },
181 plugins = { },
182 order = 2,
183 },
184 module = {
185 type = "group",
186 childGroups = "select",
187 name = L["Module Settings"],
188 desc = L["Configuration settings for each module"],
189 args = { },
190 plugins = { },
191 order = 3,
192 },
193 profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
194 },
195 plugins = { }
196 }
197
198 end 231 end
199 232
200 function ReAction:OnEnable() 233 function ReAction:OnEnable()
201 InitializeBars() 234 InitializeBars()
202 end 235 end
208 function ReAction:OnProfileChanged() 241 function ReAction:OnProfileChanged()
209 TearDownBars() 242 TearDownBars()
210 InitializeBars() 243 InitializeBars()
211 end 244 end
212 245
213 function ReAction:OnModuleEnable(module)
214 if module.ApplyToBar then
215 for _, b in pairs(bars) do
216 if b then
217 module:ApplyToBar(b)
218 end
219 end
220 end
221 end
222
223 function ReAction:OnModuleDisable(module)
224 if module.RemoveFromBar then
225 for _, b in pairs(bars) do
226 if b then
227 module:RemoveFromBar(b)
228 end
229 end
230 end
231 end
232
233 function ReAction:PLAYER_REGEN_DISABLED() 246 function ReAction:PLAYER_REGEN_DISABLED()
234 if self.configMode == true then 247 if private.configMode == true then
235 UIErrorsFrame:AddMessage(L["ReAction config mode disabled during combat."]) 248 self:UserError(L["ReAction config mode disabled during combat."])
236 self:SetConfigMode(false) 249 self:SetConfigMode(false)
237 end 250 end
238 end 251 end
239 252
240 253
243 function ReAction:UserError(msg) 256 function ReAction:UserError(msg)
244 -- any user errors should be flashed to the UIErrorsFrame 257 -- any user errors should be flashed to the UIErrorsFrame
245 UIErrorsFrame:AddMessage(msg) 258 UIErrorsFrame:AddMessage(msg)
246 end 259 end
247 260
248 function ReAction:CallMethodOnAllModules(method, ...) 261 -- usage:
249 local m = CheckMethod(method) 262 -- (1) ReAction:CreateBar(name, cfgTable)
250 for _, x in self:IterateModules() do 263 -- (2) ReAction:CreateBar(name, "barType", [nRows], [nCols], [btnSize], [btnSpacing])
251 if x then
252 SafeCall(m or x[method], x, ...)
253 end
254 end
255 end
256
257 function ReAction:CallMethodOnAllBars(method,...)
258 local m = CheckMethod(method)
259 for _, x in pairs(self.bars) do
260 if x then
261 SafeCall(m or x[method], x, ...)
262 end
263 end
264 end
265
266 function ReAction:CallModuleMethod(modulename, method, ...)
267 local m = self:GetModule(modulename,true)
268 if not m then
269 LoadAddOn(("ReAction_%s"):format(modulename))
270 m = self:GetModule(modulename,true)
271 if m then
272 dbprint(("succesfully loaded LOD module: %s"):format(modulename))
273 end
274 end
275 if m then
276 if type(m) == "table" and type(m[method]) == "function" then
277 m[method](m,...)
278 else
279 dbprint(("Bad call '%s' to %s module"):format(tostring(method),modulename));
280 end
281 else
282 self:Print(("Module '%s' not found"):format(tostring(modulename)))
283 end
284 end
285
286
287 function ReAction:CreateBar(name, ...) 264 function ReAction:CreateBar(name, ...)
288 local config = select(1,...) 265 local config = select(1,...)
289 if config and type(config) ~= "table" then 266 if config and type(config) ~= "table" then
290 bartype = select(1,...) 267 bartype = select(1,...)
291 if type(bartype) ~= "string" then 268 if type(bartype) ~= "string" then
292 error("ReAction:CreateBar() - first argument must be a config table or a default config type string") 269 error("ReAction:CreateBar() - first argument must be a config table or a default config type string")
293 end 270 end
294 config = self.defaultBarConfig[bartype] 271 config = defaultBarConfig[bartype]
295 if not config then 272 if not config then
296 error(("ReAction:CreateBar() - unknown bar type '%s'"):format(bartype)) 273 error(("ReAction:CreateBar() - unknown bar type '%s'"):format(bartype))
297 end 274 end
298 config = DeepCopy(config) 275 config = DeepCopy(config)
299 config.btnRows = select(2,...) or config.btnRows or 1 276 config.btnRows = select(2,...) or config.btnRows or 1
315 if not name then 292 if not name then
316 i = 1 293 i = 1
317 repeat 294 repeat
318 name = prefix..i 295 name = prefix..i
319 i = i + 1 296 i = i + 1
320 until self.bars[name] == nil 297 until bars[name] == nil
321 end 298 end
322 profile.bars[name] = profile.bars[name] or config 299 profile.bars[name] = profile.bars[name] or config
323 local bar = self.Bar:new( name, profile.bars[name] ) -- ReAction.Bar defined in Bar.lua 300 local bar = self.Bar:new( name, profile.bars[name] ) -- ReAction.Bar defined in Bar.lua
324 self:CallMethodOnAllModules("ApplyToBar", bar) 301 bars[name] = bar
325 self.bars[name] = bar 302 callbacks:Fire("OnCreateBar", bar, name)
326 self.callbacks:Fire("OnCreateBar", bar) 303 if private.configMode then
327 if self.configMode then
328 bar:ShowControls(true) 304 bar:ShowControls(true)
329 end 305 end
330 306
331 return bar 307 return bar
332 end 308 end
333 309
334 function ReAction:EraseBar(x) 310 function ReAction:EraseBar(x)
335 local bar, name = SelectBar(x) 311 local bar, name = SelectBar(x)
336 if name and bar then 312 if bar and name then
313 callbacks:Fire("OnEraseBar", bar, name)
337 DestroyBar(bar) 314 DestroyBar(bar)
338 self.db.profile.bars[name] = nil 315 self.db.profile.bars[name] = nil
339 self:CallMethodOnAllModules("EraseBarConfig", name)
340 self.callbacks:Fire("OnEraseBar",name)
341 end 316 end
342 end 317 end
343 318
344 function ReAction:GetBar(name) 319 function ReAction:GetBar(name)
345 return self.bars[name] 320 return bars[name]
321 end
322
323 function ReAction:IterateBars()
324 return pairs(bars)
346 end 325 end
347 326
348 function ReAction:RenameBar(x, newname) 327 function ReAction:RenameBar(x, newname)
349 local bar, name = SelectBar(x) 328 local bar, name = SelectBar(x)
350 if bar and name and newname then 329 if type(newname) ~= "string" then
351 if self.bars[newname] then 330 error("ReAction:RenameBar() - second argument must be a string")
352 UIErrorsFrame:AddMessage(("%s ('%s')"):format(L["ReAction: name already in use"],newname)) 331 end
353 else 332 if bar and name and #newname > 0 then
354 self.bars[newname] = self.bars[name] 333 if bars[newname] then
355 self.bars[name] = nil 334 self:UserError(("%s ('%s')"):format(L["ReAction: name already in use"],newname))
335 else
336 bars[newname], bars[name] = bars[name], nil
356 bar:SetName(newname or "") 337 bar:SetName(newname or "")
357 local cfg = self.db.profile.bars 338 local cfg = self.db.profile.bars
358 cfg[newname], cfg[name] = cfg[name], nil 339 cfg[newname], cfg[name] = cfg[name], nil
359 self:CallMethodOnAllModules("RenameBarConfig", name, newname) 340 callbacks:Fire("OnRenameBar", bar, name, newname)
360 self.callbacks:Fire("OnRenameBar", name, newname) 341 end
361 end 342 end
343 end
344
345 function ReAction:RefreshBar(x)
346 local bar, name = SelectBar(x)
347 if bar and name then
348 callbacks:Fire("OnRefreshBar", bar, name)
362 end 349 end
363 end 350 end
364 351
365 function ReAction:RegisterBarType( name, config, isDefaultChoice ) 352 function ReAction:RegisterBarType( name, config, isDefaultChoice )
366 self.defaultBarConfig[name] = config 353 defaultBarConfig[name] = config
367 if isDefaultChoice then 354 if isDefaultChoice then
368 self.defaultBarConfigChoice = name 355 defaultBarConfigChoice = name
369 end 356 end
370 self:RefreshOptions() 357 self:RefreshOptions()
371 end 358 end
372 359
373 function ReAction:UnregisterBarType( name ) 360 function ReAction:UnregisterBarType( name )
374 self.defaultBarConfig[name] = nil 361 defaultBarConfig[name] = nil
375 if self.defaultBarConfigChoice == name then 362 if private.defaultBarConfigChoice == name then
376 self.defaultBarConfigChoice = nil 363 private.defaultBarConfigChoice = nil
377 end 364 end
378 self:RefreshOptions() 365 self:RefreshOptions()
379 end 366 end
380 367
381 function ReAction:RegisterOptions(module, options, global) 368 function ReAction:IterateBarTypes()
382 self.options.args[global and "global" or "module"].plugins[module:GetName()] = options 369 return pairs(defaultBarConfig)
370 end
371
372 function ReAction:GetBarTypeConfig(name)
373 if name then
374 return defaultBarConfig[name]
375 end
376 end
377
378 function ReAction:GetBarTypeOptions( fill )
379 fill = fill or { }
380 for k in self:IterateBarTypes() do
381 fill[k] = k
382 end
383 return fill
384 end
385
386 function ReAction:GetDefaultBarType()
387 return private.defaultBarConfigChoice
388 end
389
390 function ReAction:RegisterOptions(module, opts, global)
391 options.args[global and "global" or "module"].plugins[module:GetName()] = opts
392 self:RefreshOptions()
383 end 393 end
384 394
385 function ReAction:RefreshOptions() 395 function ReAction:RefreshOptions()
386 self.callbacks:Fire("OnOptionsRefreshed") 396 callbacks:Fire("OnOptionsRefreshed")
397 end
398
399 --
400 -- In addition to global and general module options, options tables
401 -- must be generated dynamically for each bar.
402 --
403 -- 'func' should be a function or a method string.
404 -- The function or method will be passed the bar as its parameter.
405 -- (methods will of course get the module as the first 'self' parameter)
406 --
407 -- A generator can be unregistered by passing a nil func.
408 --
409 function ReAction:RegisterBarOptionGenerator( module, func )
410 if not module or type(module) ~= "table" then -- doesn't need to be a proper module, strictly
411 error("ReAction:RegisterBarOptionGenerator() : Invalid module")
412 end
413 if type(func) == "string" then
414 if not module[func] then
415 error(("ReAction:RegisterBarOptionGenerator() : Invalid method '%s'"):format(func))
416 end
417 elseif func and type(func) ~= "function" then
418 error("ReAction:RegisterBarOptionGenerator() : Invalid function")
419 end
420 barOptionGenerators[module] = func
421 callbacks:Fire("OnBarOptionGeneratorRegistered", module, func)
422 end
423
424 -- builds a table suitable for use as an AceConfig3 group 'plugins' sub-table
425 function ReAction:GenerateBarOptionsTable( bar )
426 local opts = { }
427 for module, func in pairs(barOptionGenerators) do
428 local success, r
429 if type(func) == "string" then
430 success, r = pcall(module[func], module, bar)
431 else
432 success, r = pcall(func, bar)
433 end
434 if success then
435 opts[module:GetName()] = { [module:GetName()] = r }
436 else
437 geterrorhandler()(r)
438 end
439 end
440 return opts
387 end 441 end
388 442
389 function ReAction:SetConfigMode( mode ) 443 function ReAction:SetConfigMode( mode )
390 self:CallMethodOnAllBars("ShowControls",mode) 444 private.configMode = mode
391 self:CallMethodOnAllModules("ApplyConfigMode",mode,self.bars) 445 callbacks:Fire("OnConfigModeChanged", mode)
392 self.configMode = mode 446 end
447
448 function ReAction:GetConfigMode()
449 return private.configMode
393 end 450 end
394 451
395 function ReAction:ShowConfig() 452 function ReAction:ShowConfig()
396 self:CallModuleMethod("ConfigUI","OpenConfig") 453 CallModuleMethod("ConfigUI","OpenConfig")
397 end 454 end
398 455
399 function ReAction:ShowEditor() 456 function ReAction:ShowEditor(bar)
400 self:CallModuleMethod("ConfigUI","LaunchBarEditor") 457 CallModuleMethod("ConfigUI","LaunchBarEditor",bar)
401 end 458 end