flickerstreak@63
|
1 --[[
|
flickerstreak@63
|
2 ReAction.lua
|
flickerstreak@63
|
3
|
flickerstreak@182
|
4 The ReAction core manages several collections:
|
flickerstreak@63
|
5 - modules (via AceAddon)
|
flickerstreak@63
|
6 - bars
|
flickerstreak@182
|
7 - bar options
|
flickerstreak@63
|
8 - bar-type constructors
|
flickerstreak@63
|
9
|
flickerstreak@90
|
10 and publishes events when those collections change. It also implements a couple properties
|
flickerstreak@63
|
11 and has a couple convenience methods which drill down to particular modules.
|
flickerstreak@63
|
12
|
flickerstreak@90
|
13 Most of the "real work" of the addon happens in Bar.lua, Overlay.lua, State.lua, and the various modules.
|
flickerstreak@63
|
14
|
flickerstreak@63
|
15 Events (with handler arguments):
|
flickerstreak@63
|
16 --------------------------------
|
flickerstreak@63
|
17 "OnCreateBar" (bar, name) : after a bar object is created
|
flickerstreak@63
|
18 "OnDestroyBar" (bar, name) : before a bar object is destroyed
|
flickerstreak@63
|
19 "OnEraseBar" (bar, name) : before a bar config is removed from the profile db
|
flickerstreak@63
|
20 "OnRenameBar" (bar, oldname, newname) : after a bar is renamed
|
flickerstreak@63
|
21 "OnRefreshBar" (bar, name) : after a bar's state has been updated
|
flickerstreak@63
|
22 "OnOptionsRefreshed" () : after the global options tree is refreshed
|
flickerstreak@63
|
23 "OnConfigModeChanged" (mode) : after the config mode is changed
|
flickerstreak@63
|
24 "OnBarOptionGeneratorRegistered" (module, function) : after an options generator function is registered
|
flickerstreak@63
|
25
|
flickerstreak@63
|
26 ReAction is also an AceAddon-3.0 and contains an AceDB-3.0, which in turn publish more events.
|
flickerstreak@63
|
27 ]]--
|
flickerstreak@182
|
28 local addonName, addonTable = ...
|
flickerstreak@182
|
29 local ReAction = LibStub("AceAddon-3.0"):NewAddon( addonName,
|
flickerstreak@33
|
30 "AceEvent-3.0"
|
flickerstreak@30
|
31 )
|
flickerstreak@184
|
32 ReAction.version = GetAddOnMetadata(addonName,"Version")
|
flickerstreak@175
|
33 addonTable.ReAction = ReAction
|
flickerstreak@27
|
34
|
flickerstreak@33
|
35 ------ LIBRARIES ------
|
flickerstreak@63
|
36 local callbacks = LibStub("CallbackHandler-1.0"):New(ReAction)
|
flickerstreak@184
|
37 local LKB = LibStub("LibKeyBound-1.0")
|
flickerstreak@33
|
38 local L = LibStub("AceLocale-3.0"):GetLocale("ReAction")
|
flickerstreak@33
|
39 ReAction.L = L
|
flickerstreak@184
|
40 ReAction.LKB = LKB
|
flickerstreak@182
|
41 ReAction.callbacks = callbacks
|
flickerstreak@33
|
42
|
flickerstreak@28
|
43 ------ PRIVATE ------
|
flickerstreak@116
|
44 local private = { }
|
flickerstreak@63
|
45 local bars = {}
|
flickerstreak@63
|
46 local defaultBarConfig = {}
|
flickerstreak@63
|
47 local barOptionGenerators = { }
|
flickerstreak@63
|
48
|
flickerstreak@116
|
49
|
flickerstreak@182
|
50 local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, CallModuleMethod
|
flickerstreak@28
|
51 do
|
flickerstreak@28
|
52 local pcall = pcall
|
flickerstreak@28
|
53 local geterrorhandler = geterrorhandler
|
flickerstreak@63
|
54 local self = ReAction
|
flickerstreak@63
|
55 local inited = false
|
flickerstreak@28
|
56
|
flickerstreak@63
|
57 function SelectBar(x)
|
flickerstreak@28
|
58 local bar, name
|
flickerstreak@28
|
59 if type(x) == "string" then
|
flickerstreak@28
|
60 name = x
|
flickerstreak@63
|
61 bar = self:GetBar(name)
|
flickerstreak@50
|
62 else
|
flickerstreak@63
|
63 for k,v in pairs(bars) do
|
flickerstreak@50
|
64 if v == x then
|
flickerstreak@28
|
65 name = k
|
flickerstreak@50
|
66 bar = x
|
flickerstreak@28
|
67 end
|
flickerstreak@28
|
68 end
|
flickerstreak@28
|
69 end
|
flickerstreak@28
|
70 return bar, name
|
flickerstreak@28
|
71 end
|
flickerstreak@28
|
72
|
flickerstreak@63
|
73 function DestroyBar(x)
|
flickerstreak@28
|
74 local bar, name = SelectBar(x)
|
flickerstreak@63
|
75 if bar and name then
|
flickerstreak@63
|
76 bars[name] = nil
|
flickerstreak@63
|
77 callbacks:Fire("OnDestroyBar", bar, name)
|
flickerstreak@28
|
78 bar:Destroy()
|
flickerstreak@28
|
79 end
|
flickerstreak@28
|
80 end
|
flickerstreak@28
|
81
|
flickerstreak@63
|
82 function InitializeBars()
|
flickerstreak@63
|
83 if not inited then
|
flickerstreak@63
|
84 for name, config in pairs(self.db.profile.bars) do
|
flickerstreak@28
|
85 if config then
|
flickerstreak@63
|
86 self:CreateBar(name, config)
|
flickerstreak@28
|
87 end
|
flickerstreak@28
|
88 end
|
flickerstreak@101
|
89 -- re-anchor and refresh in case anchor order does not match init order
|
flickerstreak@63
|
90 for name, bar in pairs(bars) do
|
flickerstreak@63
|
91 bar:ApplyAnchor()
|
flickerstreak@101
|
92 callbacks:Fire("OnRefreshBar", bar, name)
|
flickerstreak@63
|
93 end
|
flickerstreak@63
|
94 inited = true
|
flickerstreak@28
|
95 end
|
flickerstreak@28
|
96 end
|
flickerstreak@28
|
97
|
flickerstreak@63
|
98 function TearDownBars()
|
flickerstreak@63
|
99 for name, bar in pairs(bars) do
|
flickerstreak@28
|
100 if bar then
|
flickerstreak@63
|
101 bars[name] = DestroyBar(bar)
|
flickerstreak@28
|
102 end
|
flickerstreak@28
|
103 end
|
flickerstreak@63
|
104 inited = false
|
flickerstreak@28
|
105 end
|
flickerstreak@28
|
106
|
flickerstreak@63
|
107 function DeepCopy(x)
|
flickerstreak@28
|
108 if type(x) ~= "table" then
|
flickerstreak@28
|
109 return x
|
flickerstreak@28
|
110 end
|
flickerstreak@28
|
111 local r = {}
|
flickerstreak@28
|
112 for k,v in pairs(x) do
|
flickerstreak@28
|
113 r[k] = DeepCopy(v)
|
flickerstreak@28
|
114 end
|
flickerstreak@28
|
115 return r
|
flickerstreak@28
|
116 end
|
flickerstreak@28
|
117
|
flickerstreak@63
|
118 function CallModuleMethod(modulename, method, ...)
|
flickerstreak@63
|
119 local m = self:GetModule(modulename,true)
|
flickerstreak@63
|
120 if m then
|
flickerstreak@63
|
121 if type(m) == "table" and type(m[method]) == "function" then
|
flickerstreak@63
|
122 m[method](m,...)
|
flickerstreak@63
|
123 end
|
flickerstreak@30
|
124 end
|
flickerstreak@30
|
125 end
|
flickerstreak@92
|
126
|
flickerstreak@28
|
127 end
|
flickerstreak@28
|
128
|
flickerstreak@28
|
129
|
flickerstreak@28
|
130 ------ HANDLERS ------
|
flickerstreak@28
|
131 function ReAction:OnInitialize()
|
flickerstreak@28
|
132 self.db = LibStub("AceDB-3.0"):New("ReAction_DB",
|
flickerstreak@28
|
133 {
|
flickerstreak@28
|
134 profile = {
|
flickerstreak@28
|
135 bars = { },
|
flickerstreak@184
|
136 defaultBar = { },
|
flickerstreak@184
|
137 closeOptionsOnEditorLaunch = true,
|
flickerstreak@28
|
138 }
|
flickerstreak@111
|
139 },
|
flickerstreak@182
|
140 true -- use global 'Default' (locale-specific)
|
flickerstreak@28
|
141 )
|
flickerstreak@184
|
142 LKB.RegisterCallback(self,"LIBKEYBOUND_ENABLED")
|
flickerstreak@184
|
143 LKB.RegisterCallback(self,"LIBKEYBOUND_DISABLED")
|
flickerstreak@88
|
144
|
flickerstreak@182
|
145 self:RegisterEvent("PLAYER_REGEN_DISABLED")
|
flickerstreak@63
|
146
|
flickerstreak@182
|
147 self:InitializeOptions()
|
flickerstreak@28
|
148 end
|
flickerstreak@28
|
149
|
flickerstreak@28
|
150 function ReAction:OnEnable()
|
flickerstreak@28
|
151 InitializeBars()
|
flickerstreak@28
|
152 end
|
flickerstreak@28
|
153
|
flickerstreak@28
|
154 function ReAction:OnDisable()
|
flickerstreak@28
|
155 TearDownBars()
|
flickerstreak@28
|
156 end
|
flickerstreak@28
|
157
|
flickerstreak@33
|
158 function ReAction:PLAYER_REGEN_DISABLED()
|
flickerstreak@63
|
159 if private.configMode == true then
|
flickerstreak@63
|
160 self:UserError(L["ReAction config mode disabled during combat."])
|
flickerstreak@33
|
161 self:SetConfigMode(false)
|
flickerstreak@88
|
162 self:SetKeybindMode(false)
|
flickerstreak@33
|
163 end
|
flickerstreak@33
|
164 end
|
flickerstreak@33
|
165
|
flickerstreak@88
|
166 function ReAction:LIBKEYBOUND_ENABLED( evt )
|
flickerstreak@88
|
167 self:SetKeybindMode(true)
|
flickerstreak@88
|
168 end
|
flickerstreak@88
|
169
|
flickerstreak@88
|
170 function ReAction:LIBKEYBOUND_DISABLED( evt )
|
flickerstreak@88
|
171 return self:SetKeybindMode(false)
|
flickerstreak@88
|
172 end
|
flickerstreak@88
|
173
|
flickerstreak@33
|
174
|
flickerstreak@28
|
175
|
flickerstreak@28
|
176 ------ API ------
|
flickerstreak@77
|
177
|
flickerstreak@61
|
178 function ReAction:UserError(msg)
|
flickerstreak@61
|
179 -- any user errors should be flashed to the UIErrorsFrame
|
flickerstreak@61
|
180 UIErrorsFrame:AddMessage(msg)
|
flickerstreak@61
|
181 end
|
flickerstreak@61
|
182
|
flickerstreak@184
|
183 function ReAction:RebuildAll()
|
flickerstreak@184
|
184 TearDownBars()
|
flickerstreak@184
|
185 InitializeBars()
|
flickerstreak@184
|
186 end
|
flickerstreak@184
|
187
|
flickerstreak@184
|
188
|
flickerstreak@63
|
189 -- usage:
|
flickerstreak@91
|
190 -- (1) ReAction:CreateBar(name, [cfgTable])
|
flickerstreak@63
|
191 -- (2) ReAction:CreateBar(name, "barType", [nRows], [nCols], [btnSize], [btnSpacing])
|
flickerstreak@91
|
192 function ReAction:CreateBar(name, config, ...)
|
flickerstreak@91
|
193 local profile = self.db.profile
|
flickerstreak@91
|
194
|
flickerstreak@127
|
195 if name then
|
flickerstreak@127
|
196 if bars[name] then
|
flickerstreak@127
|
197 self:UserError(format(L["ReAction: name '%s' already in use"],name))
|
flickerstreak@127
|
198 return nil
|
flickerstreak@127
|
199 end
|
flickerstreak@127
|
200 else
|
flickerstreak@91
|
201 local prefix = L["Bar "]
|
flickerstreak@91
|
202 local i = 1
|
flickerstreak@91
|
203 repeat
|
flickerstreak@91
|
204 name = prefix..i
|
flickerstreak@91
|
205 i = i + 1
|
flickerstreak@91
|
206 until bars[name] == nil
|
flickerstreak@91
|
207 end
|
flickerstreak@91
|
208
|
flickerstreak@91
|
209 if type(config) == "string" then
|
flickerstreak@91
|
210 config = defaultBarConfig[config]
|
flickerstreak@48
|
211 if not config then
|
flickerstreak@91
|
212 error(("ReAction:CreateBar() - unknown bar type '%s'"):format(tostring(select(1,...))))
|
flickerstreak@48
|
213 end
|
flickerstreak@48
|
214 config = DeepCopy(config)
|
flickerstreak@91
|
215 config.btnRows = select(1,...) or config.btnRows or 1
|
flickerstreak@91
|
216 config.btnColumns = select(2,...) or config.btnColumns or 12
|
flickerstreak@91
|
217 config.btnWidth = select(3,...) or config.btnWidth or 36
|
flickerstreak@91
|
218 config.btnHeight = select(3,...) or config.btnHeight or 36
|
flickerstreak@91
|
219 config.spacing = select(4,...) or config.spacing or 3
|
flickerstreak@48
|
220 config.width = config.width or config.btnColumns*(config.btnWidth + config.spacing) + 1
|
flickerstreak@48
|
221 config.height = config.height or config.btnRows*(config.btnHeight + config.spacing) + 1
|
flickerstreak@81
|
222 config.anchor = config.anchor or "UIParent"
|
flickerstreak@81
|
223 config.point = config.point or "BOTTOM"
|
flickerstreak@81
|
224 config.relpoint = config.relpoint or "BOTTOM"
|
flickerstreak@48
|
225 config.y = config.y or 200
|
flickerstreak@48
|
226 config.x = config.x or 0
|
flickerstreak@48
|
227 end
|
flickerstreak@91
|
228 config = config or profile.bars[name] or DeepCopy(profile.defaultBar)
|
flickerstreak@91
|
229
|
flickerstreak@91
|
230 profile.bars[name] = config
|
flickerstreak@91
|
231 local bar = self.Bar:New( name, config ) -- ReAction.Bar defined in Bar.lua
|
flickerstreak@63
|
232 bars[name] = bar
|
flickerstreak@63
|
233 callbacks:Fire("OnCreateBar", bar, name)
|
flickerstreak@63
|
234 if private.configMode then
|
flickerstreak@33
|
235 bar:ShowControls(true)
|
flickerstreak@33
|
236 end
|
flickerstreak@33
|
237
|
flickerstreak@28
|
238 return bar
|
flickerstreak@28
|
239 end
|
flickerstreak@28
|
240
|
flickerstreak@28
|
241 function ReAction:EraseBar(x)
|
flickerstreak@28
|
242 local bar, name = SelectBar(x)
|
flickerstreak@63
|
243 if bar and name then
|
flickerstreak@63
|
244 callbacks:Fire("OnEraseBar", bar, name)
|
flickerstreak@28
|
245 DestroyBar(bar)
|
flickerstreak@28
|
246 self.db.profile.bars[name] = nil
|
flickerstreak@28
|
247 end
|
flickerstreak@28
|
248 end
|
flickerstreak@28
|
249
|
flickerstreak@28
|
250 function ReAction:GetBar(name)
|
flickerstreak@63
|
251 return bars[name]
|
flickerstreak@63
|
252 end
|
flickerstreak@63
|
253
|
flickerstreak@90
|
254 -- returns pairs of name, bar
|
flickerstreak@63
|
255 function ReAction:IterateBars()
|
flickerstreak@63
|
256 return pairs(bars)
|
flickerstreak@28
|
257 end
|
flickerstreak@28
|
258
|
flickerstreak@28
|
259 function ReAction:RenameBar(x, newname)
|
flickerstreak@28
|
260 local bar, name = SelectBar(x)
|
flickerstreak@63
|
261 if type(newname) ~= "string" then
|
flickerstreak@63
|
262 error("ReAction:RenameBar() - second argument must be a string")
|
flickerstreak@63
|
263 end
|
flickerstreak@63
|
264 if bar and name and #newname > 0 then
|
flickerstreak@127
|
265 if newname == name then
|
flickerstreak@127
|
266 return
|
flickerstreak@127
|
267 end
|
flickerstreak@63
|
268 if bars[newname] then
|
flickerstreak@127
|
269 self:UserError(format(L["ReAction: name '%s' already in use"],newname))
|
flickerstreak@47
|
270 else
|
flickerstreak@63
|
271 bars[newname], bars[name] = bars[name], nil
|
flickerstreak@47
|
272 bar:SetName(newname or "")
|
flickerstreak@47
|
273 local cfg = self.db.profile.bars
|
flickerstreak@47
|
274 cfg[newname], cfg[name] = cfg[name], nil
|
flickerstreak@63
|
275 callbacks:Fire("OnRenameBar", bar, name, newname)
|
flickerstreak@28
|
276 end
|
flickerstreak@28
|
277 end
|
flickerstreak@28
|
278 end
|
flickerstreak@28
|
279
|
flickerstreak@63
|
280 function ReAction:RefreshBar(x)
|
flickerstreak@63
|
281 local bar, name = SelectBar(x)
|
flickerstreak@63
|
282 if bar and name then
|
flickerstreak@63
|
283 callbacks:Fire("OnRefreshBar", bar, name)
|
flickerstreak@63
|
284 end
|
flickerstreak@63
|
285 end
|
flickerstreak@63
|
286
|
flickerstreak@53
|
287 function ReAction:RegisterBarType( name, config, isDefaultChoice )
|
flickerstreak@63
|
288 defaultBarConfig[name] = config
|
flickerstreak@48
|
289 if isDefaultChoice then
|
flickerstreak@81
|
290 private.defaultBarConfigChoice = name
|
flickerstreak@48
|
291 end
|
flickerstreak@48
|
292 self:RefreshOptions()
|
flickerstreak@48
|
293 end
|
flickerstreak@48
|
294
|
flickerstreak@53
|
295 function ReAction:UnregisterBarType( name )
|
flickerstreak@63
|
296 defaultBarConfig[name] = nil
|
flickerstreak@63
|
297 if private.defaultBarConfigChoice == name then
|
flickerstreak@63
|
298 private.defaultBarConfigChoice = nil
|
flickerstreak@48
|
299 end
|
flickerstreak@48
|
300 self:RefreshOptions()
|
flickerstreak@48
|
301 end
|
flickerstreak@48
|
302
|
flickerstreak@63
|
303 function ReAction:IterateBarTypes()
|
flickerstreak@63
|
304 return pairs(defaultBarConfig)
|
flickerstreak@63
|
305 end
|
flickerstreak@63
|
306
|
flickerstreak@63
|
307 function ReAction:GetBarTypeConfig(name)
|
flickerstreak@63
|
308 if name then
|
flickerstreak@63
|
309 return defaultBarConfig[name]
|
flickerstreak@63
|
310 end
|
flickerstreak@63
|
311 end
|
flickerstreak@63
|
312
|
flickerstreak@63
|
313 function ReAction:GetBarTypeOptions( fill )
|
flickerstreak@63
|
314 fill = fill or { }
|
flickerstreak@63
|
315 for k in self:IterateBarTypes() do
|
flickerstreak@63
|
316 fill[k] = k
|
flickerstreak@63
|
317 end
|
flickerstreak@63
|
318 return fill
|
flickerstreak@63
|
319 end
|
flickerstreak@63
|
320
|
flickerstreak@63
|
321 function ReAction:GetDefaultBarType()
|
flickerstreak@63
|
322 return private.defaultBarConfigChoice
|
flickerstreak@63
|
323 end
|
flickerstreak@63
|
324
|
flickerstreak@30
|
325 function ReAction:RefreshOptions()
|
flickerstreak@63
|
326 callbacks:Fire("OnOptionsRefreshed")
|
flickerstreak@63
|
327 end
|
flickerstreak@63
|
328
|
flickerstreak@63
|
329 --
|
flickerstreak@182
|
330 -- In addition to global options, options tables
|
flickerstreak@63
|
331 -- must be generated dynamically for each bar.
|
flickerstreak@63
|
332 --
|
flickerstreak@63
|
333 -- 'func' should be a function or a method string.
|
flickerstreak@63
|
334 -- The function or method will be passed the bar as its parameter.
|
flickerstreak@63
|
335 -- (methods will of course get the module as the first 'self' parameter)
|
flickerstreak@63
|
336 --
|
flickerstreak@63
|
337 -- A generator can be unregistered by passing a nil func.
|
flickerstreak@63
|
338 --
|
flickerstreak@63
|
339 function ReAction:RegisterBarOptionGenerator( module, func )
|
flickerstreak@63
|
340 if not module or type(module) ~= "table" then -- doesn't need to be a proper module, strictly
|
flickerstreak@63
|
341 error("ReAction:RegisterBarOptionGenerator() : Invalid module")
|
flickerstreak@63
|
342 end
|
flickerstreak@63
|
343 if type(func) == "string" then
|
flickerstreak@63
|
344 if not module[func] then
|
flickerstreak@63
|
345 error(("ReAction:RegisterBarOptionGenerator() : Invalid method '%s'"):format(func))
|
flickerstreak@63
|
346 end
|
flickerstreak@63
|
347 elseif func and type(func) ~= "function" then
|
flickerstreak@63
|
348 error("ReAction:RegisterBarOptionGenerator() : Invalid function")
|
flickerstreak@63
|
349 end
|
flickerstreak@63
|
350 barOptionGenerators[module] = func
|
flickerstreak@63
|
351 callbacks:Fire("OnBarOptionGeneratorRegistered", module, func)
|
flickerstreak@63
|
352 end
|
flickerstreak@63
|
353
|
flickerstreak@63
|
354 -- builds a table suitable for use as an AceConfig3 group 'plugins' sub-table
|
flickerstreak@63
|
355 function ReAction:GenerateBarOptionsTable( bar )
|
flickerstreak@63
|
356 local opts = { }
|
flickerstreak@63
|
357 for module, func in pairs(barOptionGenerators) do
|
flickerstreak@63
|
358 local success, r
|
flickerstreak@63
|
359 if type(func) == "string" then
|
flickerstreak@63
|
360 success, r = pcall(module[func], module, bar)
|
flickerstreak@63
|
361 else
|
flickerstreak@63
|
362 success, r = pcall(func, bar)
|
flickerstreak@63
|
363 end
|
flickerstreak@63
|
364 if success then
|
flickerstreak@90
|
365 if r then
|
flickerstreak@90
|
366 opts[module:GetName()] = { [module:GetName()] = r }
|
flickerstreak@90
|
367 end
|
flickerstreak@63
|
368 else
|
flickerstreak@63
|
369 geterrorhandler()(r)
|
flickerstreak@63
|
370 end
|
flickerstreak@63
|
371 end
|
flickerstreak@63
|
372 return opts
|
flickerstreak@30
|
373 end
|
flickerstreak@33
|
374
|
flickerstreak@33
|
375 function ReAction:SetConfigMode( mode )
|
flickerstreak@77
|
376 if mode ~= private.configMode then
|
flickerstreak@77
|
377 private.configMode = mode
|
flickerstreak@77
|
378 callbacks:Fire("OnConfigModeChanged", mode)
|
flickerstreak@77
|
379 end
|
flickerstreak@63
|
380 end
|
flickerstreak@63
|
381
|
flickerstreak@63
|
382 function ReAction:GetConfigMode()
|
flickerstreak@63
|
383 return private.configMode
|
flickerstreak@33
|
384 end
|
flickerstreak@38
|
385
|
flickerstreak@81
|
386 function ReAction:ShowEditor(bar, ...)
|
flickerstreak@81
|
387 CallModuleMethod("ConfigUI","LaunchBarEditor",bar, ...)
|
flickerstreak@47
|
388 end
|
flickerstreak@88
|
389
|
flickerstreak@88
|
390 function ReAction:SetKeybindMode( mode )
|
flickerstreak@88
|
391 if mode ~= private.kbMode then
|
flickerstreak@88
|
392 if mode then
|
flickerstreak@184
|
393 LKB:Activate()
|
flickerstreak@88
|
394 else
|
flickerstreak@184
|
395 LKB:Deactivate()
|
flickerstreak@88
|
396 end
|
flickerstreak@184
|
397 private.kbMode = LKB:IsShown() or false
|
flickerstreak@88
|
398 end
|
flickerstreak@88
|
399 end
|
flickerstreak@88
|
400
|
flickerstreak@88
|
401 function ReAction:GetKeybindMode( mode )
|
flickerstreak@88
|
402 return private.kbMode
|
flickerstreak@88
|
403 end
|