changeset 17:500f9b2bd9ac

- for RegisterAction, use a function table instead of that if/then rats nest - consequently pet bar actions work now - unlocalize bindings data - activate keyslot input manually instead of on mouse over - activate checkbox to keep input mode active - dynamic buttons update in real time for petaction/talent/profession spells
author Nenue
date Sat, 30 Jul 2016 00:08:00 -0400
parents cdd387d39137
children 91398d284a99
files SkeletonKey/ActionTypes.lua SkeletonKey/BindingsUI.lua SkeletonKey/DynamicTypes.lua SkeletonKey/Events.lua SkeletonKey/KeySlot.lua SkeletonKey/SkeletonKey.lua SkeletonKey/SkeletonKey.toc SkeletonKey/SkeletonKey.xml
diffstat 8 files changed, 507 insertions(+), 437 deletions(-) [+]
line wrap: on
line diff
--- a/SkeletonKey/ActionTypes.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/ActionTypes.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -4,8 +4,11 @@
 -- %file-revision%
 --
 local kb, print, wrap = LibStub('LibKraken').register(KeyBinder)
+local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end
 
 local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
+local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:"
+local CLICK_KEYBINDER_KEY = "CLICK KeyBinderKey:"
 
 local PETACTION_SCRIPT = {
   [PET_ACTION_MOVE_TO] = {SLASH_PET_MOVE_TO1, 'pet_move_to'},
@@ -18,62 +21,172 @@
   [PET_MODE_ASSIST] = {SLASH_PET_ASSIST1, 'pet_assist'},
 }
 
---- Caps Lock derivatives
+--- Caps Lock
 local ACTION_HANDLERS = {}
 ACTION_HANDLERS['mount'] = function(id, name)
-  return "/script C_MountJournal.SummonByID("..id..")"
+  if id == SUMMON_RANDOM_FAVORITE_MOUNT_SPELL then
+    return 'mount_random', "/script C_MountJournal.SummonByID(0)", CLICK_KEYBINDER_MACRO
+  else
+    return 'mount_'..id, "/script C_MountJournal.SummonByID("..id..")", CLICK_KEYBINDER_MACRO
+  end
 end
-ACTION_HANDLERS['macro'] = "%s"
-ACTION_HANDLERS['equipset'] = "/script UseEquipmentSet(%d)"
-ACTION_HANDLERS['spell'] = "/cast %s"
-ACTION_HANDLERS['petaction'] = function(id, name)
-  return PETACTION_SCRIPT[name] or "/cast ".. name
+ACTION_HANDLERS['macro'] = function(id, name)
+  return CLICK_KEYBINDER_MACRO, 'macro_' .. tostring(name), id
+end
+ACTION_HANDLERS['equipset'] = function(id, name)
+    return CLICK_KEYBINDER_MACRO, 'equipset_'..tostring(name), "/script UseEquipmentSet("..tostring(id)..")"
+end
+ACTION_HANDLERS['spell'] = function(id, name)
+  local attributeName = name
+  if kb.ProfessionCache[id] then
+    attributeName = "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum
+  end
+  return CLICK_KEYBINDER_KEY, attributeName, name
+end
+ACTION_HANDLERS['petaction'] = function(_, name)
+  -- ID doesn't exist for basic commands, even though they can be picked up
+  local attributeName, attributeValue = "petaction_" .. tostring(name), "/cast "..tostring(name)
+  if PETACTION_SCRIPT[name] then
+    attributeValue, attributeName = unpack(PETACTION_SCRIPT[name])
+  end
+
+  return CLICK_KEYBINDER_MACRO, attributeName, attributeValue
 end
 
-ACTION_HANDLERS['battlepet'] = SLASH_SUMMON_BATTLE_PET1 .. " %s"
-ACTION_HANDLERS['item'] = "/use %s"
-local professionMappings = {
-  [5] = 3,
-  [7] = 4,
-  [9] = 5,
-  [10] = 6
-}
+ACTION_HANDLERS['battlepet'] = function(id, name)
+  return CLICK_KEYBINDER_MACRO, 'battlepet_' .. tostring(name), SLASH_SUMMON_BATTLE_PET1 .. " " .. tostring(name)
+end
+ACTION_HANDLERS['item'] = function(id, name)
+  return CLICK_KEYBINDER_KEY, 'item_' .. tostring(name), id
+end
 
 
---- Generates the command strings needed to assign different abilities
+--- Resolves the SecureActionButton attribute names used for the given action
 kb.RegisterAction = function(actionType, id, name)
-  local macroText, macroName, command = '', '', ''
 
-  if actionType == 'spell' then
-    if kb.ProfessionCache[id] then
-      command = CLICK_KEYBINDER_KEY .. "profession_".. kb.ProfessionCache[id].profOffset .. '_' .. kb.ProfessionCache[id].spellNum
+  assert(ACTION_HANDLERS[actionType], 'Missing actionType handler for `'..tostring(actionType)..'`')
+  local target, attributeName, attributeValue  = ACTION_HANDLERS[actionType](id, name)
+
+  local command = target .. attributeName
+  local baseName, iterative = attributeName, 1
+  while (kb.macros[attributeName] and kb.macros[attributeName][1] ~= attributeValue) do
+    print(' * cannot use|cFF00FF00', attributeName, '|r"'.. tostring(kb.macros[attributeName][1]) .. '"')
+    attributeName = baseName .. '_' .. iterative
+    iterative = iterative + 1
+  end
+  if macroName ~= baseName then
+    print(' * Creating|cFF00FF00', macroName)
+  else
+    print(' * Re-using|cFF00FF00', macroName)
+  end
+  kb.macros[attributeName] = {attributeValue, command}
+
+
+  print('RegisterAction', actionType, id, '->', attributeName, attributeValue, target .. attributeName)
+  return attributeName, attributeValue, command
+end
+
+
+
+
+kb.inactiveTalentBindings = {}
+kb.ApplyTalentBinding = function(talentInfo, cache)
+  for i = 5, #talentInfo do
+    local command = CLICK_KEYBINDER_KEY.. talentInfo[2]
+    SetBinding(talentInfo[i], command)
+    cprint(' **', talentInfo[i], '->', command)
+    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
+
+do
+  local bindings = kb.bindings
+  local key, macro = KeyBinderKey, KeyBinderMacro
+  kb.LoadBinding = function(command, name, icon, actionType, actionID, macroName, macroText )
+
+    if actionType == 'spell' then
+      key:SetAttribute("*type-"..name, actionType)
+      key:SetAttribute("*"..actionType.."-"..name, name)
+    elseif actionType == 'item' then
+      key:SetAttribute("*type-"..name, actionType)
+      key:SetAttribute("*"..actionType.."-"..name, name)
+    elseif actionType == 'macro' then
+      macro:SetAttribute("*macro-"..macroName, actionID)
     else
-      command = CLICK_KEYBINDER_KEY ..name
-    end
-  else
-
-    if type(ACTION_SCRIPT[actionType]) == 'function' then
-      macroName, macroText = ACTION_SCRIPT[actionType](id, name)
-    else
-      macroName = actionType .. ' ' .. name
-      macroText = ACTION_SCRIPT[actionType]:format(name)
+      macro:SetAttribute("*macrotext-"..macroName, macroText)
     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
-    command = 'CLICK KeyBinderMacro:'.. macroName
-    macros[macroName] = {macroText, command }
+    cprint('Loading binding', actionType, actionID)
+    bindings[actionType] = bindings[actionType] or {}
+    bindings[actionType][actionID] = bindings[actionType][actionID] or {}
+    bindings[command] = bindings[actionType][actionID]
+    return bindings[actionType], actionID
   end
 
-  print('RegisterAction', actionType, id, '->', command , macroText)
-  return macroName, macroText, command
+  kb.ApplyBindings = function (profile)
+    cprint('binding profile', profile)
+    for slot, data in pairs(profile.buttons) do
+      kb.LoadBinding(unpack(data))
+    end
+
+    for key, command in pairs(profile.bindings) do
+
+      cprint(' *', key, '->', command)
+
+      --_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 = kb.bindings[actionType][actionID]
+      else
+
+        cprint('|cFFFF4400Inactive:|r', talentInfo[2])
+      end
+      func(talentInfo, dest)
+    end
+
+  end
+
+  kb.ApplyAllBindings =function ()
+    table.wipe(kb.inactiveTalentBindings)
+
+
+    -- reflect action key settings
+    if GetCVarBool("ActionButtonUseKeyDown") then
+      KeyBinderMacro:RegisterForClicks("AnyDown")
+      KeyBinderKey:RegisterForClicks("AnyDown")
+    else
+      KeyBinderMacro:RegisterForClicks("AnyUp")
+      KeyBinderKey:RegisterForClicks("AnyUp")
+    end
+
+    for i, profile in ipairs(kb.orderedProfiles) do
+      kb.ApplyBindings(profile)
+    end
+    -- do this after to ensure that profession binds are properly overridden
+    kb.UpdateProfessionInfo()
+
+
+    SaveBindings(GetCurrentBindingSet())
+  end
 end
\ No newline at end of file
--- a/SkeletonKey/BindingsUI.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/BindingsUI.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -6,17 +6,15 @@
 
 local kb, print = LibStub("LibKraken").register(KeyBinder, 'KeySlot')
 local BINDS_PER_ROW = 2
+local BINDING_TYPE_SPECIALIZATION = 3
+local BINDING_TYPE_CHARACTER = 2
+local BINDING_TYPE_GLOBAL = 1
 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 TAB_OFFSET = 12
+local NUM_KEY_SLOTS = BINDS_PER_ROW * 8
 local TAB_HEIGHT = 40
-local TAB_SPACING = 2
 
 local BINDING_SCHEME_COLOR = {
   [BINDING_TYPE_GLOBAL] = {0,.125,.5,.5},
@@ -36,12 +34,27 @@
 
 local restingAlpha = 0.7
 local fadeTime, fadeDelay = .30, 0.15
-local numButtons = BINDS_PER_ROW * 8
 local saveButton
-local KeyButton_OnKey = function(self, key)
-  kb.StoreBinding(self.saveTarget, key)
-  kb:SetScript('OnKeyUp', nil)
-  kb:SetScript('OnKeyDown', nil)
+
+local KeyButton_OnKeyDown = function(self, key)
+end
+local KeyButton_OnKeyUp = function(self, key)
+  if key == 'ESCAPE' then
+    kb.DeactivateSlot(kb.saveTarget)
+  else
+
+    if key:match('[RL]SHIFT') or key:match('[RL]ALT') or key:match('[RL]CTRL') then
+      return
+    end
+    kb.SaveSlot(kb.saveTarget, key)
+
+    if not kb.stickyMode then
+      kb:SetScript('OnKeyUp', nil)
+      kb:SetScript('OnKeyDown', nil)
+      kb.saveTarget = nil
+    end
+  end
+  kb.ui()
 end
 
 local KeyButton_OnClick = function(self, click)
@@ -51,7 +64,17 @@
     if cursorType then
       kb.DropToSlot(self)
     else
+      if IsShiftKeyDown() then
+        kb.stickyMode = true
+        KeyBinderStickyMode:SetChecked(true)
+
+        kb.saveTarget = button
+        kb:SetScript('OnKeyUp', KeyButton_OnKeyUp)
+        kb:SetScript('OnKeyDown', KeyButton_OnKeyDown)
+      end
+
       kb.ActivateSlot(self)
+      kb.ui()
     end
   elseif click == 'RightButton' then
     kb.ReleaseSlot(self)
@@ -119,7 +142,6 @@
   else
     if self.active then
       self.active = nil
-      self:SetScript('OnKeyDown', nil)
     end
   end
 end
@@ -141,6 +163,10 @@
     kb.scrollOffset = ceil(kb.scrollOffset - (delta * BINDS_PER_ROW))
   end
 
+  for i = 1, #kb.buttons do
+      kb.buttons[i]:SetSize(KEY_BUTTON_SIZE,KEY_BUTTON_SIZE)
+  end
+
   kb.ui(true)
 end
 
@@ -159,7 +185,38 @@
   kb.ConfirmBindings()
 end
 
+
 local KeyBinder_Initialize = function()
+  do
+    local leftSlot, upSlot
+    for index = 1, NUM_KEY_SLOTS do
+
+      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
+      elseif newRow then
+        button:SetPoint('TOPLEFT', upSlot, 'BOTTOMLEFT', 0, -BUTTON_SPACING)
+        upSlot = button
+      else
+        button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0)
+      end
+
+      button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
+      button:Show()
+      kb.buttons[index] = button
+      leftSlot = button
+    end
+  end
 
 
   kb.scrollOffset = 0
@@ -170,8 +227,8 @@
   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}
+  kb.controlsAnchor = {'BOTTOMRIGHT', kb.footer, -BUTTON_PADDING, BUTTON_PADDING }
+  kb.controlsGrowth = {'BOTTOMRIGHT', nil, 'BOTTOMLEFT', -BUTTON_SPACING, 0}
 
   -- order of these is important
   kb:tab('KeyBinderGlobalTab',
@@ -185,8 +242,9 @@
 
 
   --portraitLayers[1] = KeyBinderCharacterTab.icon
+  -- todo: find some generic icons for refresh/key input,etc
 
-  saveButton = kb:button('KeyBinderSaveButton', 'Refresh', 'Reload current bindings and refresh panel.', SaveButton_OnClick)
+  saveButton = kb:button('KeyBinderSaveButton', 'Update', 'Reload current bindings and refresh panel.', SaveButton_OnClick)
   --restoreButton = kb:button('KeyBinderRestoreButton', 'Discard', 'Revert all changes.', CancelButton_OnClick)
   --clearButton = kb:button('KeyBinderClearButton', 'Clear Page', 'Release all buttons.', ResetButton_OnClick)
 
@@ -213,7 +271,17 @@
     function() OpenAllBags() end,
     "Interface\\BUTTONS\\UI-MicroButtonCharacter-Up", {0, 1, .4, 1})
 
+  KeyBinderStickyMode:SetScript('OnClick', function(self)
 
+    kb.stickyMode = self:GetChecked()
+    if not kb.stickyMode then
+      if kb.saveTarget then
+        kb.DeactivateSlot(kb.saveTarget)
+      end
+    end
+
+    kb.ui()
+  end)
 
   kb.info:SetPoint('TOPLEFT', kb.UIPanels[1], 'BOTTOMLEFT', 0, -BUTTON_SPACING)
   HEADER_OFFSET = kb.UIPanels[1]:GetHeight() + BUTTON_PADDING
@@ -228,48 +296,23 @@
 
 
 --- Retrieves button at index; creates said button and instates any stored parameters
-do
-  local leftSlot, upSlot
-  kb.GetSlot = function(index)
 
-    local slot  = index + kb.scrollOffset
 
-    if not kb.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
-      elseif newRow then
-        button:SetPoint('TOPLEFT', upSlot, 'BOTTOMLEFT', 0, -BUTTON_SPACING)
-        upSlot = button
-      else
-        button:SetPoint('TOPLEFT', leftSlot, 'TOPRIGHT', BUTTON_HSPACING, 0)
-      end
-
-      button:SetSize(KEY_BUTTON_SIZE, KEY_BUTTON_SIZE)
-      button:Show()
-      kb.buttons[index] = button
-      leftSlot = button
-    end
-    return kb.buttons[index]
-  end
+kb.ActivateSlot = function(button)
+  kb.saveTarget = button
+  kb:SetScript('OnKeyUp', KeyButton_OnKeyUp)
+  kb:SetScript('OnKeyDown', KeyButton_OnKeyDown)
+  kb.savingText:ClearAllPoints()
+  kb.savingText:SetParent(button)
+  kb.savingText:SetPoint('BOTTOMLEFT', button, 'TOPLEFT', 0, 0)
 end
 
+kb.DeactivateSlot = function(button)
+  kb.saveTarget = nil
+  kb:SetScript('OnKeyUp', nil)
+  kb:SetScript('OnKeyDown', nil)
+end
 
-kb.ActivateSlot = function(self)
-  kb.saveTarget = self
-  kb:SetScript('OnKeyUp', KeyButton_OnKey)
-  kb.bg:SetColorTexture(0,.5,0,1)
-end
 
 --- push current information into living UI
 kb.ui = function(force)
@@ -287,8 +330,7 @@
     KeyBinder_Initialize()
     kb.loaded = true
   end
-  for i = 1, numButtons do
-    local button = kb.GetSlot(i)
+  for i, button in ipairs(kb.buttons) do
     button:SetID(i+kb.scrollOffset)
     kb.UpdateSlot(button, force)
   end
@@ -298,14 +340,22 @@
   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
+  local numRows = NUM_KEY_SLOTS/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[kb.db.bindMode]))
+  if kb.saveTarget then
+    kb.bg:SetColorTexture(.2,.5, .2, .5)
+    kb.savingText:Show()
+
+  else
+    kb.bg:SetColorTexture(unpack(BINDING_SCHEME_COLOR[kb.db.bindMode]))
+    kb.savingText:Hide()
+  end
+
   for i, tab in ipairs(kb.tabButtons) do
     local border = tab:GetNormalTexture()
     local tabTexture = "Interface\\Buttons\\UI-Quickslot2"
@@ -337,6 +387,15 @@
 
   kb:Show()
 
+  if kb.saveTarget then
+    KeyBinderUnbindButton:SetParent(kb.saveTarget)
+    KeyBinderUnbindButton:SetPoint('TOPRIGHT', kb.saveTarget, 'TOPLEFT', -2,0)
+    KeyBinderUnbindButton:Show()
+  else
+    KeyBinderUnbindButton:Hide()
+  end
+
+
   -- Reset this so talent cache can be rebuilt
   kb.talentsPushed = nil
 end
--- a/SkeletonKey/DynamicTypes.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/DynamicTypes.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -6,10 +6,7 @@
 local kb, print = LibStub('LibKraken').register(KeyBinder, 'PlayerInfo')
 
 local PET_SPECIAL_SUBTEXT = 'Special Ability'
-local BINDING_TYPE_SPECIALIZATION = 3
-local BINDING_TYPE_CHARACTER = 2
-local BINDING_TYPE_GLOBAL = 1
-local professionMappings = {
+local SECONDARY_PROFESSIONS = {
   [5] = 3,
   [7] = 4,
   [9] = 5,
@@ -23,16 +20,6 @@
 kb.UpdateSpecInfo = function()
   kb.specInfo.id = GetSpecialization()
   kb.specInfo.globalID, kb.specInfo.name, kb.specInfo.desc, kb.specInfo.texture = GetSpecializationInfo(kb.specInfo.id)
-  kb.loadedProfiles[BINDING_TYPE_CHARACTER][kb.specInfo.id] = kb.InitProfile(kb.loadedProfiles[BINDING_TYPE_CHARACTER][kb.specInfo.id], {
-    specID = kb.specInfo.id})
-
-  kb.configHeaders[BINDING_TYPE_SPECIALIZATION] = kb.configTitle[BINDING_TYPE_SPECIALIZATION]:format(kb.specInfo.name)
-  kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = kb.loadedProfiles[BINDING_TYPE_CHARACTER][kb.specInfo.id]
-  kb.currentProfile = kb.loadedProfiles[kb.db.bindMode]
-  print('|cFF00FF00bindMode:|r', kb.db.bindMode)
-
-  kb.profileOrder = {kb.loadedProfiles[BINDING_TYPE_GLOBAL], kb.loadedProfiles[BINDING_TYPE_CHARACTER], kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
-
   print('|cFF00FF00current spec:|r', kb.specInfo.id, 'of', GetNumSpecializations())
 end
 
@@ -40,10 +27,7 @@
   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)
@@ -61,6 +45,8 @@
     end
   end
   kb.talentsPushed = true
+
+  kb.UpdateDynamicButtons('talent')
 end
 
 kb.UpdateProfessionInfo = function()
@@ -70,10 +56,10 @@
   for i, index in ipairs(profs) do
     local profName, texture, rank, maxRank, numSpells, spellOffset = GetProfessionInfo(index)
     print(i, index, profName, numSpells, spellOffset)
-    if not professionMappings[index] then
+    if not SECONDARY_PROFESSIONS[index] then
       primaryNum = primaryNum + 1
     end
-    local profNum = professionMappings[index] or primaryNum
+    local profNum = SECONDARY_PROFESSIONS[index] or primaryNum
 
 
     kb.ProfessionCache[profNum] = kb.ProfessionCache[i] or {}
@@ -101,6 +87,7 @@
 
   end
 
+  kb.UpdateDynamicButtons('profession')
 end
 
 
@@ -166,7 +153,7 @@
     table.wipe(kb.PetCache.action)
   end
 
-  kb.UpdateCacheButtons(kb.petFrames)
+  kb.UpdateDynamicButtons('petaction')
 
 end
 
@@ -186,30 +173,10 @@
 
 end
 
-do
-  local garbage = {}
-  kb.UpdateCacheButtons = function(pending)
-    for i, button in ipairs(pending) do
-      if button.isDynamic then
-        print('flushing button', button:GetID())
-          kb.UpdateSlot(button, true)
-      end
+kb.UpdateDynamicButtons = function(dynamicType)
+  for i, button in ipairs(kb.buttons) do
+    if button.isDynamic == dynamicType then
+      kb.UpdateSlot(button, true)
     end
   end
-end
-
-
-kb.RemoveCacheButton = function(pending, button)
-  local found
-  for index, frame in ipairs(pending) do
-    if button == frame then
-      found = index
-      break
-    end
-  end
-  if found then
-    print('|cFFFF4400pruning', button:GetName(), 'from update queue')
-    tremove(pending, found)
-  end
-
 end
\ No newline at end of file
--- a/SkeletonKey/Events.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/Events.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -41,11 +41,13 @@
 kb.PLAYER_SPECIALIZATION_CHANGED =  function()
   kb.UpdateSpecInfo()
   kb.UpdateTalentInfo()
+  kb.SelectProfileSet(kb.profileName)
   kb.ApplyAllBindings()
   kb.ui(true)
 end
 kb.PLAYER_TALENT_UPDATE = function()
   kb.UpdateTalentInfo()
+  kb.SelectProfileSet(kb.profileName)
   kb.ApplyAllBindings()
   kb.ui()
 end
--- a/SkeletonKey/KeySlot.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/KeySlot.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -5,7 +5,8 @@
 -- Code dealing with the slot button innards; they are invoked by frame script and should only chain to Set/Release
 
 local kb, print = LibStub('LibKraken').register(KeyBinder, 'Slot')
-local CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_PETACTION
+local L = kb.L
+local CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_PETACTION, CURSOR_TEXTURE
 local SUMMON_RANDOM_FAVORITE_MOUNT_SPELL = 150544
 local BORDER_UNASSIGNED = {0.2,0.2,0.2,1}
 local BORDER_ASSIGNED = {1,1,1,1}
@@ -28,42 +29,44 @@
 
 -- This is needed to identify a spells that aren't reflected by GetCursorInfo()
 hooksecurefunc("PickupSpellBookItem", function(slot, bookType)
-  print('|cFFFF4400PickupSpellBookItem(..', tostring(slot),', '..tostring(bookType)..')')
+  print('|cFFFF4400PickupSpellBookItem('.. tostring(slot).. ', '..tostring(bookType)..')')
   CURSOR_SPELLSLOT = slot
   CURSOR_BOOKTYPE = bookType
+  CURSOR_TEXTURE = GetSpellBookItemTexture(slot, bookType)
+  print('current cursor info', CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_TEXTURE)
 end)
 
-do
 -- Pet actions
-  local isPickup
-  hooksecurefunc("PickupPetAction", function(slot, ...)
-    isPickup = GetCursorInfo()
-    print(slot, ...)
+hooksecurefunc("PickupPetAction", function(slot, ...)
+  local isPickup = GetCursorInfo()
+  print(slot, ...)
+  if kb.PetCache.action[slot] then
+    if isPickup then
+      local key, _, texture = unpack(kb.PetCache.action[slot])
+      local spellName = _G[key] or key
+      if spellName and kb.PetCache.spellslot[spellName] then
+        CURSOR_SPELLSLOT = kb.PetCache.spellslot[spellName][1]
+        CURSOR_BOOKTYPE = BOOKTYPE_PET
+        CURSOR_TEXTURE = _G[texture] or texture
+      end
 
+    else
+      CURSOR_SPELLSLOT = nil
+      CURSOR_BOOKTYPE = nil
+      CURSOR_TEXTURE = nil
+    end
+  print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION)
+  end
 
-    if kb.PetCache.action[slot] then
-      if isPickup then
-        local key = kb.PetCache.action[slot][1]
-        local spellName = _G[key] or key
-        if spellName and kb.PetCache.spellslot[spellName] then
-          print('picked up', spellName, kb.PetCache.spellslot[spellName][1])
-          CURSOR_SPELLSLOT = kb.PetCache.spellslot[spellName][1]
-          CURSOR_BOOKTYPE = BOOKTYPE_PET
-        end
+  local name, subtext, texture, isToken = GetPetActionInfo(slot)
+  if name then
+      kb.PetCache.action[slot] = {name, subtext, texture, isToken}
+  end
 
-      else
-        print('Dropped pet action =', GetPetActionInfo(slot))
-      end
-    print('|cFFFF4400PickupPetAction|r', isPickup, CURSOR_PETACTION)
-    end
 
-    local name, subtext, texture, isToken = GetPetActionInfo(slot)
-    if name then
-        kb.PetCache.action[slot] = {name, subtext, texture, isToken}
-    end
+  print('current cursor info', CURSOR_SPELLSLOT, CURSOR_BOOKTYPE, CURSOR_TEXTURE)
 
-  end)
-end
+end)
 
 
 kb.DropToSlot = function(self)
@@ -94,7 +97,9 @@
         local spellType, spellID = GetSpellBookItemInfo(CURSOR_SPELLSLOT, CURSOR_BOOKTYPE)
         local spellName, spellText = GetSpellBookItemName(CURSOR_SPELLSLOT, CURSOR_BOOKTYPE)
         if spellType == 'PETACTION' then
+          name = spellName
           actionID = spellText
+          icon = CURSOR_TEXTURE
         else
           name, _, icon = GetSpellInfo(spellID)
           actionID = spellID
@@ -183,6 +188,117 @@
   end
 end
 
+kb.UnbindSlot = function(self)
+
+  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 kb.currentProfile.bindings[key] then
+      kb:print(L('BINDING_REMOVED', self.actionName, kb.currentHeader))
+      kb.currentProfile.bindings[key] = nil
+    end
+    if kb.currentProfile.talents[self.actionName] then
+      kb.currentProfile.talents[self.actionName] = nil
+    end
+    kb.bindings[self.actionType][self.actionID] = nil
+  end
+  if kb.currentProfile.bound[self.command] then
+    kb.currentProfile.bound[self.command] = nil
+    --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[db.bindMode]))
+  end
+
+  self.active = false
+  kb.UpdateSlot(self, true)
+end
+
+--- Updates the current KeyBinding for the button's command
+kb.SaveSlot = function(self, key)
+
+  if not self.command then
+    return
+  end
+  print('|cFFFFFF00received|cFFFFFF00', self:GetID(), '|cFF00FFFF', key)
+
+  local modifier = ''
+  if IsAltKeyDown() then
+    modifier = 'ALT-'
+  end
+  if IsControlKeyDown() then
+    modifier = modifier.. 'CTRL-'
+  end
+  if IsShiftKeyDown() then
+    modifier = modifier..'SHIFT-'
+  end
+  local binding = modifier..key
+
+  if key == 'ESCAPE' then
+  else
+    if kb.SystemBinds[binding] then
+      kb.statustext:SetText(L('BINDING_FAILED_PROTECTED', binding, kb.SystemBinds[binding]))
+      return
+    end
+
+
+    if self.command then
+
+      local previousKeys
+      local previousAction = GetBindingAction(binding)
+      local binding1, binding2, new1, new2
+      print(type(previousAction), previousAction)
+      if previousAction ~= "" and previousAction ~= self.command then
+        if kb.SystemBindings[binding] then
+          -- bounce out if trying to use a protected key
+          kb.statustext:SetText(L('BINDING_FAILED_PROTECTED', key, GetBindingAction(binding)))
+          kb.bindingstext:SetText(nil)
+          return
+        else
+          kb:print('Discarding keybind for', previousAction)
+          -- todo: sort out retcon'd talent spells
+        end
+      end
+
+      self.binding = binding
+
+      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, profile in ipairs(kb.orderedProfiles) do
+        if (level == kb.db.bindMode) then
+          profile.bound[self.command] = true
+          if talentInfo then
+            profile.bindings[self.binding] = nil
+          else
+            profile.bindings[self.binding] = self.command
+          end
+          profile.talents[self.actionName] = talentInfo
+        else
+          profile.bindings[self.binding] = nil
+          profile.bound[self.command] = nil
+          kb.currentProfile.talents[self.actionName] = nil
+        end
+        if kb.currentProfile.talents[self.actionID] then
+          kb.currentProfile.talents[self.actionID] = nil
+        end
+      end
+
+      kb:print(L('BINDING_ASSIGNED', self.binding, self.actionName, kb.currentHeader))
+    end
+  end
+  kb.UpdateSlot(self, true)
+end
 
 
 --- Updates profile assignment and button contents
@@ -274,6 +390,7 @@
       self.icon:SetAlpha(1)
     end
 
+
     if self.actionType == 'spell' then
       self.icon:SetTexture(GetSpellTexture(self.actionID))
     end
@@ -285,6 +402,15 @@
     self.bind:SetTextColor(1,1,1,1)
   end
 
+
+  if kb.saveTarget and kb.saveTarget ~= self then
+    self:SetAlpha(0.25)
+  else
+
+    self:SetAlpha(1)
+  end
+
+
   self.header:SetText(self.statusText)
   self.bind:SetText(self.bindingText)
   self.details:SetText(self.actionName)
@@ -348,7 +474,6 @@
 
     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]
@@ -375,13 +500,7 @@
       end
     elseif actionType == 'petaction' then
       isDynamic = 'petaction'
-      if kb.PetCache.spellslot and kb.PetCache.spellslot[name] then
-        isAvailable = true
-        kb.RemoveCacheButton(kb.petFrames, self)
-      else
-        print('|cFFFF4400OnCacheUpdate, re-do #', slot)
-        tinsert(kb.petFrames, self)
-      end
+      isAvailable = (kb.PetCache.spellslot and kb.PetCache.spellslot[name])
     elseif actionType == 'macro' then
       if not actionID then
         actionID = GetMacroIndexByName(name)
@@ -415,7 +534,7 @@
 
     actionID = actionID or 0
     self:EnableKeyboard(true)
-    print(' |cFF00FF00kb.currentProfile.buttons['..slot..'] |cFF00FFFF=|r |cFF00FFFF"'.. command.. '"|r |cFF00FF00"'.. name.. '"|r |cFFFFFF00icon:'.. icon .. '|r |cFFFF8800"'.. actionType, '"|r |cFFFF0088id:'.. actionID ..'|r |cFF00FF00"'.. macroName .. '"|r')
+    print(' |cFF00FF00kb.currentProfile.buttons['..slot..'] |cFF00FFFF=|r |cFF00FFFF"'.. command.. '"|r |cFF00FF00"'.. name.. '"|r |cFFFFFF00icon:'.. tostring(icon) .. '|r |cFFFF8800"'.. actionType, '"|r |cFFFF0088id:'.. actionID ..'|r |cFF00FF00"'.. macroName .. '"|r')
     kb.currentProfile.buttons[slot] = {command, name, icon, actionType, actionID, macroName, macroText, pickupSlot, pickupBook}
 
     -- Clean up conflicting entries for loaded button
@@ -443,118 +562,6 @@
 end
 
 
---- Updates the current KeyBinding for the button's command
-kb.SaveSlot = 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)
-
-  local modifier = ''
-  if IsAltKeyDown() then
-    modifier = 'ALT-'
-  end
-  if IsControlKeyDown() then
-    modifier = modifier.. 'CTRL-'
-  end
-  if IsShiftKeyDown() then
-    modifier = modifier..'SHIFT-'
-  end
-  local binding = modifier..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 kb.currentProfile.bindings[key] then
-        kb:print(L('BINDING_REMOVED', self.actionName, kb.configHeaders[db.bindMode]))
-        kb.currentProfile.bindings[key] = nil
-      end
-      if kb.currentProfile.talents[self.actionName] then
-        kb.currentProfile.talents[self.actionName] = nil
-      end
-      bindings[self.actionType][self.actionID] = nil
-    end
-    if kb.currentProfile.bound[self.command] then
-      kb.currentProfile.bound[self.command] = nil
-      --kb:print(BINDING_REMOVED:format(self.actionName, configHeaders[db.bindMode]))
-    end
-
-    self.active = false
-  else
-    if kb.SystemBinds[binding] then
-      kb.statustext:SetText(L('BINDING_FAILED_PROTECTED', key, kb.SystemBinds[binding]))
-      return
-    end
-
-
-    if self.command then
-
-      local previousKeys
-      local previousAction = GetBindingAction(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(L('BINDING_FAILED_PROTECTED', key, GetBindingAction(binding)))
-          kb.bindingstext:SetText(nil)
-          return
-        else
-          kb:print('Discarding keybind for', previousAction)
-          -- todo: sort out retcon'd talent spells
-        end
-      end
-
-      self.binding = binding
-
-      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, profile in ipairs(kb.orderedProfiles) do
-        if (level == db.bindMode) then
-          profile.bound[self.command] = true
-          if talentInfo then
-            profile.bindings[self.binding] = nil
-          else
-            profile.bindings[self.binding] = self.command
-          end
-          profile.talents[self.actionName] = talentInfo
-        else
-          profile.bindings[self.binding] = nil
-          profile.bound[self.command] = nil
-          kb.currentProfile.talents[self.actionName] = nil
-        end
-        if kb.currentProfile.talents[self.actionID] then
-          kb.currentProfile.talents[self.actionID] = nil
-        end
-      end
-
-      kb:print(L('BINDING_ASSIGNED', self.binding, self.actionName, kb.configHeaders[db.bindMode]))
-    end
-  end
-  kb.UpdateSlot(self, true)
-  KeyBinderSaveButton:Enable()
-end
-
 
 
 --- Add to blizzard interfaces
--- a/SkeletonKey/SkeletonKey.lua	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/SkeletonKey.lua	Sat Jul 30 00:08:00 2016 -0400
@@ -16,18 +16,13 @@
 
 local _
 local kb, print = LibStub("LibKraken").register(KeyBinder)
-local cprint = DEVIAN_WORKSPACE and function(...) _G.print('Cfg', ...) end or function() end
 kb.L = setmetatable({}, {
-  __call = function(t, k, ...) return format(t[k] or '', ...) end
+  __call = function(t, k, ...) return format(t[k] or k, ...) end
 })
 local L = kb.L
 
 --- Caps Lock literals
-local CLICK_KEYBINDER_MACRO = "CLICK KeyBinderMacro:"
-local CLICK_KEYBINDER_KEY = "CLICK KeyBinderKey:"
 local CLASS_ICON_TEXTURE = "Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES"
-local FOOTER_OFFSET
-local HEADER_OFFSET
 L.BINDING_ASSIGNED = '|cFF00FF00%s|r assigned to |cFFFFFF00%s|r (%s).'
 L.BINDING_REMOVED = '|cFFFFFF00%s|r (|cFF00FFFF%s|r) unbound.'
 L.BINDING_FAILED_PROTECTED = '|cFFFF4400Unable to use |r|cFF00FF00%s|r|cFFFF4400 (currently |cFFFFFF00%s|r|cFFFF4400)|r'
@@ -37,14 +32,14 @@
 local BINDING_TYPE_CHARACTER = 2
 local BINDING_TYPE_GLOBAL = 1
 kb.configTitle = {
-  [BINDING_TYPE_GLOBAL] = 'Global Binds',
-  [BINDING_TYPE_CHARACTER] = 'Character: %s',
-  [BINDING_TYPE_SPECIALIZATION] = 'Specialization: %s'
+  [BINDING_TYPE_GLOBAL] = L('Global Binds'),
+  [BINDING_TYPE_CHARACTER] = L('Character: %%s'),
+  [BINDING_TYPE_SPECIALIZATION] = L('Specialization: %%s')
 }
 kb.configDescription = {
-  [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.',
+  [BINDING_TYPE_GLOBAL] = L('The bindings are applied globally.'),
+  [BINDING_TYPE_CHARACTER] = L('Applied when you log onto this character.'),
+  [BINDING_TYPE_SPECIALIZATION] = L('Applied when you select this specialization.'),
 }
 
 
@@ -55,21 +50,13 @@
 kb.orderedProfiles = {}
 kb.buttons = {}
 kb.macros = {}
+kb.bindings = {}
 kb.petFrames = {} -- pet data is slightly delayed, their buttons are indexed here so they can be refreshed
 kb.talentFrames = {}
 kb.professionFrames = {}
 
 -- these are sent to plugin
 
-local bindings = {}
-local macros = {}
-local talentBindings = {}
-
-local protected = {
-  ['OPENCHATSLASH'] = true,
-  ['OPENCHAT'] = true,
-}
-
 
 local db
 local bindHeader, currentHeader = '', ''
@@ -111,7 +98,7 @@
 
   end
 
-  print('|cFFFFFF00IsCommandBound:|r', command:gsub(CLICK_KEYBINDER_MACRO, ''),'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
+  print('|cFFFFFF00IsCommandBound:|r', command,'|r [profile:', db.bindMode .. ']', isAssigned, isBound, assignedBy, boundBy)
   return isAssigned, isBound, assignedBy, boundBy
 end
 
@@ -151,106 +138,6 @@
 
 
 
-
-kb.inactiveTalentBindings = {}
-kb.ApplyTalentBinding = function(talentInfo, cache)
-  for i = 5, #talentInfo do
-    local command = CLICK_KEYBINDER_KEY.. talentInfo[2]
-    SetBinding(talentInfo[i], command)
-    cprint(' **', talentInfo[i], '->', command)
-    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.LoadBinding = function(command, name, icon, actionType, actionID, macroName, macroText )
-
-  if actionType == 'spell' then
-    KeyBinderKey:SetAttribute("*type-"..name, actionType)
-    KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name)
-
-  elseif actionType == 'item' then
-    KeyBinderKey:SetAttribute("*type-"..name, actionType)
-    KeyBinderKey:SetAttribute("*"..actionType.."-"..name, name)
-  elseif actionType == 'macro' then
-    KeyBinderMacro:SetAttribute("*macro-"..macroName, actionID)
-  else
-    KeyBinderMacro:SetAttribute("*macrotext-"..macroName, macroText)
-  end
-
-  cprint('Loading binding', actionType, actionID)
-  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.LoadBinding(unpack(data))
-  end
-
-  for key, command in pairs(profile.bindings) do
-
-    cprint(' *', key, '->', command)
-
-    --_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)
-
-
-  -- reflect action key settings
-  if GetCVarBool("ActionButtonUseKeyDown") then
-    KeyBinderMacro:RegisterForClicks("AnyDown")
-    KeyBinderKey:RegisterForClicks("AnyDown")
-  else
-    KeyBinderMacro:RegisterForClicks("AnyUp")
-    KeyBinderKey:RegisterForClicks("AnyUp")
-  end
-
-  for i, profile in ipairs(kb.orderedProfiles) 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)
@@ -321,24 +208,31 @@
   print('|cFF00FF00profile:|r', name)
   print('|cFF00FF00class:|r', UnitClass('player'))
 
-  --- Global
   defaultMode = BINDING_TYPE_GLOBAL
-  kb.InitProfile(db)
-  kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db
-
-  --- Character
-  if name then
-    db[name] = kb.InitProfile(db[name],
-      {classHeader = classHeader, className = className, classID = classID})
-    kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
+  if db[name] then
     defaultMode = BINDING_TYPE_CHARACTER
+    if db[name][kb.specInfo.id] then
+      defaultMode = BINDING_TYPE_SPECIALIZATION
+    end
   end
 
-  --- Mutable skills data
-  kb.UpdateSpecInfo()
-  kb.UpdateTalentInfo()
+  db[name] = kb.InitProfile(db[name],
+    {
+      classHeader = classHeader,
+      className = className,
+      classID = classID
+    })
+  db[name][kb.specInfo.id] = kb.InitProfile(db[name][kb.specInfo.id],
+    {
+      specID = kb.specInfo.id,
+      specName = kb.specInfo.name
+    })
 
-  kb.orderedProfiles = {kb.loadedProfiles[BINDING_TYPE_GLOBAL], kb.loadedProfiles[BINDING_TYPE_CHARACTER], kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION]}
+  kb.loadedProfiles[BINDING_TYPE_GLOBAL] = db
+  kb.loadedProfiles[BINDING_TYPE_CHARACTER] = db[name]
+  kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] = db[name][kb.specInfo.id]
+  kb.orderedProfiles = {db, db[name], db[name][kb.specInfo.id]}
+
   if (not db.bindMode) or (not kb.configTitle[db.bindMode]) then
     print('fixing bad bindMode value, was', db.bindMode)
     db.bindMode = defaultMode
@@ -357,6 +251,7 @@
 
   print('|cFF00FF00bindMode:|r', db.bindMode)
   kb.currentProfile = kb.loadedProfiles[db.bindMode]
+  kb.currentHeader = kb.configHeaders[db.bindMode]
 end
 
 local scrollCache = {}
@@ -364,6 +259,7 @@
   scrollCache[db.bindMode] = kb.scrollOffset
   db.bindMode = self:GetID()
   kb.currentProfile = kb.loadedProfiles[self:GetID()]
+  kb.currentHeader = kb.configHeaders[db.bindMode]
   kb.scrollOffset = scrollCache[db.bindMode] or 0
   kb.ui(true)
 end
@@ -382,13 +278,15 @@
 
 --- post ADDON_LOADED
 kb.variables = function()
-  SkeletonKeyDB = SkeletonKeyDB or {spec = {}}
+  SkeletonKeyDB = kb.InitProfile(SkeletonKeyDB, {})
   kb.db = SkeletonKeyDB
   kb.playerName = UnitName('player')
   kb.playerRealm = SelectedRealmName()
   kb.profileName = kb.playerRealm .. '_' .. kb.playerName
   db = kb.db
 
+  kb.UpdateSpecInfo()
+  kb.UpdateTalentInfo()
   kb.SelectProfileSet(kb.profileName)
   -- todo: redo import checking
 
@@ -405,12 +303,10 @@
 end
 
 -- Volatiles Access
-kb.GetBindings = function() return bindings end
-kb.GetButtons = function() return buttons end
+kb.GetBindings = function() return kb.bindings end
+kb.GetButtons = function() return kb.buttons end
 kb.GetCharacterProfile = function () return kb.loadedProfiles[BINDING_TYPE_CHARACTER] end
 kb.GetGlobalProfile = function () return kb.loadedProfiles[BINDING_TYPE_GLOBAL] end
-kb.GetLooseTalents = function() return talentBindings end
-kb.GetReverts = function() return reverts end
 kb.GetSpecProfile = function () return kb.loadedProfiles[BINDING_TYPE_SPECIALIZATION] end
 
 
--- a/SkeletonKey/SkeletonKey.toc	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/SkeletonKey.toc	Sat Jul 30 00:08:00 2016 -0400
@@ -13,7 +13,8 @@
 libKT-1.0\libKT-1.0.xml
 SkeletonKey.xml
 SkeletonKey.lua
-Cache.lua
+ActionTypes.lua
+DynamicTypes.lua
 BindingsUI.lua
 KeySlot.lua
 
--- a/SkeletonKey/SkeletonKey.xml	Fri Jul 29 21:18:15 2016 -0400
+++ b/SkeletonKey/SkeletonKey.xml	Sat Jul 30 00:08:00 2016 -0400
@@ -131,7 +131,7 @@
             <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.headerbg" />
             <Anchor point="BOTTOM" />
           </Anchors>
-          <Color a="1" r="0.2" g="0.2" b="0.2" />
+          <Color a=".75" r="0" g="0" b="0" />
         </Texture>
 
 
@@ -191,6 +191,8 @@
           </Anchors>
           <Color a="1" r="0" g="1" b="0" />
         </FontString>
+
+        <FontString parentKey="savingText" inherits="KTHeaderFont" text="Press a key." />
       </Layer>
     </Layers>
     <Frames>
@@ -200,11 +202,34 @@
         </Anchors>
       </Button>
 
-      <CheckButton inherits="UICheckButtonTemplate" parentKey="DummyCheckButton">
+      <Button name="$parentUnbindButton" hidden="true">
+        <Size x="20" y="20" />
+        <NormalTexture>
+          <Color a="1" r="1" g="0.4" b="0" />
+
+        </NormalTexture>
+        <NormalFont style="KTUIPanelFont" />
+        <ButtonText text="X" />
+      </Button>
+
+      <CheckButton name="$parentStickyMode">
+        <Size  y="28" />
         <Anchors>
-          <Anchor point="BOTTOMLEFT" x="4" y="4" />
+          <Anchor point="BOTTOMLEFT" x="0" y="12" />
+          <Anchor point="RIGHT" relativeKey="$parent.sourcesbg" />
         </Anchors>
-        <ButtonText text="This is some crap" />
+        <ButtonText text="Receiving" inherits="KTUIPanelFont">
+          <Anchors>
+            <Anchor point="CENTER" />
+          </Anchors>
+        </ButtonText>
+        <NormalTexture>
+          <Color a="1" r=".3" g=".3" b=".3" />
+        </NormalTexture>
+        <CheckedTexture>
+
+          <Color a="1" r=".3" g="1" b=".2" />
+        </CheckedTexture>
       </CheckButton>
     </Frames>
   </Frame>