view 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
line wrap: on
line source
--------------------------------------------
-- SkeletonKey
-- Krakyn-Mal'Ganis
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 6/16/2016 3:47 AM
--------------------------------------------
-- kb
--   .StoreBinding(button, key)                     bind current keystroke to command
--   .GetSlot(index)                         return display slot
--   .SetSlot(button, command, name, icon)   assign display slot
--   .ReleaseSlot(button)                       clear button command
--   .UpdateSlot(button)                       update button contents
--   .SelectProfile(name)                         set profile character
--   .ApplyBindings(bindings)                   walk table with SetBinding()

local _
local kb, print = LibStub("LibKraken").register(KeyBinder)
local db
local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end

--- Caps Lock literals
local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:"
local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
local BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
local BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r'
local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
local FOOTER_OFFSET
local HEADER_OFFSET
local HELP_1 = "Drag and drop spells/items from your inventory, spellbook, or collections panels."
local HELP_2 = "While the cursor is above an icon, up to two key combinations will be bound to that action."
local HELP_3 = "If that key used for a client binding (e.g. game menu), a confirmation popup will appear before making the change."
local BINDS_PER_ROW = 2
local BUTTON_HSPACING = 128
local BUTTON_SPACING = 4
local BUTTON_PADDING = 12
local BINDING_TYPE_SPECIALIZATION = 3
local BINDING_TYPE_CHARACTER = 2
local BINDING_TYPE_GLOBAL = 1
local KEY_BUTTON_SIZE = 48
local MIN_BIND_SLOTS = 32
local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
local TAB_OFFSET = 12
local TAB_HEIGHT = 40
local TAB_SPACING = 2
local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 }
local BORDER_ASSIGNED = {0.5,0.5,0.5,1 }
local BORDER_DYNAMIC = {1,1,0,1}
local BORDER_PENDING = {1,0.5,0,1 }
local CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_PETACTION


--- Caps Lock derivatives
local ACTION_SCRIPT = {
  ['mount'] = "/script C_MountJournal.SummonByID(%d)",
  ['macro'] = "%s",
  ['equipset'] = "/script UseEquipmentSet(%d)",
  ['spell'] = "/cast %s",
  ['petaction'] = "/cast %s",
  ['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s",
  ['item'] = "/use %s"
}
local BUTTON_HEADERS = {
  ['spell'] = SPELLS,
  ['macro'] = MACRO,
  ['petaction'] = PET,
  ['mount'] = MOUNT,
  ['battlepet'] = BATTLEPET,


  [5] = PROFESSIONS_FIRST_AID,
  [7] = PROFESSIONS_COOKING,
  [9] = PROFESSIONS_FISHING,
  [10] = PROFESSIONS_ARCHAEOLOGY,

}

local professionMappings = {
  [5] = 3,
  [7] = 4,
  [9] = 5,
  [10] = 6
}

local BINDING_MODE = {
  [BINDING_TYPE_GLOBAL] = 'Global Binds',
  [BINDING_TYPE_CHARACTER] = 'Character: %s',
  [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s'
}
local BINDING_DESCRIPTION = {

  [BINDING_TYPE_GLOBAL] = 'The bindings are applied globally.',
  [BINDING_TYPE_CHARACTER] = 'Applied when you log onto this character.',
  [BINDING_TYPE_SPECIALIZATION] = 'Applied when you log onto this character and are that specialization.',
}

local BINDING_SCHEME_COLOR = {
  [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5},
  [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5},
  [BINDING_TYPE_SPECIALIZATION] = {.25,0,0,0.5},
}
local BINDING_SCHEME_VERTEX = {
  [BINDING_TYPE_GLOBAL] = {0,.5,1,1},
  [BINDING_TYPE_CHARACTER] = {0,1,0,1},
  [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1},
}
local BINDING_SCHEME_TEXT = {
  [BINDING_TYPE_SPECIALIZATION] = {0, 1, 1},
  [BINDING_TYPE_CHARACTER] = {0, 1, 0},
  [BINDING_TYPE_GLOBAL] = {0, 1, 1}
}



local loadedProfiles = {}
-- Profiles ordered by precedance
local priority = {}
-- Button pointers
local buttons = {}
-- Backlog of changes
local reverts = {}
-- macro buttons used for mounts and other buttonable non-spells
local macros = {}
-- currently active non-blizzard keybinds
local bindings = {}
-- unselected talents
local talentBindings = {}
kb.inactiveTalentBindings = {}
-- placeholder for the StaticPopup used for confirmations
local confirmation
-- header text
local configHeaders = {}

local protected = {
  ['OPENCHATSLASH'] = true,
  ['OPENCHAT'] = true,
}

--- Used to reflect the current working state
local bindMode = 3
local bindHeader, currentHeader = '', ''
local configProfile, character, specialization, global, character_specialization
local specID, specGlobalID, specName, specDesc, specTexture, characterHeader = 0, 0, 'SPEC_NAME', 'SPEC_DESCRIPTION', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
local classHeader, className, classID = '', '', 0
local numButtons = BINDS_PER_ROW * 8
local bindsCommitted = true
local forceButtonUpdate = false

--- Control handles
local saveButton, restoreButton, clearButton

--- Cursor "pickup" actuators
local PickupAction = {}
PickupAction.spell = _G.PickupSpell
PickupAction.macro = _G.PickupMacro
PickupAction.item = _G.PickupItem
PickupAction.mount = _G.C_MountJournal.Pickup
local GetPickupValue = {}
GetPickupValue.spell = function(self) return select(7, GetSpellInfo(self.actionID)) end

--- Returns conflicting assignment and binding profiles for use in displaying confirmations
local IsCommandBound = function(self, command)
  local isAssigned, assignedBy = false, bindMode
  local isBound, boundBy = false, bindMode


  command = command or self.command
  for i = 1, #BINDING_MODE do
    local tier = priority[i]
    if i ~= bindMode then

      if tier.commands[command] then
        isAssigned = true
        assignedBy = i
      end
      if tier.bound[command] then
        isBound = true
        boundBy = i
      end


      --print(' *', configHeaders[i], tier.commands[command], tier.bound[command])

      if isAssigned and isBound then
        break
      end
    end

  end

  print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
  return isAssigned, isBound, assignedBy, boundBy
end

local talentSpellHardCodes = {
  [109248] = 'Binding Shot',
}

--- Returns a value for use with Texture:SetDesaturated()
local BindingIsLocked = function(key)
  local success = false
  for i = 1, bindMode-1 do
    local tier = priority[i]
    if tier.bindings[key] then
      success = true
      break
    end
  end
  return success
end

--- Translates GetBindingKey() results into a printable string.
local BindingString = function(...)
  local stack = {}
  for i = 1, select('#', ...) do
    local key = select(i, ...)
    stack[i] = key:gsub('SHIFT', 's'):gsub('ALT', 'a'):gsub('CTRL', 'c'):gsub('SPACE', 'Sp'):gsub('BUTTON', 'M '):gsub('NUMPAD', '# ')
  end

  if #stack >= 1 then
    return table.concat(stack, ',')
  else
    return nil
  end
end

local restingAlpha = 0.7
local fadeTime, fadeDelay = .30, 0.15

local portraitLayers = {}
kb.UpdatePortraits = function()
  for i, layeredRegion in ipairs(portraitLayers) do
    SetPortraitTexture(layeredRegion , 'player')
  end
end



local KeyButton_OnKeyDown = function(self, key)
  kb.StoreBinding(self, key)
end
local KeyButton_OnClick = function(self, click)
  print(self:GetName(), 'OnMouseDown', click)
  if click == 'LeftButton' then
    kb.DropToSlot(self)
  elseif click == 'RightButton' then
    kb.ReleaseSlot(self)
  else
    kb.StoreBinding(self, click:upper())
  end
end

local KeyButton_OnDragStart = function(self)
  kb.PickupSlot(self)
end

local KeyButton_OnReceiveDrag = function(self, ...)
  kb.DropToSlot(self)
end


local KeyBinder_OnUpdate = function(self, elapsed)
  self.elapsed = self.elapsed + elapsed
  self.throttle = self.throttle + elapsed

  if (self.throttle >= 0.032) then
    self.throttle = 0
  else
    return
  end

  local progress = 1
  if self.elapsed > fadeTime then
    self.elapsed = 0
    self.fadeStep = 0
    --self.statustext:SetText(nil)
    --self.bindingstext:SetText(nil)
    self:SetScript('OnUpdate', nil)
  else
    if self.elapsed < fadeDelay then
      progress = 0
    else
      self.fadeStep = self.fadeStep + 1
      progress = (self.elapsed - fadeDelay) /(fadeTime - fadeDelay)
    end
    --print(self.fadeStep, format('%.02f/%.02f', (self.elapsed - fadeDelay) ,(fadeTime - fadeDelay)) , progress)
  end

  local alpha = 1 - progress * (1- restingAlpha)
  self.statustext:SetAlpha(alpha)
  self.bindingstext:SetAlpha(alpha)
end

local KeyButton_OnUpdate = function(self)
  if not self.command then
    return
  end

  if self:IsMouseOver() then
    kb.elapsed = 0
    if not self.active then
      -- only set this handler when the button is activated/mouseOver
      self.active = true
      self:SetScript('OnKeyDown', KeyButton_OnKeyDown)

      kb.statustext:SetText(self.statusText .. ': '..self.actionName)
      kb.bindingstext:SetText(self.bindingText)
      kb.fadeStep = 0
      kb.throttle = 0
      kb:SetScript('OnUpdate', KeyBinder_OnUpdate)

    end
  else
    if self.active then
      self.active = nil
      self:SetScript('OnKeyDown', nil)
    end
  end
end

local KeyBinder_OnMouseWheel = function(self, delta)
  print(self, delta, self.scrollOffset, (self.scrollOffset <= 0))


  if IsControlKeyDown() then
    KEY_BUTTON_SIZE = KEY_BUTTON_SIZE - delta
  else


    if (delta > 0) and (self.scrollOffset <= 0) then
      return
    elseif delta < 0 and kb.scrollOffset >= 42 then
      return
    end
    kb.scrollOffset = ceil(kb.scrollOffset - (delta * BINDS_PER_ROW))
  end

  kb.ui(true)
end

local KeyBinder_OnHide = function()
  KeyBinderImportLog:Hide()
end

local CloseButton_OnClick = function()
  db.showUI = false
  kb:Hide()
end
local CancelButton_OnClick = function()
  kb.RevertBindings()
end
local SaveButton_OnClick = function()
  kb.ConfirmBindings()
end

local KeyBinder_Initialize = function()
  for i = 1, GetNumBindings() do
    local command = GetBinding(i)
    bindings[command] = true
  end

  kb.scrollOffset = 0
  kb.tabAnchor = {'TOPLEFT', kb.profilebg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING}
  kb.tabGrowth = {'TOPLEFT', nil,'TOPRIGHT', BUTTON_SPACING, 0}
  kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT }
  kb.UIPanelAnchor = {'TOPLEFT', kb.sourcesbg, 'TOPLEFT', BUTTON_PADDING, -BUTTON_SPACING}
  kb.UIPanelGrowth = {'TOPLEFT', nil, 'BOTTOMLEFT', 0, -2 }
  kb.UIPanelSize = {84, 32 }
  kb.UIPanelIcon = {24, 32, 'LEFT', -12, 0}
  kb.controlsAnchor = {'BOTTOMLEFT', kb.footer, BUTTON_PADDING, BUTTON_PADDING }
  kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0}

  -- order of these is important
  kb:tab('KeyBinderGlobalTab',
    BINDING_MODE[BINDING_TYPE_GLOBAL] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_GLOBAL], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85})
  kb:tab('KeyBinderCharacterTab',
    configHeaders[BINDING_TYPE_CHARACTER] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_CHARACTER], nil)
  kb:tab('KeyBinderSpecTab',
    configHeaders[BINDING_TYPE_SPECIALIZATION] .. '\n' .. BINDING_DESCRIPTION[BINDING_TYPE_SPECIALIZATION], specTexture)
  KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85)



  portraitLayers[1] = KeyBinderCharacterTab.icon

  saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', SaveButton_OnClick)
  --restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', CancelButton_OnClick)
  --clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', ResetButton_OnClick)

  kb:uibutton(
    'KeyBinderSpellBookButton', 'SpellBook', nil,
    function() ToggleSpellBook(BOOKTYPE_SPELL) end,
    "Interface\\BUTTONS\\UI-MicroButton-Spellbook-Up", {0, 1, .4, 1})
  kb:uibutton(
    'KeyBinderTalentFrameButton', TALENTS, SPECIALIZATION,
    function() ToggleTalentFrame() end,
    "Interface\\BUTTONS\\UI-MicroButton-Talents-Up", {0, 1, .4, 1})

  kb:uibutton(
    'KeyBinderMacroFrameButton', 'Macros', nil,
    function() if MacroFrame and MacroFrame:IsVisible() then
      HideUIPanel(MacroFrame)
    else
      ShowMacroFrame() end
    end,
    "Interface\\BUTTONS\\UI-MicroButton-Help-Up", {0, 1, .4, 1})

  kb:uibutton(
    'KeyBinderInventoryButton', 'Bags', nil,
    function() OpenAllBags() end,
    "Interface\\BUTTONS\\UI-MicroButtonCharacter-Up", {0, 1, .4, 1})



  kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING)
  HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING
    + kb.info:GetHeight()
  FOOTER_OFFSET = saveButton:GetHeight() + BUTTON_PADDING

  kb:SetScript('OnHide', KeyBinder_OnHide)
  kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel)
  kb.CloseButton:SetScript('OnClick', CloseButton_OnClick)

  kb.UpdatePortraits()
end

kb.DropToSlot = function(self)

  print(self:GetName(),'|cFF0088FFreceived|r')
  local actionType, actionID, subType, subData = GetCursorInfo()
  print('GetCursorInfo', GetCursorInfo())


  if actionType then

    if actionType == 'flyout' then
      ClearCursor()
      ResetCursor()
      return
    end


    local macroName, macroText
    local command, name, icon, _
    local pickupID, pickupBook

    if actionType == 'spell' then
      actionID = subData
      name, _, icon = GetSpellInfo(actionID)

    elseif actionType == 'macro' then
      name, icon = GetMacroInfo(actionID)
      actionID = name
    elseif actionType == 'petaction' then
      if not (CURSOR_SPELLSLOT and CURSOR_BOOKTYPE) then

        ClearCursor()
        ResetCursor()
      end

      local bookType, spellID = GetSpellBookItemInfo(CURSOR_SPELLSLOT, CURSOR_BOOKTYPE)
      pickupID = CURSOR_SPELLSLOT
      pickupBook = CURSOR_BOOKTYPE
      name, _, icon = GetSpellInfo(spellID)
      actionID = name

    elseif actionType == 'mount' then
      if subType == 0 then
        name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL)
        actionID = 0
      else
        name, _, icon = C_MountJournal.GetMountInfoByID(actionID)
      end
    elseif actionType == 'item' then
      name = GetItemInfo(actionID)
      icon = GetItemIcon(actionID)
      actionID = name
    elseif actionType == 'battlepet' then

      local speciesID, customName, level, xp, maxXp, displayID, isFavorite, petName, petIcon, petType, creatureID = C_PetJournal.GetPetInfoByPetID(detail);
      name = customName or petName
      icon = petIcon

    end
    macroName, macroText, command = kb.RegisterAction(actionType, actionID)


    local isAssigned, isBound, assignedBy, boundBy = IsCommandBound(self, command)
    if isAssigned then
      local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"]
      popup.slot = self
      popup.text = "Currently assigned in |cFFFFFF00"..tostring(configHeaders[assignedBy]).."|r. Are you sure?"
      popup.oldProfile = assignedBy
      popup.args = {command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook }
      kb:SetScript('OnMouseWheel', nil) -- disable scrolling
      StaticPopup_Show('SKELETONKEY_CONFIRM_ASSIGN_SLOT')
    else
      kb.SetSlot(self, command, name, icon, actionType, actionID, macroName, macroText, pickupID, pickupBook)
      kb.UpdateSlot(self)
      self.active = nil
      KeyButton_OnUpdate(self, 0)
      ClearCursor()
      ResetCursor()
    end
  end
end

kb.PickupSlot = function(self)
  if not self.command then
    return
  end
  print(self.actionType)
  if self.actionType == 'spell' then
    -- It can't be picked up if SpellInfo(name) returns void
    local dummy = GetSpellInfo(self.actionName)
    if not dummy then
      return
    end
  elseif self.actionType == 'petaction' then
    PickupSpellBookItem(self.pickupSlot, self.pickupBook)
  end
  if PickupAction[self.actionType] then
    if GetPickupValue[self.actionType] then
      PickupAction[self.actionType](GetPickupValue[self.actionType](self))
    else
      PickupAction[self.actionType](self.actionID)
    end
    kb.ReleaseSlot(self)
    kb.UpdateSlot(self)
  end
end


--- Resolve the appropriate command and assign the corresponding secure state driver
kb.RegisterAction = function(type, id)

  if type == 'spell' then

    id = GetSpellInfo(id)
  end

  local macroText
  local macroName = type ..'_' .. id

  if kb.ProfessionCache[id] then
    macroName = "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum
    macroText = "/cast " .. kb.ProfessionCache[id].spellName
    macros[macroName] = nil
  else
    macroName = macroName:gsub(' ', '')
    macroText = ACTION_SCRIPT[type]:format(id)
  end

  local baseName, iterative = macroName, 1
  while (macros[macroName] and macros[macroName][1] ~= macroText) do
    print(' * cannot use|cFF00FF00', macroName, '|r"'.. (macros[macroName][1] or '') .. '"')
    macroName = baseName .. '_' .. iterative
    iterative = iterative + 1
  end
  if macroName ~= baseName then
    print(' * Creating|cFF00FF00', macroName)
  else
    print(' * Re-using|cFF00FF00', macroName)
  end

  local command = 'CLICK KeyBinderMacro:'.. macroName
  macros[macroName] = {macroText, command }
  print('RegisterAction', command , macroText)
  if type == 'macro' then
    kb.LoadMacro(macroName)
  else
    kb.LoadAction(macroName, macroText, command)
  end


  return macroName, macroText, command
end

kb.LoadMacro = function(macroName)
  KeyBinderMacro:SetAttribute('*macro-'..macroName, macros[macroName][1])
  return true
end

kb.LoadAction = function(macroName)
  if not macros[macroName] then
    return false
  end
  KeyBinderMacro:SetAttribute('*macrotext-'..macroName, macros[macroName][1])
  return true
end

local profressionsCache

kb.AcceptAssignment = function(self, ...)
  local popup = StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"]
  local source = loadedProfiles[popup.oldProfile]
  kb.SetSlot(popup.slot, unpack(popup.args))
  kb.UpdateSlot(popup.slot)
  kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) -- re-enable scrolling
  ClearCursor()
  ResetCursor()
end


--- Updates the current KeyBinding for the button's command
kb.StoreBinding = function(self, key)

  if not self.command then
    return
  end

  if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then
    return
  end
  print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)

  if key == 'ESCAPE' then
    local keys = {GetBindingKey(self.command) }
    --print('detected', #keys, 'bindings')
    for i, key in pairs(keys) do
      --print('clearing', key)
      SetBinding(key, nil)
      SaveBindings(GetCurrentBindingSet())
      if configProfile.bindings[key] then
        kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode]))
        configProfile.bindings[key] = nil
      end
      if configProfile.talents[self.actionName] then
        configProfile.talents[self.actionName] = nil
      end
      bindings[self.actionType][self.actionID] = nil
    end
    if configProfile.bound[self.command] then
      configProfile.bound[self.command] = nil
      --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[bindMode]))
    end

    bindsCommitted = false
    self.active = false
  else

    local modifier = ''
    if IsAltKeyDown() then
      modifier = 'ALT-'
    end
    if IsControlKeyDown() then
      modifier = modifier.. 'CTRL-'
    end
    if IsShiftKeyDown() then
      modifier = modifier..'SHIFT-'
    end


    if self.command then
      self.binding = modifier..key

      local previousKeys
      local previousAction = GetBindingAction(self.binding)
      local binding1, binding2, new1, new2
      print(type(previousAction), previousAction)
      if previousAction ~= "" and previousAction ~= self.command then
        if protected[previousAction] then
          -- bounce out if trying to use a protected key
          kb.statustext:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(previousAction)))
          kb.bindingstext:SetText(nil)
          return
        else
          kb:print('Discarding keybind for', previousAction)
          -- todo: sort out retcon'd talent spells
        end
      end

      self.pending = true

      bindsCommitted = false
      SetBinding(self.binding, self.command)
      SaveBindings(GetCurrentBindingSet())

      local talentInfo
      if self.actionType == 'spell' and kb.TalentCache[self.actionID] then
        print('conditional binding (talent = "'..self.actionName..'")')
        talentInfo = {self.macroName, self.actionName, self.actionType, self.actionID}
        local bindings = {GetBindingKey(self.command) }
        for i, key in ipairs(bindings) do
          tinsert(talentInfo, key)
        end
      end

      for level, configProfile in ipairs(priority) do
        if (level == bindMode) then
          configProfile.bound[self.command] = true
          if talentInfo then
            configProfile.bindings[self.binding] = nil
          else
            configProfile.bindings[self.binding] = self.command
          end
          configProfile.talents[self.actionName] = talentInfo
        else
          configProfile.bindings[self.binding] = nil
          configProfile.bound[self.command] = nil
          configProfile.talents[self.actionName] = nil
        end
        if configProfile.talents[self.actionID] then
          configProfile.talents[self.actionID] = nil
        end

      end



      kb:print(BINDING_ASSIGNED:format(self.binding, self.actionName, configHeaders[bindMode]))

    end
  end

  kb.UpdateSlot(self, true)
  KeyBinderSaveButton:Enable()

end

--- Resets button command
kb.ReleaseSlot = function(self)
  local slot = self:GetID()


  if configProfile.buttons[slot] then
    configProfile.buttons[slot] = nil
  end
  if self.command then
    configProfile.commands[self.command] = nil
  end
  if self.actionType == 'spell' and IsTalentSpell(self.actionName) then
    if configProfile.talents[self.actionID] then
      configProfile.talents[self.actionID] = nil
    end
  end
  local droppedKeys = {}

  -- doing removal in second loop to avoid possible iterator shenanigans
  for k,v in pairs(configProfile.bindings) do
    if v == self.command then
      tinsert(droppedKeys, k)
    end
  end
  if #droppedKeys >=1 then
    for i, k in ipairs(droppedKeys) do
      configProfile.bindings[k] = nil
    end
  end

  self.isAvailable = nil
  self.isDynamic = nil
  self.bindingText = nil
  self.statusText = nil
  self.command = nil
  self.actionType = nil
  self.actionID = nil
  self.actionName = nil
  self.pickupSlot = nil
  self.pickupBook = nil
  self.macroName = nil
  self.profile = nil
  self.icon:SetTexture(nil)
  self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
  self:EnableKeyboard(false)
  self:SetScript('OnKeyDown', nil)
end

kb.SetSlot = function(self, command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook)
  local slot = self:GetID()
  local isDynamic, isAvailable

  print('|cFFFFFF00SetSlot|r:', self:GetID())
  if command then

    if actionType == 'spell' then
      local professionNum, spellNum = command:match("profession_(%d)_(%d)")

      if (professionNum and spellNum) then
        isDynamic = 'profession'
        local cacheInfo = kb.ProfessionCache[professionNum..'_'..spellNum]
        if cacheInfo then
          isAvailable = true
          name = cacheInfo.spellName
          icon = cacheInfo.icon
          actionID = cacheInfo.spellID
          self.profIndex = cacheInfo.profIndex
          self.spellOffset = cacheInfo.spellOffset
        end
        print(' Special slot: |cFF00FFFFProfession|r', professionNum, spellNum, isDynamic, isAvailable)

        self.professionNum = tonumber(professionNum)
        self.spellNum = tonumber(spellNum)

      elseif kb.TalentCache[actionID] then

        isDynamic = 'talent'
        isAvailable = GetSpellInfo(name)
        print(' Special slot: |cFFBBFF00talent|r', name, isAvailable)
      end
      if not actionID then
        actionID = select(7, GetSpellInfo(name))
      end
    elseif actionType == 'macro' then
      if not actionID then
        actionID = GetMacroIndexByName(name)
      end
    else
      --- Journal selections
      -- todo: consider using the deep end of blizzard action bar instead
      if not actionID then
        actionID = command:match("^KeyBinderMacro:(.+)")
      end
    end

    if not macroName then
      local previousCommand = command
      macroName, macroText, command = kb.RegisterAction(actionType, actionID)

      -- Clean up conflicting command entry for loaded buttons
      if macroName and command ~= previousCommand then
        print('  Repaired corruption in |cFFFFFF00'..currentHeader..'|r button #'.. self:GetID())
        configProfile.commands[previousCommand] = nil
        configProfile.bound[previousCommand] = nil
      end
    end

    if actionType == 'petaction' then
      self.pickupSlot = pickupSlot
      self.pickupBook = pickupBook
    else
      self.pickupSlot = nil
      self.pickupBook = nil
    end

    actionID = actionID or 0
    self:EnableKeyboard(true)
    print(' |cFF00FF00configProfile.buttons['..slot..'] |cFF00FFFF=|r |cFF00FFFF"'.. command.. '"|r |cFF00FF00"'.. name, '"|r |cFFFFFF00icon:'.. icon .. '|r |cFFFF8800"'.. actionType, '"|r |cFFFF0088id:'.. actionID ..'|r |cFF00FF00"'.. macroName .. '"|r')
    configProfile.buttons[slot] = {command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook}

    -- Clean up conflicting entries for loaded button
    local previous = configProfile.commands[command]
    if previous ~= slot and buttons[previous] then
      kb.ReleaseSlot(buttons[previous])
    end
    configProfile.commands[command] = slot
  end

  self.isAvailable = isAvailable
  self.isDynamic = isDynamic

  self.macroText = macroText
  self.macroName = macroName
  self.actionType = actionType
  self.actionID = actionID
  self.actionName = name
  self.command = command
  self.icon:SetTexture(icon)
  self.profile = bindMode
  self:RegisterForDrag('LeftButton')
end

--- Retrieves button at index; creates said button and instates any stored parameters
local leftSlot, upSlot
local buttonsDepth = 0
kb.GetSlot = function(index)

  local slot  = index + kb.scrollOffset

  if not buttons[index] then
    local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton')
    button:SetScript('OnClick', KeyButton_OnClick)
    button:SetScript('OnUpdate', KeyButton_OnUpdate)
    button:SetScript('OnDragStart', KeyButton_OnDragStart)
    button:SetScript('OnReceiveDrag', KeyButton_OnReceiveDrag)
    button:RegisterForClicks('AnyUp')


    local newRow = (mod(index, BINDS_PER_ROW) == 1)

    if index == 1 then
      button:SetPoint('TOPLEFT', kb.bg, 'TOPLEFT', BUTTON_PADDING, - BUTTON_PADDING)
      upSlot = button
      buttonsDepth = KEY_BUTTON_SIZE + BUTTON_PADDING * 2
    elseif newRow then
      button:SetPoint('TOPLEFT', upSlot, 'BOTTOMLEFT', 0, -BUTTON_SPACING)
      upSlot = button
      buttonsDepth = buttonsDepth + KEY_BUTTON_SIZE + BUTTON_SPACING
    else
      button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0)
    end

    button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
    button:Show()
    buttons[index] = button
    leftSlot = button
  end
  return buttons[index]
end

--- Updates profile assignment and button contents
kb.UpdateSlot = function(self, force)
  local slot = self:GetID()

  if force then
    if configProfile.buttons[slot] then
      kb.SetSlot(self, unpack(configProfile.buttons[slot]))
    else
      kb.ReleaseSlot(self)
    end
  end

  if self.command then
    print('['..slot..'] =', self.command, GetBindingKey(self.command))

    if self.pending then
      self.border:SetColorTexture(unpack(BORDER_PENDING))
    elseif self.isDynamic then
      self.border:SetColorTexture(unpack(BORDER_DYNAMIC))
    else
      self.border:SetColorTexture(unpack(BORDER_ASSIGNED))
    end

    if self.actionType == 'macro' then
      self.macro:Show()
    else
      self.macro:Hide()
      if self.actionType == 'spell' then
        local dummy = GetSpellInfo(self.actionName)
        if not dummy then
          self.icon:SetDesaturated(true)
        else
          self.icon:SetDesaturated(false)
        end

      end
    end

    if self.isDynamic then
      print('|cFFFFBB00UpdateSlot|r: ', self.isDynamic, self.isAvailable, self.actionID)
    end

    if self.isDynamic == 'profession'  then
      local profText = (self.spellNum == 1) and TRADE_SKILLS or (BUTTON_HEADERS[self.profIndex] or GetProfessionInfo(self.profIndex))
      if self.isAvailable then
        print(self.profIndex, 'spnum', type(self.spellNum), (self.spellNum == 1))

        self.statusText = '|cFFFFFF00'..profText..'|r'
        self.bindingText = BindingString(GetBindingKey(self.command))
      else
        self.statusText = '|cFFFF4400'..profText..'|r'
        self.actionName = '(need to train profession #'..self.profNum..')'
        self.bindingText ='?'
      end
    elseif self.isDynamic == 'talent' then

      self.statusText = '|cFF00FFFF'.. TALENT .. '|r'
      if self.isAvailable then
        self.bindingText = BindingString(GetBindingKey(self.command))
      else
        print(self.actionID, #kb.inactiveTalentBindings[self.actionID])
        self.bindingText= BindingString(unpack(kb.inactiveTalentBindings[self.actionID]))
      end
    else
      self.statusText = '|cFF00FF00'.. (BUTTON_HEADERS[self.actionType] and BUTTON_HEADERS[self.actionType] or self.actionType) .. '|r'
      self.bindingText = BindingString(GetBindingKey(self.command))
    end

    local locked, layer = IsCommandBound(self)
    if locked then
      self.icon:SetAlpha(0.5)
    else
      self.icon:SetAlpha(1)
    end

    if self.actionType == 'spell' then
      self.icon:SetTexture(GetSpellTexture(self.actionID))
    end
  end

  if not self.isAvailable then
    self.bind:SetTextColor(0.7,0.7,0.7,1)
  else
    self.bind:SetTextColor(1,1,1,1)
  end

  self.header:SetText(self.statusText)
  self.bind:SetText(self.bindingText)
  self.macro:SetText(self.macroName)
  self.details:SetText(self.actionName)
end


kb.ApplyTalentBinding = function(talentInfo, cache)
  for i = 5, #talentInfo do
    SetBinding(talentInfo[i], "CLICK KeyBinderMacro:".. talentInfo[1])
    tinsert(cache, talentInfo[i])
  end
end
kb.CacheTalentBinding = function(talentInfo, cache)
  local spellID = talentInfo[4]
  kb.inactiveTalentBindings[spellID] = kb.inactiveTalentBindings[spellID] or {}
  kb.inactiveTalentBindings[spellID] = {select(5,unpack(talentInfo)) }
  cprint(spellID, unpack(kb.inactiveTalentBindings[spellID]))
end

kb.ApplyBinding = function(command, name, icon, actionType, actionID, macroName, macroText )

  if actionType == 'macro' then
    KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID)
  else
    KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText)
  end
  bindings[actionType] = bindings[actionType] or {}
  bindings[actionType][actionID] = bindings[actionType][actionID] or {}
  bindings[command] = bindings[actionType][actionID]
  return bindings[actionType], actionID
end

kb.ApplyBindings = function (profile)
  cprint('binding profile', profile)
  for slot, data in pairs(profile.buttons) do
    kb.ApplyBinding(unpack(data))
  end

  for key, command in pairs(profile.bindings) do

    cprint('Bindings data registered', command, key)

    --_G.print('HotKey','loading', key, command)
    SetBinding(key, command)
    if bindings[command] and not tContains(bindings[command], key) then
      tinsert(bindings[command], key)
    end
  end

  for spellName, talentInfo in pairs(profile.talents) do
    local dummy = GetSpellInfo(spellName)
    local func = kb.CacheTalentBinding
    local dest = kb.inactiveTalentBindings
    if dummy then
      cprint('|cFFBBFF00Active:|r', dummy)
      local macroName, spellName, actionType, actionID = unpack(talentInfo)
      bindings[actionType] = bindings[actionType] or {}
      bindings[actionType][actionID] = {}
      func = kb.ApplyTalentBinding
      dest = bindings[actionType][actionID]
    else

      cprint('|cFFFF4400Inactive:|r', talentInfo[2])
    end
    func(talentInfo, dest)
  end

  SaveBindings(GetCurrentBindingSet())
end

kb.ApplyAllBindings =function ()
  table.wipe(kb.inactiveTalentBindings)

  for i, profile in ipairs(priority) do
    kb.ApplyBindings(profile)
  end
  -- do this after to ensure that profession binds are properly overridden
  kb.UpdateProfessionInfo()
end

kb.Command = function(args, editor)
  if args:match("import") then
    kb.ImportCommmit(args)
    return
  elseif args:match("scan") then
    kb.ImportScan(args)
    kb.ui()
    return
  elseif args:match("load") then
    kb:ApplyAllBindings()
    return
  end

  if db.showUI then
    db.showUI = false
    kb:print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
    kb:Hide()
  else
    db.showUI = true
    kb:print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
  end
  kb.ui(true)
end

kb.InitProfile = function(profile, prototype)
  if not profile then
    profile = {}
  end
  if prototype then
    print('appplying prototype', prototype)
    for k,v in pairs(prototype) do
      if not profile[k] then
        profile[k] = v
      end
    end
  end

  profile.bound = profile.bound or {}
  profile.buttons = profile.buttons or {}
  profile.commands = profile.commands or {}
  profile.bindings = profile.bindings or {}
  profile.macros = profile.macros or {}
  profile.talents = profile.talents or {}
  return profile
end

kb.ResetProfile = function(profile, prototype)
  if profile == configProfile then
    for i, button in pairs(buttons) do
      kb.ReleaseSlot(button)
    end
  end
  table.wipe(profile)
  kb.InitProfile(profile, prototype)
end



--- Handles constructing spec profiles as they are selected


kb.TalentCache = {}

kb.UpdateSpecInfo = function()
  specID = GetSpecialization()
  specGlobalID, specName, specDesc , specTexture = GetSpecializationInfo(specID)
  loadedProfiles[BINDING_TYPE_CHARACTER][specID] = kb.InitProfile(loadedProfiles[BINDING_TYPE_CHARACTER][specID], {
    specID = specID})

  configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName)
  loadedProfiles[BINDING_TYPE_SPECIALIZATION] = loadedProfiles[BINDING_TYPE_CHARACTER][specID]
  configProfile = loadedProfiles[bindMode]
  print('|cFF00FF00bindMode:|r', bindMode)

  priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]}

  print('|cFF00FF00current spec:|r', specID, 'of', GetNumSpecializations())
end

kb.UpdateTalentInfo = function()
  if kb.talentsPushed then
    return
  end


  table.wipe(kb.TalentCache)

  for row =1, MAX_TALENT_TIERS do
    for col = 1, NUM_TALENT_COLUMNS do
      local talentID, talentName, icon, selected, available, spellID = GetTalentInfo(row, col, 1)
      local talentInfo = kb.TalentCache[spellID] or {}
      talentInfo.row = 1
      talentInfo.col = col
      talentInfo.name = talentName
      talentInfo.talentID = talentID
      talentInfo.selected = selected
      talentInfo.available = available
      talentInfo.spellID = spellID
      kb.TalentCache[spellID] = talentInfo
      print('Talent ', row, col, spellID, talentName)
    end
  end
  kb.talentsPushed = true
end


kb.ProfessionCache = {}
kb.UpdateProfessionInfo = function()
  table.wipe(kb.ProfessionCache)
  local profs = {GetProfessions() }
  local primaryNum = 0
  for i, index in ipairs(profs) do
    local profName, texture, rank, maxRank, numSpells, spellOffset = GetProfessionInfo(index)
    cprint(i, index, profName, numSpells, spellOffset)
    if not professionMappings[index] then
      primaryNum = primaryNum + 1
    end
    local profNum = professionMappings[index] or primaryNum


      kb.ProfessionCache[profNum] = kb.ProfessionCache[i] or {}

      for j = 1, numSpells do
        local spellName, _, icon, _, _, _, spellID = GetSpellInfo(spellOffset+j, BOOKTYPE_PROFESSION)

        local profInfo = {
          spellName = spellName,
          spellID = spellID,
          icon = icon,
          profOffset = i,
          profIndex = index,
          spellOffset = (spellOffset+j),
          spellNum = j
        }
        KeyBinderMacro:SetAttribute("*macrotext-profession_"..i .. '_' ..j, "/cast ".. spellName)

        kb.ProfessionCache[i .. '_' .. j] = profInfo
        kb.ProfessionCache[spellName] = profInfo
        kb.ProfessionCache[spellID] = profInfo
        cprint('  |cFF0088FF['..i..']|r|cFFFF44BB['..spellOffset+i..']|r', spellName, "*macrotext-profession_"..i .. '_' ..j)
      end

  end

end

--- Obtains profile data or creates the necessary tables
kb.SelectProfileSet = function(name)

  --- General info
  classHeader, className, classID = UnitClass('player')
  print('|cFF00FF00profile:|r', name)
  print('|cFF00FF00class:|r', UnitClass('player'))

  --- Global
  bindMode = BINDING_TYPE_GLOBAL
  kb.InitProfile(db)
  loadedProfiles[BINDING_TYPE_GLOBAL] = db

  --- Character
  if name then
    db[name] = kb.InitProfile(db[name],
      {classHeader = classHeader, className = className, classID = classID})
    loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
    bindMode = BINDING_TYPE_CHARACTER
  end

  --- Mutable skills data
  kb.UpdateSpecInfo()
  kb.UpdateTalentInfo()

  priority = {loadedProfiles[BINDING_TYPE_GLOBAL], loadedProfiles[BINDING_TYPE_CHARACTER], loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
  if db.bindMode and loadedProfiles[db.bindMode] then
    bindMode = db.bindMode
  end

  db.bindMode = bindMode

  if not BINDING_MODE[bindMode] then
    bindMode = 3
    db.bindMode = 3
    print('overriding', bindMode)
  end

  print(BINDING_TYPE_GLOBAL)
  configHeaders[BINDING_TYPE_GLOBAL] = BINDING_MODE[BINDING_TYPE_GLOBAL]
  configHeaders[BINDING_TYPE_CHARACTER] = BINDING_MODE[BINDING_TYPE_CHARACTER]:format(UnitName('player', true))
  configHeaders[BINDING_TYPE_SPECIALIZATION] = BINDING_MODE[BINDING_TYPE_SPECIALIZATION]:format(specName)


  setmetatable(loadedProfiles[BINDING_TYPE_GLOBAL], {__tostring =function() return configHeaders[BINDING_TYPE_GLOBAL] end})
  setmetatable(loadedProfiles[BINDING_TYPE_CHARACTER], {__tostring =function() return configHeaders[BINDING_TYPE_CHARACTER] end})
  setmetatable(loadedProfiles[BINDING_TYPE_SPECIALIZATION], {__tostring =function() return configHeaders[BINDING_TYPE_SPECIALIZATION] end})

  print('|cFF00FF00bindMode:|r', bindMode)
  configProfile = loadedProfiles[bindMode]
end

local scrollCache = {}
kb.SelectTab = function(self)
  scrollCache[bindMode] = kb.scrollOffset
  bindMode = self:GetID()
  configProfile = loadedProfiles[self:GetID()]
  db.bindMode = self:GetID()
  kb.scrollOffset = scrollCache[bindMode] or 0
  kb.ui(true)
end

kb.RevertBindings = function()
  -- todo: reversion code
end

kb.ConfirmBindings = function()
  SaveBindings(GetCurrentBindingSet())
  bindsCommitted = true
  for i, button in ipairs(buttons) do
    button.pending = false
  end
  kb.ApplyAllBindings()

  kb.ui()
  kb:print('Keybinds saved.')
end






--- push current information into living UI
kb.ui = function(force)
  for i, module in ipairs(kb.modules) do
    if module.ui then
      module.ui(force)
    end
  end

  if not db.showUI then
    print('---end of refresh')
    return
  end
  if not kb.loaded then
    KeyBinder_Initialize()
    kb.loaded = true
  end
  for i = 1, numButtons do
    local button = kb.GetSlot(i)
    button:SetID(i+kb.scrollOffset)
    kb.UpdateSlot(button, force)
  end

  if bindsCommitted then
    KeyBinderSaveButton:Disable()
    --KeyBinderRestoreButton:Disable()
  else
    KeyBinderSaveButton:Enable()
    --KeyBinderRestoreButton:Enable()
  end

  --- Frame Sizing
  kb.profilebg:SetHeight(kb.tabSize[2] + BUTTON_PADDING * 2 + kb.profiletext:GetStringHeight())

  kb.bg:SetWidth((KEY_BUTTON_SIZE + BUTTON_HSPACING + BUTTON_SPACING) * BINDS_PER_ROW + BUTTON_PADDING*2 - BUTTON_SPACING)
  local numRows = numButtons/BINDS_PER_ROW

  kb.bg:SetHeight((KEY_BUTTON_SIZE + BUTTON_SPACING) * numRows + BUTTON_PADDING*2 - BUTTON_SPACING)

  kb:SetHeight(kb.headerbg:GetHeight() + kb.profilebg:GetHeight() + kb.bg:GetHeight() + kb.footer:GetHeight())
  kb:SetWidth((kb.sourcesbg:GetWidth() +(BINDS_PER_ROW * (KEY_BUTTON_SIZE + BUTTON_HSPACING) + (BINDS_PER_ROW - 1) * BUTTON_SPACING + BUTTON_PADDING * 2) ))

  kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode]))
  for i, tab in ipairs(kb.tabButtons) do
    local border = tab:GetNormalTexture()
    local tabTexture = "Interface\\Buttons\\UI-Quickslot2"
    local left, top, right, bottom = -12, 12, 13, -13
    if i == bindMode then
      tabTexture = "Interface\\Buttons\\CheckButtonGlow"
      left, top, right, bottom = -14, 14, 15, -15
      tab.icon:SetDesaturated(false)
      if tab.icon2 then tab.icon2:SetDesaturated(false) end
      border:SetDesaturated(true)
      border:SetVertexColor(1,1,1, 1)
    else
      tab.icon:SetDesaturated(true)
      if tab.icon2 then tab.icon2:SetDesaturated(true) end
      border:SetDesaturated(false)
      border:SetVertexColor(1,1,1)
    end
    border:SetTexture(tabTexture)
    border:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top)
    border:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom)
  end

  KeyBinderSpecTab.icon:SetTexture(specTexture)

  kb.profiletext:SetText(configHeaders[bindMode])
  print(bindMode, configHeaders[bindMode], kb:GetSize())
  print(kb:GetPoint(1))

  kb:Show()

  -- Reset this so talent cache can be rebuilt
  kb.talentsPushed = nil
end

--- post ADDON_LOADED
kb.variables = function()
  SkeletonKeyDB = SkeletonKeyDB or {spec = {}}
  kb.db = SkeletonKeyDB
  kb.playerName = UnitName('player')
  kb.playerRealm = SelectedRealmName()
  kb.profileName = kb.playerRealm .. '_' .. kb.playerName
  db = kb.db

  kb.SelectProfileSet(kb.profileName)
  if not configProfile.imported then
    kb.ImportScan()
  end
  kb.ApplyAllBindings()

  kb.ui(true)
end


kb.wrap = function(module)
  kb.modules = kb.modules or {}
  tinsert(kb.modules, module)
end

-- Volatiles Access
kb.BindingIsLocked = BindingIsLocked
kb.BindingString = BindingString
kb.GetBindings = function() return bindings end
kb.GetButtons = function() return buttons end
kb.GetCharacterProfile = function () return loadedProfiles[BINDING_TYPE_CHARACTER] end
kb.GetGlobalProfile = function () return loadedProfiles[BINDING_TYPE_GLOBAL] end
kb.GetLooseTalents = function() return talentBindings end
kb.GetProfileStack = function() return priority end
kb.GetReverts = function() return reverts end
kb.GetSpecProfile = function () return loadedProfiles[BINDING_TYPE_SPECIALIZATION] end

--- Add to blizzard interfaces
StaticPopupDialogs["SKELETONKEY_CONFIRM_ASSIGN_SLOT"] = {
  text = "Confirm moving an assigned command.",
  button1 = OKAY,
  button2 = CANCEL,
  timeout = 0,
  whileDead = 1,
  showAlert = 1,
  OnAccept = kb.AcceptAssignment,
  OnCancel = function() kb:SetScript('OnMouseWheel', KeyBinder_OnMouseWheel) end
}

SLASH_SKB1 = "/skb"
SLASH_SKB2 = "/skeletonkey"
SlashCmdList.SKB = kb.Command

-- This is needed to identify a spells that aren't reflected by GetCursorInfo()
hooksecurefunc("PickupSpellBookItem", function(slot, bookType)
  print('|cFFFF4400PickupSpellBookItem(..', tostring(slot),', '..tostring(bookType)..')')
  CURSOR_SPELLSLOT = slot
  CURSOR_BOOKTYPE = bookType
end)

-- Pet actions
local isPickup
hooksecurefunc("PickupPetAction", function(slot, ...)
  isPickup = GetCursorInfo()

  CURSOR_PETACTION = isPickup and slot
  print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION)
end)