diff 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 diff
--- a/SkeletonKey/KeyBinds.lua	Tue Jun 21 11:56:14 2016 -0400
+++ b/SkeletonKey/KeyBinds.lua	Tue Jul 26 19:29:44 2016 -0400
@@ -1,126 +1,201 @@
 --------------------------------------------
--- KrakTool
--- Nick
+-- SkeletonKey
+-- Krakyn-Mal'Ganis
 -- @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()
+--   .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 KT = LibKT.register(KeyBinder)
-local kb = KeyBinder
+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 BINDS_PER_ROW = 8
-local KEY_BUTTON_SIZE = 40
+local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
 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 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_SPECIALIZATION] = 'Specialization: %s',
+  [BINDING_TYPE_GLOBAL] = 'Global Binds',
   [BINDING_TYPE_CHARACTER] = 'Character: %s',
-  [BINDING_TYPE_GLOBAL] = 'Global Binds'
+  [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_SPECIALIZATION] = {0,0,0,0.5},
+  [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5},
   [BINDING_TYPE_CHARACTER] = {0,0.25,0,0.5},
-  [BINDING_TYPE_GLOBAL] = {0,.125,.5,.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},
-  [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_SPECIALIZATION] = {0, 1, 1},
   [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 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 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,
 }
+
+--- 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
 
---- 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
+--- 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]
-    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
+    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
-  return desaturated, layer
+
+  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
@@ -139,7 +214,7 @@
   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')
+    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
@@ -149,131 +224,387 @@
   end
 end
 
---- This keeps our KeyDown handler from getting stuck with game controls
-KeyButton.OnUpdate = function(self)
+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', kb.bind)
+      self:SetScript('OnKeyDown', KeyButton_OnKeyDown)
 
-      local bindText = self.command
-      if self.bind:GetText() then
-        bindText = bindText .. ': |cFF00FF00' .. self.bind:GetText()
-      end
+      kb.statustext:SetText(self.statusText .. ': '..self.actionName)
+      kb.bindingstext:SetText(self.bindingText)
+      kb.fadeStep = 0
+      kb.throttle = 0
+      kb:SetScript('OnUpdate', KeyBinder_OnUpdate)
 
-      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.
+local KeyBinder_OnMouseWheel = function(self, delta)
+  print(self, delta, self.scrollOffset, (self.scrollOffset <= 0))
 
-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)
+
+  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
 
-      pickup(value)
-      kb.release(self)
-      break
+      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
 
---- 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)
+kb.PickupSlot = 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
+  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
-
-    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
+  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.assign(self, command, name, icon)
-    kb.refresh(self)
-    ClearCursor()
+    kb.ReleaseSlot(self)
+    kb.UpdateSlot(self)
   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)
+
+--- 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
-    kb.bind(self)
+    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.bind = function(self, key)
+kb.StoreBinding = function(self, key)
 
-  print('|cFFFFFF00bind|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
   if not self.command then
     return
   end
@@ -281,509 +612,828 @@
   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 protected[GetBindingAction(key)] then
-    return
-    kb.bindlist:SetText(BINDING_FAILED_PROTECTED:format(key, GetBindingAction(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
 
-  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
+  kb.UpdateSlot(self, true)
+  KeyBinderSaveButton:Enable()
 
-  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()
+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.macro:SetText(nil)
+  self.pickupSlot = nil
+  self.pickupBook = nil
+  self.macroName = 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.SetSlot = function(self, command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook)
+  local slot = self:GetID()
+  local isDynamic, isAvailable
 
-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 ''))
+  print('|cFFFFFF00SetSlot|r:', self:GetID())
+  if command then
 
+    if actionType == 'spell' then
+      local professionNum, spellNum = command:match("profession_(%d)_(%d)")
 
-  if command then
-    if command:match(COMMAND_SPELL) then
-      name = command:match(COMMAND_SPELL)
+      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)
 
-    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])
+      -- 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
 
-    profile.commands[command] = index
+    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.profile = bindMode
+  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
-kb.keyslot = function(index)
+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('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)
+    button:SetScript('OnClick', KeyButton_OnClick)
+    button:SetScript('OnUpdate', KeyButton_OnUpdate)
+    button:SetScript('OnDragStart', KeyButton_OnDragStart)
+    button:SetScript('OnReceiveDrag', KeyButton_OnReceiveDrag)
+    button:RegisterForClicks('AnyUp')
 
-    if profile.buttons[index]  and  type(profile.buttons[index] ) == 'table' then
-      kb.assign(button, unpack(profile.buttons[index] ))
+
+    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
-      kb.release(button)
+      button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0)
     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
+    leftSlot = 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()]))
+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.release(self)
+      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
-    --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]))
+
+    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.border:SetColorTexture(unpack(BORDER_UNASSIGNED))
-    --self.macro:SetText(nil)
-    self.bind:SetText(nil)
+    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
-
-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
+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
 
---- Invokes the KeyBinder frame (from the /kb function or some other source)
-kb.ui = function()
-  if not db.showUI then
+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 not kb:IsVisible() then
-    kb:Show()
+  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
-    SetupUI()
+    KeyBinder_Initialize()
     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 = ''
+    local button = kb.GetSlot(i)
+    button:SetID(i+kb.scrollOffset)
+    kb.UpdateSlot(button, force)
   end
 
   if bindsCommitted then
     KeyBinderSaveButton:Disable()
-    KeyBinderRestoreButton:Disable()
+    --KeyBinderRestoreButton:Disable()
   else
     KeyBinderSaveButton:Enable()
-    KeyBinderRestoreButton:Enable()
+    --KeyBinderRestoreButton:Enable()
   end
 
-  --- panel attributes
+  --- 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: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: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 n = tab:GetNormalTexture()
+    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
-    n:SetTexture(tabTexture)
-    n:SetPoint('TOPLEFT', tab, 'TOPLEFT', left, top)
-    n:SetPoint('BOTTOMRIGHT', tab, 'BOTTOMRIGHT', right, bottom)
+    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
 
-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)
+--- 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
-  SaveBindings()
+  kb.ApplyAllBindings()
+
+  kb.ui(true)
 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)
-  if bind then
-    button.HotKey:SetText(BindingString(bind))
-    button.HotKey:Show()
-  end
+kb.wrap = function(module)
+  kb.modules = kb.modules or {}
+  tinsert(kb.modules, module)
 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()
+-- 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
 
-  for i, button in pairs(buttons) do
-    kb.release(button)
-  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
+}
 
-  profile.commands = {}
-  profile.bindings = {}
-  profile.macros = {}
-end
+SLASH_SKB1 = "/skb"
+SLASH_SKB2 = "/skeletonkey"
+SlashCmdList.SKB = kb.Command
 
---- 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 }
+-- 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()
 
-
-  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
-    kb.HotKeyText(i)
-  end
-  if db.showUI then
-    kb.ui()
-  end
-end
-
-kb.ACTIONBAR_SLOT_CHANGED = function(self, event, slot)
-  kb.HotKeyText(slot)
-  return true
-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
+  CURSOR_PETACTION = isPickup and slot
+  print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION)
+end)
\ No newline at end of file