Nenue@0: -------------------------------------------- Nenue@5: -- SkeletonKey Nenue@5: -- Krakyn-Mal'Ganis Nenue@0: -- @project-revision@ @project-hash@ Nenue@0: -- @file-revision@ @file-hash@ Nenue@0: -- Created: 6/16/2016 3:47 AM Nenue@0: -------------------------------------------- Nenue@0: -- kb Nenue@5: -- .StoreBinding(button, key) bind current keystroke to command Nenue@5: -- .GetSlot(index) return display slot Nenue@5: -- .SetSlot(button, command, name, icon) assign display slot Nenue@5: -- .ReleaseSlot(button) clear button command Nenue@5: -- .UpdateSlot(button) update button contents Nenue@5: -- .SelectProfile(name) set profile character Nenue@5: -- .ApplyBindings(bindings) walk table with SetBinding() Nenue@0: Nenue@5: local _ Nenue@5: local kb, print = LibStub("LibKraken").register(KeyBinder) Nenue@1: local db Nenue@5: local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end Nenue@1: Nenue@5: --- Caps Lock literals Nenue@5: local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:" Nenue@5: local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).' Nenue@5: local BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.' Nenue@5: local BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r' Nenue@5: local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES" Nenue@5: local FOOTER_OFFSET Nenue@5: local HEADER_OFFSET Nenue@5: local HELP_1 = "Drag and drop spells/items from your inventory, spellbook, or collections panels." Nenue@5: local HELP_2 = "While the cursor is above an icon, up to two key combinations will be bound to that action." Nenue@5: local HELP_3 = "If that key used for a client binding (e.g. game menu), a confirmation popup will appear before making the change." Nenue@5: local BINDS_PER_ROW = 2 Nenue@5: local BUTTON_HSPACING = 128 Nenue@5: local BUTTON_SPACING = 4 Nenue@5: local BUTTON_PADDING = 12 Nenue@5: local BINDING_TYPE_SPECIALIZATION = 3 Nenue@5: local BINDING_TYPE_CHARACTER = 2 Nenue@5: local BINDING_TYPE_GLOBAL = 1 Nenue@5: local KEY_BUTTON_SIZE = 48 Nenue@0: local MIN_BIND_SLOTS = 32 Nenue@5: local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544 Nenue@0: local TAB_OFFSET = 12 Nenue@0: local TAB_HEIGHT = 40 Nenue@0: local TAB_SPACING = 2 Nenue@5: local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 } Nenue@5: local BORDER_ASSIGNED = {0.5,0.5,0.5,1 } Nenue@5: local BORDER_DYNAMIC = {1,1,0,1} Nenue@5: local BORDER_PENDING = {1,0.5,0,1 } Nenue@5: local CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_PETACTION Nenue@5: Nenue@5: Nenue@5: --- Caps Lock derivatives Nenue@5: local ACTION_SCRIPT = { Nenue@5: ['mount'] = "/script C_MountJournal.SummonByID(%d)", Nenue@5: ['macro'] = "%s", Nenue@5: ['equipset'] = "/script UseEquipmentSet(%d)", Nenue@5: ['spell'] = "/cast %s", Nenue@5: ['petaction'] = "/cast %s", Nenue@5: ['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s", Nenue@5: ['item'] = "/use %s" Nenue@5: } Nenue@5: local BUTTON_HEADERS = { Nenue@5: ['spell'] = SPELLS, Nenue@5: ['macro'] = MACRO, Nenue@5: ['petaction'] = PET, Nenue@5: ['mount'] = MOUNT, Nenue@5: ['battlepet'] = BATTLEPET, Nenue@5: Nenue@5: Nenue@5: [5] = PROFESSIONS_FIRST_AID, Nenue@5: [7] = PROFESSIONS_COOKING, Nenue@5: [9] = PROFESSIONS_FISHING, Nenue@5: [10] = PROFESSIONS_ARCHAEOLOGY, Nenue@5: Nenue@5: } Nenue@5: Nenue@5: local professionMappings = { Nenue@5: [5] = 3, Nenue@5: [7] = 4, Nenue@5: [9] = 5, Nenue@5: [10] = 6 Nenue@5: } Nenue@5: Nenue@0: local BINDING_MODE = { Nenue@5: [BINDING_TYPE_GLOBAL] = 'Global Binds', Nenue@0: [BINDING_TYPE_CHARACTER] = 'Character: %s', Nenue@5: [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s' Nenue@0: } Nenue@5: local BINDING_DESCRIPTION = { Nenue@5: Nenue@5: [BINDING_TYPE_GLOBAL] = 'The bindings are applied globally.', Nenue@5: [BINDING_TYPE_CHARACTER] = 'Applied when you log onto this character.', Nenue@5: [BINDING_TYPE_SPECIALIZATION] = 'Applied when you log onto this character and are that specialization.', Nenue@5: } Nenue@5: Nenue@0: local BINDING_SCHEME_COLOR = { Nenue@5: [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5}, Nenue@0: [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5}, Nenue@5: [BINDING_TYPE_SPECIALIZATION] = {.25,0,0,0.5}, Nenue@0: } Nenue@0: local BINDING_SCHEME_VERTEX = { Nenue@5: [BINDING_TYPE_GLOBAL] = {0,.5,1,1}, Nenue@5: [BINDING_TYPE_CHARACTER] = {0,1,0,1}, Nenue@0: [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1}, Nenue@0: } Nenue@0: local BINDING_SCHEME_TEXT = { Nenue@5: [BINDING_TYPE_SPECIALIZATION] = {0, 1, 1}, Nenue@0: [BINDING_TYPE_CHARACTER] = {0, 1, 0}, Nenue@0: [BINDING_TYPE_GLOBAL] = {0, 1, 1} Nenue@0: } Nenue@0: Nenue@0: Nenue@0: Nenue@5: local loadedProfiles = {} Nenue@5: -- Profiles ordered by precedance Nenue@5: local priority = {} Nenue@5: -- Button pointers Nenue@5: local buttons = {} Nenue@5: -- Backlog of changes Nenue@5: local reverts = {} Nenue@5: -- macro buttons used for mounts and other buttonable non-spells Nenue@5: local macros = {} Nenue@5: -- currently active non-blizzard keybinds Nenue@5: local bindings = {} Nenue@5: -- unselected talents Nenue@5: local talentBindings = {} Nenue@5: kb.inactiveTalentBindings = {} Nenue@5: -- placeholder for the StaticPopup used for confirmations Nenue@5: local confirmation Nenue@5: -- header text Nenue@5: local configHeaders = {} Nenue@0: Nenue@0: local protected = { Nenue@0: ['OPENCHATSLASH'] = true, Nenue@0: ['OPENCHAT'] = true, Nenue@0: } Nenue@5: Nenue@5: --- Used to reflect the current working state Nenue@5: local bindMode = 3 Nenue@5: local bindHeader, currentHeader = '', '' Nenue@5: local configProfile, character, specialization, global, character_specialization Nenue@5: local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME' Nenue@5: local classHeader, className, classID = '', '', 0 Nenue@5: local numButtons = BINDS_PER_ROW * 8 Nenue@5: local bindsCommitted = true Nenue@5: local forceButtonUpdate = false Nenue@5: Nenue@5: --- Control handles Nenue@0: local saveButton, restoreButton, clearButton Nenue@0: Nenue@5: --- Cursor "pickup" actuators Nenue@5: local PickupAction = {} Nenue@5: PickupAction.spell = _G.PickupSpell Nenue@5: PickupAction.macro = _G.PickupMacro Nenue@5: PickupAction.item = _G.PickupItem Nenue@5: PickupAction.mount = _G.C_MountJournal.Pickup Nenue@5: local GetPickupValue = {} Nenue@5: GetPickupValue.spell = function(self) return select(7, GetSpellInfo(self.actionID)) end Nenue@5: Nenue@5: --- Returns conflicting assignment and binding profiles for use in displaying confirmations Nenue@5: local IsCommandBound = function(self, command) Nenue@5: local isAssigned, assignedBy = false, bindMode Nenue@5: local isBound, boundBy = false, bindMode Nenue@5: Nenue@5: Nenue@5: command = command or self.command Nenue@5: for i = 1, #BINDING_MODE do Nenue@0: local tier = priority[i] Nenue@5: if i ~= bindMode then Nenue@5: Nenue@5: if tier.commands[command] then Nenue@5: isAssigned = true Nenue@5: assignedBy = i Nenue@5: end Nenue@5: if tier.bound[command] then Nenue@5: isBound = true Nenue@5: boundBy = i Nenue@5: end Nenue@5: Nenue@5: Nenue@5: --print(' *', configHeaders[i], tier.commands[command], tier.bound[command]) Nenue@5: Nenue@5: if isAssigned and isBound then Nenue@0: break Nenue@0: end Nenue@0: end Nenue@5: Nenue@0: end Nenue@5: Nenue@5: print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', bindMode .. ']', isAssigned, isBound, assignedBy, boundBy) Nenue@5: return isAssigned, isBound, assignedBy, boundBy Nenue@0: end Nenue@0: Nenue@5: local talentSpellHardCodes = { Nenue@5: [109248] = 'Binding Shot', Nenue@5: } Nenue@5: Nenue@0: --- Returns a value for use with Texture:SetDesaturated() Nenue@0: local BindingIsLocked = function(key) Nenue@0: local success = false Nenue@0: for i = 1, bindMode-1 do Nenue@0: local tier = priority[i] Nenue@0: if tier.bindings[key] then Nenue@0: success = true Nenue@0: break Nenue@0: end Nenue@0: end Nenue@0: return success Nenue@0: end Nenue@0: Nenue@0: --- Translates GetBindingKey() results into a printable string. Nenue@0: local BindingString = function(...) Nenue@0: local stack = {} Nenue@0: for i = 1, select('#', ...) do Nenue@0: local key = select(i, ...) Nenue@5: stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ') Nenue@0: end Nenue@0: Nenue@0: if #stack >= 1 then Nenue@0: return table.concat(stack, ',') Nenue@0: else Nenue@0: return nil Nenue@0: end Nenue@0: end Nenue@0: Nenue@5: local restingAlpha = 0.7 Nenue@5: local fadeTime, fadeDelay = .30, 0.15 Nenue@5: Nenue@5: local portraitLayers = {} Nenue@5: kb.UpdatePortraits = function() Nenue@5: for i, layeredRegion in ipairs(portraitLayers) do Nenue@5: SetPortraitTexture(layeredRegion , 'player') Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: local KeyButton_OnKeyDown = function(self, key) Nenue@5: kb.StoreBinding(self, key) Nenue@5: end Nenue@5: local KeyButton_OnClick = function(self, click) Nenue@5: print(self:GetName(), 'OnMouseDown', click) Nenue@5: if click == 'LeftButton' then Nenue@5: kb.DropToSlot(self) Nenue@5: elseif click == 'RightButton' then Nenue@5: kb.ReleaseSlot(self) Nenue@5: else Nenue@5: kb.StoreBinding(self, click:upper()) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: local KeyButton_OnDragStart = function(self) Nenue@5: kb.PickupSlot(self) Nenue@5: end Nenue@5: Nenue@5: local KeyButton_OnReceiveDrag = function(self, ...) Nenue@5: kb.DropToSlot(self) Nenue@5: end Nenue@5: Nenue@5: Nenue@5: local KeyBinder_OnUpdate = function(self, elapsed) Nenue@5: self.elapsed = self.elapsed + elapsed Nenue@5: self.throttle = self.throttle + elapsed Nenue@5: Nenue@5: if (self.throttle >= 0.032) then Nenue@5: self.throttle = 0 Nenue@5: else Nenue@5: return Nenue@5: end Nenue@5: Nenue@5: local progress = 1 Nenue@5: if self.elapsed > fadeTime then Nenue@5: self.elapsed = 0 Nenue@5: self.fadeStep = 0 Nenue@5: --self.statustext:SetText(nil) Nenue@5: --self.bindingstext:SetText(nil) Nenue@5: self:SetScript('OnUpdate', nil) Nenue@5: else Nenue@5: if self.elapsed < fadeDelay then Nenue@5: progress = 0 Nenue@5: else Nenue@5: self.fadeStep = self.fadeStep + 1 Nenue@5: progress = (self.elapsed - fadeDelay) /(fadeTime - fadeDelay) Nenue@5: end Nenue@5: --print(self.fadeStep, format('%.02f/%.02f', (self.elapsed - fadeDelay) ,(fadeTime - fadeDelay)) , progress) Nenue@5: end Nenue@5: Nenue@5: local alpha = 1 - progress * (1- restingAlpha) Nenue@5: self.statustext:SetAlpha(alpha) Nenue@5: self.bindingstext:SetAlpha(alpha) Nenue@5: end Nenue@5: Nenue@5: local KeyButton_OnUpdate = function(self) Nenue@0: if not self.command then Nenue@0: return Nenue@0: end Nenue@0: Nenue@0: if self:IsMouseOver() then Nenue@5: kb.elapsed = 0 Nenue@0: if not self.active then Nenue@0: -- only set this handler when the button is activated/mouseOver Nenue@0: self.active = true Nenue@5: self:SetScript('OnKeyDown', KeyButton_OnKeyDown) Nenue@0: Nenue@5: kb.statustext:SetText(self.statusText .. ': '..self.actionName) Nenue@5: kb.bindingstext:SetText(self.bindingText) Nenue@5: kb.fadeStep = 0 Nenue@5: kb.throttle = 0 Nenue@5: kb:SetScript('OnUpdate', KeyBinder_OnUpdate) Nenue@0: Nenue@0: end Nenue@0: else Nenue@0: if self.active then Nenue@0: self.active = nil Nenue@0: self:SetScript('OnKeyDown', nil) Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: Nenue@5: local KeyBinder_OnMouseWheel = function(self, delta) Nenue@5: print(self, delta, self.scrollOffset, (self.scrollOffset <= 0)) Nenue@0: Nenue@5: Nenue@5: if IsControlKeyDown() then Nenue@5: KEY_BUTTON_SIZE = KEY_BUTTON_SIZE - delta Nenue@5: else Nenue@5: Nenue@5: Nenue@5: if (delta > 0) and (self.scrollOffset <= 0) then Nenue@5: return Nenue@5: elseif delta < 0 and kb.scrollOffset >= 42 then Nenue@5: return Nenue@5: end Nenue@5: kb.scrollOffset = ceil(kb.scrollOffset - (delta * BINDS_PER_ROW)) Nenue@5: end Nenue@5: Nenue@5: kb.ui(true) Nenue@5: end Nenue@5: Nenue@5: local KeyBinder_OnHide = function() Nenue@5: KeyBinderImportLog:Hide() Nenue@5: end Nenue@5: Nenue@5: local CloseButton_OnClick = function() Nenue@5: db.showUI = false Nenue@5: kb:Hide() Nenue@5: end Nenue@5: local CancelButton_OnClick = function() Nenue@5: kb.RevertBindings() Nenue@5: end Nenue@5: local SaveButton_OnClick = function() Nenue@5: kb.ConfirmBindings() Nenue@5: end Nenue@5: Nenue@5: local KeyBinder_Initialize = function() Nenue@5: for i = 1, GetNumBindings() do Nenue@5: local command = GetBinding(i) Nenue@5: bindings[command] = true Nenue@5: end Nenue@5: Nenue@5: kb.scrollOffset = 0 Nenue@5: kb.tabAnchor = {'TOPLEFT', kb.profilebg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING} Nenue@5: kb.tabGrowth = {'TOPLEFT', nil,'TOPRIGHT', BUTTON_SPACING, 0} Nenue@5: kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT } Nenue@5: kb.UIPanelAnchor = {'TOPLEFT', kb.sourcesbg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING} Nenue@5: kb.UIPanelGrowth = {'TOPLEFT', nil, 'BOTTOMLEFT', 0, -2 } Nenue@5: kb.UIPanelSize = {84, 32 } Nenue@5: kb.UIPanelIcon = {24, 32, 'LEFT', -12, 0} Nenue@5: kb.controlsAnchor = {'BOTTOMLEFT', kb.footer, BUTTON_PADDING, BUTTON_PADDING } Nenue@5: kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0} Nenue@5: Nenue@5: -- order of these is important Nenue@5: kb:tab('KeyBinderGlobalTab', Nenue@5: BINDING_MODE[BINDING_TYPE_GLOBAL] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_GLOBAL], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85}) Nenue@5: kb:tab('KeyBinderCharacterTab', Nenue@5: configHeaders[BINDING_TYPE_CHARACTER] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_CHARACTER], nil) Nenue@5: kb:tab('KeyBinderSpecTab', Nenue@5: configHeaders[BINDING_TYPE_SPECIALIZATION] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_SPECIALIZATION], specTexture) Nenue@5: KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85) Nenue@5: Nenue@5: Nenue@5: Nenue@5: portraitLayers[1] = KeyBinderCharacterTab.icon Nenue@5: Nenue@5: saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', SaveButton_OnClick) Nenue@5: --restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', CancelButton_OnClick) Nenue@5: --clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', ResetButton_OnClick) Nenue@5: Nenue@5: kb:uibutton( Nenue@5: 'KeyBinderSpellBookButton', 'SpellBook', nil, Nenue@5: function() ToggleSpellBook(BOOKTYPE_SPELL) end, Nenue@5: "Interface\\BUTTONS\\UI-MicroButton-Spellbook-Up", {0, 1, .4, 1}) Nenue@5: kb:uibutton( Nenue@5: 'KeyBinderTalentFrameButton', TALENTS, SPECIALIZATION, Nenue@5: function() ToggleTalentFrame() end, Nenue@5: "Interface\\BUTTONS\\UI-MicroButton-Talents-Up", {0, 1, .4, 1}) Nenue@5: Nenue@5: kb:uibutton( Nenue@5: 'KeyBinderMacroFrameButton', 'Macros', nil, Nenue@5: function() if MacroFrame and MacroFrame:IsVisible() then Nenue@5: HideUIPanel(MacroFrame) Nenue@5: else Nenue@5: ShowMacroFrame() end Nenue@5: end, Nenue@5: "Interface\\BUTTONS\\UI-MicroButton-Help-Up", {0, 1, .4, 1}) Nenue@5: Nenue@5: kb:uibutton( Nenue@5: 'KeyBinderInventoryButton', 'Bags', nil, Nenue@5: function() OpenAllBags() end, Nenue@5: "Interface\\BUTTONS\\UI-MicroButtonCharacter-Up", {0, 1, .4, 1}) Nenue@5: Nenue@5: Nenue@5: Nenue@5: kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING) Nenue@5: HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING Nenue@5: + kb.info:GetHeight() Nenue@5: FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING Nenue@5: Nenue@5: kb:SetScript('OnHide', KeyBinder_OnHide) Nenue@5: kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) Nenue@5: kb.CloseButton:SetScript('OnClick', CloseButton_OnClick) Nenue@5: Nenue@5: kb.UpdatePortraits() Nenue@5: end Nenue@5: Nenue@5: kb.DropToSlot = function(self) Nenue@5: Nenue@5: print(self:GetName(),'|cFF0088FFreceived|r') Nenue@5: local actionType, actionID, subType, subData = GetCursorInfo() Nenue@5: print('GetCursorInfo', GetCursorInfo()) Nenue@5: Nenue@5: Nenue@5: if actionType then Nenue@5: Nenue@5: if actionType == 'flyout' then Nenue@5: ClearCursor() Nenue@5: ResetCursor() Nenue@5: return Nenue@5: end Nenue@5: Nenue@5: Nenue@5: local macroName, macroText Nenue@5: local command, name, icon, _ Nenue@5: local pickupID, pickupBook Nenue@5: Nenue@5: if actionType == 'spell' then Nenue@5: actionID = subData Nenue@5: name, _, icon = GetSpellInfo(actionID) Nenue@5: Nenue@5: elseif actionType == 'macro' then Nenue@5: name, icon = GetMacroInfo(actionID) Nenue@5: actionID = name Nenue@5: elseif actionType == 'petaction' then Nenue@5: if not (CURSOR_SPELLSLOT and CURSOR_BOOKTYPE) then Nenue@5: Nenue@5: ClearCursor() Nenue@5: ResetCursor() Nenue@0: end Nenue@0: Nenue@5: local bookType, spellID = GetSpellBookItemInfo(CURSOR_SPELLSLOT, CURSOR_BOOKTYPE) Nenue@5: pickupID = CURSOR_SPELLSLOT Nenue@5: pickupBook = CURSOR_BOOKTYPE Nenue@5: name, _, icon = GetSpellInfo(spellID) Nenue@5: actionID = name Nenue@5: Nenue@5: elseif actionType == 'mount' then Nenue@5: if subType == 0 then Nenue@5: name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL) Nenue@5: actionID = 0 Nenue@5: else Nenue@5: name, _, icon = C_MountJournal.GetMountInfoByID(actionID) Nenue@5: end Nenue@5: elseif actionType == 'item' then Nenue@5: name = GetItemInfo(actionID) Nenue@5: icon = GetItemIcon(actionID) Nenue@5: actionID = name Nenue@5: elseif actionType == 'battlepet' then Nenue@5: Nenue@5: local speciesID, customName, level, xp, maxXp, displayID, isFavorite, petName, petIcon, petType, creatureID = C_PetJournal.GetPetInfoByPetID(detail); Nenue@5: name = customName or petName Nenue@5: icon = petIcon Nenue@5: Nenue@5: end Nenue@5: macroName, macroText, command = kb.RegisterAction(actionType, actionID) Nenue@5: Nenue@5: Nenue@5: local isAssigned, isBound, assignedBy, boundBy = IsCommandBound(self, command) Nenue@5: if isAssigned then Nenue@5: local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"] Nenue@5: popup.slot = self Nenue@5: popup.text = "Currently assigned in |cFFFFFF00"..tostring(configHeaders[assignedBy]).."|r. Are you sure?" Nenue@5: popup.oldProfile = assignedBy Nenue@5: popup.args = {command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook } Nenue@5: kb:SetScript('OnMouseWheel', nil) -- disable scrolling Nenue@5: StaticPopup_Show('SKELETONKEY_CONFIRM_ASSIGN_SLOT') Nenue@5: else Nenue@5: kb.SetSlot(self, command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook) Nenue@5: kb.UpdateSlot(self) Nenue@5: self.active = nil Nenue@5: KeyButton_OnUpdate(self, 0) Nenue@5: ClearCursor() Nenue@5: ResetCursor() Nenue@0: end Nenue@0: end Nenue@0: end Nenue@0: Nenue@5: kb.PickupSlot = function(self) Nenue@0: if not self.command then Nenue@0: return Nenue@0: end Nenue@5: print(self.actionType) Nenue@5: if self.actionType == 'spell' then Nenue@5: -- It can't be picked up if SpellInfo(name) returns void Nenue@5: local dummy = GetSpellInfo(self.actionName) Nenue@5: if not dummy then Nenue@5: return Nenue@0: end Nenue@5: elseif self.actionType == 'petaction' then Nenue@5: PickupSpellBookItem(self.pickupSlot, self.pickupBook) Nenue@5: end Nenue@5: if PickupAction[self.actionType] then Nenue@5: if GetPickupValue[self.actionType] then Nenue@5: PickupAction[self.actionType](GetPickupValue[self.actionType](self)) Nenue@5: else Nenue@5: PickupAction[self.actionType](self.actionID) Nenue@0: end Nenue@5: kb.ReleaseSlot(self) Nenue@5: kb.UpdateSlot(self) Nenue@0: end Nenue@0: end Nenue@0: Nenue@5: Nenue@5: --- Resolve the appropriate command and assign the corresponding secure state driver Nenue@5: kb.RegisterAction = function(type, id) Nenue@5: Nenue@5: if type == 'spell' then Nenue@5: Nenue@5: id = GetSpellInfo(id) Nenue@5: end Nenue@5: Nenue@5: local macroText Nenue@5: local macroName = type ..'_' .. id Nenue@5: Nenue@5: if kb.ProfessionCache[id] then Nenue@5: macroName = "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum Nenue@5: macroText = "/cast " .. kb.ProfessionCache[id].spellName Nenue@5: macros[macroName] = nil Nenue@0: else Nenue@5: macroName = macroName:gsub(' ', '') Nenue@5: macroText = ACTION_SCRIPT[type]:format(id) Nenue@0: end Nenue@5: Nenue@5: local baseName, iterative = macroName, 1 Nenue@5: while (macros[macroName] and macros[macroName][1] ~= macroText) do Nenue@5: print(' * cannot use|cFF00FF00', macroName, '|r"'.. (macros[macroName][1] or '') .. '"') Nenue@5: macroName = baseName .. '_' .. iterative Nenue@5: iterative = iterative + 1 Nenue@5: end Nenue@5: if macroName ~= baseName then Nenue@5: print(' * Creating|cFF00FF00', macroName) Nenue@5: else Nenue@5: print(' * Re-using|cFF00FF00', macroName) Nenue@5: end Nenue@5: Nenue@5: local command = 'CLICK KeyBinderMacro:'.. macroName Nenue@5: macros[macroName] = {macroText, command } Nenue@5: print('RegisterAction', command , macroText) Nenue@5: if type == 'macro' then Nenue@5: kb.LoadMacro(macroName) Nenue@5: else Nenue@5: kb.LoadAction(macroName, macroText, command) Nenue@5: end Nenue@5: Nenue@5: Nenue@5: return macroName, macroText, command Nenue@0: end Nenue@0: Nenue@5: kb.LoadMacro = function(macroName) Nenue@5: KeyBinderMacro:SetAttribute('*macro-'..macroName, macros[macroName][1]) Nenue@5: return true Nenue@5: end Nenue@5: Nenue@5: kb.LoadAction = function(macroName) Nenue@5: if not macros[macroName] then Nenue@5: return false Nenue@5: end Nenue@5: KeyBinderMacro:SetAttribute('*macrotext-'..macroName, macros[macroName][1]) Nenue@5: return true Nenue@5: end Nenue@5: Nenue@5: local profressionsCache Nenue@5: Nenue@5: kb.AcceptAssignment = function(self, ...) Nenue@5: local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"] Nenue@5: local source = loadedProfiles[popup.oldProfile] Nenue@5: kb.SetSlot(popup.slot, unpack(popup.args)) Nenue@5: kb.UpdateSlot(popup.slot) Nenue@5: kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) -- re-enable scrolling Nenue@5: ClearCursor() Nenue@5: ResetCursor() Nenue@5: end Nenue@5: Nenue@0: Nenue@0: --- Updates the current KeyBinding for the button's command Nenue@5: kb.StoreBinding = function(self, key) Nenue@0: Nenue@0: if not self.command then Nenue@0: return Nenue@0: end Nenue@0: Nenue@0: if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then Nenue@0: return Nenue@0: end Nenue@5: print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key) Nenue@0: Nenue@5: if key == 'ESCAPE' then Nenue@5: local keys = {GetBindingKey(self.command) } Nenue@5: --print('detected', #keys, 'bindings') Nenue@5: for i, key in pairs(keys) do Nenue@5: --print('clearing', key) Nenue@5: SetBinding(key, nil) Nenue@5: SaveBindings(GetCurrentBindingSet()) Nenue@5: if configProfile.bindings[key] then Nenue@5: kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode])) Nenue@5: configProfile.bindings[key] = nil Nenue@5: end Nenue@5: if configProfile.talents[self.actionName] then Nenue@5: configProfile.talents[self.actionName] = nil Nenue@5: end Nenue@5: bindings[self.actionType][self.actionID] = nil Nenue@5: end Nenue@5: if configProfile.bound[self.command] then Nenue@5: configProfile.bound[self.command] = nil Nenue@5: --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode])) Nenue@5: end Nenue@5: Nenue@5: bindsCommitted = false Nenue@5: self.active = false Nenue@5: else Nenue@5: Nenue@5: local modifier = '' Nenue@5: if IsAltKeyDown() then Nenue@5: modifier = 'ALT-' Nenue@5: end Nenue@5: if IsControlKeyDown() then Nenue@5: modifier = modifier.. 'CTRL-' Nenue@5: end Nenue@5: if IsShiftKeyDown() then Nenue@5: modifier = modifier..'SHIFT-' Nenue@5: end Nenue@5: Nenue@5: Nenue@5: if self.command then Nenue@5: self.binding = modifier..key Nenue@5: Nenue@5: local previousKeys Nenue@5: local previousAction = GetBindingAction(self.binding) Nenue@5: local binding1, binding2, new1, new2 Nenue@5: print(type(previousAction), previousAction) Nenue@5: if previousAction ~= "" and previousAction ~= self.command then Nenue@5: if protected[previousAction] then Nenue@5: -- bounce out if trying to use a protected key Nenue@5: kb.statustext:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(previousAction))) Nenue@5: kb.bindingstext:SetText(nil) Nenue@5: return Nenue@5: else Nenue@5: kb:print('Discarding keybind for', previousAction) Nenue@5: -- todo: sort out retcon'd talent spells Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: self.pending = true Nenue@5: Nenue@5: bindsCommitted = false Nenue@5: SetBinding(self.binding, self.command) Nenue@5: SaveBindings(GetCurrentBindingSet()) Nenue@5: Nenue@5: local talentInfo Nenue@5: if self.actionType == 'spell' and kb.TalentCache[self.actionID] then Nenue@5: print('conditional binding (talent = "'..self.actionName..'")') Nenue@5: talentInfo = {self.macroName, self.actionName, self.actionType, self.actionID} Nenue@5: local bindings = {GetBindingKey(self.command) } Nenue@5: for i, key in ipairs(bindings) do Nenue@5: tinsert(talentInfo, key) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: for level, configProfile in ipairs(priority) do Nenue@5: if (level == bindMode) then Nenue@5: configProfile.bound[self.command] = true Nenue@5: if talentInfo then Nenue@5: configProfile.bindings[self.binding] = nil Nenue@5: else Nenue@5: configProfile.bindings[self.binding] = self.command Nenue@5: end Nenue@5: configProfile.talents[self.actionName] = talentInfo Nenue@5: else Nenue@5: configProfile.bindings[self.binding] = nil Nenue@5: configProfile.bound[self.command] = nil Nenue@5: configProfile.talents[self.actionName] = nil Nenue@5: end Nenue@5: if configProfile.talents[self.actionID] then Nenue@5: configProfile.talents[self.actionID] = nil Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: kb:print(BINDING_ASSIGNED:format(self.binding, self.actionName, configHeaders[bindMode])) Nenue@5: Nenue@5: end Nenue@0: end Nenue@0: Nenue@5: kb.UpdateSlot(self, true) Nenue@5: KeyBinderSaveButton:Enable() Nenue@0: Nenue@0: end Nenue@0: Nenue@0: --- Resets button command Nenue@5: kb.ReleaseSlot = function(self) Nenue@5: local slot = self:GetID() Nenue@5: Nenue@5: Nenue@5: if configProfile.buttons[slot] then Nenue@5: configProfile.buttons[slot] = nil Nenue@5: end Nenue@5: if self.command then Nenue@5: configProfile.commands[self.command] = nil Nenue@5: end Nenue@5: if self.actionType == 'spell' and IsTalentSpell(self.actionName) then Nenue@5: if configProfile.talents[self.actionID] then Nenue@5: configProfile.talents[self.actionID] = nil Nenue@5: end Nenue@5: end Nenue@5: local droppedKeys = {} Nenue@5: Nenue@5: -- doing removal in second loop to avoid possible iterator shenanigans Nenue@5: for k,v in pairs(configProfile.bindings) do Nenue@5: if v == self.command then Nenue@5: tinsert(droppedKeys, k) Nenue@5: end Nenue@5: end Nenue@5: if #droppedKeys >=1 then Nenue@5: for i, k in ipairs(droppedKeys) do Nenue@5: configProfile.bindings[k] = nil Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: self.isAvailable = nil Nenue@5: self.isDynamic = nil Nenue@5: self.bindingText = nil Nenue@5: self.statusText = nil Nenue@0: self.command = nil Nenue@5: self.actionType = nil Nenue@5: self.actionID = nil Nenue@0: self.actionName = nil Nenue@5: self.pickupSlot = nil Nenue@5: self.pickupBook = nil Nenue@5: self.macroName = nil Nenue@0: self.profile = nil Nenue@0: self.icon:SetTexture(nil) Nenue@0: self.border:SetColorTexture(unpack(BORDER_UNASSIGNED)) Nenue@0: self:EnableKeyboard(false) Nenue@0: self:SetScript('OnKeyDown', nil) Nenue@0: end Nenue@0: Nenue@5: kb.SetSlot = function(self, command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook) Nenue@5: local slot = self:GetID() Nenue@5: local isDynamic, isAvailable Nenue@0: Nenue@5: print('|cFFFFFF00SetSlot|r:', self:GetID()) Nenue@5: if command then Nenue@0: Nenue@5: if actionType == 'spell' then Nenue@5: local professionNum, spellNum = command:match("profession_(%d)_(%d)") Nenue@0: Nenue@5: if (professionNum and spellNum) then Nenue@5: isDynamic = 'profession' Nenue@5: local cacheInfo = kb.ProfessionCache[professionNum..'_'..spellNum] Nenue@5: if cacheInfo then Nenue@5: isAvailable = true Nenue@5: name = cacheInfo.spellName Nenue@5: icon = cacheInfo.icon Nenue@5: actionID = cacheInfo.spellID Nenue@5: self.profIndex = cacheInfo.profIndex Nenue@5: self.spellOffset = cacheInfo.spellOffset Nenue@5: end Nenue@5: print(' Special slot: |cFF00FFFFProfession|r', professionNum, spellNum, isDynamic, isAvailable) Nenue@5: Nenue@5: self.professionNum = tonumber(professionNum) Nenue@5: self.spellNum = tonumber(spellNum) Nenue@5: Nenue@5: elseif kb.TalentCache[actionID] then Nenue@5: Nenue@5: isDynamic = 'talent' Nenue@5: isAvailable = GetSpellInfo(name) Nenue@5: print(' Special slot: |cFFBBFF00talent|r', name, isAvailable) Nenue@5: end Nenue@5: if not actionID then Nenue@5: actionID = select(7, GetSpellInfo(name)) Nenue@5: end Nenue@5: elseif actionType == 'macro' then Nenue@5: if not actionID then Nenue@5: actionID = GetMacroIndexByName(name) Nenue@5: end Nenue@5: else Nenue@5: --- Journal selections Nenue@5: -- todo: consider using the deep end of blizzard action bar instead Nenue@5: if not actionID then Nenue@5: actionID = command:match("^KeyBinderMacro:(.+)") Nenue@5: end Nenue@0: end Nenue@0: Nenue@5: if not macroName then Nenue@5: local previousCommand = command Nenue@5: macroName, macroText, command = kb.RegisterAction(actionType, actionID) Nenue@0: Nenue@5: -- Clean up conflicting command entry for loaded buttons Nenue@5: if macroName and command ~= previousCommand then Nenue@5: print(' Repaired corruption in |cFFFFFF00'..currentHeader..'|r button #'.. self:GetID()) Nenue@5: configProfile.commands[previousCommand] = nil Nenue@5: configProfile.bound[previousCommand] = nil Nenue@5: end Nenue@0: end Nenue@0: Nenue@5: if actionType == 'petaction' then Nenue@5: self.pickupSlot = pickupSlot Nenue@5: self.pickupBook = pickupBook Nenue@5: else Nenue@5: self.pickupSlot = nil Nenue@5: self.pickupBook = nil Nenue@5: end Nenue@5: Nenue@5: actionID = actionID or 0 Nenue@5: self:EnableKeyboard(true) Nenue@5: print(' |cFF00FF00configProfile.buttons['..slot..'] |cFF00FFFF=|r |cFF00FFFF"'.. command.. '"|r |cFF00FF00"'.. name, '"|r |cFFFFFF00icon:'.. icon .. '|r |cFFFF8800"'.. actionType, '"|r |cFFFF0088id:'.. actionID ..'|r |cFF00FF00"'.. macroName .. '"|r') Nenue@5: configProfile.buttons[slot] = {command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook} Nenue@5: Nenue@5: -- Clean up conflicting entries for loaded button Nenue@5: local previous = configProfile.commands[command] Nenue@5: if previous ~= slot and buttons[previous] then Nenue@5: kb.ReleaseSlot(buttons[previous]) Nenue@5: end Nenue@5: configProfile.commands[command] = slot Nenue@0: end Nenue@0: Nenue@5: self.isAvailable = isAvailable Nenue@5: self.isDynamic = isDynamic Nenue@5: Nenue@5: self.macroText = macroText Nenue@5: self.macroName = macroName Nenue@5: self.actionType = actionType Nenue@5: self.actionID = actionID Nenue@0: self.actionName = name Nenue@0: self.command = command Nenue@0: self.icon:SetTexture(icon) Nenue@5: self.profile = bindMode Nenue@0: self:RegisterForDrag('LeftButton') Nenue@0: end Nenue@0: Nenue@0: --- Retrieves button at index; creates said button and instates any stored parameters Nenue@5: local leftSlot, upSlot Nenue@5: local buttonsDepth = 0 Nenue@5: kb.GetSlot = function(index) Nenue@5: Nenue@5: local slot = index + kb.scrollOffset Nenue@5: Nenue@0: if not buttons[index] then Nenue@0: local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton') Nenue@5: button:SetScript('OnClick', KeyButton_OnClick) Nenue@5: button:SetScript('OnUpdate', KeyButton_OnUpdate) Nenue@5: button:SetScript('OnDragStart', KeyButton_OnDragStart) Nenue@5: button:SetScript('OnReceiveDrag', KeyButton_OnReceiveDrag) Nenue@5: button:RegisterForClicks('AnyUp') Nenue@0: Nenue@5: Nenue@5: local newRow = (mod(index, BINDS_PER_ROW) == 1) Nenue@5: Nenue@5: if index == 1 then Nenue@5: button:SetPoint('TOPLEFT', kb.bg, 'TOPLEFT', BUTTON_PADDING, - BUTTON_PADDING) Nenue@5: upSlot = button Nenue@5: buttonsDepth = KEY_BUTTON_SIZE + BUTTON_PADDING * 2 Nenue@5: elseif newRow then Nenue@5: button:SetPoint('TOPLEFT', upSlot, 'BOTTOMLEFT', 0, -BUTTON_SPACING) Nenue@5: upSlot = button Nenue@5: buttonsDepth = buttonsDepth + KEY_BUTTON_SIZE + BUTTON_SPACING Nenue@0: else Nenue@5: button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0) Nenue@0: end Nenue@0: Nenue@0: button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE) Nenue@0: button:Show() Nenue@0: buttons[index] = button Nenue@5: leftSlot = button Nenue@0: end Nenue@0: return buttons[index] Nenue@0: end Nenue@0: Nenue@0: --- Updates profile assignment and button contents Nenue@5: kb.UpdateSlot = function(self, force) Nenue@5: local slot = self:GetID() Nenue@5: Nenue@5: if force then Nenue@5: if configProfile.buttons[slot] then Nenue@5: kb.SetSlot(self, unpack(configProfile.buttons[slot])) Nenue@0: else Nenue@5: kb.ReleaseSlot(self) Nenue@0: end Nenue@0: end Nenue@0: Nenue@0: if self.command then Nenue@5: print('['..slot..'] =', self.command, GetBindingKey(self.command)) Nenue@5: Nenue@0: if self.pending then Nenue@0: self.border:SetColorTexture(unpack(BORDER_PENDING)) Nenue@5: elseif self.isDynamic then Nenue@5: self.border:SetColorTexture(unpack(BORDER_DYNAMIC)) Nenue@0: else Nenue@0: self.border:SetColorTexture(unpack(BORDER_ASSIGNED)) Nenue@0: end Nenue@5: Nenue@5: if self.actionType == 'macro' then Nenue@5: self.macro:Show() Nenue@5: else Nenue@5: self.macro:Hide() Nenue@5: if self.actionType == 'spell' then Nenue@5: local dummy = GetSpellInfo(self.actionName) Nenue@5: if not dummy then Nenue@5: self.icon:SetDesaturated(true) Nenue@5: else Nenue@5: self.icon:SetDesaturated(false) Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: if self.isDynamic then Nenue@5: print('|cFFFFBB00UpdateSlot|r: ', self.isDynamic, self.isAvailable, self.actionID) Nenue@5: end Nenue@5: Nenue@5: if self.isDynamic == 'profession' then Nenue@5: local profText = (self.spellNum == 1) and TRADE_SKILLS or (BUTTON_HEADERS[self.profIndex] or GetProfessionInfo(self.profIndex)) Nenue@5: if self.isAvailable then Nenue@5: print(self.profIndex, 'spnum', type(self.spellNum), (self.spellNum == 1)) Nenue@5: Nenue@5: self.statusText = '|cFFFFFF00'..profText..'|r' Nenue@5: self.bindingText = BindingString(GetBindingKey(self.command)) Nenue@5: else Nenue@5: self.statusText = '|cFFFF4400'..profText..'|r' Nenue@5: self.actionName = '(need to train profession #'..self.profNum..')' Nenue@5: self.bindingText ='?' Nenue@5: end Nenue@5: elseif self.isDynamic == 'talent' then Nenue@5: Nenue@5: self.statusText = '|cFF00FFFF'.. TALENT .. '|r' Nenue@5: if self.isAvailable then Nenue@5: self.bindingText = BindingString(GetBindingKey(self.command)) Nenue@5: else Nenue@5: print(self.actionID, #kb.inactiveTalentBindings[self.actionID]) Nenue@5: self.bindingText= BindingString(unpack(kb.inactiveTalentBindings[self.actionID])) Nenue@5: end Nenue@5: else Nenue@5: self.statusText = '|cFF00FF00'.. (BUTTON_HEADERS[self.actionType] and BUTTON_HEADERS[self.actionType] or self.actionType) .. '|r' Nenue@5: self.bindingText = BindingString(GetBindingKey(self.command)) Nenue@5: end Nenue@5: Nenue@5: local locked, layer = IsCommandBound(self) Nenue@5: if locked then Nenue@5: self.icon:SetAlpha(0.5) Nenue@5: else Nenue@5: self.icon:SetAlpha(1) Nenue@5: end Nenue@5: Nenue@5: if self.actionType == 'spell' then Nenue@5: self.icon:SetTexture(GetSpellTexture(self.actionID)) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: if not self.isAvailable then Nenue@5: self.bind:SetTextColor(0.7,0.7,0.7,1) Nenue@0: else Nenue@5: self.bind:SetTextColor(1,1,1,1) Nenue@5: end Nenue@5: Nenue@5: self.header:SetText(self.statusText) Nenue@5: self.bind:SetText(self.bindingText) Nenue@5: self.macro:SetText(self.macroName) Nenue@5: self.details:SetText(self.actionName) Nenue@5: end Nenue@5: Nenue@5: Nenue@5: kb.ApplyTalentBinding = function(talentInfo, cache) Nenue@5: for i = 5, #talentInfo do Nenue@5: SetBinding(talentInfo[i], "CLICK KeyBinderMacro:".. talentInfo[1]) Nenue@5: tinsert(cache, talentInfo[i]) Nenue@0: end Nenue@0: end Nenue@5: kb.CacheTalentBinding = function(talentInfo, cache) Nenue@5: local spellID = talentInfo[4] Nenue@5: kb.inactiveTalentBindings[spellID] = kb.inactiveTalentBindings[spellID] or {} Nenue@5: kb.inactiveTalentBindings[spellID] = {select(5,unpack(talentInfo)) } Nenue@5: cprint(spellID, unpack(kb.inactiveTalentBindings[spellID])) Nenue@0: end Nenue@0: Nenue@5: kb.ApplyBinding = function(command, name, icon, actionType, actionID, macroName, macroText ) Nenue@5: Nenue@5: if actionType == 'macro' then Nenue@5: KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID) Nenue@5: else Nenue@5: KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText) Nenue@5: end Nenue@5: bindings[actionType] = bindings[actionType] or {} Nenue@5: bindings[actionType][actionID] = bindings[actionType][actionID] or {} Nenue@5: bindings[command] = bindings[actionType][actionID] Nenue@5: return bindings[actionType], actionID Nenue@5: end Nenue@5: Nenue@5: kb.ApplyBindings = function (profile) Nenue@5: cprint('binding profile', profile) Nenue@5: for slot, data in pairs(profile.buttons) do Nenue@5: kb.ApplyBinding(unpack(data)) Nenue@5: end Nenue@5: Nenue@5: for key, command in pairs(profile.bindings) do Nenue@5: Nenue@5: cprint('Bindings data registered', command, key) Nenue@5: Nenue@5: --_G.print('HotKey','loading', key, command) Nenue@5: SetBinding(key, command) Nenue@5: if bindings[command] and not tContains(bindings[command], key) then Nenue@5: tinsert(bindings[command], key) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: for spellName, talentInfo in pairs(profile.talents) do Nenue@5: local dummy = GetSpellInfo(spellName) Nenue@5: local func = kb.CacheTalentBinding Nenue@5: local dest = kb.inactiveTalentBindings Nenue@5: if dummy then Nenue@5: cprint('|cFFBBFF00Active:|r', dummy) Nenue@5: local macroName, spellName, actionType, actionID = unpack(talentInfo) Nenue@5: bindings[actionType] = bindings[actionType] or {} Nenue@5: bindings[actionType][actionID] = {} Nenue@5: func = kb.ApplyTalentBinding Nenue@5: dest = bindings[actionType][actionID] Nenue@5: else Nenue@5: Nenue@5: cprint('|cFFFF4400Inactive:|r', talentInfo[2]) Nenue@5: end Nenue@5: func(talentInfo, dest) Nenue@5: end Nenue@5: Nenue@5: SaveBindings(GetCurrentBindingSet()) Nenue@5: end Nenue@5: Nenue@5: kb.ApplyAllBindings =function () Nenue@5: table.wipe(kb.inactiveTalentBindings) Nenue@5: Nenue@5: for i, profile in ipairs(priority) do Nenue@5: kb.ApplyBindings(profile) Nenue@5: end Nenue@5: -- do this after to ensure that profession binds are properly overridden Nenue@5: kb.UpdateProfessionInfo() Nenue@5: end Nenue@5: Nenue@5: kb.Command = function(args, editor) Nenue@5: if args:match("import") then Nenue@5: kb.ImportCommmit(args) Nenue@5: return Nenue@5: elseif args:match("scan") then Nenue@5: kb.ImportScan(args) Nenue@5: kb.ui() Nenue@5: return Nenue@5: elseif args:match("load") then Nenue@5: kb:ApplyAllBindings() Nenue@0: return Nenue@0: end Nenue@0: Nenue@5: if db.showUI then Nenue@5: db.showUI = false Nenue@5: kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.') Nenue@5: kb:Hide() Nenue@5: else Nenue@1: db.showUI = true Nenue@5: kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.') Nenue@5: end Nenue@5: kb.ui(true) Nenue@5: end Nenue@5: Nenue@5: kb.InitProfile = function(profile, prototype) Nenue@5: if not profile then Nenue@5: profile = {} Nenue@5: end Nenue@5: if prototype then Nenue@5: print('appplying prototype', prototype) Nenue@5: for k,v in pairs(prototype) do Nenue@5: if not profile[k] then Nenue@5: profile[k] = v Nenue@5: end Nenue@5: end Nenue@0: end Nenue@0: Nenue@5: profile.bound = profile.bound or {} Nenue@5: profile.buttons = profile.buttons or {} Nenue@5: profile.commands = profile.commands or {} Nenue@5: profile.bindings = profile.bindings or {} Nenue@5: profile.macros = profile.macros or {} Nenue@5: profile.talents = profile.talents or {} Nenue@5: return profile Nenue@5: end Nenue@5: Nenue@5: kb.ResetProfile = function(profile, prototype) Nenue@5: if profile == configProfile then Nenue@5: for i, button in pairs(buttons) do Nenue@5: kb.ReleaseSlot(button) Nenue@5: end Nenue@5: end Nenue@5: table.wipe(profile) Nenue@5: kb.InitProfile(profile, prototype) Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: --- Handles constructing spec profiles as they are selected Nenue@5: Nenue@5: Nenue@5: kb.TalentCache = {} Nenue@5: Nenue@5: kb.UpdateSpecInfo = function() Nenue@5: specID = GetSpecialization() Nenue@5: specGlobalID, specName, specDesc , specTexture = GetSpecializationInfo(specID) Nenue@5: loadedProfiles[BINDING_TYPE_CHARACTER][specID] = kb.InitProfile(loadedProfiles[BINDING_TYPE_CHARACTER][specID], { Nenue@5: specID = specID}) Nenue@5: Nenue@5: configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName) Nenue@5: loadedProfiles[BINDING_TYPE_SPECIALIZATION] = loadedProfiles[BINDING_TYPE_CHARACTER][specID] Nenue@5: configProfile = loadedProfiles[bindMode] Nenue@5: print('|cFF00FF00bindMode:|r', bindMode) Nenue@5: Nenue@5: priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]} Nenue@5: Nenue@5: print('|cFF00FF00current spec:|r', specID, 'of', GetNumSpecializations()) Nenue@5: end Nenue@5: Nenue@5: kb.UpdateTalentInfo = function() Nenue@5: if kb.talentsPushed then Nenue@5: return Nenue@5: end Nenue@5: Nenue@5: Nenue@5: table.wipe(kb.TalentCache) Nenue@5: Nenue@5: for row =1, MAX_TALENT_TIERS do Nenue@5: for col = 1, NUM_TALENT_COLUMNS do Nenue@5: local talentID, talentName, icon, selected, available, spellID = GetTalentInfo(row, col, 1) Nenue@5: local talentInfo = kb.TalentCache[spellID] or {} Nenue@5: talentInfo.row = 1 Nenue@5: talentInfo.col = col Nenue@5: talentInfo.name = talentName Nenue@5: talentInfo.talentID = talentID Nenue@5: talentInfo.selected = selected Nenue@5: talentInfo.available = available Nenue@5: talentInfo.spellID = spellID Nenue@5: kb.TalentCache[spellID] = talentInfo Nenue@5: print('Talent ', row, col, spellID, talentName) Nenue@5: end Nenue@5: end Nenue@5: kb.talentsPushed = true Nenue@5: end Nenue@5: Nenue@5: Nenue@5: kb.ProfessionCache = {} Nenue@5: kb.UpdateProfessionInfo = function() Nenue@5: table.wipe(kb.ProfessionCache) Nenue@5: local profs = {GetProfessions() } Nenue@5: local primaryNum = 0 Nenue@5: for i, index in ipairs(profs) do Nenue@5: local profName, texture, rank, maxRank, numSpells, spellOffset = GetProfessionInfo(index) Nenue@5: cprint(i, index, profName, numSpells, spellOffset) Nenue@5: if not professionMappings[index] then Nenue@5: primaryNum = primaryNum + 1 Nenue@5: end Nenue@5: local profNum = professionMappings[index] or primaryNum Nenue@5: Nenue@5: Nenue@5: kb.ProfessionCache[profNum] = kb.ProfessionCache[i] or {} Nenue@5: Nenue@5: for j = 1, numSpells do Nenue@5: local spellName, _, icon, _, _, _, spellID = GetSpellInfo(spellOffset+j, BOOKTYPE_PROFESSION) Nenue@5: Nenue@5: local profInfo = { Nenue@5: spellName = spellName, Nenue@5: spellID = spellID, Nenue@5: icon = icon, Nenue@5: profOffset = i, Nenue@5: profIndex = index, Nenue@5: spellOffset = (spellOffset+j), Nenue@5: spellNum = j Nenue@5: } Nenue@5: KeyBinderMacro:SetAttribute("*macrotext-profession_"..i .. '_' ..j, "/cast ".. spellName) Nenue@5: Nenue@5: kb.ProfessionCache[i .. '_' .. j] = profInfo Nenue@5: kb.ProfessionCache[spellName] = profInfo Nenue@5: kb.ProfessionCache[spellID] = profInfo Nenue@5: cprint(' |cFF0088FF['..i..']|r|cFFFF44BB['..spellOffset+i..']|r', spellName, "*macrotext-profession_"..i .. '_' ..j) Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: Nenue@5: end Nenue@5: Nenue@5: --- Obtains profile data or creates the necessary tables Nenue@5: kb.SelectProfileSet = function(name) Nenue@5: Nenue@5: --- General info Nenue@5: classHeader, className, classID = UnitClass('player') Nenue@5: print('|cFF00FF00profile:|r', name) Nenue@5: print('|cFF00FF00class:|r', UnitClass('player')) Nenue@5: Nenue@5: --- Global Nenue@5: bindMode = BINDING_TYPE_GLOBAL Nenue@5: kb.InitProfile(db) Nenue@5: loadedProfiles[BINDING_TYPE_GLOBAL] = db Nenue@5: Nenue@5: --- Character Nenue@5: if name then Nenue@5: db[name] = kb.InitProfile(db[name], Nenue@5: {classHeader = classHeader, className = className, classID = classID}) Nenue@5: loadedProfiles[BINDING_TYPE_CHARACTER] = db[name] Nenue@5: bindMode = BINDING_TYPE_CHARACTER Nenue@5: end Nenue@5: Nenue@5: --- Mutable skills data Nenue@5: kb.UpdateSpecInfo() Nenue@5: kb.UpdateTalentInfo() Nenue@5: Nenue@5: priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]} Nenue@5: if db.bindMode and loadedProfiles[db.bindMode] then Nenue@5: bindMode = db.bindMode Nenue@5: end Nenue@5: Nenue@5: db.bindMode = bindMode Nenue@5: Nenue@5: if not BINDING_MODE[bindMode] then Nenue@5: bindMode = 3 Nenue@5: db.bindMode = 3 Nenue@5: print('overriding', bindMode) Nenue@5: end Nenue@5: Nenue@5: print(BINDING_TYPE_GLOBAL) Nenue@5: configHeaders[BINDING_TYPE_GLOBAL] = BINDING_MODE[BINDING_TYPE_GLOBAL] Nenue@5: configHeaders[BINDING_TYPE_CHARACTER] = BINDING_MODE[BINDING_TYPE_CHARACTER]:format(UnitName('player', true)) Nenue@5: configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName) Nenue@5: Nenue@5: Nenue@5: setmetatable(loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return configHeaders[BINDING_TYPE_GLOBAL] end}) Nenue@5: setmetatable(loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return configHeaders[BINDING_TYPE_CHARACTER] end}) Nenue@5: setmetatable(loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return configHeaders[BINDING_TYPE_SPECIALIZATION] end}) Nenue@5: Nenue@5: print('|cFF00FF00bindMode:|r', bindMode) Nenue@5: configProfile = loadedProfiles[bindMode] Nenue@5: end Nenue@5: Nenue@5: local scrollCache = {} Nenue@5: kb.SelectTab = function(self) Nenue@5: scrollCache[bindMode] = kb.scrollOffset Nenue@5: bindMode = self:GetID() Nenue@5: configProfile = loadedProfiles[self:GetID()] Nenue@5: db.bindMode = self:GetID() Nenue@5: kb.scrollOffset = scrollCache[bindMode] or 0 Nenue@5: kb.ui(true) Nenue@5: end Nenue@5: Nenue@5: kb.RevertBindings = function() Nenue@5: -- todo: reversion code Nenue@5: end Nenue@5: Nenue@5: kb.ConfirmBindings = function() Nenue@5: SaveBindings(GetCurrentBindingSet()) Nenue@5: bindsCommitted = true Nenue@5: for i, button in ipairs(buttons) do Nenue@5: button.pending = false Nenue@5: end Nenue@5: kb.ApplyAllBindings() Nenue@5: Nenue@5: kb.ui() Nenue@5: kb:print('Keybinds saved.') Nenue@5: end Nenue@5: Nenue@5: Nenue@5: Nenue@5: Nenue@5: Nenue@5: Nenue@5: --- push current information into living UI Nenue@5: kb.ui = function(force) Nenue@5: for i, module in ipairs(kb.modules) do Nenue@5: if module.ui then Nenue@5: module.ui(force) Nenue@5: end Nenue@5: end Nenue@5: Nenue@5: if not db.showUI then Nenue@5: print('---end of refresh') Nenue@5: return Nenue@5: end Nenue@0: if not kb.loaded then Nenue@5: KeyBinder_Initialize() Nenue@0: kb.loaded = true Nenue@0: end Nenue@0: for i = 1, numButtons do Nenue@5: local button = kb.GetSlot(i) Nenue@5: button:SetID(i+kb.scrollOffset) Nenue@5: kb.UpdateSlot(button, force) Nenue@0: end Nenue@0: Nenue@0: if bindsCommitted then Nenue@0: KeyBinderSaveButton:Disable() Nenue@5: --KeyBinderRestoreButton:Disable() Nenue@0: else Nenue@0: KeyBinderSaveButton:Enable() Nenue@5: --KeyBinderRestoreButton:Enable() Nenue@0: end Nenue@0: Nenue@5: --- Frame Sizing Nenue@5: kb.profilebg:SetHeight(kb.tabSize[2] + BUTTON_PADDING * 2 + kb.profiletext:GetStringHeight()) Nenue@5: Nenue@5: kb.bg:SetWidth((KEY_BUTTON_SIZE + BUTTON_HSPACING + BUTTON_SPACING) * BINDS_PER_ROW + BUTTON_PADDING*2 - BUTTON_SPACING) Nenue@0: local numRows = numButtons/BINDS_PER_ROW Nenue@5: Nenue@5: kb.bg:SetHeight((KEY_BUTTON_SIZE + BUTTON_SPACING) * numRows + BUTTON_PADDING*2 - BUTTON_SPACING) Nenue@5: Nenue@5: kb:SetHeight(kb.headerbg:GetHeight() + kb.profilebg:GetHeight() + kb.bg:GetHeight() + kb.footer:GetHeight()) Nenue@5: kb:SetWidth((kb.sourcesbg:GetWidth() +(BINDS_PER_ROW * (KEY_BUTTON_SIZE + BUTTON_HSPACING) + (BINDS_PER_ROW - 1) * BUTTON_SPACING + BUTTON_PADDING * 2) )) Nenue@5: Nenue@0: kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode])) Nenue@0: for i, tab in ipairs(kb.tabButtons) do Nenue@5: local border = tab:GetNormalTexture() Nenue@0: local tabTexture = "Interface\\Buttons\\UI-Quickslot2" Nenue@0: local left, top, right, bottom = -12, 12, 13, -13 Nenue@0: if i == bindMode then Nenue@0: tabTexture = "Interface\\Buttons\\CheckButtonGlow" Nenue@0: left, top, right, bottom = -14, 14, 15, -15 Nenue@5: tab.icon:SetDesaturated(false) Nenue@5: if tab.icon2 then tab.icon2:SetDesaturated(false) end Nenue@5: border:SetDesaturated(true) Nenue@5: border:SetVertexColor(1,1,1, 1) Nenue@5: else Nenue@5: tab.icon:SetDesaturated(true) Nenue@5: if tab.icon2 then tab.icon2:SetDesaturated(true) end Nenue@5: border:SetDesaturated(false) Nenue@5: border:SetVertexColor(1,1,1) Nenue@0: end Nenue@5: border:SetTexture(tabTexture) Nenue@5: border:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top) Nenue@5: border:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom) Nenue@0: end Nenue@5: Nenue@5: KeyBinderSpecTab.icon:SetTexture(specTexture) Nenue@5: Nenue@5: kb.profiletext:SetText(configHeaders[bindMode]) Nenue@5: print(bindMode, configHeaders[bindMode], kb:GetSize()) Nenue@5: print(kb:GetPoint(1)) Nenue@5: Nenue@5: kb:Show() Nenue@5: Nenue@5: -- Reset this so talent cache can be rebuilt Nenue@5: kb.talentsPushed = nil Nenue@0: end Nenue@0: Nenue@5: --- post ADDON_LOADED Nenue@5: kb.variables = function() Nenue@5: SkeletonKeyDB = SkeletonKeyDB or {spec = {}} Nenue@5: kb.db = SkeletonKeyDB Nenue@5: kb.playerName = UnitName('player') Nenue@5: kb.playerRealm = SelectedRealmName() Nenue@5: kb.profileName = kb.playerRealm .. '_' .. kb.playerName Nenue@5: db = kb.db Nenue@5: Nenue@5: kb.SelectProfileSet(kb.profileName) Nenue@5: if not configProfile.imported then Nenue@5: kb.ImportScan() Nenue@0: end Nenue@5: kb.ApplyAllBindings() Nenue@5: Nenue@5: kb.ui(true) Nenue@0: end Nenue@0: Nenue@1: Nenue@5: kb.wrap = function(module) Nenue@5: kb.modules = kb.modules or {} Nenue@5: tinsert(kb.modules, module) Nenue@0: end Nenue@0: Nenue@5: -- Volatiles Access Nenue@5: kb.BindingIsLocked = BindingIsLocked Nenue@5: kb.BindingString = BindingString Nenue@5: kb.GetBindings = function() return bindings end Nenue@5: kb.GetButtons = function() return buttons end Nenue@5: kb.GetCharacterProfile = function () return loadedProfiles[BINDING_TYPE_CHARACTER] end Nenue@5: kb.GetGlobalProfile = function () return loadedProfiles[BINDING_TYPE_GLOBAL] end Nenue@5: kb.GetLooseTalents = function() return talentBindings end Nenue@5: kb.GetProfileStack = function() return priority end Nenue@5: kb.GetReverts = function() return reverts end Nenue@5: kb.GetSpecProfile = function () return loadedProfiles[BINDING_TYPE_SPECIALIZATION] end Nenue@0: Nenue@5: --- Add to blizzard interfaces Nenue@5: StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"] = { Nenue@5: text = "Confirm moving an assigned command.", Nenue@5: button1 = OKAY, Nenue@5: button2 = CANCEL, Nenue@5: timeout = 0, Nenue@5: whileDead = 1, Nenue@5: showAlert = 1, Nenue@5: OnAccept = kb.AcceptAssignment, Nenue@5: OnCancel = function() kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) end Nenue@5: } Nenue@0: Nenue@5: SLASH_SKB1 = "/skb" Nenue@5: SLASH_SKB2 = "/skeletonkey" Nenue@5: SlashCmdList.SKB = kb.Command Nenue@0: Nenue@5: -- This is needed to identify a spells that aren't reflected by GetCursorInfo() Nenue@5: hooksecurefunc("PickupSpellBookItem", function(slot, bookType) Nenue@5: print('|cFFFF4400PickupSpellBookItem(..', tostring(slot),', '..tostring(bookType)..')') Nenue@5: CURSOR_SPELLSLOT = slot Nenue@5: CURSOR_BOOKTYPE = bookType Nenue@5: end) Nenue@0: Nenue@5: -- Pet actions Nenue@5: local isPickup Nenue@5: hooksecurefunc("PickupPetAction", function(slot, ...) Nenue@5: isPickup = GetCursorInfo() Nenue@0: Nenue@5: CURSOR_PETACTION = isPickup and slot Nenue@5: print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION) Nenue@5: end)