view SkeletonKey/KeyBinds.lua @ 1:cd7d06bcd98d

KeyBinds: set hotkey text for blizzard action buttons UnitFrame: prototype templates for the majority of units
author Nenue
date Tue, 21 Jun 2016 04:47:52 -0400
parents 69e828f4238a
children 07293831dd7b
line wrap: on
line source
--------------------------------------------
-- KrakTool
-- Nick
-- @project-revision@ @project-hash@
-- @file-revision@ @file-hash@
-- Created: 6/16/2016 3:47 AM
--------------------------------------------
-- kb
--   .bind(button, key)                     bind current keystroke to command
--   .assign(button, command, name, icon)   set button command
--   .release(button)                       clear button command
--   .refresh(button)                       update button contents
--   .ui()                                  invoke interface
--   .profile(name)                         set profile character
--   .loadbinds(bindings)                   walk table with SetBinding()

local KT = LibKT.register(KeyBinder)
local kb = KeyBinder
local db

local MIN_BIND_SLOTS = 32
local BINDS_PER_ROW = 8
local KEY_BUTTON_SIZE = 40
local TAB_OFFSET = 12
local TAB_HEIGHT = 40
local TAB_SPACING = 2
local BUTTON_SPACING = 4
local BUTTON_PADDING = 12
local HEADER_OFFSET, FOOTER_OFFSET
local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544;
local BINDING_TYPE_SPECIALIZATION = 3
local BINDING_TYPE_CHARACTER = 2
local BINDING_TYPE_GLOBAL = 1
local BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
local BINDING_FAILED_PROTECTED = '|cFF00FF00%s|r used by |cFFFFFF00%s|r!'
local BINDING_MODE = {
  [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s',
  [BINDING_TYPE_CHARACTER] = 'Character: %s',
  [BINDING_TYPE_GLOBAL] = 'Global Binds'
}
local BINDING_SCHEME_COLOR = {
  [BINDING_TYPE_SPECIALIZATION] = {0,0,0,0.5},
  [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5},
  [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5}
}
local BINDING_SCHEME_VERTEX = {

  [BINDING_TYPE_SPECIALIZATION] = {1,1,1,1},
  [BINDING_TYPE_CHARACTER] = {0,1,0,1},
  [BINDING_TYPE_GLOBAL] = {0,.5,1,1}
}

local BINDING_SCHEME_TEXT = {
  [BINDING_TYPE_SPECIALIZATION] = {1, 1, 0},
  [BINDING_TYPE_CHARACTER] = {0, 1, 0},
  [BINDING_TYPE_GLOBAL] = {0, 1, 1}
}
local ACTION_SCRIPT = {
  ['mount'] = "/script C_MountJournal.SummonByID(%d)",
  ['equipset'] = "/script UseEquipmentSet(%d)",
}

local COMMAND_SPELL = "^SPELL (%S.+)"
local COMMAND_MACRO = "^MACRO (%S.+)"
local COMMAND_ITEM = "^ITEM (%S.+)"
local COMMAND_MOUNT = "^CLICK KeyBinderMacro:mount(%d+)"
local COMMAND_EQUIPSET = "^CLICK KeyBinderMacro:equipset(%d+)"

local PICKUP_TYPES = {
  [COMMAND_SPELL] = PickupSpell,
  [COMMAND_MACRO] = PickupMacro,
  [COMMAND_ITEM] = PickupItem,
  [COMMAND_MOUNT] = C_MountJournal.Pickup,
  [COMMAND_EQUIPSET] = PickupEquipmentSet
}
local PICKUP_VALUES = {
  [COMMAND_SPELL] = function(name) return select(7, GetSpellInfo(name)) end
}
local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
local BORDER_UNASSIGNED = {0.2,0.2,0.2,1 }
local BORDER_ASSIGNED = {0.5,0.5,0.5,1 }
local BORDER_PENDING = {1,0.5,0,1 }

local bindMode = 3
local bindHeader = ''
local specHeader, specTexture, characterHeader = 'SPEC_NAME', 'Interface\\ICONS\\INV_Misc_QuestionMark', 'PLAYER_NAME'
local numButtons = BINDS_PER_ROW * 4
local bindsCommitted = true

local profile, character, specialization, global
local priority = {}
local buttons = {}
local reverts = {}
local KeyButton = {}  -- collection of KeyButton template handlers
local Action = {}     -- collection of special action buttons for special binds
local protected = {
  ['OPENCHATSLASH'] = true,
  ['OPENCHAT'] = true,
}
local saveButton, restoreButton, clearButton

--- Returns a value for use with Texture:SetDesaturated()
local CommandIsLocked = function(self)
  print('command check: 1-'..(bindMode-1))
  local desaturated, layer = false, 3
  for i = 1, bindMode-1 do
    local tier = priority[i]
    local existing = tier.commands[self.command]
    print(' ', i, tier.commands[self.command])
    if existing then
      if self:GetID() ~= existing then
        -- sanitize bad data
        tier.commands[self.command] = nil
      else
        layer = i
        desaturated = true
        break
      end
    end
  end
  return desaturated, layer
end

--- 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')
  end

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

--- This keeps our KeyDown handler from getting stuck with game controls
KeyButton.OnUpdate = function(self)
  if not self.command then
    return
  end

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

      local bindText = self.command
      if self.bind:GetText() then
        bindText = bindText .. ': |cFF00FF00' .. self.bind:GetText()
      end

      kb.bindlist:SetText(bindText)
      GameTooltip:SetOwner(self)
      GameTooltip:SetAnchorType('ANCHOR_BOTTOMRIGHT')
      GameTooltip:SetText(self.actionName)
      GameTooltip:Show()
    end
  else
    if self.active then
      GameTooltip:Hide()
      self.active = nil
      self:SetScript('OnKeyDown', nil)
    end
  end
end

--- Cursor pickup handler
 -- Walks through PICKUP_TYPES and runs the function if match(command, key) turns up a result.
 -- Passes the result through PICKUP_VALUES[pattern]() if defined.

kb.pickup = function(self)
  for pattern, pickup in pairs(PICKUP_TYPES) do
    local value = self.command:match(pattern)
    if value then
      if PICKUP_VALUES[pattern] then
        value = PICKUP_VALUES[pattern](value)
      end

      pickup(value)
      kb.release(self)
      break
    end
  end
end

--- Setup an action button base on template info
kb.action = function(type, id)

  local macroName = type .. id
  macroName = macroName:gsub(' ', '')

  local attribute = '*macrotext-'..macroName
  local value = ACTION_SCRIPT[type]:format(id)
  local command = 'CLICK KeyBinderMacro:'.. macroName
  profile.macros[attribute] = {value, command}

  KeyBinderMacro:SetAttribute(attribute, value)
  return command
end

KeyButton.OnDragStart = function(self)
  if not self.command then
    return
  end
  kb.pickup(self)
end

KeyButton.OnReceiveDrag = function(self, ...)
  print(self:GetName(),'|cFF0088FFreceived|r', ...)
  local type, value, subType, subData = GetCursorInfo()
  print('GetCursorInfo', type, value, subType, subData)
  if type then
    if type == 'spell' then
      value = subData
    end

    local command, name, icon, _
    if type == 'spell' then
      name, _, icon = GetSpellInfo(value)
      command = 'SPELL ' .. name
      name = ''
    elseif type == 'macro' then
      name, icon = GetMacroInfo(value)
      command = 'MACRO ' .. name
    elseif type == 'mount' then
      if subType == 0 then
        name, _, icon = GetSpellInfo(SUMMON_RANDOM_FAVORITE_MOUNT_SPELL)
        value= 0
      else
        name, _, icon = C_MountJournal.GetMountInfoByID(value)
      end
      command = kb.action(type, value)
    elseif type == 'item' then
      name = GetItemInfo(value)
      icon = GetItemIcon(value)
      command = 'ITEM ' .. name
    end
    kb.assign(self, command, name, icon)
    kb.refresh(self)
    ClearCursor()
  end
end

KeyButton.OnMouseDown = function(self, click)
  print(self:GetName(), 'OnMouseDown', click)
  if click == 'LeftButton' then
    KeyButton.OnReceiveDrag(self)
  elseif click == 'RightButton' then
    kb.release(self)
  else
    kb.bind(self)
  end
end


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

  print('|cFFFFFF00bind|cFFFFFF00', self:GetID(), '|cFF00FFFF', 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

  if protected[GetBindingAction(key)] then
    return
    kb.bindlist:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(key)))
  end

  if key == 'ESCAPE' then
    local key1, key2 = GetBindingKey(self.command)
    if key1 then
      SetBinding(key1, nil)
      print('Unbound', key1)
    end
    if key2 then
      SetBinding(key2, nil)
      print('Unbound', key2)
    end
    self.active = false
    return
  end

  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
    self.pending = true
    self.border:SetColorTexture(1,.5,0, 1)

    local old = GetBindingAction(self.binding)
    local binding1, binding2, new1, new2
    if old and old ~= self.command then
      print('Discarding keybind for', old)
      local binding1, binding2 = GetBindingKey(old)
       -- need to preserve argument order
    end
    tinsert(reverts, {old, binding1, binding2, new1, new2})


    bindsCommitted = false
    SetBinding(self.binding, self.command)
    for level, profile in ipairs(priority) do
      profile.bindings[self.binding] = (level == bindMode) and self.command or nil
    end
    print(BINDING_ASSIGNED:format(self.binding, self.command, BINDING_MODE[bindMode]:format(bindHeader)))

    kb.refresh(self)
  end
end

--- Resets button command
kb.release = function(self)
  local index = self:GetID()
  self.command = nil
  self.actionName = nil
  self.macro:SetText(nil)
  self.profile = nil
  self.bind:SetText(nil)
  self.icon:SetTexture(nil)
  self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
  self:EnableKeyboard(false)
  self:SetScript('OnKeyDown', nil)


  if profile.buttons[index] then
    profile.buttons[index] = nil
  end
end

-- Sets button command

kb.assign = function(self, command, name, icon)
  local index = self:GetID()
  print('|cFF00FFFFassign|cFF0088FF', index, '|cFFFFFF00'.. (command or 'none'), '|cFF00FF00'.. (name or ''), '|cFF00FFFF' .. (icon or ''))


  if command then
    if command:match(COMMAND_SPELL) then
      name = command:match(COMMAND_SPELL)
    end


    self:EnableKeyboard(true)
    print('profile.buttons['..index..'] |cFF00FFFF=|r ', command, name, icon)
    profile.buttons[index] = {command, name, icon}

    --- Clean up any residual buttons
    local previous = profile.commands[command]
    if previous ~= index and buttons[previous] then
      kb.release(buttons[previous])
    end

    profile.commands[command] = index
  end

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

--- Retrieves button at index; creates said button and instates any stored parameters
kb.keyslot = function(index)
  if not buttons[index] then
    local button = CreateFrame('CheckButton', 'KeyBinderSlot'..index, kb, 'KeyButton')
    button:SetScript('OnMouseDown', KeyButton.OnMouseDown)
    button:SetScript('OnMouseUp', KeyButton.OnMouseUp)
    button:SetScript('OnUpdate', KeyButton.OnUpdate)
    button:SetScript('OnDragStart', KeyButton.OnDragStart)
    button:SetScript('OnReceiveDrag', KeyButton.OnReceiveDrag)
    button:SetID(index)

    if profile.buttons[index]  and  type(profile.buttons[index] ) == 'table' then
      kb.assign(button, unpack(profile.buttons[index] ))
    else
      kb.release(button)
    end

    local x, y = BUTTON_PADDING, - (BUTTON_PADDING + HEADER_OFFSET)
    if index ~= 1 then
      local col = mod(index, BINDS_PER_ROW)
      if col == 0 then
        col = BINDS_PER_ROW - 1
      else
        col = col - 1
      end
      x = col * (KEY_BUTTON_SIZE + BUTTON_SPACING) + BUTTON_PADDING
      y = (ceil(index/ BINDS_PER_ROW)-1) * - (KEY_BUTTON_SIZE + BUTTON_SPACING) - BUTTON_PADDING - HEADER_OFFSET
    end
    button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
    button:SetPoint('TOPLEFT', kb, 'TOPLEFT', x, y)
    button:Show()
    buttons[index] = button
  end
  return buttons[index]
end

--- Updates profile assignment and button contents
kb.refresh = function(self)
  if self.profile ~= bindMode then
    if profile.buttons[self:GetID()] then
      kb.assign(self, unpack(profile.buttons[self:GetID()]))
    else
      kb.release(self)
    end
  end

  if self.command then
    if self.pending then
      self.border:SetColorTexture(unpack(BORDER_PENDING))
    else
      self.border:SetColorTexture(unpack(BORDER_ASSIGNED))
    end
    --self.macro:SetText(self.actionName)
    self.bind:SetText(BindingString(GetBindingKey(self.command)))
    local locked, layer = CommandIsLocked(self)
    self.icon:SetDesaturated(locked)
    self.icon:SetVertexColor(unpack(BINDING_SCHEME_VERTEX[layer]))
  else
    self.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
    --self.macro:SetText(nil)
    self.bind:SetText(nil)
  end
end

local SetupUI = function()


  kb.tabAnchor = {'TOPLEFT', kb, 'TOPRIGHT', 2, -TAB_OFFSET}
  kb.tabGrowth = {'TOPLEFT', nil,'BOTTOMLEFT', 0, -TAB_SPACING}
  kb.tabSize = {TAB_HEIGHT, TAB_HEIGHT }
  kb.UIPanelAnchor = {'TOPLEFT', kb, 'TOPLEFT', BUTTON_PADDING + 12, -BUTTON_PADDING}
  kb.UIPanelGrowth = {'TOPLEFT', nil, 'TOPRIGHT', 14, 0 }
  kb.controlsAnchor = {'BOTTOMLEFT', kb, BUTTON_PADDING, BUTTON_PADDING }
  kb.controlsGrowth = {'BOTTOMLEFT', nil, 'BOTTOMRIGHT', BUTTON_SPACING, 0}

  --tab() frame, name, tooltip, texture, coords
  kb:tab('KeyBinderGlobalTab', BINDING_MODE[1], "Interface\\ICONS\\item_azereansphere", {0.15,.85,.15,.85})
  kb:tab('KeyBinderCharacterTab', characterHeader, nil)
  kb:tab('KeyBinderSpecTab', specHeader, specTexture)
  SetPortraitTexture(KeyBinderCharacterTab.icon, 'player')
  KeyBinderCharacterTab.icon:SetTexCoord(0.15,.85,.15,.85)

  saveButton = kb:button('KeyBinderSaveButton', 'Save', 'Commit all changes.', nil, kb.save)
  restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', nil, kb.restore)
  clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', nil, kb.ResetProfile)

  kb:uibutton(
    'KeyBinderSpellBookButton', 'SpellBook', nil,
    function() ToggleSpellBook(BOOKTYPE_SPELL) end,
    "Interface\\Spellbook\\Spellbook-Icon")
  kb:uibutton(
    'KeyBinderTalentFrameButton', 'Talents', nil,
    function() ToggleTalentFrame() end,
    "Interface\\TargetingFrame\\UI-Classes-Circles",
    CLASS_ICON_TCOORDS[strupper(select(2,UnitClass("player")))])

  kb:uibutton(
    'KeyBinderMacroFrameButton', 'Macros', nil,
    function() if MacroFrame then HideUIPanel(MacroFrame) else ShowMacroFrame() end end,
    "Interface\\MacroFrame\\MacroFrame-Icon")

  kb:uibutton(
    'KeyBinderInventoryButton', 'Bags', nil,
    function() OpenAllBags() end,
    "Interface\\BUTTONS\\Button-Backpack-Up")

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

--- Invokes the KeyBinder frame (from the /kb function or some other source)
kb.ui = function()
  if not db.showUI then
    return
  end

  if not kb:IsVisible() then
    kb:Show()
    db.showUI = true
  end

  if not kb.loaded then
    SetupUI()
    kb.loaded = true
  end

  for i = 1, numButtons do
    kb.refresh(kb.keyslot(i))
  end

  if bindMode == BINDING_TYPE_SPECIALIZATION then
    bindHeader = select(2,GetSpecializationInfo(GetSpecialization()))
  elseif bindMode == BINDING_TYPE_CHARACTER then
    bindHeader = UnitName('player')
  else
    bindHeader = ''
  end

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

  --- panel attributes
  local numRows = numButtons/BINDS_PER_ROW
  kb:SetHeight( numRows * (KEY_BUTTON_SIZE) + (numRows - 1) * BUTTON_SPACING + HEADER_OFFSET + FOOTER_OFFSET + BUTTON_PADDING * 2)
  kb:SetWidth((BINDS_PER_ROW - 1) * BUTTON_SPACING + BINDS_PER_ROW * KEY_BUTTON_SIZE + BUTTON_PADDING * 2)
  kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[bindMode]))


  for i, tab in ipairs(kb.tabButtons) do

    local n = 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
    end
    n:SetTexture(tabTexture)
    n:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top)
    n:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom)
  end
end

kb.loadbinds = function (bindings)
  for key, command in pairs(bindings) do
    -- store for reversion
    local oldAction = GetBindingAction(key)
    if oldAction ~= command then
      local bind1, bind2 = GetBindingKey(oldAction)
      if bind1 and not reverts[bind1] then
        reverts[bind1] = oldAction
      end
      if bind2 and not reverts[bind2] then
        reverts[bind2] = oldAction
      end
    end
    SetBindings(key, command)
  end
  SaveBindings()
end

local ACTION_BARS = {
  {'ActionButton', 0},
  {'MultiBarLeftButton', 24},
  {'MultiBarRightButton',  36},
  {'MultiBarBottomRighttButton', 48},
  {'MultiBarBottomLeftButton', 60},
}
kb.HotKeyText = function (slot)
  local i, offset = 0, 0
  local actionbar

  -- figure out which bar the slot belongs to
  for i, bar in ipairs(ACTION_BARS) do
    actionbar, offset = unpack(ACTION_BARS[i])
    if bar[2] > slot then
      break
    end
  end
  local button = _G[actionbar .. (slot - offset)]

  if not button then
    return
  end

  local type, id, subType, subID = GetActionInfo(slot)

  if not type then
    return
  end

  local bind, command
  if type == 'spell' then
    local name = GetSpellInfo(id)
    command = 'SPELL '..name
  elseif type == 'macro' then
    command = 'MACRO ' .. id
  else
      return
  end
  bind = GetBindingKey(command)
  print(slot, bind, '->', command)
  if bind then
    button.HotKey:SetText(BindingString(bind))
    button.HotKey:Show()
  end
end

kb.InitProfile = function(profile)
  profile.buttons = profile.buttons or {}
  profile.commands = profile.commands or {}
  profile.bindings = profile.bindings or {}
  profile.macros = profile.macros or {}
  return profile
end
kb.ResetProfile = function()

  for i, button in pairs(buttons) do
    kb.release(button)
  end

  profile.commands = {}
  profile.bindings = {}
  profile.macros = {}
end

--- Gives us the profile structure to work with while instating data
kb.profile = function(name)
  global = kb.InitProfile(db)
  profile = global
  local subtitle
  if name then
    db[name] = db[name] or {}
    db[name] = kb.InitProfile(db[name])
    character = db[name]
    local spec = GetSpecialization()
    if spec then
      db[name][spec] = db[name][spec] or {}
      profile = kb.InitProfile(db[name][spec])
      bindMode = BINDING_TYPE_SPECIALIZATION
      subtitle = select(2,GetSpecializationInfo(spec))
      specialization = db[name][spec]
    else
      profile = kb.InitProfile(db[name])
      bindMode = BINDING_TYPE_CHARACTER
      subtitle = name
      specialization = character
    end
  end
  priority = {global, character, specialization }



  if not db.bindsPage then
    db.bindsPage = bindMode
  end
  bindMode = db.bindsPage


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

  profile = priority[bindMode]


  local _
  _, specHeader, _, specTexture = GetSpecializationInfo(GetSpecialization())
  print(GetSpecializationInfo(GetSpecialization()))
  specHeader = BINDING_MODE[2]:format(specHeader)
  characterHeader = BINDING_MODE[2]:format(UnitName('player'))

  print('Using binding profile |cFF00FF88'..BINDING_MODE[bindMode]:format(subtitle)..'|r')
end

kb.SelectTab = function(self)
  bindMode = self:GetID()
  profile = priority[self:GetID()]
  db.bindsPage = self:GetID()
  kb.ui()
end
kb.save = function()
  SaveBindings(GetCurrentBindingSet())
  bindsCommitted = true
  for i, button in ipairs(buttons) do
    button.pending = false
  end

  kb.ui()
  print('Bindings saved.')
end
kb.restore = function()
  for i, button in pairs(buttons) do
    button.pending = false
  end
  bindsCommitted = true
  LoadBindings(GetCurrentBindingSet())
  print('All changes discarded.')
end

--- Tells all the hud buttons what to do
kb.init = function()
  KeyBinderMacro:SetAttribute('*type*', 'macro')
end

--- Get started
kb.variables = function()
  SkeletonKeyDB = SkeletonKeyDB or {}
  db = SkeletonKeyDB
  kb.profile(GetUnitName('player', true))
  for i = 1, 3 do
    for attribute, data in pairs(priority[i].macros) do
      KeyBinderMacro:SetAttribute(attribute, data[1])
    end
  end

  kb.UPDATE_BINDINGS()
  kb:RegisterEvent('UPDATE_BINDINGS')
  kb:RegisterEvent('UPDATE_MACROS')
  kb:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED')
  kb:RegisterEvent('PLAYER_EQUIPMENT_CHANGED')
  kb:RegisterEvent('PLAYER_REGEN_DISABLED')
  kb:RegisterEvent('PLAYER_REGEN_ENABLED')
  kb:RegisterEvent('ACTIONBAR_SLOT_CHANGED')
end

kb.close = function()
  db.showUI = false
  kb:Hide()
end

kb.PLAYER_REGEN_DISABLED = function()
  if db.showUI then
    kb:Hide()
  end
end

kb.PLAYER_REGEN_ENABLED = function()
  if db.showUI then
    kb.ui()
  end
end
--- Refresh buttons if macros are updated
kb.UPDATE_BINDINGS = function()
  for i = 1, 120 do
    print(i)
    kb.HotKeyText(i)
  end
  if db.showUI then
    kb.ui()
  end
end

kb.ACTIONBAR_SLOT_CHANGED = function(self, event, slot)
  kb.HotKeyText(slot)
end

kb.UPDATE_MACROS = kb.UPDATE_BINDINGS
SLASH_KB1 = "/kb"
SlashCmdList.KB = function(self, input)
  if db.showUI then
    db.showUI = false
    print('|cFFFFFF00KeyBinds|r trace, |cFFFF0000OFF|r.')
    kb:Hide()
  else
    db.showUI = true
    print('|cFFFFFF00KeyBinds|r trace, |cFF00FF00ON|r.')
    kb.ui()
  end
end