| flickerstreak@201 | 1 local addonName, addonTable = ... | 
| flickerstreak@205 | 2 local pcall = pcall | 
| flickerstreak@205 | 3 local pairs = pairs | 
| flickerstreak@205 | 4 local type = type | 
| flickerstreak@205 | 5 local geterrorhandler = geterrorhandler | 
| flickerstreak@33 | 6 local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") | 
| Flick@230 | 7 local LKB = LibStub("LibKeyBound-1.0",true) | 
| Flick@230 | 8 if not LKB then | 
| Flick@230 | 9   LoadAddOn("LibKeyBound-1.0") | 
| Flick@230 | 10   LKB = LibStub("LibKeyBound-1.0") | 
| Flick@230 | 11 end | 
| flickerstreak@33 | 12 | 
| flickerstreak@205 | 13 ------ Utility ------ | 
| Flick@241 | 14 -- make a deep copy of a table | 
| Flick@241 | 15 local function tcopy(x) | 
| Flick@241 | 16   if type(x) ~= "table" then | 
| Flick@241 | 17     return x | 
| flickerstreak@28 | 18   end | 
| Flick@241 | 19   local r = {} | 
| Flick@241 | 20   for k,v in pairs(x) do | 
| Flick@241 | 21     r[k] = tcopy(v) | 
| Flick@241 | 22   end | 
| Flick@241 | 23   return r | 
| flickerstreak@28 | 24 end | 
| flickerstreak@28 | 25 | 
| Flick@241 | 26 -- traverse a table tree by key list and fetch the result or first nil | 
| Flick@241 | 27 local function tfetch(t, ...) | 
| Flick@241 | 28   for i = 1, select('#', ...) do | 
| Flick@241 | 29     t = t and t[select(i, ...)] | 
| Flick@241 | 30   end | 
| Flick@241 | 31   return t | 
| Flick@241 | 32 end | 
| Flick@241 | 33 | 
| Flick@241 | 34 -- traverse a table tree by key list and build tree as necessary | 
| Flick@241 | 35 local function tbuild(t, ...) | 
| Flick@241 | 36   for i = 1, select('#', ...) do | 
| Flick@241 | 37     local key = select(i, ...) | 
| Flick@241 | 38     if not t[key] then t[key] = { } end | 
| Flick@241 | 39     t = t[key] | 
| Flick@241 | 40   end | 
| Flick@241 | 41   return t | 
| Flick@241 | 42 end | 
| Flick@241 | 43 | 
| Flick@241 | 44 -- return a new array of keys of table 't', sorted by comparing | 
| Flick@241 | 45 -- sub-fields (obtained via tfetch) of the table values | 
| Flick@241 | 46 local function fieldsort( t, ... ) | 
| Flick@241 | 47   local r = { } | 
| Flick@241 | 48   for k in pairs(t) do | 
| Flick@241 | 49     table.insert(r,k) | 
| Flick@241 | 50   end | 
| Flick@241 | 51   local path = { ... } | 
| Flick@241 | 52   table.sort(r, function(lhs, rhs) | 
| Flick@241 | 53      local olhs = tfetch(t[lhs], unpack(path)) or 0 | 
| Flick@241 | 54      local orhs = tfetch(t[rhs], unpack(path)) or 0 | 
| Flick@241 | 55      return olhs < orhs | 
| Flick@241 | 56     end) | 
| Flick@241 | 57   return r | 
| Flick@241 | 58 end | 
| Flick@241 | 59 | 
| Flick@241 | 60 -- store in the addon table | 
| Flick@241 | 61 addonTable.tcopy = tcopy | 
| Flick@241 | 62 addonTable.tfetch = tfetch | 
| Flick@241 | 63 addonTable.tbuild = tbuild | 
| Flick@241 | 64 addonTable.fieldsort = fieldsort | 
| Flick@241 | 65 | 
| flickerstreak@205 | 66 ------ Core ------ | 
| flickerstreak@205 | 67 local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", | 
| flickerstreak@205 | 68   "AceEvent-3.0" | 
| flickerstreak@205 | 69 ) | 
| flickerstreak@205 | 70 addonTable.ReAction = ReAction | 
| Flick@246 | 71 ReAction.version = GetAddOnMetadata("ReAction","Version") | 
| flickerstreak@205 | 72 ReAction.L = L | 
| flickerstreak@205 | 73 ReAction.LKB = LKB | 
| flickerstreak@28 | 74 | 
| flickerstreak@205 | 75 | 
| flickerstreak@223 | 76 ReAction.barTypes = { } | 
| flickerstreak@223 | 77 | 
| flickerstreak@205 | 78 ------ Handlers ------ | 
| flickerstreak@28 | 79 function ReAction:OnInitialize() | 
| flickerstreak@28 | 80   self.db = LibStub("AceDB-3.0"):New("ReAction_DB", | 
| flickerstreak@213 | 81     self.defaultProfile, | 
| flickerstreak@182 | 82     true -- use global 'Default' (locale-specific) | 
| flickerstreak@28 | 83   ) | 
| flickerstreak@205 | 84 | 
| flickerstreak@213 | 85   self:UpgradeProfile() | 
| flickerstreak@211 | 86 | 
| flickerstreak@205 | 87   self.bars = { } | 
| flickerstreak@205 | 88 | 
| flickerstreak@213 | 89   self.LBF = LibStub("LibButtonFacade",true) | 
| flickerstreak@213 | 90   if self.LBF then | 
| flickerstreak@213 | 91     self.LBF:RegisterSkinCallback("ReAction", self.OnSkinChanged, self) | 
| flickerstreak@213 | 92   end | 
| flickerstreak@213 | 93 | 
| flickerstreak@211 | 94   -- It's fairly normal to use the Blizzard vehicle bar, and to have | 
| flickerstreak@211 | 95   -- your regular buttons in the same location. If you do this, and don't | 
| flickerstreak@211 | 96   -- bother to hide your buttons, they'll obscure some parts of the vehicle bar. | 
| flickerstreak@211 | 97   VehicleMenuBar:SetFrameLevel(VehicleMenuBar:GetFrameLevel()+3) | 
| flickerstreak@211 | 98 | 
| flickerstreak@205 | 99   self.callbacks = LibStub("CallbackHandler-1.0"):New(self) | 
| Flick@227 | 100 | 
| flickerstreak@184 | 101   LKB.RegisterCallback(self,"LIBKEYBOUND_ENABLED") | 
| flickerstreak@184 | 102   LKB.RegisterCallback(self,"LIBKEYBOUND_DISABLED") | 
| flickerstreak@207 | 103   LKB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED") | 
| Flick@227 | 104 | 
| Flick@227 | 105   -- see Profile.lua for these callback implementations | 
| Flick@227 | 106   self.db.RegisterCallback(self,"OnProfileChanged") | 
| Flick@227 | 107   self.db.RegisterCallback(self,"OnProfileCopied","OnProfileChanged") | 
| Flick@227 | 108   self.db.RegisterCallback(self,"OnNewProfile") | 
| Flick@227 | 109   self.db.RegisterCallback(self,"OnProfileReset", "OnNewProfile") | 
| Flick@227 | 110 | 
| flickerstreak@182 | 111   self:RegisterEvent("PLAYER_REGEN_DISABLED") | 
| Flick@242 | 112   self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") | 
| Flick@227 | 113 | 
| flickerstreak@182 | 114   self:InitializeOptions() | 
| flickerstreak@28 | 115 end | 
| flickerstreak@28 | 116 | 
| flickerstreak@28 | 117 function ReAction:OnEnable() | 
| flickerstreak@205 | 118   self:InitializeBars() | 
| Flick@242 | 119   self:UPDATE_SHAPESHIFT_FORMS() -- it doesn't fire on a /reloadui | 
| flickerstreak@28 | 120 end | 
| flickerstreak@28 | 121 | 
| flickerstreak@28 | 122 function ReAction:OnDisable() | 
| flickerstreak@205 | 123   self:TearDownBars() | 
| flickerstreak@28 | 124 end | 
| flickerstreak@28 | 125 | 
| flickerstreak@33 | 126 function ReAction:PLAYER_REGEN_DISABLED() | 
| flickerstreak@205 | 127   if self.configMode == true then | 
| flickerstreak@63 | 128     self:UserError(L["ReAction config mode disabled during combat."]) | 
| flickerstreak@33 | 129     self:SetConfigMode(false) | 
| flickerstreak@88 | 130     self:SetKeybindMode(false) | 
| flickerstreak@185 | 131     self:CloseEditor() | 
| flickerstreak@33 | 132   end | 
| flickerstreak@33 | 133 end | 
| flickerstreak@33 | 134 | 
| Flick@243 | 135 function ReAction:UPDATE_SHAPESHIFT_FORMS() | 
| Flick@242 | 136   -- Re-parse the rules table according to the new form list. | 
| Flick@242 | 137   -- This happens both at initial login (after PLAYER_ENTERING_WORLD) | 
| Flick@242 | 138   -- as well as when gaining new abilities. | 
| Flick@243 | 139   self.Bar:InitRuleFormats() | 
| Flick@242 | 140   for _, bar in self:IterateBars() do | 
| Flick@242 | 141     bar:ApplyStates() | 
| Flick@242 | 142   end | 
| Flick@242 | 143 end | 
| Flick@242 | 144 | 
| flickerstreak@88 | 145 function ReAction:LIBKEYBOUND_ENABLED( evt ) | 
| flickerstreak@88 | 146   self:SetKeybindMode(true) | 
| flickerstreak@88 | 147 end | 
| flickerstreak@88 | 148 | 
| flickerstreak@88 | 149 function ReAction:LIBKEYBOUND_DISABLED( evt ) | 
| flickerstreak@88 | 150   return self:SetKeybindMode(false) | 
| flickerstreak@88 | 151 end | 
| flickerstreak@88 | 152 | 
| flickerstreak@213 | 153 function ReAction:OnSkinChanged( skinID, gloss, backdrop, group, button, colors ) | 
| flickerstreak@213 | 154   if group == nil then | 
| flickerstreak@213 | 155     -- don't store global | 
| flickerstreak@213 | 156   else | 
| flickerstreak@213 | 157     -- 'group' is the bar-name | 
| flickerstreak@213 | 158     local bar = self:GetBar(group) | 
| flickerstreak@213 | 159     if bar then | 
| flickerstreak@213 | 160       local c = bar:GetConfig().ButtonFacade | 
| flickerstreak@213 | 161       if c then | 
| flickerstreak@213 | 162         c.skinID   = skinID | 
| flickerstreak@213 | 163         c.gloss    = gloss | 
| flickerstreak@213 | 164         c.backdrop = backdrop | 
| flickerstreak@213 | 165         c.colors   = colors | 
| flickerstreak@213 | 166       end | 
| flickerstreak@213 | 167     end | 
| flickerstreak@213 | 168   end | 
| flickerstreak@213 | 169 end | 
| flickerstreak@213 | 170 | 
| flickerstreak@33 | 171 | 
| flickerstreak@205 | 172 ------ Methods ------ | 
| flickerstreak@77 | 173 | 
| flickerstreak@61 | 174 function ReAction:UserError(msg) | 
| flickerstreak@61 | 175   UIErrorsFrame:AddMessage(msg) | 
| flickerstreak@61 | 176 end | 
| flickerstreak@61 | 177 | 
| flickerstreak@205 | 178 function ReAction:GetBar(arg) | 
| flickerstreak@205 | 179   if type(arg) == "string" then | 
| flickerstreak@205 | 180     return self.bars[arg], arg | 
| flickerstreak@205 | 181   elseif type(arg) == "table" then -- reverse lookup | 
| flickerstreak@205 | 182     for name, bar in pairs(self.bars) do | 
| flickerstreak@205 | 183       if arg == bar then | 
| flickerstreak@205 | 184         return bar, name | 
| flickerstreak@205 | 185       end | 
| flickerstreak@205 | 186     end | 
| flickerstreak@205 | 187   else | 
| flickerstreak@205 | 188     error("ReAction:GetBar() requires either a name or a bar table arg") | 
| flickerstreak@205 | 189   end | 
| flickerstreak@184 | 190 end | 
| flickerstreak@184 | 191 | 
| flickerstreak@205 | 192 function ReAction:IterateBars() | 
| flickerstreak@205 | 193   return pairs(self.bars) | 
| flickerstreak@205 | 194 end | 
| flickerstreak@184 | 195 | 
| flickerstreak@63 | 196 -- usage: | 
| flickerstreak@91 | 197 --  (1) ReAction:CreateBar(name, [cfgTable]) | 
| flickerstreak@63 | 198 --  (2) ReAction:CreateBar(name, "barType", [nRows], [nCols], [btnSize], [btnSpacing]) | 
| flickerstreak@91 | 199 function ReAction:CreateBar(name, config, ...) | 
| flickerstreak@91 | 200   local profile = self.db.profile | 
| flickerstreak@91 | 201 | 
| flickerstreak@217 | 202   name = tostring(name) | 
| flickerstreak@217 | 203   if not name or name == "" then | 
| flickerstreak@217 | 204     error("ReAction:CreateBar() - bar name string required") | 
| flickerstreak@217 | 205   elseif self.bars[name] then | 
| flickerstreak@217 | 206     self:UserError(format(L["ReAction: name '%s' already in use"],name)) | 
| flickerstreak@217 | 207     return nil | 
| flickerstreak@91 | 208   end | 
| flickerstreak@91 | 209 | 
| flickerstreak@223 | 210   local class | 
| flickerstreak@91 | 211   if type(config) == "string" then | 
| flickerstreak@223 | 212     class = self.barTypes[config] | 
| flickerstreak@218 | 213     if not class then | 
| flickerstreak@218 | 214       error(("ReAction:CreateBar() - unknown bar type '%s'"):format(config)) | 
| flickerstreak@48 | 215     end | 
| flickerstreak@218 | 216     config = tcopy(class:GetDefaultBarConfig()) | 
| flickerstreak@218 | 217     config.btnRows    = select(1,...) or config.btnRows | 
| flickerstreak@218 | 218     config.btnColumns = select(2,...) or config.btnColumns | 
| flickerstreak@218 | 219     config.btnWidth   = select(3,...) or config.btnWidth | 
| flickerstreak@218 | 220     config.btnHeight  = select(3,...) or config.btnHeight | 
| flickerstreak@218 | 221     config.spacing    = select(4,...) or config.spacing | 
| flickerstreak@48 | 222     config.width      = config.width or config.btnColumns*(config.btnWidth + config.spacing) + 1 | 
| flickerstreak@48 | 223     config.height     = config.height or config.btnRows*(config.btnHeight + config.spacing) + 1 | 
| flickerstreak@81 | 224     config.anchor     = config.anchor or "UIParent" | 
| flickerstreak@81 | 225     config.point      = config.point or "BOTTOM" | 
| flickerstreak@81 | 226     config.relpoint   = config.relpoint or "BOTTOM" | 
| flickerstreak@48 | 227     config.y          = config.y or 200 | 
| flickerstreak@48 | 228     config.x          = config.x or 0 | 
| flickerstreak@223 | 229   else | 
| flickerstreak@223 | 230     config = config or profile.bars[name] or { } | 
| Flick@248 | 231     if not config then | 
| Flick@248 | 232       error(("ReAction: Unable to fetch config table for bar '%s'"):format(name)) | 
| Flick@248 | 233     elseif not config.type or not self.barTypes[config.type] then | 
| Flick@248 | 234       error(("ReAction: Unrecognized bar type '%s' for bar '%s'"):format(tostring(config.type), name)) | 
| flickerstreak@223 | 235     end | 
| flickerstreak@223 | 236     class = self.barTypes[config.type] | 
| flickerstreak@48 | 237   end | 
| flickerstreak@91 | 238 | 
| flickerstreak@91 | 239   profile.bars[name] = config | 
| Flick@234 | 240   local bar = self.Bar:New( name, config, class )  -- ReAction.Bar defined in Bar.lua | 
| flickerstreak@205 | 241   self.bars[name] = bar | 
| flickerstreak@205 | 242   self.callbacks:Fire("OnCreateBar", bar, name) | 
| flickerstreak@205 | 243   if self.configMode then | 
| flickerstreak@33 | 244     bar:ShowControls(true) | 
| flickerstreak@33 | 245   end | 
| flickerstreak@33 | 246 | 
| flickerstreak@28 | 247   return bar | 
| flickerstreak@28 | 248 end | 
| flickerstreak@28 | 249 | 
| flickerstreak@205 | 250 function ReAction:DestroyBar(x) | 
| flickerstreak@205 | 251   local bar, name = self:GetBar(x) | 
| flickerstreak@63 | 252   if bar and name then | 
| flickerstreak@205 | 253     self.bars[name] = nil | 
| flickerstreak@205 | 254     self.callbacks:Fire("OnDestroyBar", bar, name) | 
| flickerstreak@205 | 255     bar:Destroy() | 
| flickerstreak@28 | 256   end | 
| flickerstreak@28 | 257 end | 
| flickerstreak@28 | 258 | 
| flickerstreak@205 | 259 function ReAction:InitializeBars() | 
| flickerstreak@205 | 260   if not self.barsInitialized then | 
| flickerstreak@211 | 261     self:ManageBlizzardBars() | 
| flickerstreak@211 | 262 | 
| flickerstreak@205 | 263     for name, config in pairs(self.db.profile.bars) do | 
| flickerstreak@205 | 264       if config then | 
| flickerstreak@205 | 265         self:CreateBar(name, config) | 
| flickerstreak@205 | 266       end | 
| flickerstreak@205 | 267     end | 
| Flick@242 | 268     -- re-anchor in case anchor order does not match init order | 
| flickerstreak@205 | 269     for name, bar in pairs(self.bars) do | 
| flickerstreak@205 | 270       bar:ApplyAnchor() | 
| flickerstreak@205 | 271     end | 
| flickerstreak@205 | 272     self.barsInitialized = true | 
| flickerstreak@205 | 273   end | 
| flickerstreak@205 | 274 end | 
| flickerstreak@205 | 275 | 
| flickerstreak@205 | 276 function ReAction:TearDownBars() | 
| flickerstreak@205 | 277   for name, bar in pairs(self.bars) do | 
| flickerstreak@205 | 278     if bar then | 
| flickerstreak@208 | 279       self.bars[name] = self:DestroyBar(bar) | 
| flickerstreak@205 | 280     end | 
| flickerstreak@205 | 281   end | 
| flickerstreak@205 | 282   self.barsInitialized = false | 
| flickerstreak@205 | 283 end | 
| flickerstreak@205 | 284 | 
| flickerstreak@205 | 285 function ReAction:RebuildAll() | 
| flickerstreak@205 | 286   self:TearDownBars() | 
| flickerstreak@205 | 287   self:InitializeBars() | 
| flickerstreak@28 | 288 end | 
| flickerstreak@28 | 289 | 
| flickerstreak@28 | 290 function ReAction:RenameBar(x, newname) | 
| flickerstreak@205 | 291   local bar, name = self:GetBar(x) | 
| flickerstreak@63 | 292   if type(newname) ~= "string" then | 
| flickerstreak@63 | 293     error("ReAction:RenameBar() - second argument must be a string") | 
| flickerstreak@63 | 294   end | 
| flickerstreak@63 | 295   if bar and name and #newname > 0 then | 
| flickerstreak@127 | 296     if newname == name then | 
| flickerstreak@127 | 297       return | 
| flickerstreak@127 | 298     end | 
| flickerstreak@205 | 299     if self.bars[newname] then | 
| flickerstreak@127 | 300       self:UserError(format(L["ReAction: name '%s' already in use"],newname)) | 
| flickerstreak@47 | 301     else | 
| flickerstreak@205 | 302       self.bars[newname], self.bars[name] = self.bars[name], nil | 
| flickerstreak@47 | 303       bar:SetName(newname or "") | 
| flickerstreak@47 | 304       local cfg = self.db.profile.bars | 
| flickerstreak@47 | 305       cfg[newname], cfg[name] = cfg[name], nil | 
| flickerstreak@205 | 306       self.callbacks:Fire("OnRenameBar", bar, name, newname) | 
| flickerstreak@28 | 307     end | 
| flickerstreak@28 | 308   end | 
| flickerstreak@28 | 309 end | 
| flickerstreak@28 | 310 | 
| flickerstreak@205 | 311 function ReAction:EraseBar(x) | 
| flickerstreak@205 | 312   local bar, name = self:GetBar(x) | 
| flickerstreak@63 | 313   if bar and name then | 
| flickerstreak@208 | 314     self:DestroyBar(bar) | 
| flickerstreak@205 | 315     self.db.profile.bars[name] = nil | 
| flickerstreak@63 | 316   end | 
| flickerstreak@63 | 317 end | 
| flickerstreak@63 | 318 | 
| flickerstreak@211 | 319 local blizzFrames = { | 
| flickerstreak@211 | 320   MainMenuBar, | 
| flickerstreak@211 | 321   MultiBarLeft, | 
| flickerstreak@211 | 322   MultiBarRight, | 
| flickerstreak@211 | 323   MultiBarBottomLeft, | 
| flickerstreak@211 | 324   MultiBarBottomRight, | 
| flickerstreak@211 | 325 } | 
| flickerstreak@211 | 326 | 
| flickerstreak@211 | 327 local hideFrame = CreateFrame("Frame") | 
| flickerstreak@211 | 328 hideFrame:Hide() | 
| flickerstreak@211 | 329 local hiddenParents = { } | 
| flickerstreak@211 | 330 local function ManageBlizzFrame(f, hide) | 
| flickerstreak@211 | 331   if hide and not hiddenParents[f] then | 
| flickerstreak@211 | 332     hiddenParents[f] = f:GetParent() | 
| flickerstreak@211 | 333     f:SetParent(hideFrame) | 
| flickerstreak@211 | 334   elseif not hide and hiddenParents[f] then | 
| flickerstreak@211 | 335     f:SetParent(hiddenParents[f]) | 
| flickerstreak@211 | 336     hiddenParents[f] = nil | 
| flickerstreak@211 | 337     if f:IsShown() then | 
| flickerstreak@211 | 338       f:Show() -- refresh | 
| flickerstreak@211 | 339     end | 
| flickerstreak@211 | 340   end | 
| flickerstreak@211 | 341 end | 
| flickerstreak@211 | 342 | 
| flickerstreak@211 | 343 function ReAction:ManageBlizzardBars() | 
| flickerstreak@211 | 344   for _, f in pairs(blizzFrames) do | 
| flickerstreak@211 | 345     ManageBlizzFrame(f, self.db.profile.options.hideBlizzardBars) | 
| flickerstreak@211 | 346   end | 
| flickerstreak@211 | 347   ManageBlizzFrame(VehicleMenuBar, self.db.profile.options.hideBlizzardVehicleBar) | 
| flickerstreak@211 | 348 end | 
| flickerstreak@211 | 349 | 
| flickerstreak@218 | 350 function ReAction:RegisterBarType( class, isDefault ) | 
| Flick@231 | 351   local name = class:GetButtonTypeID() | 
| flickerstreak@218 | 352   self.barTypes[name] = class | 
| flickerstreak@218 | 353   if isDefault then | 
| flickerstreak@218 | 354     self.defaultBarType = name | 
| flickerstreak@48 | 355   end | 
| flickerstreak@48 | 356 end | 
| flickerstreak@48 | 357 | 
| flickerstreak@63 | 358 function ReAction:IterateBarTypes() | 
| flickerstreak@218 | 359   return pairs(self.barTypes) | 
| flickerstreak@63 | 360 end | 
| flickerstreak@63 | 361 | 
| flickerstreak@218 | 362 function ReAction:GetDefaultBarConfig(barType) | 
| flickerstreak@218 | 363   if barType and self.barTypes[barType] then | 
| flickerstreak@218 | 364     return self.barTypes[barType]:GetDefaultBarConfig() | 
| flickerstreak@63 | 365   end | 
| flickerstreak@63 | 366 end | 
| flickerstreak@63 | 367 | 
| flickerstreak@63 | 368 function ReAction:GetBarTypeOptions( fill ) | 
| flickerstreak@63 | 369   fill = fill or { } | 
| flickerstreak@63 | 370   for k in self:IterateBarTypes() do | 
| flickerstreak@63 | 371     fill[k] = k | 
| flickerstreak@63 | 372   end | 
| flickerstreak@63 | 373   return fill | 
| flickerstreak@63 | 374 end | 
| flickerstreak@63 | 375 | 
| flickerstreak@63 | 376 function ReAction:GetDefaultBarType() | 
| flickerstreak@218 | 377   return self.defaultBarType | 
| flickerstreak@63 | 378 end | 
| flickerstreak@63 | 379 | 
| flickerstreak@33 | 380 function ReAction:SetConfigMode( mode ) | 
| flickerstreak@205 | 381   if mode ~= self.configMode then | 
| flickerstreak@207 | 382     if mode then | 
| flickerstreak@207 | 383       self:SetKeybindMode(false) | 
| flickerstreak@207 | 384     end | 
| flickerstreak@205 | 385     self.configMode = mode | 
| flickerstreak@205 | 386     self.callbacks:Fire("OnConfigModeChanged", mode) | 
| flickerstreak@77 | 387   end | 
| flickerstreak@63 | 388 end | 
| flickerstreak@63 | 389 | 
| flickerstreak@63 | 390 function ReAction:GetConfigMode() | 
| flickerstreak@205 | 391   return self.configMode | 
| flickerstreak@33 | 392 end | 
| flickerstreak@38 | 393 | 
| flickerstreak@88 | 394 function ReAction:SetKeybindMode( mode ) | 
| flickerstreak@205 | 395   if mode ~= self.kbMode then | 
| flickerstreak@88 | 396     if mode then | 
| flickerstreak@207 | 397       self:SetConfigMode(false) | 
| flickerstreak@184 | 398       LKB:Activate() | 
| flickerstreak@88 | 399     else | 
| flickerstreak@184 | 400       LKB:Deactivate() | 
| flickerstreak@88 | 401     end | 
| flickerstreak@207 | 402     for _, bar in self:IterateBars() do | 
| flickerstreak@207 | 403       bar:SetKeybindMode(mode) | 
| flickerstreak@207 | 404     end | 
| flickerstreak@205 | 405     self.kbMode = LKB:IsShown() or false | 
| flickerstreak@88 | 406   end | 
| flickerstreak@88 | 407 end | 
| flickerstreak@88 | 408 | 
| flickerstreak@88 | 409 function ReAction:GetKeybindMode( mode ) | 
| flickerstreak@205 | 410   return self.kbMode | 
| flickerstreak@88 | 411 end | 
| Flick@225 | 412 | 
| Flick@225 | 413 | 
| Flick@225 | 414 -- ConfigMode support | 
| Flick@225 | 415 CONFIGMODE_CALLBACKS = CONFIGMODE_CALLBACKS or {} | 
| Flick@225 | 416 | 
| Flick@225 | 417 function CONFIGMODE_CALLBACKS.ReAction( action, mode ) | 
| Flick@225 | 418   if action == "ON" then | 
| Flick@225 | 419     ReAction:SetConfigMode(true) | 
| Flick@225 | 420   elseif action == "OFF" then | 
| Flick@225 | 421     ReAction:SetConfigMode(false) | 
| Flick@225 | 422   elseif action == "LISTMODES" then | 
| Flick@225 | 423     -- no modes | 
| Flick@225 | 424   end | 
| Flick@225 | 425 end | 
| Flick@225 | 426 |