comparison SkeletonKey/KeyBinds.lua @ 5:9ac29fe77455

- dynamic profession spell mapping - dynamic talent spell mapping - protection of dynamic slots that aren't in use - plugin abstractors for accessing state data - a lot of fixes related to the 7.0.3 API
author Nenue
date Tue, 26 Jul 2016 19:29:44 -0400
parents 07293831dd7b
children f6d1c192afc6
comparison
equal deleted inserted replaced
4:a30285f8191e 5:9ac29fe77455
1 -------------------------------------------- 1 --------------------------------------------
2 -- KrakTool 2 -- SkeletonKey
3 -- Nick 3 -- Krakyn-Mal'Ganis
4 -- @project-revision@ @project-hash@ 4 -- @project-revision@ @project-hash@
5 -- @file-revision@ @file-hash@ 5 -- @file-revision@ @file-hash@
6 -- Created: 6/16/2016 3:47 AM 6 -- Created: 6/16/2016 3:47 AM
7 -------------------------------------------- 7 --------------------------------------------
8 -- kb 8 -- kb
9 -- .bind(button, key) bind current keystroke to command 9 -- .StoreBinding(button, key) bind current keystroke to command
10 -- .assign(button, command, name, icon) set button command 10 -- .GetSlot(index) return display slot
11 -- .release(button) clear button command 11 -- .SetSlot(button, command, name, icon) assign display slot
12 -- .refresh(button) update button contents 12 -- .ReleaseSlot(button) clear button command
13 -- .ui() invoke interface 13 -- .UpdateSlot(button) update button contents
14 -- .profile(name) set profile character 14 -- .SelectProfile(name) set profile character
15 -- .loadbinds(bindings) walk table with SetBinding() 15 -- .ApplyBindings(bindings) walk table with SetBinding()
16 16
17 local KT = LibKT.register(KeyBinder) 17 local _
18 local kb = KeyBinder 18 local kb, print = LibStub("LibKraken").register(KeyBinder)
19 local db 19 local db
20 20 local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end
21
22 --- Caps Lock literals
23 local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:"
24 local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
25 local BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
26 local BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r'
27 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
28 local FOOTER_OFFSET
29 local HEADER_OFFSET
30 local HELP_1 = "Drag and drop spells/items from your inventory, spellbook, or collections panels."
31 local HELP_2 = "While the cursor is above an icon, up to two key combinations will be bound to that action."
32 local HELP_3 = "If that key used for a client binding (e.g. game menu), a confirmation popup will appear before making the change."
33 local BINDS_PER_ROW = 2
34 local BUTTON_HSPACING = 128
35 local BUTTON_SPACING = 4
36 local BUTTON_PADDING = 12
37 local BINDING_TYPE_SPECIALIZATION = 3
38 local BINDING_TYPE_CHARACTER = 2
39 local BINDING_TYPE_GLOBAL = 1
40 local KEY_BUTTON_SIZE = 48
21 local MIN_BIND_SLOTS = 32 41 local MIN_BIND_SLOTS = 32
22 local BINDS_PER_ROW = 8 42 local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
23 local KEY_BUTTON_SIZE = 40
24 local TAB_OFFSET = 12 43 local TAB_OFFSET = 12
25 local TAB_HEIGHT = 40 44 local TAB_HEIGHT = 40
26 local TAB_SPACING = 2 45 local TAB_SPACING = 2
27 local BUTTON_SPACING = 4 46 local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 }
28 local BUTTON_PADDING = 12 47 local BORDER_ASSIGNED = {0.5,0.5,0.5,1 }
29 local HEADER_OFFSET, FOOTER_OFFSET 48 local BORDER_DYNAMIC = {1,1,0,1}
30 local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544; 49 local BORDER_PENDING = {1,0.5,0,1 }
31 local BINDING_TYPE_SPECIALIZATION = 3 50 local CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_PETACTION
32 local BINDING_TYPE_CHARACTER = 2 51
33 local BINDING_TYPE_GLOBAL = 1 52
34 local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' 53 --- Caps Lock derivatives
35 local BINDING_FAILED_PROTECTED = '|cFF00FF00%s|r used by |cFFFFFF00%s|r!' 54 local ACTION_SCRIPT = {
55 ['mount'] = "/script C_MountJournal.SummonByID(%d)",
56 ['macro'] = "%s",
57 ['equipset'] = "/script UseEquipmentSet(%d)",
58 ['spell'] = "/cast %s",
59 ['petaction'] = "/cast %s",
60 ['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s",
61 ['item'] = "/use %s"
62 }
63 local BUTTON_HEADERS = {
64 ['spell'] = SPELLS,
65 ['macro'] = MACRO,
66 ['petaction'] = PET,
67 ['mount'] = MOUNT,
68 ['battlepet'] = BATTLEPET,
69
70
71 [5] = PROFESSIONS_FIRST_AID,
72 [7] = PROFESSIONS_COOKING,
73 [9] = PROFESSIONS_FISHING,
74 [10] = PROFESSIONS_ARCHAEOLOGY,
75
76 }
77
78 local professionMappings = {
79 [5] = 3,
80 [7] = 4,
81 [9] = 5,
82 [10] = 6
83 }
84
36 local BINDING_MODE = { 85 local BINDING_MODE = {
37 [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s', 86 [BINDING_TYPE_GLOBAL] = 'Global Binds',
38 [BINDING_TYPE_CHARACTER] = 'Character: %s', 87 [BINDING_TYPE_CHARACTER] = 'Character: %s',
39 [BINDING_TYPE_GLOBAL] = 'Global Binds' 88 [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s'
40 } 89 }
90 local BINDING_DESCRIPTION = {
91
92 [BINDING_TYPE_GLOBAL] = 'The bindings are applied globally.',
93 [BINDING_TYPE_CHARACTER] = 'Applied when you log onto this character.',
94 [BINDING_TYPE_SPECIALIZATION] = 'Applied when you log onto this character and are that specialization.',
95 }
96
41 local BINDING_SCHEME_COLOR = { 97 local BINDING_SCHEME_COLOR = {
42 [BINDING_TYPE_SPECIALIZATION] = {0,0,0,0.5}, 98 [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5},
43 [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5}, 99 [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5},
44 [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5} 100 [BINDING_TYPE_SPECIALIZATION] = {.25,0,0,0.5},
45 } 101 }
46 local BINDING_SCHEME_VERTEX = { 102 local BINDING_SCHEME_VERTEX = {
47 103 [BINDING_TYPE_GLOBAL] = {0,.5,1,1},
104 [BINDING_TYPE_CHARACTER] = {0,1,0,1},
48 [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1}, 105 [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1},
49 [BINDING_TYPE_CHARACTER] = {0,1,0,1},
50 [BINDING_TYPE_GLOBAL] = {0,.5,1,1}
51 } 106 }
52
53 local BINDING_SCHEME_TEXT = { 107 local BINDING_SCHEME_TEXT = {
54 [BINDING_TYPE_SPECIALIZATION] = {1, 1, 0}, 108 [BINDING_TYPE_SPECIALIZATION] = {0, 1, 1},
55 [BINDING_TYPE_CHARACTER] = {0, 1, 0}, 109 [BINDING_TYPE_CHARACTER] = {0, 1, 0},
56 [BINDING_TYPE_GLOBAL] = {0, 1, 1} 110 [BINDING_TYPE_GLOBAL] = {0, 1, 1}
57 } 111 }
58 local ACTION_SCRIPT = { 112
59 ['mount'] = "/script C_MountJournal.SummonByID(%d)", 113
60 ['equipset'] = "/script UseEquipmentSet(%d)", 114
61 } 115 local loadedProfiles = {}
62 116 -- Profiles ordered by precedance
63 local COMMAND_SPELL = "^SPELL (%S.+)"
64 local COMMAND_MACRO = "^MACRO (%S.+)"
65 local COMMAND_ITEM = "^ITEM (%S.+)"
66 local COMMAND_MOUNT = "^CLICK KeyBinderMacro:mount(%d+)"
67 local COMMAND_EQUIPSET = "^CLICK KeyBinderMacro:equipset(%d+)"
68
69 local PICKUP_TYPES = {
70 [COMMAND_SPELL] = PickupSpell,
71 [COMMAND_MACRO] = PickupMacro,
72 [COMMAND_ITEM] = PickupItem,
73 [COMMAND_MOUNT] = C_MountJournal.Pickup,
74 [COMMAND_EQUIPSET] = PickupEquipmentSet
75 }
76 local PICKUP_VALUES = {
77 [COMMAND_SPELL] = function(name) return select(7, GetSpellInfo(name)) end
78 }
79 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
80 local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 }
81 local BORDER_ASSIGNED = {0.5,0.5,0.5,1 }
82 local BORDER_PENDING = {1,0.5,0,1 }
83
84 local bindMode = 3
85 local bindHeader = ''
86 local specHeader, specTexture, characterHeader = 'SPEC_NAME', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
87 local numButtons = BINDS_PER_ROW * 4
88 local bindsCommitted = true
89
90 local profile, character, specialization, global
91 local priority = {} 117 local priority = {}
118 -- Button pointers
92 local buttons = {} 119 local buttons = {}
120 -- Backlog of changes
93 local reverts = {} 121 local reverts = {}
94 local KeyButton = {} -- collection of KeyButton template handlers 122 -- macro buttons used for mounts and other buttonable non-spells
95 local Action = {} -- collection of special action buttons for special binds 123 local macros = {}
124 -- currently active non-blizzard keybinds
125 local bindings = {}
126 -- unselected talents
127 local talentBindings = {}
128 kb.inactiveTalentBindings = {}
129 -- placeholder for the StaticPopup used for confirmations
130 local confirmation
131 -- header text
132 local configHeaders = {}
133
96 local protected = { 134 local protected = {
97 ['OPENCHATSLASH'] = true, 135 ['OPENCHATSLASH'] = true,
98 ['OPENCHAT'] = true, 136 ['OPENCHAT'] = true,
99 } 137 }
138
139 --- Used to reflect the current working state
140 local bindMode = 3
141 local bindHeader, currentHeader = '', ''
142 local configProfile, character, specialization, global, character_specialization
143 local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
144 local classHeader, className, classID = '', '', 0
145 local numButtons = BINDS_PER_ROW * 8
146 local bindsCommitted = true
147 local forceButtonUpdate = false
148
149 --- Control handles
100 local saveButton, restoreButton, clearButton 150 local saveButton, restoreButton, clearButton
101 151
102 --- Returns a value for use with Texture:SetDesaturated() 152 --- Cursor "pickup" actuators
103 local CommandIsLocked = function(self) 153 local PickupAction = {}
104 print('command check: 1-'..(bindMode-1)) 154 PickupAction.spell = _G.PickupSpell
105 local desaturated, layer = false, 3 155 PickupAction.macro = _G.PickupMacro
106 for i = 1, bindMode-1 do 156 PickupAction.item = _G.PickupItem
157 PickupAction.mount = _G.C_MountJournal.Pickup
158 local GetPickupValue = {}
159 GetPickupValue.spell = function(self) return select(7, GetSpellInfo(self.actionID)) end
160
161 --- Returns conflicting assignment and binding profiles for use in displaying confirmations
162 local IsCommandBound = function(self, command)
163 local isAssigned, assignedBy = false, bindMode
164 local isBound, boundBy = false, bindMode
165
166
167 command = command or self.command
168 for i = 1, #BINDING_MODE do
107 local tier = priority[i] 169 local tier = priority[i]
108 local existing = tier.commands[self.command] 170 if i ~= bindMode then
109 print(' ', i, tier.commands[self.command]) 171
110 if existing then 172 if tier.commands[command] then
111 if self:GetID() ~= existing then 173 isAssigned = true
112 -- sanitize bad data 174 assignedBy = i
113 tier.commands[self.command] = nil 175 end
114 else 176 if tier.bound[command] then
115 layer = i 177 isBound = true
116 desaturated = true 178 boundBy = i
179 end
180
181
182 --print(' *', configHeaders[i], tier.commands[command], tier.bound[command])
183
184 if isAssigned and isBound then
117 break 185 break
118 end 186 end
119 end 187 end
120 end 188
121 return desaturated, layer 189 end
122 end 190
191 print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
192 return isAssigned, isBound, assignedBy, boundBy
193 end
194
195 local talentSpellHardCodes = {
196 [109248] = 'Binding Shot',
197 }
123 198
124 --- Returns a value for use with Texture:SetDesaturated() 199 --- Returns a value for use with Texture:SetDesaturated()
125 local BindingIsLocked = function(key) 200 local BindingIsLocked = function(key)
126 local success = false 201 local success = false
127 for i = 1, bindMode-1 do 202 for i = 1, bindMode-1 do
137 --- Translates GetBindingKey() results into a printable string. 212 --- Translates GetBindingKey() results into a printable string.
138 local BindingString = function(...) 213 local BindingString = function(...)
139 local stack = {} 214 local stack = {}
140 for i = 1, select('#', ...) do 215 for i = 1, select('#', ...) do
141 local key = select(i, ...) 216 local key = select(i, ...)
142 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp') 217 stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ')
143 end 218 end
144 219
145 if #stack >= 1 then 220 if #stack >= 1 then
146 return table.concat(stack, ',') 221 return table.concat(stack, ',')
147 else 222 else
148 return nil 223 return nil
149 end 224 end
150 end 225 end
151 226
152 --- This keeps our KeyDown handler from getting stuck with game controls 227 local restingAlpha = 0.7
153 KeyButton.OnUpdate = function(self) 228 local fadeTime, fadeDelay = .30, 0.15
229
230 local portraitLayers = {}
231 kb.UpdatePortraits = function()
232 for i, layeredRegion in ipairs(portraitLayers) do
233 SetPortraitTexture(layeredRegion , 'player')
234 end
235 end
236
237
238
239 local KeyButton_OnKeyDown = function(self, key)
240 kb.StoreBinding(self, key)
241 end
242 local KeyButton_OnClick = function(self, click)
243 print(self:GetName(), 'OnMouseDown', click)
244 if click == 'LeftButton' then
245 kb.DropToSlot(self)
246 elseif click == 'RightButton' then
247 kb.ReleaseSlot(self)
248 else
249 kb.StoreBinding(self, click:upper())
250 end
251 end
252
253 local KeyButton_OnDragStart = function(self)
254 kb.PickupSlot(self)
255 end
256
257 local KeyButton_OnReceiveDrag = function(self, ...)
258 kb.DropToSlot(self)
259 end
260
261
262 local KeyBinder_OnUpdate = function(self, elapsed)
263 self.elapsed = self.elapsed + elapsed
264 self.throttle = self.throttle + elapsed
265
266 if (self.throttle >= 0.032) then
267 self.throttle = 0
268 else
269 return
270 end
271
272 local progress = 1
273 if self.elapsed > fadeTime then
274 self.elapsed = 0
275 self.fadeStep = 0
276 --self.statustext:SetText(nil)
277 --self.bindingstext:SetText(nil)
278 self:SetScript('OnUpdate', nil)
279 else
280 if self.elapsed < fadeDelay then
281 progress = 0
282 else
283 self.fadeStep = self.fadeStep + 1
284 progress = (self.elapsed - fadeDelay) /(fadeTime - fadeDelay)
285 end
286 --print(self.fadeStep, format('%.02f/%.02f', (self.elapsed - fadeDelay) ,(fadeTime - fadeDelay)) , progress)
287 end
288
289 local alpha = 1 - progress * (1- restingAlpha)
290 self.statustext:SetAlpha(alpha)
291 self.bindingstext:SetAlpha(alpha)
292 end
293
294 local KeyButton_OnUpdate = function(self)
154 if not self.command then 295 if not self.command then
155 return 296 return
156 end 297 end
157 298
158 if self:IsMouseOver() then 299 if self:IsMouseOver() then
300 kb.elapsed = 0
159 if not self.active then 301 if not self.active then
160 -- only set this handler when the button is activated/mouseOver 302 -- only set this handler when the button is activated/mouseOver
161 self.active = true 303 self.active = true
162 self:SetScript('OnKeyDown', kb.bind) 304 self:SetScript('OnKeyDown', KeyButton_OnKeyDown)
163 305
164 local bindText = self.command 306 kb.statustext:SetText(self.statusText .. ': '..self.actionName)
165 if self.bind:GetText() then 307 kb.bindingstext:SetText(self.bindingText)
166 bindText = bindText .. ': |cFF00FF00' .. self.bind:GetText() 308 kb.fadeStep = 0
167 end 309 kb.throttle = 0
168 310 kb:SetScript('OnUpdate', KeyBinder_OnUpdate)
169 kb.bindlist:SetText(bindText) 311
170 GameTooltip:SetOwner(self)
171 GameTooltip:SetAnchorType('ANCHOR_BOTTOMRIGHT')
172 GameTooltip:SetText(self.actionName)
173 GameTooltip:Show()
174 end 312 end
175 else 313 else
176 if self.active then 314 if self.active then
177 GameTooltip:Hide()
178 self.active = nil 315 self.active = nil
179 self:SetScript('OnKeyDown', nil) 316 self:SetScript('OnKeyDown', nil)
180 end 317 end
181 end 318 end
182 end 319 end
183 320
184 --- Cursor pickup handler 321 local KeyBinder_OnMouseWheel = function(self, delta)
185 -- Walks through PICKUP_TYPES and runs the function if match(command, key) turns up a result. 322 print(self, delta, self.scrollOffset, (self.scrollOffset <= 0))
186 -- Passes the result through PICKUP_VALUES[pattern]() if defined. 323
187 324
188 kb.pickup = function(self) 325 if IsControlKeyDown() then
189 for pattern, pickup in pairs(PICKUP_TYPES) do 326 KEY_BUTTON_SIZE = KEY_BUTTON_SIZE - delta
190 local value = self.command:match(pattern) 327 else
191 if value then 328
192 if PICKUP_VALUES[pattern] then 329
193 value = PICKUP_VALUES[pattern](value) 330 if (delta > 0) and (self.scrollOffset <= 0) then
194 end 331 return
195 332 elseif delta < 0 and kb.scrollOffset >= 42 then
196 pickup(value) 333 return
197 kb.release(self) 334 end
198 break 335 kb.scrollOffset = ceil(kb.scrollOffset - (delta * BINDS_PER_ROW))
199 end 336 end
200 end 337
201 end 338 kb.ui(true)
202 339 end
203 --- Setup an action button base on template info 340
204 kb.action = function(type, id) 341 local KeyBinder_OnHide = function()
205 342 KeyBinderImportLog:Hide()
206 local macroName = type .. id 343 end
207 macroName = macroName:gsub(' ', '') 344
208 345 local CloseButton_OnClick = function()
209 local attribute = '*macrotext-'..macroName 346 db.showUI = false
210 local value = ACTION_SCRIPT[type]:format(id) 347 kb:Hide()
211 local command = 'CLICK KeyBinderMacro:'.. macroName 348 end
212 profile.macros[attribute] = {value, command} 349 local CancelButton_OnClick = function()
213 350 kb.RevertBindings()
214 KeyBinderMacro:SetAttribute(attribute, value) 351 end
215 return command 352 local SaveButton_OnClick = function()
216 end 353 kb.ConfirmBindings()
217 354 end
218 KeyButton.OnDragStart = function(self) 355
356 local KeyBinder_Initialize = function()
357 for i = 1, GetNumBindings() do
358 local command = GetBinding(i)
359 bindings[command] = true
360 end
361
362 kb.scrollOffset = 0
363 kb.tabAnchor = {'TOPLEFT', kb.profilebg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING}
364 kb.tabGrowth = {'TOPLEFT', nil,'TOPRIGHT', BUTTON_SPACING, 0}
365 kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT }
366 kb.UIPanelAnchor = {'TOPLEFT', kb.sourcesbg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING}
367 kb.UIPanelGrowth = {'TOPLEFT', nil, 'BOTTOMLEFT', 0, -2 }
368 kb.UIPanelSize = {84, 32 }
369 kb.UIPanelIcon = {24, 32, 'LEFT', -12, 0}
370 kb.controlsAnchor = {'BOTTOMLEFT', kb.footer, BUTTON_PADDING, BUTTON_PADDING }
371 kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0}
372
373 -- order of these is important
374 kb:tab('KeyBinderGlobalTab',
375 BINDING_MODE[BINDING_TYPE_GLOBAL] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_GLOBAL], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85})
376 kb:tab('KeyBinderCharacterTab',
377 configHeaders[BINDING_TYPE_CHARACTER] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_CHARACTER], nil)
378 kb:tab('KeyBinderSpecTab',
379 configHeaders[BINDING_TYPE_SPECIALIZATION] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_SPECIALIZATION], specTexture)
380 KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85)
381
382
383
384 portraitLayers[1] = KeyBinderCharacterTab.icon
385
386 saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', SaveButton_OnClick)
387 --restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', CancelButton_OnClick)
388 --clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', ResetButton_OnClick)
389
390 kb:uibutton(
391 'KeyBinderSpellBookButton', 'SpellBook', nil,
392 function() ToggleSpellBook(BOOKTYPE_SPELL) end,
393 "Interface\\BUTTONS\\UI-MicroButton-Spellbook-Up", {0, 1, .4, 1})
394 kb:uibutton(
395 'KeyBinderTalentFrameButton', TALENTS, SPECIALIZATION,
396 function() ToggleTalentFrame() end,
397 "Interface\\BUTTONS\\UI-MicroButton-Talents-Up", {0, 1, .4, 1})
398
399 kb:uibutton(
400 'KeyBinderMacroFrameButton', 'Macros', nil,
401 function() if MacroFrame and MacroFrame:IsVisible() then
402 HideUIPanel(MacroFrame)
403 else
404 ShowMacroFrame() end
405 end,
406 "Interface\\BUTTONS\\UI-MicroButton-Help-Up", {0, 1, .4, 1})
407
408 kb:uibutton(
409 'KeyBinderInventoryButton', 'Bags', nil,
410 function() OpenAllBags() end,
411 "Interface\\BUTTONS\\UI-MicroButtonCharacter-Up", {0, 1, .4, 1})
412
413
414
415 kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING)
416 HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING
417 + kb.info:GetHeight()
418 FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING
419
420 kb:SetScript('OnHide', KeyBinder_OnHide)
421 kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel)
422 kb.CloseButton:SetScript('OnClick', CloseButton_OnClick)
423
424 kb.UpdatePortraits()
425 end
426
427 kb.DropToSlot = function(self)
428
429 print(self:GetName(),'|cFF0088FFreceived|r')
430 local actionType, actionID, subType, subData = GetCursorInfo()
431 print('GetCursorInfo', GetCursorInfo())
432
433
434 if actionType then
435
436 if actionType == 'flyout' then
437 ClearCursor()
438 ResetCursor()
439 return
440 end
441
442
443 local macroName, macroText
444 local command, name, icon, _
445 local pickupID, pickupBook
446
447 if actionType == 'spell' then
448 actionID = subData
449 name, _, icon = GetSpellInfo(actionID)
450
451 elseif actionType == 'macro' then
452 name, icon = GetMacroInfo(actionID)
453 actionID = name
454 elseif actionType == 'petaction' then
455 if not (CURSOR_SPELLSLOT and CURSOR_BOOKTYPE) then
456
457 ClearCursor()
458 ResetCursor()
459 end
460
461 local bookType, spellID = GetSpellBookItemInfo(CURSOR_SPELLSLOT, CURSOR_BOOKTYPE)
462 pickupID = CURSOR_SPELLSLOT
463 pickupBook = CURSOR_BOOKTYPE
464 name, _, icon = GetSpellInfo(spellID)
465 actionID = name
466
467 elseif actionType == 'mount' then
468 if subType == 0 then
469 name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL)
470 actionID = 0
471 else
472 name, _, icon = C_MountJournal.GetMountInfoByID(actionID)
473 end
474 elseif actionType == 'item' then
475 name = GetItemInfo(actionID)
476 icon = GetItemIcon(actionID)
477 actionID = name
478 elseif actionType == 'battlepet' then
479
480 local speciesID, customName, level, xp, maxXp, displayID, isFavorite, petName, petIcon, petType, creatureID = C_PetJournal.GetPetInfoByPetID(detail);
481 name = customName or petName
482 icon = petIcon
483
484 end
485 macroName, macroText, command = kb.RegisterAction(actionType, actionID)
486
487
488 local isAssigned, isBound, assignedBy, boundBy = IsCommandBound(self, command)
489 if isAssigned then
490 local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"]
491 popup.slot = self
492 popup.text = "Currently assigned in |cFFFFFF00"..tostring(configHeaders[assignedBy]).."|r. Are you sure?"
493 popup.oldProfile = assignedBy
494 popup.args = {command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook }
495 kb:SetScript('OnMouseWheel', nil) -- disable scrolling
496 StaticPopup_Show('SKELETONKEY_CONFIRM_ASSIGN_SLOT')
497 else
498 kb.SetSlot(self, command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook)
499 kb.UpdateSlot(self)
500 self.active = nil
501 KeyButton_OnUpdate(self, 0)
502 ClearCursor()
503 ResetCursor()
504 end
505 end
506 end
507
508 kb.PickupSlot = function(self)
219 if not self.command then 509 if not self.command then
220 return 510 return
221 end 511 end
222 kb.pickup(self) 512 print(self.actionType)
223 end 513 if self.actionType == 'spell' then
224 514 -- It can't be picked up if SpellInfo(name) returns void
225 KeyButton.OnReceiveDrag = function(self, ...) 515 local dummy = GetSpellInfo(self.actionName)
226 print(self:GetName(),'|cFF0088FFreceived|r', ...) 516 if not dummy then
227 local type, value, subType, subData = GetCursorInfo() 517 return
228 print('GetCursorInfo', type, value, subType, subData) 518 end
229 if type then 519 elseif self.actionType == 'petaction' then
230 if type == 'spell' then 520 PickupSpellBookItem(self.pickupSlot, self.pickupBook)
231 value = subData 521 end
232 end 522 if PickupAction[self.actionType] then
233 523 if GetPickupValue[self.actionType] then
234 local command, name, icon, _ 524 PickupAction[self.actionType](GetPickupValue[self.actionType](self))
235 if type == 'spell' then 525 else
236 name, _, icon = GetSpellInfo(value) 526 PickupAction[self.actionType](self.actionID)
237 command = 'SPELL ' .. name 527 end
238 name = '' 528 kb.ReleaseSlot(self)
239 elseif type == 'macro' then 529 kb.UpdateSlot(self)
240 name, icon = GetMacroInfo(value) 530 end
241 command = 'MACRO ' .. name 531 end
242 elseif type == 'mount' then 532
243 if subType == 0 then 533
244 name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL) 534 --- Resolve the appropriate command and assign the corresponding secure state driver
245 value= 0 535 kb.RegisterAction = function(type, id)
246 else 536
247 name, _, icon = C_MountJournal.GetMountInfoByID(value) 537 if type == 'spell' then
248 end 538
249 command = kb.action(type, value) 539 id = GetSpellInfo(id)
250 elseif type == 'item' then 540 end
251 name = GetItemInfo(value) 541
252 icon = GetItemIcon(value) 542 local macroText
253 command = 'ITEM ' .. name 543 local macroName = type ..'_' .. id
254 end 544
255 kb.assign(self, command, name, icon) 545 if kb.ProfessionCache[id] then
256 kb.refresh(self) 546 macroName = "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum
257 ClearCursor() 547 macroText = "/cast " .. kb.ProfessionCache[id].spellName
258 end 548 macros[macroName] = nil
259 end
260
261 KeyButton.OnMouseDown = function(self, click)
262 print(self:GetName(), 'OnMouseDown', click)
263 if click == 'LeftButton' then
264 KeyButton.OnReceiveDrag(self)
265 elseif click == 'RightButton' then
266 kb.release(self)
267 else 549 else
268 kb.bind(self) 550 macroName = macroName:gsub(' ', '')
269 end 551 macroText = ACTION_SCRIPT[type]:format(id)
552 end
553
554 local baseName, iterative = macroName, 1
555 while (macros[macroName] and macros[macroName][1] ~= macroText) do
556 print(' * cannot use|cFF00FF00', macroName, '|r"'.. (macros[macroName][1] or '') .. '"')
557 macroName = baseName .. '_' .. iterative
558 iterative = iterative + 1
559 end
560 if macroName ~= baseName then
561 print(' * Creating|cFF00FF00', macroName)
562 else
563 print(' * Re-using|cFF00FF00', macroName)
564 end
565
566 local command = 'CLICK KeyBinderMacro:'.. macroName
567 macros[macroName] = {macroText, command }
568 print('RegisterAction', command , macroText)
569 if type == 'macro' then
570 kb.LoadMacro(macroName)
571 else
572 kb.LoadAction(macroName, macroText, command)
573 end
574
575
576 return macroName, macroText, command
577 end
578
579 kb.LoadMacro = function(macroName)
580 KeyBinderMacro:SetAttribute('*macro-'..macroName, macros[macroName][1])
581 return true
582 end
583
584 kb.LoadAction = function(macroName)
585 if not macros[macroName] then
586 return false
587 end
588 KeyBinderMacro:SetAttribute('*macrotext-'..macroName, macros[macroName][1])
589 return true
590 end
591
592 local profressionsCache
593
594 kb.AcceptAssignment = function(self, ...)
595 local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"]
596 local source = loadedProfiles[popup.oldProfile]
597 kb.SetSlot(popup.slot, unpack(popup.args))
598 kb.UpdateSlot(popup.slot)
599 kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) -- re-enable scrolling
600 ClearCursor()
601 ResetCursor()
270 end 602 end
271 603
272 604
273 --- Updates the current KeyBinding for the button's command 605 --- Updates the current KeyBinding for the button's command
274 kb.bind = function(self, key) 606 kb.StoreBinding = function(self, key)
275 607
276 print('|cFFFFFF00bind|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
277 if not self.command then 608 if not self.command then
278 return 609 return
279 end 610 end
280 611
281 if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then 612 if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then
282 return 613 return
283 end 614 end
284 615 print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
285 if protected[GetBindingAction(key)] then
286 return
287 kb.bindlist:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(key)))
288 end
289 616
290 if key == 'ESCAPE' then 617 if key == 'ESCAPE' then
291 local key1, key2 = GetBindingKey(self.command) 618 local keys = {GetBindingKey(self.command) }
292 if key1 then 619 --print('detected', #keys, 'bindings')
293 SetBinding(key1, nil) 620 for i, key in pairs(keys) do
294 print('Unbound', key1) 621 --print('clearing', key)
295 end 622 SetBinding(key, nil)
296 if key2 then 623 SaveBindings(GetCurrentBindingSet())
297 SetBinding(key2, nil) 624 if configProfile.bindings[key] then
298 print('Unbound', key2) 625 kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode]))
299 end 626 configProfile.bindings[key] = nil
627 end
628 if configProfile.talents[self.actionName] then
629 configProfile.talents[self.actionName] = nil
630 end
631 bindings[self.actionType][self.actionID] = nil
632 end
633 if configProfile.bound[self.command] then
634 configProfile.bound[self.command] = nil
635 --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode]))
636 end
637
638 bindsCommitted = false
300 self.active = false 639 self.active = false
301 return 640 else
302 end 641
303 642 local modifier = ''
304 local modifier = '' 643 if IsAltKeyDown() then
305 if IsAltKeyDown() then 644 modifier = 'ALT-'
306 modifier = 'ALT-' 645 end
307 end 646 if IsControlKeyDown() then
308 if IsControlKeyDown() then 647 modifier = modifier.. 'CTRL-'
309 modifier = modifier.. 'CTRL-' 648 end
310 end 649 if IsShiftKeyDown() then
311 if IsShiftKeyDown() then 650 modifier = modifier..'SHIFT-'
312 modifier = modifier..'SHIFT-' 651 end
313 end 652
314 653
654 if self.command then
655 self.binding = modifier..key
656
657 local previousKeys
658 local previousAction = GetBindingAction(self.binding)
659 local binding1, binding2, new1, new2
660 print(type(previousAction), previousAction)
661 if previousAction ~= "" and previousAction ~= self.command then
662 if protected[previousAction] then
663 -- bounce out if trying to use a protected key
664 kb.statustext:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(previousAction)))
665 kb.bindingstext:SetText(nil)
666 return
667 else
668 kb:print('Discarding keybind for', previousAction)
669 -- todo: sort out retcon'd talent spells
670 end
671 end
672
673 self.pending = true
674
675 bindsCommitted = false
676 SetBinding(self.binding, self.command)
677 SaveBindings(GetCurrentBindingSet())
678
679 local talentInfo
680 if self.actionType == 'spell' and kb.TalentCache[self.actionID] then
681 print('conditional binding (talent = "'..self.actionName..'")')
682 talentInfo = {self.macroName, self.actionName, self.actionType, self.actionID}
683 local bindings = {GetBindingKey(self.command) }
684 for i, key in ipairs(bindings) do
685 tinsert(talentInfo, key)
686 end
687 end
688
689 for level, configProfile in ipairs(priority) do
690 if (level == bindMode) then
691 configProfile.bound[self.command] = true
692 if talentInfo then
693 configProfile.bindings[self.binding] = nil
694 else
695 configProfile.bindings[self.binding] = self.command
696 end
697 configProfile.talents[self.actionName] = talentInfo
698 else
699 configProfile.bindings[self.binding] = nil
700 configProfile.bound[self.command] = nil
701 configProfile.talents[self.actionName] = nil
702 end
703 if configProfile.talents[self.actionID] then
704 configProfile.talents[self.actionID] = nil
705 end
706
707 end
708
709
710
711 kb:print(BINDING_ASSIGNED:format(self.binding, self.actionName, configHeaders[bindMode]))
712
713 end
714 end
715
716 kb.UpdateSlot(self, true)
717 KeyBinderSaveButton:Enable()
718
719 end
720
721 --- Resets button command
722 kb.ReleaseSlot = function(self)
723 local slot = self:GetID()
724
725
726 if configProfile.buttons[slot] then
727 configProfile.buttons[slot] = nil
728 end
315 if self.command then 729 if self.command then
316 self.binding = modifier..key 730 configProfile.commands[self.command] = nil
317 self.pending = true 731 end
318 self.border:SetColorTexture(1,.5,0, 1) 732 if self.actionType == 'spell' and IsTalentSpell(self.actionName) then
319 733 if configProfile.talents[self.actionID] then
320 local old = GetBindingAction(self.binding) 734 configProfile.talents[self.actionID] = nil
321 local binding1, binding2, new1, new2 735 end
322 if old and old ~= self.command then 736 end
323 print('Discarding keybind for', old) 737 local droppedKeys = {}
324 local binding1, binding2 = GetBindingKey(old) 738
325 -- need to preserve argument order 739 -- doing removal in second loop to avoid possible iterator shenanigans
326 end 740 for k,v in pairs(configProfile.bindings) do
327 tinsert(reverts, {old, binding1, binding2, new1, new2}) 741 if v == self.command then
328 742 tinsert(droppedKeys, k)
329 743 end
330 bindsCommitted = false 744 end
331 SetBinding(self.binding, self.command) 745 if #droppedKeys >=1 then
332 for level, profile in ipairs(priority) do 746 for i, k in ipairs(droppedKeys) do
333 profile.bindings[self.binding] = (level == bindMode) and self.command or nil 747 configProfile.bindings[k] = nil
334 end 748 end
335 print(BINDING_ASSIGNED:format(self.binding, self.command, BINDING_MODE[bindMode]:format(bindHeader))) 749 end
336 750
337 kb.refresh(self) 751 self.isAvailable = nil
338 end 752 self.isDynamic = nil
339 end 753 self.bindingText = nil
340 754 self.statusText = nil
341 --- Resets button command
342 kb.release = function(self)
343 local index = self:GetID()
344 self.command = nil 755 self.command = nil
756 self.actionType = nil
757 self.actionID = nil
345 self.actionName = nil 758 self.actionName = nil
346 self.macro:SetText(nil) 759 self.pickupSlot = nil
760 self.pickupBook = nil
761 self.macroName = nil
347 self.profile = nil 762 self.profile = nil
348 self.bind:SetText(nil)
349 self.icon:SetTexture(nil) 763 self.icon:SetTexture(nil)
350 self.border:SetColorTexture(unpack(BORDER_UNASSIGNED)) 764 self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
351 self:EnableKeyboard(false) 765 self:EnableKeyboard(false)
352 self:SetScript('OnKeyDown', nil) 766 self:SetScript('OnKeyDown', nil)
353 767 end
354 768
355 if profile.buttons[index] then 769 kb.SetSlot = function(self, command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook)
356 profile.buttons[index] = nil 770 local slot = self:GetID()
357 end 771 local isDynamic, isAvailable
358 end 772
359 773 print('|cFFFFFF00SetSlot|r:', self:GetID())
360 -- Sets button command
361
362 kb.assign = function(self, command, name, icon)
363 local index = self:GetID()
364 print('|cFF00FFFFassign|cFF0088FF', index, '|cFFFFFF00'.. (command or 'none'), '|cFF00FF00'.. (name or ''), '|cFF00FFFF' .. (icon or ''))
365
366
367 if command then 774 if command then
368 if command:match(COMMAND_SPELL) then 775
369 name = command:match(COMMAND_SPELL) 776 if actionType == 'spell' then
370 end 777 local professionNum, spellNum = command:match("profession_(%d)_(%d)")
371 778
372 779 if (professionNum and spellNum) then
780 isDynamic = 'profession'
781 local cacheInfo = kb.ProfessionCache[professionNum..'_'..spellNum]
782 if cacheInfo then
783 isAvailable = true
784 name = cacheInfo.spellName
785 icon = cacheInfo.icon
786 actionID = cacheInfo.spellID
787 self.profIndex = cacheInfo.profIndex
788 self.spellOffset = cacheInfo.spellOffset
789 end
790 print(' Special slot: |cFF00FFFFProfession|r', professionNum, spellNum, isDynamic, isAvailable)
791
792 self.professionNum = tonumber(professionNum)
793 self.spellNum = tonumber(spellNum)
794
795 elseif kb.TalentCache[actionID] then
796
797 isDynamic = 'talent'
798 isAvailable = GetSpellInfo(name)
799 print(' Special slot: |cFFBBFF00talent|r', name, isAvailable)
800 end
801 if not actionID then
802 actionID = select(7, GetSpellInfo(name))
803 end
804 elseif actionType == 'macro' then
805 if not actionID then
806 actionID = GetMacroIndexByName(name)
807 end
808 else
809 --- Journal selections
810 -- todo: consider using the deep end of blizzard action bar instead
811 if not actionID then
812 actionID = command:match("^KeyBinderMacro:(.+)")
813 end
814 end
815
816 if not macroName then
817 local previousCommand = command
818 macroName, macroText, command = kb.RegisterAction(actionType, actionID)
819
820 -- Clean up conflicting command entry for loaded buttons
821 if macroName and command ~= previousCommand then
822 print(' Repaired corruption in |cFFFFFF00'..currentHeader..'|r button #'.. self:GetID())
823 configProfile.commands[previousCommand] = nil
824 configProfile.bound[previousCommand] = nil
825 end
826 end
827
828 if actionType == 'petaction' then
829 self.pickupSlot = pickupSlot
830 self.pickupBook = pickupBook
831 else
832 self.pickupSlot = nil
833 self.pickupBook = nil
834 end
835
836 actionID = actionID or 0
373 self:EnableKeyboard(true) 837 self:EnableKeyboard(true)
374 print('profile.buttons['..index..'] |cFF00FFFF=|r ', command, name, icon) 838 print(' |cFF00FF00configProfile.buttons['..slot..'] |cFF00FFFF=|r |cFF00FFFF"'.. command.. '"|r |cFF00FF00"'.. name, '"|r |cFFFFFF00icon:'.. icon .. '|r |cFFFF8800"'.. actionType, '"|r |cFFFF0088id:'.. actionID ..'|r |cFF00FF00"'.. macroName .. '"|r')
375 profile.buttons[index] = {command, name, icon} 839 configProfile.buttons[slot] = {command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook}
376 840
377 --- Clean up any residual buttons 841 -- Clean up conflicting entries for loaded button
378 local previous = profile.commands[command] 842 local previous = configProfile.commands[command]
379 if previous ~= index and buttons[previous] then 843 if previous ~= slot and buttons[previous] then
380 kb.release(buttons[previous]) 844 kb.ReleaseSlot(buttons[previous])
381 end 845 end
382 846 configProfile.commands[command] = slot
383 profile.commands[command] = index 847 end
384 end 848
385 849 self.isAvailable = isAvailable
386 self.profile = bindMode 850 self.isDynamic = isDynamic
851
852 self.macroText = macroText
853 self.macroName = macroName
854 self.actionType = actionType
855 self.actionID = actionID
387 self.actionName = name 856 self.actionName = name
388 self.command = command 857 self.command = command
389 self.icon:SetTexture(icon) 858 self.icon:SetTexture(icon)
859 self.profile = bindMode
390 self:RegisterForDrag('LeftButton') 860 self:RegisterForDrag('LeftButton')
391 end 861 end
392 862
393 --- Retrieves button at index; creates said button and instates any stored parameters 863 --- Retrieves button at index; creates said button and instates any stored parameters
394 kb.keyslot = function(index) 864 local leftSlot, upSlot
865 local buttonsDepth = 0
866 kb.GetSlot = function(index)
867
868 local slot = index + kb.scrollOffset
869
395 if not buttons[index] then 870 if not buttons[index] then
396 local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton') 871 local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton')
397 button:SetScript('OnMouseDown', KeyButton.OnMouseDown) 872 button:SetScript('OnClick', KeyButton_OnClick)
398 button:SetScript('OnMouseUp', KeyButton.OnMouseUp) 873 button:SetScript('OnUpdate', KeyButton_OnUpdate)
399 button:SetScript('OnUpdate', KeyButton.OnUpdate) 874 button:SetScript('OnDragStart', KeyButton_OnDragStart)
400 button:SetScript('OnDragStart', KeyButton.OnDragStart) 875 button:SetScript('OnReceiveDrag', KeyButton_OnReceiveDrag)
401 button:SetScript('OnReceiveDrag', KeyButton.OnReceiveDrag) 876 button:RegisterForClicks('AnyUp')
402 button:SetID(index) 877
403 878
404 if profile.buttons[index] and type(profile.buttons[index] ) == 'table' then 879 local newRow = (mod(index, BINDS_PER_ROW) == 1)
405 kb.assign(button, unpack(profile.buttons[index] )) 880
881 if index == 1 then
882 button:SetPoint('TOPLEFT', kb.bg, 'TOPLEFT', BUTTON_PADDING, - BUTTON_PADDING)
883 upSlot = button
884 buttonsDepth = KEY_BUTTON_SIZE + BUTTON_PADDING * 2
885 elseif newRow then
886 button:SetPoint('TOPLEFT', upSlot, 'BOTTOMLEFT', 0, -BUTTON_SPACING)
887 upSlot = button
888 buttonsDepth = buttonsDepth + KEY_BUTTON_SIZE + BUTTON_SPACING
406 else 889 else
407 kb.release(button) 890 button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0)
408 end 891 end
409 892
410 local x, y = BUTTON_PADDING, - (BUTTON_PADDING + HEADER_OFFSET)
411 if index ~= 1 then
412 local col = mod(index, BINDS_PER_ROW)
413 if col == 0 then
414 col = BINDS_PER_ROW - 1
415 else
416 col = col - 1
417 end
418 x = col * (KEY_BUTTON_SIZE + BUTTON_SPACING) + BUTTON_PADDING
419 y = (ceil(index/ BINDS_PER_ROW)-1) * - (KEY_BUTTON_SIZE + BUTTON_SPACING) - BUTTON_PADDING - HEADER_OFFSET
420 end
421 button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE) 893 button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
422 button:SetPoint('TOPLEFT', kb, 'TOPLEFT', x, y)
423 button:Show() 894 button:Show()
424 buttons[index] = button 895 buttons[index] = button
896 leftSlot = button
425 end 897 end
426 return buttons[index] 898 return buttons[index]
427 end 899 end
428 900
429 --- Updates profile assignment and button contents 901 --- Updates profile assignment and button contents
430 kb.refresh = function(self) 902 kb.UpdateSlot = function(self, force)
431 if self.profile ~= bindMode then 903 local slot = self:GetID()
432 if profile.buttons[self:GetID()] then 904
433 kb.assign(self, unpack(profile.buttons[self:GetID()])) 905 if force then
906 if configProfile.buttons[slot] then
907 kb.SetSlot(self, unpack(configProfile.buttons[slot]))
434 else 908 else
435 kb.release(self) 909 kb.ReleaseSlot(self)
436 end 910 end
437 end 911 end
438 912
439 if self.command then 913 if self.command then
914 print('['..slot..'] =', self.command, GetBindingKey(self.command))
915
440 if self.pending then 916 if self.pending then
441 self.border:SetColorTexture(unpack(BORDER_PENDING)) 917 self.border:SetColorTexture(unpack(BORDER_PENDING))
918 elseif self.isDynamic then
919 self.border:SetColorTexture(unpack(BORDER_DYNAMIC))
442 else 920 else
443 self.border:SetColorTexture(unpack(BORDER_ASSIGNED)) 921 self.border:SetColorTexture(unpack(BORDER_ASSIGNED))
444 end 922 end
445 --self.macro:SetText(self.actionName) 923
446 self.bind:SetText(BindingString(GetBindingKey(self.command))) 924 if self.actionType == 'macro' then
447 local locked, layer = CommandIsLocked(self) 925 self.macro:Show()
448 self.icon:SetDesaturated(locked) 926 else
449 self.icon:SetVertexColor(unpack(BINDING_SCHEME_VERTEX[layer])) 927 self.macro:Hide()
928 if self.actionType == 'spell' then
929 local dummy = GetSpellInfo(self.actionName)
930 if not dummy then
931 self.icon:SetDesaturated(true)
932 else
933 self.icon:SetDesaturated(false)
934 end
935
936 end
937 end
938
939 if self.isDynamic then
940 print('|cFFFFBB00UpdateSlot|r: ', self.isDynamic, self.isAvailable, self.actionID)
941 end
942
943 if self.isDynamic == 'profession' then
944 local profText = (self.spellNum == 1) and TRADE_SKILLS or (BUTTON_HEADERS[self.profIndex] or GetProfessionInfo(self.profIndex))
945 if self.isAvailable then
946 print(self.profIndex, 'spnum', type(self.spellNum), (self.spellNum == 1))
947
948 self.statusText = '|cFFFFFF00'..profText..'|r'
949 self.bindingText = BindingString(GetBindingKey(self.command))
950 else
951 self.statusText = '|cFFFF4400'..profText..'|r'
952 self.actionName = '(need to train profession #'..self.profNum..')'
953 self.bindingText ='?'
954 end
955 elseif self.isDynamic == 'talent' then
956
957 self.statusText = '|cFF00FFFF'.. TALENT .. '|r'
958 if self.isAvailable then
959 self.bindingText = BindingString(GetBindingKey(self.command))
960 else
961 print(self.actionID, #kb.inactiveTalentBindings[self.actionID])
962 self.bindingText= BindingString(unpack(kb.inactiveTalentBindings[self.actionID]))
963 end
964 else
965 self.statusText = '|cFF00FF00'.. (BUTTON_HEADERS[self.actionType] and BUTTON_HEADERS[self.actionType] or self.actionType) .. '|r'
966 self.bindingText = BindingString(GetBindingKey(self.command))
967 end
968
969 local locked, layer = IsCommandBound(self)
970 if locked then
971 self.icon:SetAlpha(0.5)
972 else
973 self.icon:SetAlpha(1)
974 end
975
976 if self.actionType == 'spell' then
977 self.icon:SetTexture(GetSpellTexture(self.actionID))
978 end
979 end
980
981 if not self.isAvailable then
982 self.bind:SetTextColor(0.7,0.7,0.7,1)
450 else 983 else
451 self.border:SetColorTexture(unpack(BORDER_UNASSIGNED)) 984 self.bind:SetTextColor(1,1,1,1)
452 --self.macro:SetText(nil) 985 end
453 self.bind:SetText(nil) 986
454 end 987 self.header:SetText(self.statusText)
455 end 988 self.bind:SetText(self.bindingText)
456 989 self.macro:SetText(self.macroName)
457 local SetupUI = function() 990 self.details:SetText(self.actionName)
458 991 end
459 992
460 kb.tabAnchor = {'TOPLEFT', kb, 'TOPRIGHT', 2, -TAB_OFFSET} 993
461 kb.tabGrowth = {'TOPLEFT', nil,'BOTTOMLEFT', 0, -TAB_SPACING} 994 kb.ApplyTalentBinding = function(talentInfo, cache)
462 kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT } 995 for i = 5, #talentInfo do
463 kb.UIPanelAnchor = {'TOPLEFT', kb, 'TOPLEFT', BUTTON_PADDING + 12, -BUTTON_PADDING} 996 SetBinding(talentInfo[i], "CLICK KeyBinderMacro:".. talentInfo[1])
464 kb.UIPanelGrowth = {'TOPLEFT', nil, 'TOPRIGHT', 14, 0 } 997 tinsert(cache, talentInfo[i])
465 kb.controlsAnchor = {'BOTTOMLEFT', kb, BUTTON_PADDING, BUTTON_PADDING } 998 end
466 kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0} 999 end
467 1000 kb.CacheTalentBinding = function(talentInfo, cache)
468 --tab() frame, name, tooltip, texture, coords 1001 local spellID = talentInfo[4]
469 kb:tab('KeyBinderGlobalTab', BINDING_MODE[1], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85}) 1002 kb.inactiveTalentBindings[spellID] = kb.inactiveTalentBindings[spellID] or {}
470 kb:tab('KeyBinderCharacterTab', characterHeader, nil) 1003 kb.inactiveTalentBindings[spellID] = {select(5,unpack(talentInfo)) }
471 kb:tab('KeyBinderSpecTab', specHeader, specTexture) 1004 cprint(spellID, unpack(kb.inactiveTalentBindings[spellID]))
472 SetPortraitTexture(KeyBinderCharacterTab.icon, 'player') 1005 end
473 KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85) 1006
474 1007 kb.ApplyBinding = function(command, name, icon, actionType, actionID, macroName, macroText )
475 saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', nil, kb.save) 1008
476 restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', nil, kb.restore) 1009 if actionType == 'macro' then
477 clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', nil, kb.ResetProfile) 1010 KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID)
478 1011 else
479 kb:uibutton( 1012 KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText)
480 'KeyBinderSpellBookButton', 'SpellBook', nil, 1013 end
481 function() ToggleSpellBook(BOOKTYPE_SPELL) end, 1014 bindings[actionType] = bindings[actionType] or {}
482 "Interface\\Spellbook\\Spellbook-Icon") 1015 bindings[actionType][actionID] = bindings[actionType][actionID] or {}
483 kb:uibutton( 1016 bindings[command] = bindings[actionType][actionID]
484 'KeyBinderTalentFrameButton', 'Talents', nil, 1017 return bindings[actionType], actionID
485 function() ToggleTalentFrame() end, 1018 end
486 "Interface\\TargetingFrame\\UI-Classes-Circles", 1019
487 CLASS_ICON_TCOORDS[strupper(select(2,UnitClass("player")))]) 1020 kb.ApplyBindings = function (profile)
488 1021 cprint('binding profile', profile)
489 kb:uibutton( 1022 for slot, data in pairs(profile.buttons) do
490 'KeyBinderMacroFrameButton', 'Macros', nil, 1023 kb.ApplyBinding(unpack(data))
491 function() if MacroFrame then HideUIPanel(MacroFrame) else ShowMacroFrame() end end, 1024 end
492 "Interface\\MacroFrame\\MacroFrame-Icon") 1025
493 1026 for key, command in pairs(profile.bindings) do
494 kb:uibutton( 1027
495 'KeyBinderInventoryButton', 'Bags', nil, 1028 cprint('Bindings data registered', command, key)
496 function() OpenAllBags() end, 1029
497 "Interface\\BUTTONS\\Button-Backpack-Up") 1030 --_G.print('HotKey','loading', key, command)
498 1031 SetBinding(key, command)
499 kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING) 1032 if bindings[command] and not tContains(bindings[command], key) then
500 HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING 1033 tinsert(bindings[command], key)
501 FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING 1034 end
502 end 1035 end
503 1036
504 --- Invokes the KeyBinder frame (from the /kb function or some other source) 1037 for spellName, talentInfo in pairs(profile.talents) do
505 kb.ui = function() 1038 local dummy = GetSpellInfo(spellName)
1039 local func = kb.CacheTalentBinding
1040 local dest = kb.inactiveTalentBindings
1041 if dummy then
1042 cprint('|cFFBBFF00Active:|r', dummy)
1043 local macroName, spellName, actionType, actionID = unpack(talentInfo)
1044 bindings[actionType] = bindings[actionType] or {}
1045 bindings[actionType][actionID] = {}
1046 func = kb.ApplyTalentBinding
1047 dest = bindings[actionType][actionID]
1048 else
1049
1050 cprint('|cFFFF4400Inactive:|r', talentInfo[2])
1051 end
1052 func(talentInfo, dest)
1053 end
1054
1055 SaveBindings(GetCurrentBindingSet())
1056 end
1057
1058 kb.ApplyAllBindings =function ()
1059 table.wipe(kb.inactiveTalentBindings)
1060
1061 for i, profile in ipairs(priority) do
1062 kb.ApplyBindings(profile)
1063 end
1064 -- do this after to ensure that profession binds are properly overridden
1065 kb.UpdateProfessionInfo()
1066 end
1067
1068 kb.Command = function(args, editor)
1069 if args:match("import") then
1070 kb.ImportCommmit(args)
1071 return
1072 elseif args:match("scan") then
1073 kb.ImportScan(args)
1074 kb.ui()
1075 return
1076 elseif args:match("load") then
1077 kb:ApplyAllBindings()
1078 return
1079 end
1080
1081 if db.showUI then
1082 db.showUI = false
1083 kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
1084 kb:Hide()
1085 else
1086 db.showUI = true
1087 kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
1088 end
1089 kb.ui(true)
1090 end
1091
1092 kb.InitProfile = function(profile, prototype)
1093 if not profile then
1094 profile = {}
1095 end
1096 if prototype then
1097 print('appplying prototype', prototype)
1098 for k,v in pairs(prototype) do
1099 if not profile[k] then
1100 profile[k] = v
1101 end
1102 end
1103 end
1104
1105 profile.bound = profile.bound or {}
1106 profile.buttons = profile.buttons or {}
1107 profile.commands = profile.commands or {}
1108 profile.bindings = profile.bindings or {}
1109 profile.macros = profile.macros or {}
1110 profile.talents = profile.talents or {}
1111 return profile
1112 end
1113
1114 kb.ResetProfile = function(profile, prototype)
1115 if profile == configProfile then
1116 for i, button in pairs(buttons) do
1117 kb.ReleaseSlot(button)
1118 end
1119 end
1120 table.wipe(profile)
1121 kb.InitProfile(profile, prototype)
1122 end
1123
1124
1125
1126 --- Handles constructing spec profiles as they are selected
1127
1128
1129 kb.TalentCache = {}
1130
1131 kb.UpdateSpecInfo = function()
1132 specID = GetSpecialization()
1133 specGlobalID, specName, specDesc , specTexture = GetSpecializationInfo(specID)
1134 loadedProfiles[BINDING_TYPE_CHARACTER][specID] = kb.InitProfile(loadedProfiles[BINDING_TYPE_CHARACTER][specID], {
1135 specID = specID})
1136
1137 configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName)
1138 loadedProfiles[BINDING_TYPE_SPECIALIZATION] = loadedProfiles[BINDING_TYPE_CHARACTER][specID]
1139 configProfile = loadedProfiles[bindMode]
1140 print('|cFF00FF00bindMode:|r', bindMode)
1141
1142 priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
1143
1144 print('|cFF00FF00current spec:|r', specID, 'of', GetNumSpecializations())
1145 end
1146
1147 kb.UpdateTalentInfo = function()
1148 if kb.talentsPushed then
1149 return
1150 end
1151
1152
1153 table.wipe(kb.TalentCache)
1154
1155 for row =1, MAX_TALENT_TIERS do
1156 for col = 1, NUM_TALENT_COLUMNS do
1157 local talentID, talentName, icon, selected, available, spellID = GetTalentInfo(row, col, 1)
1158 local talentInfo = kb.TalentCache[spellID] or {}
1159 talentInfo.row = 1
1160 talentInfo.col = col
1161 talentInfo.name = talentName
1162 talentInfo.talentID = talentID
1163 talentInfo.selected = selected
1164 talentInfo.available = available
1165 talentInfo.spellID = spellID
1166 kb.TalentCache[spellID] = talentInfo
1167 print('Talent ', row, col, spellID, talentName)
1168 end
1169 end
1170 kb.talentsPushed = true
1171 end
1172
1173
1174 kb.ProfessionCache = {}
1175 kb.UpdateProfessionInfo = function()
1176 table.wipe(kb.ProfessionCache)
1177 local profs = {GetProfessions() }
1178 local primaryNum = 0
1179 for i, index in ipairs(profs) do
1180 local profName, texture, rank, maxRank, numSpells, spellOffset = GetProfessionInfo(index)
1181 cprint(i, index, profName, numSpells, spellOffset)
1182 if not professionMappings[index] then
1183 primaryNum = primaryNum + 1
1184 end
1185 local profNum = professionMappings[index] or primaryNum
1186
1187
1188 kb.ProfessionCache[profNum] = kb.ProfessionCache[i] or {}
1189
1190 for j = 1, numSpells do
1191 local spellName, _, icon, _, _, _, spellID = GetSpellInfo(spellOffset+j, BOOKTYPE_PROFESSION)
1192
1193 local profInfo = {
1194 spellName = spellName,
1195 spellID = spellID,
1196 icon = icon,
1197 profOffset = i,
1198 profIndex = index,
1199 spellOffset = (spellOffset+j),
1200 spellNum = j
1201 }
1202 KeyBinderMacro:SetAttribute("*macrotext-profession_"..i .. '_' ..j, "/cast ".. spellName)
1203
1204 kb.ProfessionCache[i .. '_' .. j] = profInfo
1205 kb.ProfessionCache[spellName] = profInfo
1206 kb.ProfessionCache[spellID] = profInfo
1207 cprint(' |cFF0088FF['..i..']|r|cFFFF44BB['..spellOffset+i..']|r', spellName, "*macrotext-profession_"..i .. '_' ..j)
1208 end
1209
1210 end
1211
1212 end
1213
1214 --- Obtains profile data or creates the necessary tables
1215 kb.SelectProfileSet = function(name)
1216
1217 --- General info
1218 classHeader, className, classID = UnitClass('player')
1219 print('|cFF00FF00profile:|r', name)
1220 print('|cFF00FF00class:|r', UnitClass('player'))
1221
1222 --- Global
1223 bindMode = BINDING_TYPE_GLOBAL
1224 kb.InitProfile(db)
1225 loadedProfiles[BINDING_TYPE_GLOBAL] = db
1226
1227 --- Character
1228 if name then
1229 db[name] = kb.InitProfile(db[name],
1230 {classHeader = classHeader, className = className, classID = classID})
1231 loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
1232 bindMode = BINDING_TYPE_CHARACTER
1233 end
1234
1235 --- Mutable skills data
1236 kb.UpdateSpecInfo()
1237 kb.UpdateTalentInfo()
1238
1239 priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
1240 if db.bindMode and loadedProfiles[db.bindMode] then
1241 bindMode = db.bindMode
1242 end
1243
1244 db.bindMode = bindMode
1245
1246 if not BINDING_MODE[bindMode] then
1247 bindMode = 3
1248 db.bindMode = 3
1249 print('overriding', bindMode)
1250 end
1251
1252 print(BINDING_TYPE_GLOBAL)
1253 configHeaders[BINDING_TYPE_GLOBAL] = BINDING_MODE[BINDING_TYPE_GLOBAL]
1254 configHeaders[BINDING_TYPE_CHARACTER] = BINDING_MODE[BINDING_TYPE_CHARACTER]:format(UnitName('player', true))
1255 configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName)
1256
1257
1258 setmetatable(loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return configHeaders[BINDING_TYPE_GLOBAL] end})
1259 setmetatable(loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return configHeaders[BINDING_TYPE_CHARACTER] end})
1260 setmetatable(loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return configHeaders[BINDING_TYPE_SPECIALIZATION] end})
1261
1262 print('|cFF00FF00bindMode:|r', bindMode)
1263 configProfile = loadedProfiles[bindMode]
1264 end
1265
1266 local scrollCache = {}
1267 kb.SelectTab = function(self)
1268 scrollCache[bindMode] = kb.scrollOffset
1269 bindMode = self:GetID()
1270 configProfile = loadedProfiles[self:GetID()]
1271 db.bindMode = self:GetID()
1272 kb.scrollOffset = scrollCache[bindMode] or 0
1273 kb.ui(true)
1274 end
1275
1276 kb.RevertBindings = function()
1277 -- todo: reversion code
1278 end
1279
1280 kb.ConfirmBindings = function()
1281 SaveBindings(GetCurrentBindingSet())
1282 bindsCommitted = true
1283 for i, button in ipairs(buttons) do
1284 button.pending = false
1285 end
1286 kb.ApplyAllBindings()
1287
1288 kb.ui()
1289 kb:print('Keybinds saved.')
1290 end
1291
1292
1293
1294
1295
1296
1297 --- push current information into living UI
1298 kb.ui = function(force)
1299 for i, module in ipairs(kb.modules) do
1300 if module.ui then
1301 module.ui(force)
1302 end
1303 end
1304
506 if not db.showUI then 1305 if not db.showUI then
1306 print('---end of refresh')
507 return 1307 return
508 end 1308 end
509
510 if not kb:IsVisible() then
511 kb:Show()
512 db.showUI = true
513 end
514
515 if not kb.loaded then 1309 if not kb.loaded then
516 SetupUI() 1310 KeyBinder_Initialize()
517 kb.loaded = true 1311 kb.loaded = true
518 end 1312 end
519
520 for i = 1, numButtons do 1313 for i = 1, numButtons do
521 kb.refresh(kb.keyslot(i)) 1314 local button = kb.GetSlot(i)
522 end 1315 button:SetID(i+kb.scrollOffset)
523 1316 kb.UpdateSlot(button, force)
524 if bindMode == BINDING_TYPE_SPECIALIZATION then
525 bindHeader = select(2,GetSpecializationInfo(GetSpecialization()))
526 elseif bindMode == BINDING_TYPE_CHARACTER then
527 bindHeader = UnitName('player')
528 else
529 bindHeader = ''
530 end 1317 end
531 1318
532 if bindsCommitted then 1319 if bindsCommitted then
533 KeyBinderSaveButton:Disable() 1320 KeyBinderSaveButton:Disable()
534 KeyBinderRestoreButton:Disable() 1321 --KeyBinderRestoreButton:Disable()
535 else 1322 else
536 KeyBinderSaveButton:Enable() 1323 KeyBinderSaveButton:Enable()
537 KeyBinderRestoreButton:Enable() 1324 --KeyBinderRestoreButton:Enable()
538 end 1325 end
539 1326
540 --- panel attributes 1327 --- Frame Sizing
1328 kb.profilebg:SetHeight(kb.tabSize[2] + BUTTON_PADDING * 2 + kb.profiletext:GetStringHeight())
1329
1330 kb.bg:SetWidth((KEY_BUTTON_SIZE + BUTTON_HSPACING + BUTTON_SPACING) * BINDS_PER_ROW + BUTTON_PADDING*2 - BUTTON_SPACING)
541 local numRows = numButtons/BINDS_PER_ROW 1331 local numRows = numButtons/BINDS_PER_ROW
542 kb:SetHeight( numRows * (KEY_BUTTON_SIZE) + (numRows - 1) * BUTTON_SPACING + HEADER_OFFSET + FOOTER_OFFSET + BUTTON_PADDING * 2) 1332
543 kb:SetWidth((BINDS_PER_ROW - 1) * BUTTON_SPACING + BINDS_PER_ROW * KEY_BUTTON_SIZE + BUTTON_PADDING * 2) 1333 kb.bg:SetHeight((KEY_BUTTON_SIZE + BUTTON_SPACING) * numRows + BUTTON_PADDING*2 - BUTTON_SPACING)
1334
1335 kb:SetHeight(kb.headerbg:GetHeight() + kb.profilebg:GetHeight() + kb.bg:GetHeight() + kb.footer:GetHeight())
1336 kb:SetWidth((kb.sourcesbg:GetWidth() +(BINDS_PER_ROW * (KEY_BUTTON_SIZE + BUTTON_HSPACING) + (BINDS_PER_ROW - 1) * BUTTON_SPACING + BUTTON_PADDING * 2) ))
1337
544 kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode])) 1338 kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode]))
545
546
547 for i, tab in ipairs(kb.tabButtons) do 1339 for i, tab in ipairs(kb.tabButtons) do
548 1340 local border = tab:GetNormalTexture()
549 local n = tab:GetNormalTexture()
550 local tabTexture = "Interface\\Buttons\\UI-Quickslot2" 1341 local tabTexture = "Interface\\Buttons\\UI-Quickslot2"
551 local left, top, right, bottom = -12, 12, 13, -13 1342 local left, top, right, bottom = -12, 12, 13, -13
552 if i == bindMode then 1343 if i == bindMode then
553 tabTexture = "Interface\\Buttons\\CheckButtonGlow" 1344 tabTexture = "Interface\\Buttons\\CheckButtonGlow"
554 left, top, right, bottom = -14, 14, 15, -15 1345 left, top, right, bottom = -14, 14, 15, -15
555 end 1346 tab.icon:SetDesaturated(false)
556 n:SetTexture(tabTexture) 1347 if tab.icon2 then tab.icon2:SetDesaturated(false) end
557 n:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top) 1348 border:SetDesaturated(true)
558 n:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom) 1349 border:SetVertexColor(1,1,1, 1)
559 end 1350 else
560 end 1351 tab.icon:SetDesaturated(true)
561 1352 if tab.icon2 then tab.icon2:SetDesaturated(true) end
562 kb.loadbinds = function (bindings) 1353 border:SetDesaturated(false)
563 for key, command in pairs(bindings) do 1354 border:SetVertexColor(1,1,1)
564 -- store for reversion 1355 end
565 local oldAction = GetBindingAction(key) 1356 border:SetTexture(tabTexture)
566 if oldAction ~= command then 1357 border:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top)
567 local bind1, bind2 = GetBindingKey(oldAction) 1358 border:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom)
568 if bind1 and not reverts[bind1] then 1359 end
569 reverts[bind1] = oldAction 1360
570 end 1361 KeyBinderSpecTab.icon:SetTexture(specTexture)
571 if bind2 and not reverts[bind2] then 1362
572 reverts[bind2] = oldAction 1363 kb.profiletext:SetText(configHeaders[bindMode])
573 end 1364 print(bindMode, configHeaders[bindMode], kb:GetSize())
574 end 1365 print(kb:GetPoint(1))
575 SetBindings(key, command) 1366
576 end 1367 kb:Show()
577 SaveBindings() 1368
578 end 1369 -- Reset this so talent cache can be rebuilt
579 1370 kb.talentsPushed = nil
580 local ACTION_BARS = { 1371 end
581 {'ActionButton', 0}, 1372
582 {'MultiBarLeftButton', 24}, 1373 --- post ADDON_LOADED
583 {'MultiBarRightButton', 36}, 1374 kb.variables = function()
584 {'MultiBarBottomRighttButton', 48}, 1375 SkeletonKeyDB = SkeletonKeyDB or {spec = {}}
585 {'MultiBarBottomLeftButton', 60}, 1376 kb.db = SkeletonKeyDB
1377 kb.playerName = UnitName('player')
1378 kb.playerRealm = SelectedRealmName()
1379 kb.profileName = kb.playerRealm .. '_' .. kb.playerName
1380 db = kb.db
1381
1382 kb.SelectProfileSet(kb.profileName)
1383 if not configProfile.imported then
1384 kb.ImportScan()
1385 end
1386 kb.ApplyAllBindings()
1387
1388 kb.ui(true)
1389 end
1390
1391
1392 kb.wrap = function(module)
1393 kb.modules = kb.modules or {}
1394 tinsert(kb.modules, module)
1395 end
1396
1397 -- Volatiles Access
1398 kb.BindingIsLocked = BindingIsLocked
1399 kb.BindingString = BindingString
1400 kb.GetBindings = function() return bindings end
1401 kb.GetButtons = function() return buttons end
1402 kb.GetCharacterProfile = function () return loadedProfiles[BINDING_TYPE_CHARACTER] end
1403 kb.GetGlobalProfile = function () return loadedProfiles[BINDING_TYPE_GLOBAL] end
1404 kb.GetLooseTalents = function() return talentBindings end
1405 kb.GetProfileStack = function() return priority end
1406 kb.GetReverts = function() return reverts end
1407 kb.GetSpecProfile = function () return loadedProfiles[BINDING_TYPE_SPECIALIZATION] end
1408
1409 --- Add to blizzard interfaces
1410 StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"] = {
1411 text = "Confirm moving an assigned command.",
1412 button1 = OKAY,
1413 button2 = CANCEL,
1414 timeout = 0,
1415 whileDead = 1,
1416 showAlert = 1,
1417 OnAccept = kb.AcceptAssignment,
1418 OnCancel = function() kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) end
586 } 1419 }
587 kb.HotKeyText = function (slot) 1420
588 local i, offset = 0, 0 1421 SLASH_SKB1 = "/skb"
589 local actionbar 1422 SLASH_SKB2 = "/skeletonkey"
590 1423 SlashCmdList.SKB = kb.Command
591 -- figure out which bar the slot belongs to 1424
592 for i, bar in ipairs(ACTION_BARS) do 1425 -- This is needed to identify a spells that aren't reflected by GetCursorInfo()
593 actionbar, offset = unpack(ACTION_BARS[i]) 1426 hooksecurefunc("PickupSpellBookItem", function(slot, bookType)
594 if bar[2] > slot then 1427 print('|cFFFF4400PickupSpellBookItem(..', tostring(slot),', '..tostring(bookType)..')')
595 break 1428 CURSOR_SPELLSLOT = slot
596 end 1429 CURSOR_BOOKTYPE = bookType
597 end 1430 end)
598 local button = _G[actionbar .. (slot - offset)] 1431
599 1432 -- Pet actions
600 if not button then 1433 local isPickup
601 return 1434 hooksecurefunc("PickupPetAction", function(slot, ...)
602 end 1435 isPickup = GetCursorInfo()
603 1436
604 local type, id, subType, subID = GetActionInfo(slot) 1437 CURSOR_PETACTION = isPickup and slot
605 1438 print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION)
606 if not type then 1439 end)
607 return
608 end
609
610 local bind, command
611 if type == 'spell' then
612 local name = GetSpellInfo(id)
613 command = 'SPELL '..name
614 elseif type == 'macro' then
615 command = 'MACRO ' .. id
616 else
617 return
618 end
619 bind = GetBindingKey(command)
620 if bind then
621 button.HotKey:SetText(BindingString(bind))
622 button.HotKey:Show()
623 end
624 end
625
626 kb.InitProfile = function(profile)
627 profile.buttons = profile.buttons or {}
628 profile.commands = profile.commands or {}
629 profile.bindings = profile.bindings or {}
630 profile.macros = profile.macros or {}
631 return profile
632 end
633 kb.ResetProfile = function()
634
635 for i, button in pairs(buttons) do
636 kb.release(button)
637 end
638
639 profile.commands = {}
640 profile.bindings = {}
641 profile.macros = {}
642 end
643
644 --- Gives us the profile structure to work with while instating data
645 kb.profile = function(name)
646 global = kb.InitProfile(db)
647 profile = global
648 local subtitle
649 if name then
650 db[name] = db[name] or {}
651 db[name] = kb.InitProfile(db[name])
652 character = db[name]
653 local spec = GetSpecialization()
654 if spec then
655 db[name][spec] = db[name][spec] or {}
656 profile = kb.InitProfile(db[name][spec])
657 bindMode = BINDING_TYPE_SPECIALIZATION
658 subtitle = select(2,GetSpecializationInfo(spec))
659 specialization = db[name][spec]
660 else
661 profile = kb.InitProfile(db[name])
662 bindMode = BINDING_TYPE_CHARACTER
663 subtitle = name
664 specialization = character
665 end
666 end
667 priority = {global, character, specialization }
668
669
670
671 if not db.bindsPage then
672 db.bindsPage = bindMode
673 end
674 bindMode = db.bindsPage
675
676
677 if not BINDING_MODE[bindMode] then
678 bindMode = 3
679 db.bindsPage = 3
680 print('overriding', bindMode)
681 end
682
683 profile = priority[bindMode]
684
685
686 local _
687 _, specHeader, _, specTexture = GetSpecializationInfo(GetSpecialization())
688 print(GetSpecializationInfo(GetSpecialization()))
689 specHeader = BINDING_MODE[2]:format(specHeader)
690 characterHeader = BINDING_MODE[2]:format(UnitName('player'))
691
692 print('Using binding profile |cFF00FF88'..BINDING_MODE[bindMode]:format(subtitle)..'|r')
693 end
694
695 kb.SelectTab = function(self)
696 bindMode = self:GetID()
697 profile = priority[self:GetID()]
698 db.bindsPage = self:GetID()
699 kb.ui()
700 end
701 kb.save = function()
702 SaveBindings(GetCurrentBindingSet())
703 bindsCommitted = true
704 for i, button in ipairs(buttons) do
705 button.pending = false
706 end
707
708 kb.ui()
709 print('Bindings saved.')
710 end
711 kb.restore = function()
712 for i, button in pairs(buttons) do
713 button.pending = false
714 end
715 bindsCommitted = true
716 LoadBindings(GetCurrentBindingSet())
717 print('All changes discarded.')
718 end
719
720 --- Tells all the hud buttons what to do
721 kb.init = function()
722 KeyBinderMacro:SetAttribute('*type*', 'macro')
723 end
724
725 --- Get started
726 kb.variables = function()
727 SkeletonKeyDB = SkeletonKeyDB or {}
728 db = SkeletonKeyDB
729 kb.profile(GetUnitName('player', true))
730 for i = 1, 3 do
731 for attribute, data in pairs(priority[i].macros) do
732 KeyBinderMacro:SetAttribute(attribute, data[1])
733 end
734 end
735
736 kb.UPDATE_BINDINGS()
737 kb:RegisterEvent('UPDATE_BINDINGS')
738 kb:RegisterEvent('UPDATE_MACROS')
739 kb:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED')
740 kb:RegisterEvent('PLAYER_EQUIPMENT_CHANGED')
741 kb:RegisterEvent('PLAYER_REGEN_DISABLED')
742 kb:RegisterEvent('PLAYER_REGEN_ENABLED')
743 kb:RegisterEvent('ACTIONBAR_SLOT_CHANGED')
744 end
745
746 kb.close = function()
747 db.showUI = false
748 kb:Hide()
749 end
750
751 kb.PLAYER_REGEN_DISABLED = function()
752 if db.showUI then
753 kb:Hide()
754 end
755 end
756
757 kb.PLAYER_REGEN_ENABLED = function()
758 if db.showUI then
759 kb.ui()
760 end
761 end
762 --- Refresh buttons if macros are updated
763 kb.UPDATE_BINDINGS = function()
764 for i = 1, 120 do
765 kb.HotKeyText(i)
766 end
767 if db.showUI then
768 kb.ui()
769 end
770 end
771
772 kb.ACTIONBAR_SLOT_CHANGED = function(self, event, slot)
773 kb.HotKeyText(slot)
774 return true
775 end
776
777 kb.UPDATE_MACROS = kb.UPDATE_BINDINGS
778 SLASH_KB1 = "/kb"
779 SlashCmdList.KB = function(self, input)
780 if db.showUI then
781 db.showUI = false
782 print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
783 kb:Hide()
784 else
785 db.showUI = true
786 print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
787 kb.ui()
788 end
789 end