Nenue@0
|
1 --------------------------------------------
|
Nenue@0
|
2 -- KrakTool
|
Nenue@0
|
3 -- Nick
|
Nenue@0
|
4 -- @project-revision@ @project-hash@
|
Nenue@0
|
5 -- @file-revision@ @file-hash@
|
Nenue@0
|
6 -- Created: 6/16/2016 3:47 AM
|
Nenue@0
|
7 --------------------------------------------
|
Nenue@0
|
8 -- kb
|
Nenue@0
|
9 -- .bind(button, key) bind current keystroke to command
|
Nenue@0
|
10 -- .assign(button, command, name, icon) set button command
|
Nenue@0
|
11 -- .release(button) clear button command
|
Nenue@0
|
12 -- .refresh(button) update button contents
|
Nenue@0
|
13 -- .ui() invoke interface
|
Nenue@0
|
14 -- .profile(name) set profile character
|
Nenue@0
|
15 -- .loadbinds(bindings) walk table with SetBinding()
|
Nenue@0
|
16
|
Nenue@1
|
17 local KT = LibKT.register(KeyBinder)
|
Nenue@0
|
18 local kb = KeyBinder
|
Nenue@1
|
19 local db
|
Nenue@1
|
20
|
Nenue@0
|
21 local MIN_BIND_SLOTS = 32
|
Nenue@0
|
22 local BINDS_PER_ROW = 8
|
Nenue@0
|
23 local KEY_BUTTON_SIZE = 40
|
Nenue@0
|
24 local TAB_OFFSET = 12
|
Nenue@0
|
25 local TAB_HEIGHT = 40
|
Nenue@0
|
26 local TAB_SPACING = 2
|
Nenue@0
|
27 local BUTTON_SPACING = 4
|
Nenue@0
|
28 local BUTTON_PADDING = 12
|
Nenue@1
|
29 local HEADER_OFFSET, FOOTER_OFFSET
|
Nenue@0
|
30 local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544;
|
Nenue@0
|
31 local BINDING_TYPE_SPECIALIZATION = 3
|
Nenue@0
|
32 local BINDING_TYPE_CHARACTER = 2
|
Nenue@0
|
33 local BINDING_TYPE_GLOBAL = 1
|
Nenue@0
|
34 local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
|
Nenue@0
|
35 local BINDING_FAILED_PROTECTED = '|cFF00FF00%s|r used by |cFFFFFF00%s|r!'
|
Nenue@0
|
36 local BINDING_MODE = {
|
Nenue@0
|
37 [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s',
|
Nenue@0
|
38 [BINDING_TYPE_CHARACTER] = 'Character: %s',
|
Nenue@0
|
39 [BINDING_TYPE_GLOBAL] = 'Global Binds'
|
Nenue@0
|
40 }
|
Nenue@0
|
41 local BINDING_SCHEME_COLOR = {
|
Nenue@0
|
42 [BINDING_TYPE_SPECIALIZATION] = {0,0,0,0.5},
|
Nenue@0
|
43 [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5},
|
Nenue@0
|
44 [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5}
|
Nenue@0
|
45 }
|
Nenue@0
|
46 local BINDING_SCHEME_VERTEX = {
|
Nenue@0
|
47
|
Nenue@0
|
48 [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1},
|
Nenue@0
|
49 [BINDING_TYPE_CHARACTER] = {0,1,0,1},
|
Nenue@0
|
50 [BINDING_TYPE_GLOBAL] = {0,.5,1,1}
|
Nenue@0
|
51 }
|
Nenue@0
|
52
|
Nenue@0
|
53 local BINDING_SCHEME_TEXT = {
|
Nenue@0
|
54 [BINDING_TYPE_SPECIALIZATION] = {1, 1, 0},
|
Nenue@0
|
55 [BINDING_TYPE_CHARACTER] = {0, 1, 0},
|
Nenue@0
|
56 [BINDING_TYPE_GLOBAL] = {0, 1, 1}
|
Nenue@0
|
57 }
|
Nenue@0
|
58 local ACTION_SCRIPT = {
|
Nenue@0
|
59 ['mount'] = "/script C_MountJournal.SummonByID(%d)",
|
Nenue@0
|
60 ['equipset'] = "/script UseEquipmentSet(%d)",
|
Nenue@0
|
61 }
|
Nenue@0
|
62
|
Nenue@0
|
63 local COMMAND_SPELL = "^SPELL (%S.+)"
|
Nenue@0
|
64 local COMMAND_MACRO = "^MACRO (%S.+)"
|
Nenue@0
|
65 local COMMAND_ITEM = "^ITEM (%S.+)"
|
Nenue@0
|
66 local COMMAND_MOUNT = "^CLICK KeyBinderMacro:mount(%d+)"
|
Nenue@0
|
67 local COMMAND_EQUIPSET = "^CLICK KeyBinderMacro:equipset(%d+)"
|
Nenue@0
|
68
|
Nenue@0
|
69 local PICKUP_TYPES = {
|
Nenue@0
|
70 [COMMAND_SPELL] = PickupSpell,
|
Nenue@0
|
71 [COMMAND_MACRO] = PickupMacro,
|
Nenue@0
|
72 [COMMAND_ITEM] = PickupItem,
|
Nenue@0
|
73 [COMMAND_MOUNT] = C_MountJournal.Pickup,
|
Nenue@0
|
74 [COMMAND_EQUIPSET] = PickupEquipmentSet
|
Nenue@0
|
75 }
|
Nenue@0
|
76 local PICKUP_VALUES = {
|
Nenue@0
|
77 [COMMAND_SPELL] = function(name) return select(7, GetSpellInfo(name)) end
|
Nenue@0
|
78 }
|
Nenue@0
|
79 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
|
Nenue@0
|
80 local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 }
|
Nenue@0
|
81 local BORDER_ASSIGNED = {0.5,0.5,0.5,1 }
|
Nenue@0
|
82 local BORDER_PENDING = {1,0.5,0,1 }
|
Nenue@0
|
83
|
Nenue@1
|
84 local bindMode = 3
|
Nenue@0
|
85 local bindHeader = ''
|
Nenue@0
|
86 local specHeader, specTexture, characterHeader = 'SPEC_NAME', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
|
Nenue@1
|
87 local numButtons = BINDS_PER_ROW * 4
|
Nenue@0
|
88 local bindsCommitted = true
|
Nenue@0
|
89
|
Nenue@1
|
90 local profile, character, specialization, global
|
Nenue@0
|
91 local priority = {}
|
Nenue@0
|
92 local buttons = {}
|
Nenue@0
|
93 local reverts = {}
|
Nenue@0
|
94 local KeyButton = {} -- collection of KeyButton template handlers
|
Nenue@0
|
95 local Action = {} -- collection of special action buttons for special binds
|
Nenue@0
|
96 local protected = {
|
Nenue@0
|
97 ['OPENCHATSLASH'] = true,
|
Nenue@0
|
98 ['OPENCHAT'] = true,
|
Nenue@0
|
99 }
|
Nenue@0
|
100 local saveButton, restoreButton, clearButton
|
Nenue@0
|
101
|
Nenue@0
|
102 --- Returns a value for use with Texture:SetDesaturated()
|
Nenue@0
|
103 local CommandIsLocked = function(self)
|
Nenue@0
|
104 print('command check: 1-'..(bindMode-1))
|
Nenue@0
|
105 local desaturated, layer = false, 3
|
Nenue@0
|
106 for i = 1, bindMode-1 do
|
Nenue@0
|
107 local tier = priority[i]
|
Nenue@0
|
108 local existing = tier.commands[self.command]
|
Nenue@0
|
109 print(' ', i, tier.commands[self.command])
|
Nenue@0
|
110 if existing then
|
Nenue@0
|
111 if self:GetID() ~= existing then
|
Nenue@0
|
112 -- sanitize bad data
|
Nenue@0
|
113 tier.commands[self.command] = nil
|
Nenue@0
|
114 else
|
Nenue@0
|
115 layer = i
|
Nenue@0
|
116 desaturated = true
|
Nenue@0
|
117 break
|
Nenue@0
|
118 end
|
Nenue@0
|
119 end
|
Nenue@0
|
120 end
|
Nenue@0
|
121 return desaturated, layer
|
Nenue@0
|
122 end
|
Nenue@0
|
123
|
Nenue@0
|
124 --- Returns a value for use with Texture:SetDesaturated()
|
Nenue@0
|
125 local BindingIsLocked = function(key)
|
Nenue@0
|
126 local success = false
|
Nenue@0
|
127 for i = 1, bindMode-1 do
|
Nenue@0
|
128 local tier = priority[i]
|
Nenue@0
|
129 if tier.bindings[key] then
|
Nenue@0
|
130 success = true
|
Nenue@0
|
131 break
|
Nenue@0
|
132 end
|
Nenue@0
|
133 end
|
Nenue@0
|
134 return success
|
Nenue@0
|
135 end
|
Nenue@0
|
136
|
Nenue@0
|
137 --- Translates GetBindingKey() results into a printable string.
|
Nenue@0
|
138 local BindingString = function(...)
|
Nenue@0
|
139 local stack = {}
|
Nenue@0
|
140 for i = 1, select('#', ...) do
|
Nenue@0
|
141 local key = select(i, ...)
|
Nenue@0
|
142 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp')
|
Nenue@0
|
143 end
|
Nenue@0
|
144
|
Nenue@0
|
145 if #stack >= 1 then
|
Nenue@0
|
146 return table.concat(stack, ',')
|
Nenue@0
|
147 else
|
Nenue@0
|
148 return nil
|
Nenue@0
|
149 end
|
Nenue@0
|
150 end
|
Nenue@0
|
151
|
Nenue@0
|
152 --- This keeps our KeyDown handler from getting stuck with game controls
|
Nenue@0
|
153 KeyButton.OnUpdate = function(self)
|
Nenue@0
|
154 if not self.command then
|
Nenue@0
|
155 return
|
Nenue@0
|
156 end
|
Nenue@0
|
157
|
Nenue@0
|
158 if self:IsMouseOver() then
|
Nenue@0
|
159 if not self.active then
|
Nenue@0
|
160 -- only set this handler when the button is activated/mouseOver
|
Nenue@0
|
161 self.active = true
|
Nenue@0
|
162 self:SetScript('OnKeyDown', kb.bind)
|
Nenue@0
|
163
|
Nenue@0
|
164 local bindText = self.command
|
Nenue@0
|
165 if self.bind:GetText() then
|
Nenue@0
|
166 bindText = bindText .. ': |cFF00FF00' .. self.bind:GetText()
|
Nenue@0
|
167 end
|
Nenue@0
|
168
|
Nenue@0
|
169 kb.bindlist:SetText(bindText)
|
Nenue@0
|
170 GameTooltip:SetOwner(self)
|
Nenue@0
|
171 GameTooltip:SetAnchorType('ANCHOR_BOTTOMRIGHT')
|
Nenue@0
|
172 GameTooltip:SetText(self.actionName)
|
Nenue@0
|
173 GameTooltip:Show()
|
Nenue@0
|
174 end
|
Nenue@0
|
175 else
|
Nenue@0
|
176 if self.active then
|
Nenue@0
|
177 GameTooltip:Hide()
|
Nenue@0
|
178 self.active = nil
|
Nenue@0
|
179 self:SetScript('OnKeyDown', nil)
|
Nenue@0
|
180 end
|
Nenue@0
|
181 end
|
Nenue@0
|
182 end
|
Nenue@0
|
183
|
Nenue@0
|
184 --- Cursor pickup handler
|
Nenue@0
|
185 -- Walks through PICKUP_TYPES and runs the function if match(command, key) turns up a result.
|
Nenue@0
|
186 -- Passes the result through PICKUP_VALUES[pattern]() if defined.
|
Nenue@0
|
187
|
Nenue@0
|
188 kb.pickup = function(self)
|
Nenue@0
|
189 for pattern, pickup in pairs(PICKUP_TYPES) do
|
Nenue@0
|
190 local value = self.command:match(pattern)
|
Nenue@0
|
191 if value then
|
Nenue@0
|
192 if PICKUP_VALUES[pattern] then
|
Nenue@0
|
193 value = PICKUP_VALUES[pattern](value)
|
Nenue@0
|
194 end
|
Nenue@0
|
195
|
Nenue@0
|
196 pickup(value)
|
Nenue@0
|
197 kb.release(self)
|
Nenue@0
|
198 break
|
Nenue@0
|
199 end
|
Nenue@0
|
200 end
|
Nenue@0
|
201 end
|
Nenue@0
|
202
|
Nenue@0
|
203 --- Setup an action button base on template info
|
Nenue@0
|
204 kb.action = function(type, id)
|
Nenue@0
|
205
|
Nenue@0
|
206 local macroName = type .. id
|
Nenue@0
|
207 macroName = macroName:gsub(' ', '')
|
Nenue@0
|
208
|
Nenue@0
|
209 local attribute = '*macrotext-'..macroName
|
Nenue@0
|
210 local value = ACTION_SCRIPT[type]:format(id)
|
Nenue@0
|
211 local command = 'CLICK KeyBinderMacro:'.. macroName
|
Nenue@0
|
212 profile.macros[attribute] = {value, command}
|
Nenue@0
|
213
|
Nenue@0
|
214 KeyBinderMacro:SetAttribute(attribute, value)
|
Nenue@0
|
215 return command
|
Nenue@0
|
216 end
|
Nenue@0
|
217
|
Nenue@0
|
218 KeyButton.OnDragStart = function(self)
|
Nenue@0
|
219 if not self.command then
|
Nenue@0
|
220 return
|
Nenue@0
|
221 end
|
Nenue@0
|
222 kb.pickup(self)
|
Nenue@0
|
223 end
|
Nenue@0
|
224
|
Nenue@0
|
225 KeyButton.OnReceiveDrag = function(self, ...)
|
Nenue@0
|
226 print(self:GetName(),'|cFF0088FFreceived|r', ...)
|
Nenue@0
|
227 local type, value, subType, subData = GetCursorInfo()
|
Nenue@0
|
228 print('GetCursorInfo', type, value, subType, subData)
|
Nenue@0
|
229 if type then
|
Nenue@0
|
230 if type == 'spell' then
|
Nenue@0
|
231 value = subData
|
Nenue@0
|
232 end
|
Nenue@0
|
233
|
Nenue@0
|
234 local command, name, icon, _
|
Nenue@0
|
235 if type == 'spell' then
|
Nenue@0
|
236 name, _, icon = GetSpellInfo(value)
|
Nenue@0
|
237 command = 'SPELL ' .. name
|
Nenue@0
|
238 name = ''
|
Nenue@0
|
239 elseif type == 'macro' then
|
Nenue@0
|
240 name, icon = GetMacroInfo(value)
|
Nenue@0
|
241 command = 'MACRO ' .. name
|
Nenue@0
|
242 elseif type == 'mount' then
|
Nenue@0
|
243 if subType == 0 then
|
Nenue@0
|
244 name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL)
|
Nenue@0
|
245 value= 0
|
Nenue@0
|
246 else
|
Nenue@0
|
247 name, _, icon = C_MountJournal.GetMountInfoByID(value)
|
Nenue@0
|
248 end
|
Nenue@0
|
249 command = kb.action(type, value)
|
Nenue@0
|
250 elseif type == 'item' then
|
Nenue@0
|
251 name = GetItemInfo(value)
|
Nenue@0
|
252 icon = GetItemIcon(value)
|
Nenue@0
|
253 command = 'ITEM ' .. name
|
Nenue@0
|
254 end
|
Nenue@0
|
255 kb.assign(self, command, name, icon)
|
Nenue@0
|
256 kb.refresh(self)
|
Nenue@0
|
257 ClearCursor()
|
Nenue@0
|
258 end
|
Nenue@0
|
259 end
|
Nenue@0
|
260
|
Nenue@0
|
261 KeyButton.OnMouseDown = function(self, click)
|
Nenue@0
|
262 print(self:GetName(), 'OnMouseDown', click)
|
Nenue@0
|
263 if click == 'LeftButton' then
|
Nenue@0
|
264 KeyButton.OnReceiveDrag(self)
|
Nenue@0
|
265 elseif click == 'RightButton' then
|
Nenue@0
|
266 kb.release(self)
|
Nenue@0
|
267 else
|
Nenue@0
|
268 kb.bind(self)
|
Nenue@0
|
269 end
|
Nenue@0
|
270 end
|
Nenue@0
|
271
|
Nenue@0
|
272
|
Nenue@0
|
273 --- Updates the current KeyBinding for the button's command
|
Nenue@0
|
274 kb.bind = function(self, key)
|
Nenue@0
|
275
|
Nenue@0
|
276 print('|cFFFFFF00bind|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
|
Nenue@0
|
277 if not self.command then
|
Nenue@0
|
278 return
|
Nenue@0
|
279 end
|
Nenue@0
|
280
|
Nenue@0
|
281 if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then
|
Nenue@0
|
282 return
|
Nenue@0
|
283 end
|
Nenue@0
|
284
|
Nenue@0
|
285 if protected[GetBindingAction(key)] then
|
Nenue@0
|
286 return
|
Nenue@0
|
287 kb.bindlist:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(key)))
|
Nenue@0
|
288 end
|
Nenue@0
|
289
|
Nenue@0
|
290 if key == 'ESCAPE' then
|
Nenue@0
|
291 local key1, key2 = GetBindingKey(self.command)
|
Nenue@0
|
292 if key1 then
|
Nenue@0
|
293 SetBinding(key1, nil)
|
Nenue@0
|
294 print('Unbound', key1)
|
Nenue@0
|
295 end
|
Nenue@0
|
296 if key2 then
|
Nenue@0
|
297 SetBinding(key2, nil)
|
Nenue@0
|
298 print('Unbound', key2)
|
Nenue@0
|
299 end
|
Nenue@0
|
300 self.active = false
|
Nenue@0
|
301 return
|
Nenue@0
|
302 end
|
Nenue@0
|
303
|
Nenue@0
|
304 local modifier = ''
|
Nenue@0
|
305 if IsAltKeyDown() then
|
Nenue@0
|
306 modifier = 'ALT-'
|
Nenue@0
|
307 end
|
Nenue@0
|
308 if IsControlKeyDown() then
|
Nenue@0
|
309 modifier = modifier.. 'CTRL-'
|
Nenue@0
|
310 end
|
Nenue@0
|
311 if IsShiftKeyDown() then
|
Nenue@0
|
312 modifier = modifier..'SHIFT-'
|
Nenue@0
|
313 end
|
Nenue@0
|
314
|
Nenue@0
|
315 if self.command then
|
Nenue@0
|
316 self.binding = modifier..key
|
Nenue@0
|
317 self.pending = true
|
Nenue@0
|
318 self.border:SetColorTexture(1,.5,0, 1)
|
Nenue@0
|
319
|
Nenue@0
|
320 local old = GetBindingAction(self.binding)
|
Nenue@0
|
321 local binding1, binding2, new1, new2
|
Nenue@0
|
322 if old and old ~= self.command then
|
Nenue@0
|
323 print('Discarding keybind for', old)
|
Nenue@0
|
324 local binding1, binding2 = GetBindingKey(old)
|
Nenue@0
|
325 -- need to preserve argument order
|
Nenue@0
|
326 end
|
Nenue@0
|
327 tinsert(reverts, {old, binding1, binding2, new1, new2})
|
Nenue@0
|
328
|
Nenue@0
|
329
|
Nenue@0
|
330 bindsCommitted = false
|
Nenue@0
|
331 SetBinding(self.binding, self.command)
|
Nenue@0
|
332 for level, profile in ipairs(priority) do
|
Nenue@0
|
333 profile.bindings[self.binding] = (level == bindMode) and self.command or nil
|
Nenue@0
|
334 end
|
Nenue@0
|
335 print(BINDING_ASSIGNED:format(self.binding, self.command, BINDING_MODE[bindMode]:format(bindHeader)))
|
Nenue@0
|
336
|
Nenue@0
|
337 kb.refresh(self)
|
Nenue@0
|
338 end
|
Nenue@0
|
339 end
|
Nenue@0
|
340
|
Nenue@0
|
341 --- Resets button command
|
Nenue@0
|
342 kb.release = function(self)
|
Nenue@0
|
343 local index = self:GetID()
|
Nenue@0
|
344 self.command = nil
|
Nenue@0
|
345 self.actionName = nil
|
Nenue@0
|
346 self.macro:SetText(nil)
|
Nenue@0
|
347 self.profile = nil
|
Nenue@0
|
348 self.bind:SetText(nil)
|
Nenue@0
|
349 self.icon:SetTexture(nil)
|
Nenue@0
|
350 self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
|
Nenue@0
|
351 self:EnableKeyboard(false)
|
Nenue@0
|
352 self:SetScript('OnKeyDown', nil)
|
Nenue@0
|
353
|
Nenue@0
|
354
|
Nenue@0
|
355 if profile.buttons[index] then
|
Nenue@0
|
356 profile.buttons[index] = nil
|
Nenue@0
|
357 end
|
Nenue@0
|
358 end
|
Nenue@0
|
359
|
Nenue@0
|
360 -- Sets button command
|
Nenue@0
|
361
|
Nenue@0
|
362 kb.assign = function(self, command, name, icon)
|
Nenue@0
|
363 local index = self:GetID()
|
Nenue@0
|
364 print('|cFF00FFFFassign|cFF0088FF', index, '|cFFFFFF00'.. (command or 'none'), '|cFF00FF00'.. (name or ''), '|cFF00FFFF' .. (icon or ''))
|
Nenue@0
|
365
|
Nenue@0
|
366
|
Nenue@0
|
367 if command then
|
Nenue@0
|
368 if command:match(COMMAND_SPELL) then
|
Nenue@0
|
369 name = command:match(COMMAND_SPELL)
|
Nenue@0
|
370 end
|
Nenue@0
|
371
|
Nenue@0
|
372
|
Nenue@0
|
373 self:EnableKeyboard(true)
|
Nenue@0
|
374 print('profile.buttons['..index..'] |cFF00FFFF=|r ', command, name, icon)
|
Nenue@0
|
375 profile.buttons[index] = {command, name, icon}
|
Nenue@0
|
376
|
Nenue@0
|
377 --- Clean up any residual buttons
|
Nenue@0
|
378 local previous = profile.commands[command]
|
Nenue@0
|
379 if previous ~= index and buttons[previous] then
|
Nenue@0
|
380 kb.release(buttons[previous])
|
Nenue@0
|
381 end
|
Nenue@0
|
382
|
Nenue@0
|
383 profile.commands[command] = index
|
Nenue@0
|
384 end
|
Nenue@0
|
385
|
Nenue@0
|
386 self.profile = bindMode
|
Nenue@0
|
387 self.actionName = name
|
Nenue@0
|
388 self.command = command
|
Nenue@0
|
389 self.icon:SetTexture(icon)
|
Nenue@0
|
390 self:RegisterForDrag('LeftButton')
|
Nenue@0
|
391 end
|
Nenue@0
|
392
|
Nenue@0
|
393 --- Retrieves button at index; creates said button and instates any stored parameters
|
Nenue@0
|
394 kb.keyslot = function(index)
|
Nenue@0
|
395 if not buttons[index] then
|
Nenue@0
|
396 local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton')
|
Nenue@0
|
397 button:SetScript('OnMouseDown', KeyButton.OnMouseDown)
|
Nenue@0
|
398 button:SetScript('OnMouseUp', KeyButton.OnMouseUp)
|
Nenue@0
|
399 button:SetScript('OnUpdate', KeyButton.OnUpdate)
|
Nenue@0
|
400 button:SetScript('OnDragStart', KeyButton.OnDragStart)
|
Nenue@0
|
401 button:SetScript('OnReceiveDrag', KeyButton.OnReceiveDrag)
|
Nenue@0
|
402 button:SetID(index)
|
Nenue@0
|
403
|
Nenue@0
|
404 if profile.buttons[index] and type(profile.buttons[index] ) == 'table' then
|
Nenue@0
|
405 kb.assign(button, unpack(profile.buttons[index] ))
|
Nenue@0
|
406 else
|
Nenue@0
|
407 kb.release(button)
|
Nenue@0
|
408 end
|
Nenue@0
|
409
|
Nenue@0
|
410 local x, y = BUTTON_PADDING, - (BUTTON_PADDING + HEADER_OFFSET)
|
Nenue@0
|
411 if index ~= 1 then
|
Nenue@0
|
412 local col = mod(index, BINDS_PER_ROW)
|
Nenue@0
|
413 if col == 0 then
|
Nenue@0
|
414 col = BINDS_PER_ROW - 1
|
Nenue@0
|
415 else
|
Nenue@0
|
416 col = col - 1
|
Nenue@0
|
417 end
|
Nenue@0
|
418 x = col * (KEY_BUTTON_SIZE + BUTTON_SPACING) + BUTTON_PADDING
|
Nenue@0
|
419 y = (ceil(index/ BINDS_PER_ROW)-1) * - (KEY_BUTTON_SIZE + BUTTON_SPACING) - BUTTON_PADDING - HEADER_OFFSET
|
Nenue@0
|
420 end
|
Nenue@0
|
421 button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
|
Nenue@0
|
422 button:SetPoint('TOPLEFT', kb, 'TOPLEFT', x, y)
|
Nenue@0
|
423 button:Show()
|
Nenue@0
|
424 buttons[index] = button
|
Nenue@0
|
425 end
|
Nenue@0
|
426 return buttons[index]
|
Nenue@0
|
427 end
|
Nenue@0
|
428
|
Nenue@0
|
429 --- Updates profile assignment and button contents
|
Nenue@0
|
430 kb.refresh = function(self)
|
Nenue@0
|
431 if self.profile ~= bindMode then
|
Nenue@0
|
432 if profile.buttons[self:GetID()] then
|
Nenue@0
|
433 kb.assign(self, unpack(profile.buttons[self:GetID()]))
|
Nenue@0
|
434 else
|
Nenue@0
|
435 kb.release(self)
|
Nenue@0
|
436 end
|
Nenue@0
|
437 end
|
Nenue@0
|
438
|
Nenue@0
|
439 if self.command then
|
Nenue@0
|
440 if self.pending then
|
Nenue@0
|
441 self.border:SetColorTexture(unpack(BORDER_PENDING))
|
Nenue@0
|
442 else
|
Nenue@0
|
443 self.border:SetColorTexture(unpack(BORDER_ASSIGNED))
|
Nenue@0
|
444 end
|
Nenue@0
|
445 --self.macro:SetText(self.actionName)
|
Nenue@0
|
446 self.bind:SetText(BindingString(GetBindingKey(self.command)))
|
Nenue@0
|
447 local locked, layer = CommandIsLocked(self)
|
Nenue@0
|
448 self.icon:SetDesaturated(locked)
|
Nenue@0
|
449 self.icon:SetVertexColor(unpack(BINDING_SCHEME_VERTEX[layer]))
|
Nenue@0
|
450 else
|
Nenue@0
|
451 self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
|
Nenue@0
|
452 --self.macro:SetText(nil)
|
Nenue@0
|
453 self.bind:SetText(nil)
|
Nenue@0
|
454 end
|
Nenue@0
|
455 end
|
Nenue@0
|
456
|
Nenue@0
|
457 local SetupUI = function()
|
Nenue@0
|
458
|
Nenue@0
|
459
|
Nenue@0
|
460 kb.tabAnchor = {'TOPLEFT', kb, 'TOPRIGHT', 2, -TAB_OFFSET}
|
Nenue@0
|
461 kb.tabGrowth = {'TOPLEFT', nil,'BOTTOMLEFT', 0, -TAB_SPACING}
|
Nenue@0
|
462 kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT }
|
Nenue@0
|
463 kb.UIPanelAnchor = {'TOPLEFT', kb, 'TOPLEFT', BUTTON_PADDING + 12, -BUTTON_PADDING}
|
Nenue@0
|
464 kb.UIPanelGrowth = {'TOPLEFT', nil, 'TOPRIGHT', 14, 0 }
|
Nenue@0
|
465 kb.controlsAnchor = {'BOTTOMLEFT', kb, BUTTON_PADDING, BUTTON_PADDING }
|
Nenue@0
|
466 kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0}
|
Nenue@0
|
467
|
Nenue@0
|
468 --tab() frame, name, tooltip, texture, coords
|
Nenue@0
|
469 kb:tab('KeyBinderGlobalTab', BINDING_MODE[1], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85})
|
Nenue@0
|
470 kb:tab('KeyBinderCharacterTab', characterHeader, nil)
|
Nenue@0
|
471 kb:tab('KeyBinderSpecTab', specHeader, specTexture)
|
Nenue@0
|
472 SetPortraitTexture(KeyBinderCharacterTab.icon, 'player')
|
Nenue@0
|
473 KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85)
|
Nenue@0
|
474
|
Nenue@0
|
475 saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', nil, kb.save)
|
Nenue@0
|
476 restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', nil, kb.restore)
|
Nenue@0
|
477 clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', nil, kb.ResetProfile)
|
Nenue@0
|
478
|
Nenue@0
|
479 kb:uibutton(
|
Nenue@0
|
480 'KeyBinderSpellBookButton', 'SpellBook', nil,
|
Nenue@0
|
481 function() ToggleSpellBook(BOOKTYPE_SPELL) end,
|
Nenue@0
|
482 "Interface\\Spellbook\\Spellbook-Icon")
|
Nenue@0
|
483 kb:uibutton(
|
Nenue@0
|
484 'KeyBinderTalentFrameButton', 'Talents', nil,
|
Nenue@0
|
485 function() ToggleTalentFrame() end,
|
Nenue@0
|
486 "Interface\\TargetingFrame\\UI-Classes-Circles",
|
Nenue@0
|
487 CLASS_ICON_TCOORDS[strupper(select(2,UnitClass("player")))])
|
Nenue@0
|
488
|
Nenue@0
|
489 kb:uibutton(
|
Nenue@0
|
490 'KeyBinderMacroFrameButton', 'Macros', nil,
|
Nenue@0
|
491 function() if MacroFrame then HideUIPanel(MacroFrame) else ShowMacroFrame() end end,
|
Nenue@0
|
492 "Interface\\MacroFrame\\MacroFrame-Icon")
|
Nenue@0
|
493
|
Nenue@0
|
494 kb:uibutton(
|
Nenue@0
|
495 'KeyBinderInventoryButton', 'Bags', nil,
|
Nenue@0
|
496 function() OpenAllBags() end,
|
Nenue@0
|
497 "Interface\\BUTTONS\\Button-Backpack-Up")
|
Nenue@0
|
498
|
Nenue@0
|
499 kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING)
|
Nenue@0
|
500 HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING
|
Nenue@0
|
501 FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING
|
Nenue@0
|
502 end
|
Nenue@0
|
503
|
Nenue@0
|
504 --- Invokes the KeyBinder frame (from the /kb function or some other source)
|
Nenue@0
|
505 kb.ui = function()
|
Nenue@1
|
506 if not db.showUI then
|
Nenue@0
|
507 return
|
Nenue@0
|
508 end
|
Nenue@0
|
509
|
Nenue@0
|
510 if not kb:IsVisible() then
|
Nenue@0
|
511 kb:Show()
|
Nenue@1
|
512 db.showUI = true
|
Nenue@0
|
513 end
|
Nenue@0
|
514
|
Nenue@0
|
515 if not kb.loaded then
|
Nenue@0
|
516 SetupUI()
|
Nenue@0
|
517 kb.loaded = true
|
Nenue@0
|
518 end
|
Nenue@0
|
519
|
Nenue@0
|
520 for i = 1, numButtons do
|
Nenue@0
|
521 kb.refresh(kb.keyslot(i))
|
Nenue@0
|
522 end
|
Nenue@0
|
523
|
Nenue@0
|
524 if bindMode == BINDING_TYPE_SPECIALIZATION then
|
Nenue@0
|
525 bindHeader = select(2,GetSpecializationInfo(GetSpecialization()))
|
Nenue@0
|
526 elseif bindMode == BINDING_TYPE_CHARACTER then
|
Nenue@0
|
527 bindHeader = UnitName('player')
|
Nenue@0
|
528 else
|
Nenue@0
|
529 bindHeader = ''
|
Nenue@0
|
530 end
|
Nenue@0
|
531
|
Nenue@0
|
532 if bindsCommitted then
|
Nenue@0
|
533 KeyBinderSaveButton:Disable()
|
Nenue@0
|
534 KeyBinderRestoreButton:Disable()
|
Nenue@0
|
535 else
|
Nenue@0
|
536 KeyBinderSaveButton:Enable()
|
Nenue@0
|
537 KeyBinderRestoreButton:Enable()
|
Nenue@0
|
538 end
|
Nenue@0
|
539
|
Nenue@0
|
540 --- panel attributes
|
Nenue@0
|
541 local numRows = numButtons/BINDS_PER_ROW
|
Nenue@0
|
542 kb:SetHeight( numRows * (KEY_BUTTON_SIZE) + (numRows - 1) * BUTTON_SPACING + HEADER_OFFSET + FOOTER_OFFSET + BUTTON_PADDING * 2)
|
Nenue@0
|
543 kb:SetWidth((BINDS_PER_ROW - 1) * BUTTON_SPACING + BINDS_PER_ROW * KEY_BUTTON_SIZE + BUTTON_PADDING * 2)
|
Nenue@0
|
544 kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode]))
|
Nenue@0
|
545
|
Nenue@0
|
546
|
Nenue@0
|
547 for i, tab in ipairs(kb.tabButtons) do
|
Nenue@0
|
548
|
Nenue@0
|
549 local n = tab:GetNormalTexture()
|
Nenue@0
|
550 local tabTexture = "Interface\\Buttons\\UI-Quickslot2"
|
Nenue@0
|
551 local left, top, right, bottom = -12, 12, 13, -13
|
Nenue@0
|
552 if i == bindMode then
|
Nenue@0
|
553 tabTexture = "Interface\\Buttons\\CheckButtonGlow"
|
Nenue@0
|
554 left, top, right, bottom = -14, 14, 15, -15
|
Nenue@0
|
555 end
|
Nenue@0
|
556 n:SetTexture(tabTexture)
|
Nenue@0
|
557 n:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top)
|
Nenue@0
|
558 n:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom)
|
Nenue@0
|
559 end
|
Nenue@0
|
560 end
|
Nenue@0
|
561
|
Nenue@0
|
562 kb.loadbinds = function (bindings)
|
Nenue@0
|
563 for key, command in pairs(bindings) do
|
Nenue@0
|
564 -- store for reversion
|
Nenue@0
|
565 local oldAction = GetBindingAction(key)
|
Nenue@0
|
566 if oldAction ~= command then
|
Nenue@0
|
567 local bind1, bind2 = GetBindingKey(oldAction)
|
Nenue@0
|
568 if bind1 and not reverts[bind1] then
|
Nenue@0
|
569 reverts[bind1] = oldAction
|
Nenue@0
|
570 end
|
Nenue@0
|
571 if bind2 and not reverts[bind2] then
|
Nenue@0
|
572 reverts[bind2] = oldAction
|
Nenue@0
|
573 end
|
Nenue@0
|
574 end
|
Nenue@0
|
575 SetBindings(key, command)
|
Nenue@0
|
576 end
|
Nenue@0
|
577 SaveBindings()
|
Nenue@0
|
578 end
|
Nenue@0
|
579
|
Nenue@0
|
580 local ACTION_BARS = {
|
Nenue@1
|
581 {'ActionButton', 0},
|
Nenue@1
|
582 {'MultiBarLeftButton', 24},
|
Nenue@1
|
583 {'MultiBarRightButton', 36},
|
Nenue@1
|
584 {'MultiBarBottomRighttButton', 48},
|
Nenue@1
|
585 {'MultiBarBottomLeftButton', 60},
|
Nenue@0
|
586 }
|
Nenue@1
|
587 kb.HotKeyText = function (slot)
|
Nenue@1
|
588 local i, offset = 0, 0
|
Nenue@1
|
589 local actionbar
|
Nenue@1
|
590
|
Nenue@1
|
591 -- figure out which bar the slot belongs to
|
Nenue@1
|
592 for i, bar in ipairs(ACTION_BARS) do
|
Nenue@1
|
593 actionbar, offset = unpack(ACTION_BARS[i])
|
Nenue@1
|
594 if bar[2] > slot then
|
Nenue@1
|
595 break
|
Nenue@0
|
596 end
|
Nenue@0
|
597 end
|
Nenue@1
|
598 local button = _G[actionbar .. (slot - offset)]
|
Nenue@1
|
599
|
Nenue@1
|
600 if not button then
|
Nenue@1
|
601 return
|
Nenue@1
|
602 end
|
Nenue@1
|
603
|
Nenue@1
|
604 local type, id, subType, subID = GetActionInfo(slot)
|
Nenue@1
|
605
|
Nenue@1
|
606 if not type then
|
Nenue@1
|
607 return
|
Nenue@1
|
608 end
|
Nenue@1
|
609
|
Nenue@1
|
610 local bind, command
|
Nenue@1
|
611 if type == 'spell' then
|
Nenue@1
|
612 local name = GetSpellInfo(id)
|
Nenue@1
|
613 command = 'SPELL '..name
|
Nenue@1
|
614 elseif type == 'macro' then
|
Nenue@1
|
615 command = 'MACRO ' .. id
|
Nenue@1
|
616 else
|
Nenue@1
|
617 return
|
Nenue@1
|
618 end
|
Nenue@1
|
619 bind = GetBindingKey(command)
|
Nenue@1
|
620 if bind then
|
Nenue@1
|
621 button.HotKey:SetText(BindingString(bind))
|
Nenue@1
|
622 button.HotKey:Show()
|
Nenue@1
|
623 end
|
Nenue@0
|
624 end
|
Nenue@0
|
625
|
Nenue@0
|
626 kb.InitProfile = function(profile)
|
Nenue@0
|
627 profile.buttons = profile.buttons or {}
|
Nenue@0
|
628 profile.commands = profile.commands or {}
|
Nenue@0
|
629 profile.bindings = profile.bindings or {}
|
Nenue@0
|
630 profile.macros = profile.macros or {}
|
Nenue@0
|
631 return profile
|
Nenue@0
|
632 end
|
Nenue@0
|
633 kb.ResetProfile = function()
|
Nenue@0
|
634
|
Nenue@0
|
635 for i, button in pairs(buttons) do
|
Nenue@0
|
636 kb.release(button)
|
Nenue@0
|
637 end
|
Nenue@0
|
638
|
Nenue@0
|
639 profile.commands = {}
|
Nenue@0
|
640 profile.bindings = {}
|
Nenue@0
|
641 profile.macros = {}
|
Nenue@0
|
642 end
|
Nenue@0
|
643
|
Nenue@0
|
644 --- Gives us the profile structure to work with while instating data
|
Nenue@0
|
645 kb.profile = function(name)
|
Nenue@1
|
646 global = kb.InitProfile(db)
|
Nenue@0
|
647 profile = global
|
Nenue@0
|
648 local subtitle
|
Nenue@0
|
649 if name then
|
Nenue@1
|
650 db[name] = db[name] or {}
|
Nenue@1
|
651 db[name] = kb.InitProfile(db[name])
|
Nenue@1
|
652 character = db[name]
|
Nenue@0
|
653 local spec = GetSpecialization()
|
Nenue@0
|
654 if spec then
|
Nenue@1
|
655 db[name][spec] = db[name][spec] or {}
|
Nenue@1
|
656 profile = kb.InitProfile(db[name][spec])
|
Nenue@0
|
657 bindMode = BINDING_TYPE_SPECIALIZATION
|
Nenue@0
|
658 subtitle = select(2,GetSpecializationInfo(spec))
|
Nenue@1
|
659 specialization = db[name][spec]
|
Nenue@0
|
660 else
|
Nenue@1
|
661 profile = kb.InitProfile(db[name])
|
Nenue@0
|
662 bindMode = BINDING_TYPE_CHARACTER
|
Nenue@0
|
663 subtitle = name
|
Nenue@0
|
664 specialization = character
|
Nenue@0
|
665 end
|
Nenue@0
|
666 end
|
Nenue@0
|
667 priority = {global, character, specialization }
|
Nenue@0
|
668
|
Nenue@0
|
669
|
Nenue@0
|
670
|
Nenue@1
|
671 if not db.bindsPage then
|
Nenue@1
|
672 db.bindsPage = bindMode
|
Nenue@0
|
673 end
|
Nenue@1
|
674 bindMode = db.bindsPage
|
Nenue@0
|
675
|
Nenue@0
|
676
|
Nenue@0
|
677 if not BINDING_MODE[bindMode] then
|
Nenue@0
|
678 bindMode = 3
|
Nenue@1
|
679 db.bindsPage = 3
|
Nenue@0
|
680 print('overriding', bindMode)
|
Nenue@0
|
681 end
|
Nenue@0
|
682
|
Nenue@0
|
683 profile = priority[bindMode]
|
Nenue@0
|
684
|
Nenue@0
|
685
|
Nenue@0
|
686 local _
|
Nenue@0
|
687 _, specHeader, _, specTexture = GetSpecializationInfo(GetSpecialization())
|
Nenue@0
|
688 print(GetSpecializationInfo(GetSpecialization()))
|
Nenue@0
|
689 specHeader = BINDING_MODE[2]:format(specHeader)
|
Nenue@0
|
690 characterHeader = BINDING_MODE[2]:format(UnitName('player'))
|
Nenue@0
|
691
|
Nenue@0
|
692 print('Using binding profile |cFF00FF88'..BINDING_MODE[bindMode]:format(subtitle)..'|r')
|
Nenue@0
|
693 end
|
Nenue@0
|
694
|
Nenue@0
|
695 kb.SelectTab = function(self)
|
Nenue@0
|
696 bindMode = self:GetID()
|
Nenue@0
|
697 profile = priority[self:GetID()]
|
Nenue@1
|
698 db.bindsPage = self:GetID()
|
Nenue@0
|
699 kb.ui()
|
Nenue@0
|
700 end
|
Nenue@0
|
701 kb.save = function()
|
Nenue@0
|
702 SaveBindings(GetCurrentBindingSet())
|
Nenue@0
|
703 bindsCommitted = true
|
Nenue@0
|
704 for i, button in ipairs(buttons) do
|
Nenue@0
|
705 button.pending = false
|
Nenue@0
|
706 end
|
Nenue@0
|
707
|
Nenue@0
|
708 kb.ui()
|
Nenue@0
|
709 print('Bindings saved.')
|
Nenue@0
|
710 end
|
Nenue@0
|
711 kb.restore = function()
|
Nenue@0
|
712 for i, button in pairs(buttons) do
|
Nenue@0
|
713 button.pending = false
|
Nenue@0
|
714 end
|
Nenue@0
|
715 bindsCommitted = true
|
Nenue@0
|
716 LoadBindings(GetCurrentBindingSet())
|
Nenue@0
|
717 print('All changes discarded.')
|
Nenue@0
|
718 end
|
Nenue@0
|
719
|
Nenue@0
|
720 --- Tells all the hud buttons what to do
|
Nenue@0
|
721 kb.init = function()
|
Nenue@0
|
722 KeyBinderMacro:SetAttribute('*type*', 'macro')
|
Nenue@0
|
723 end
|
Nenue@0
|
724
|
Nenue@0
|
725 --- Get started
|
Nenue@0
|
726 kb.variables = function()
|
Nenue@1
|
727 SkeletonKeyDB = SkeletonKeyDB or {}
|
Nenue@1
|
728 db = SkeletonKeyDB
|
Nenue@0
|
729 kb.profile(GetUnitName('player', true))
|
Nenue@0
|
730 for i = 1, 3 do
|
Nenue@0
|
731 for attribute, data in pairs(priority[i].macros) do
|
Nenue@0
|
732 KeyBinderMacro:SetAttribute(attribute, data[1])
|
Nenue@0
|
733 end
|
Nenue@0
|
734 end
|
Nenue@0
|
735
|
Nenue@0
|
736 kb.UPDATE_BINDINGS()
|
Nenue@0
|
737 kb:RegisterEvent('UPDATE_BINDINGS')
|
Nenue@0
|
738 kb:RegisterEvent('UPDATE_MACROS')
|
Nenue@0
|
739 kb:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED')
|
Nenue@0
|
740 kb:RegisterEvent('PLAYER_EQUIPMENT_CHANGED')
|
Nenue@0
|
741 kb:RegisterEvent('PLAYER_REGEN_DISABLED')
|
Nenue@0
|
742 kb:RegisterEvent('PLAYER_REGEN_ENABLED')
|
Nenue@0
|
743 kb:RegisterEvent('ACTIONBAR_SLOT_CHANGED')
|
Nenue@0
|
744 end
|
Nenue@0
|
745
|
Nenue@0
|
746 kb.close = function()
|
Nenue@1
|
747 db.showUI = false
|
Nenue@0
|
748 kb:Hide()
|
Nenue@0
|
749 end
|
Nenue@0
|
750
|
Nenue@0
|
751 kb.PLAYER_REGEN_DISABLED = function()
|
Nenue@1
|
752 if db.showUI then
|
Nenue@0
|
753 kb:Hide()
|
Nenue@0
|
754 end
|
Nenue@0
|
755 end
|
Nenue@0
|
756
|
Nenue@0
|
757 kb.PLAYER_REGEN_ENABLED = function()
|
Nenue@1
|
758 if db.showUI then
|
Nenue@0
|
759 kb.ui()
|
Nenue@0
|
760 end
|
Nenue@0
|
761 end
|
Nenue@0
|
762 --- Refresh buttons if macros are updated
|
Nenue@0
|
763 kb.UPDATE_BINDINGS = function()
|
Nenue@1
|
764 for i = 1, 120 do
|
Nenue@1
|
765 kb.HotKeyText(i)
|
Nenue@1
|
766 end
|
Nenue@1
|
767 if db.showUI then
|
Nenue@0
|
768 kb.ui()
|
Nenue@0
|
769 end
|
Nenue@0
|
770 end
|
Nenue@0
|
771
|
Nenue@1
|
772 kb.ACTIONBAR_SLOT_CHANGED = function(self, event, slot)
|
Nenue@1
|
773 kb.HotKeyText(slot)
|
Nenue@3
|
774 return true
|
Nenue@0
|
775 end
|
Nenue@0
|
776
|
Nenue@0
|
777 kb.UPDATE_MACROS = kb.UPDATE_BINDINGS
|
Nenue@0
|
778 SLASH_KB1 = "/kb"
|
Nenue@1
|
779 SlashCmdList.KB = function(self, input)
|
Nenue@1
|
780 if db.showUI then
|
Nenue@1
|
781 db.showUI = false
|
Nenue@0
|
782 print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
|
Nenue@0
|
783 kb:Hide()
|
Nenue@0
|
784 else
|
Nenue@1
|
785 db.showUI = true
|
Nenue@0
|
786 print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
|
Nenue@0
|
787 kb.ui()
|
Nenue@0
|
788 end
|
Nenue@1
|
789 end
|