changeset 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 a30285f8191e
children f6d1c192afc6
files LibKraken/LibKraken-1.0.lua LibKraken/LibKraken-1.0.xml LibKraken/LibKraken.iml LibKraken/LibKraken.toc LibKraken/LibStub/LibStub.lua SkeletonKey/KeyBinds.lua SkeletonKey/KeyBinds.xml SkeletonKey/SkeletonKey.toc
diffstat 8 files changed, 2015 insertions(+), 592 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LibKraken/LibKraken-1.0.lua	Tue Jul 26 19:29:44 2016 -0400
@@ -0,0 +1,357 @@
+--[[
+-- KrakynTools
+-- AddOn prototyping library.
+--
+-- Implements
+--  KT.register(frame) to hook the following (all optional):
+--    frame:init()                            run immediately after KT sets itself up
+--    frame:profile("Name-TruncatedRealm")    called the first time SavedVars data becomes available
+--    frame:variables()                       called upon variables being available
+--    frame:event(event, ...)                 replaces the event callback
+--    frame:ui()                              called by /ui when activating
+--
+--    frame:tab(name, tooltip, texture, coord)
+--      produces a serial button that changes display tabs
+--
+--    frame:button(name, text, tooltip, onClick)
+--      produces a button with OnClick script
+--
+--    frame:uibutton(name, text, tooltip, onClick, texture, coord)
+--      produces a header button with desired onClick
+--
+]]--
+
+local LIBKT_MAJOR, LIBKT_MINOR = "LibKraken", 1
+local KT = LibStub:NewLibrary(LIBKT_MAJOR, LIBKT_MINOR)
+
+--GLOBALS: LibKT, KrakTool, KTErrorFrame, LibKTError, SlashCmdList, SLASH_RL1, SLASH_UI1
+local CreateFrame, debugstack, tostring, select = CreateFrame, debugstack, tostring, select
+local print, max, unpack, tinsert = print, max, unpack, tinsert
+local ipairs, xpcall, next, safecall = ipairs, xpcall, next, safecall
+local UI_TOGGLE = false
+local db
+
+
+KT.handler = CreateFrame('Frame', 'LibKTHostFrame', UIParent)
+KT.addons = {}
+KT.initStack = {}
+KT.varsStack = {}
+local print = DEVIAN_WORKSPACE and function(...) print('LKT', ...) end or function() end
+local registeredHandles = {}
+
+--- /rl
+-- ReloadUI shortcut
+SLASH_RL1 = "/rl"
+SlashCmdList.RL = function ()
+  ReloadUI()
+end
+
+--- /kv addon ...
+-- Dumps table values from "addon", using the arguments as index values.
+-- Numerics are converted, and names that end with "()" will be called as such
+SLASH_KV1 = "/kv"
+SlashCmdList.KV = function (editbox, input)
+
+end
+
+
+--- /ui
+-- Run any addon:ui() methods
+SLASH_UI1 = "/ui"
+SlashCmdList.UI = function ()
+  if UI_TOGGLE then
+    UI_TOGGLE = false
+  else
+    UI_TOGGLE = true
+  end
+  for i, frame in pairs(KT.frames) do
+    if UI_TOGGLE then
+      if frame.close then
+        frame.close()
+      else
+        frame:Hide()
+      end
+    else
+      if frame.ui then
+        frame.ui()
+      end
+      frame:Show()
+    end
+  end
+end
+
+LibKTError = function(msg)
+  local dstack = debugstack(2)
+  :gsub("Interface\\AddOns\\",'')
+  :gsub("<(.-)>", function(a) return '|cFF00FFFF<'.. a ..'>|r' end)
+
+
+
+  KTErrorFrame.errmsg:SetText(msg)
+  KTErrorFrame.debugstack:SetText(dstack)
+  KTErrorFrame:SetHeight(KTErrorFrame.debugstack:GetStringHeight() + KTErrorFrame.errmsg:GetStringHeight() + 12)
+  KTErrorFrame:Show()
+end
+
+
+local pending = {}
+local processing = false
+local isHandled = false
+local nodebug = false
+function KT.OnEvent (addon, event, ...)
+  if processing then
+    local args = {...}
+    C_Timer.After(0, function() KT.OnEvent(addon, event, unpack(args)) end)
+    return
+  else
+
+  end
+  --- reset state
+  processing = true
+  isHandled = false
+  nodebug = false
+
+
+  if addon.event then
+    nodebug = addon.event(addon, event, ...)
+  end
+
+  if addon[event] then
+    nodebug = addon[event](addon, event, ...) or nodebug
+    addon.missed = 0
+    addon.handled = addon.handled + 1
+    isHandled = true
+  else
+    addon.firstEvent = false
+    addon.unhandled = addon.unhandled + 1
+    addon.missed = addon.missed + 1
+  end
+
+  for i, module in ipairs(addon.modules) do
+    --print(i, module)
+    if module[event] then
+      nodebug = module[event](addon, event, ...) or nodebug
+      addon.missed = 0
+      addon.handled = addon.handled + 1
+      isHandled = true
+    else
+      addon.firstEvent = false
+      addon.unhandled = addon.unhandled + 1
+      addon.missed = addon.missed + 1
+    end
+
+  end
+  if nodebug then
+    processing = false
+    return
+  else
+    KT.UpdateEventStatus(addon, event, ...)
+    processing = false
+  end
+
+end
+
+KT.UpdateEventStatus = function(addon, event, ...)
+  print(addon:GetName(), event, ...)
+
+  -- debug outputs
+  if addon.status then
+    addon.status:SetText(event .. '\n|cFF00FF00' .. addon.handled .. '|r |cFFFF8800' .. addon.missed .. '|r |cFFFF4400' .. addon.unhandled .. '|r')
+    if isHandled then
+      addon.status:SetTextColor(0,1,0)
+      if addon.log then
+        local logtext = event
+        for i = 1, select('#',...) do
+          logtext = logtext .. '\n' .. i .. ':' .. tostring(select(i,...))
+        end
+        addon.log:SetText('|cFFFFFF00last|r\n' .. logtext)
+        local newWidth = addon.log:GetStringWidth()
+
+        if addon.logfirst then
+          if not addon.firstEvent then
+            addon.firstEvent = event
+            addon.logfirst:SetText('|cFF00FF88first|r\n' .. logtext)
+          end
+
+          newWidth = newWidth + addon.logfirst:GetStringWidth()
+        end
+        if addon.logdiff then
+          if not event ~= addon.firstEvent then
+            addon.firstEvent = event
+            addon.logdiff:SetText('|cFF0088FFdiff|r\n' .. logtext)
+          end
+          newWidth = newWidth + addon.logdiff:GetStringWidth()
+        end
+        --addon:SetWidth(newWidth)
+      end
+    else
+      addon.status:SetTextColor(1,0,0)
+    end
+  end
+end
+
+KT.register = function(addon, name, noGUI)
+  if registeredHandles[addon] then
+    name = name or debugstack(2,1,0):gsub("\\n.+", ""):gsub("^Interface\\AddOns\\", ""):gsub("%s+$", "")
+  else
+    if not name then
+      assert(type(addon) == 'table', 'Need a valid table.')
+      if addon.GetName then
+        name = addon:GetName()
+      else
+        name = debugstack(2,1,0):gsub("\\n.+", ""):gsub("^Interface\\AddOns\\", ""):gsub("%s+$", "")
+      end
+      assert(type(name) == 'string', 'Unable to resolve a valid stub name.')
+    end
+    -- if calling again, assume name is a file handle
+
+    registeredHandles[addon] = name
+    KT.addons[name] = addon
+    if addon.SetScript then
+      addon:SetScript('OnEvent', KT.OnEvent)
+    end
+    addon.unhandled = 0
+    addon.missed = 0
+    addon.handled = 0
+    addon.firstEvent = false
+    addon.modules = {}
+    tinsert(KT.initStack, addon)
+    tinsert(KT.varsStack, addon)
+
+    if addon.GetName and (not noGUI) then
+      addon.UIPanelAnchor = {'TOPLEFT', addon, 'TOPLEFT', 12, -12 }
+      addon.UIPanelGrowth = {'TOPLEFT', 'TOPRIGHT', 14, 0}
+      addon.button = KT.button
+      addon.uibutton = KT.uibutton
+      addon.tab = KT.tab
+      addon.print = KT.print
+    end
+  end
+
+  return addon, (DEVIAN_WORKSPACE and function(...) _G.print(name, ...) end or function() end)
+end
+
+
+
+local onEvent = function(self, event, arg1)
+  if (event == 'ADDON_LOADED' and arg1 ~= 'Blizzard_DebugTools') or event == 'PLAYER_LOGIN' then
+    -- run any init blocks left in the queue
+    while #KT.initStack >= 1 do
+      local addon = tremove(KT.initStack, 1)
+      print('KT', addon:GetName(), 'init')
+      if addon.init then
+        xpcall(addon.init, LibKTError)
+        for i, module in ipairs(addon.modules) do
+          if module.init then
+            xpcall(module.init, LibKTError)
+          end
+        end
+      end
+    end
+
+    -- run any variables blocks if player variables are ready 
+    if IsLoggedIn() and #KT.varsStack >= 1 then
+      while #KT.varsStack >= 1 do
+        local addon = tremove(KT.varsStack, 1)
+        print(addon:GetName())
+        if addon.variables then
+          xpcall(addon.variables, LibKTError)
+          for i, module in ipairs(addon.modules) do
+            if module.variables then
+              xpcall(module.variables, LibKTError)
+            end
+          end
+        end
+      end
+    end
+  end
+end
+
+KT.print = function(module, ...)
+  local msg = '|cFF00FFFF'..module:GetName()..'|r:'
+  for i = 1, select('#', ...) do
+    msg = msg .. ' ' .. tostring(select(i, ...))
+  end
+  DEFAULT_CHAT_FRAME:AddMessage(msg)
+end
+
+--- Button generators
+
+local GetButtonTemplate = function(name, parent, template, onClick)
+  if _G[name] then
+    return _G[name]
+  end
+
+  local button = CreateFrame('Button', name, parent, template)
+  button:RegisterForClicks('AnyUp')
+  button:SetScript('OnClick', onClick)
+  return button
+end
+
+local SetButtonAnchor = function(self, collector, anchor, growth)
+  if self:GetID() == 0 then
+    self:SetID(#collector)
+    print('registered TabButton #', self:GetID())
+  end
+
+  if self:GetID() == 1 then
+    self:SetPoint(unpack(anchor))
+  else
+    growth[2] = collector[self:GetID()-1]
+    self:SetPoint(unpack(growth))
+  end
+end
+
+KT.tab = function(self, name, tooltip, texture, coords)
+  local button = GetButtonTemplate(name, self,  'KTTabButton', self.SelectTab)
+  button.icon:SetTexture(texture)
+  button.tooltip = tooltip
+  button:SetSize(unpack(self.tabSize))
+  if coords then
+    button.icon:SetTexCoord(unpack(coords))
+  end
+  SetButtonAnchor(button, self.tabButtons, self.tabAnchor, self.tabGrowth)
+  return button
+end
+
+KT.button = function(self, name, text, tooltip, onClick)
+  local button = GetButtonTemplate(name, self, 'KTButton', onClick)
+
+  button.tooltip = tooltip
+  button:SetText(text)
+  button:SetWidth(max(button:GetWidth(), button:GetFontString():GetStringWidth() + 12))
+
+  SetButtonAnchor(button, self.controls, self.controlsAnchor, self.controlsGrowth)
+  return button
+end
+
+KT.uibutton = function(self, name, text, tooltip, onClick, texture, coords)
+  local button = GetButtonTemplate(name, self, 'KTUIPanelButton', onClick)
+
+  button.tooltip = tooltip
+  button:SetText(text)
+
+  if self.UIPanelIcon then
+    local w, h, anchor, x, y = unpack(self.UIPanelIcon)
+    button.icon:SetTexture(texture)
+    button.icon:SetSize(w, h)
+    button.icon:ClearAllPoints()
+    button.icon:SetPoint(anchor, button, anchor, x, y)
+  end
+
+  if not self.UIPanelSize then
+    button:SetWidth(button:GetFontString():GetStringWidth() + button.icon:GetWidth()/1.5)
+  else
+    button:SetSize(unpack(self.UIPanelSize))
+  end
+  if coords then
+    button.icon:SetTexCoord(unpack(coords))
+  end
+  SetButtonAnchor(button, self.UIPanels, self.UIPanelAnchor, self.UIPanelGrowth)
+  return button
+end
+
+
+KT.handler:RegisterEvent('ADDON_LOADED')
+KT.handler:RegisterEvent('PLAYER_LOGIN')
+KT.handler:SetScript('OnEvent', onEvent)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LibKraken/LibKraken-1.0.xml	Tue Jul 26 19:29:44 2016 -0400
@@ -0,0 +1,218 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+
+  <Script file="LibKraken-1.0.lua" />
+
+  <Font name="KTLogString" font="Fonts\ARIALN.TTF" height="12" justifyH="LEFT" justifyV="TOP" inherits="NumberFontNormal" virtual="true">
+    <Color a="1" r="1" g="1" b="1" />
+  </Font>
+
+  <Font name="KTHeaderFont" height="18" justifyH="LEFT" justifyV="TOP" virtual="true" inherits="NumberFont_Outline_Huge" />
+  <Font name="KTHeader2Font" height="14" font="Fonts\skurri.ttf" outline="NORMAL" justifyH="LEFT" justifyV="TOP" virtual="true" />
+  <Font name="KTUIPanelFont" justifyH="LEFT" virtual="true"  inherits="NumberFontNormal"  />
+  <Font name="KTMacroButtonFont" height="12" font="Fonts\ARIALN.TTF" justifyH="LEFT" virtual="true" >
+    <Color a="1" r="1" g="1" b="1" />
+  </Font>
+
+
+  <Button name="KTButton" parentArray="controls" virtual="true">
+    <Size x="72" y="28" />
+    <Scripts>
+      <OnEnter>
+        if self.tooltip then
+          GameTooltip:SetOwner(self)
+          GameTooltip:SetAnchorType('ANCHOR_TOPRIGHT')
+          GameTooltip:SetText(self.tooltip)
+          GameTooltip:Show()
+        end
+      </OnEnter>
+      <OnLeave>
+        GameTooltip:Hide()
+      </OnLeave>
+    </Scripts>
+    <NormalFont style="NumberFontNormal" />
+    <NormalTexture setAllPoints="true">
+      <Color a="1" r="0" g=".4" b="1" />
+    </NormalTexture>
+    <DisabledTexture>
+      <Color a="1" r="0.5" b="0.5" g="0.5" />
+    </DisabledTexture>
+    <DisabledColor a="0.5" r="1" g="1" b="1" />
+    <PushedTexture>
+      <Color a="1" r="1" g="0.25" b="0.25" />
+    </PushedTexture>
+    <HighlightTexture alphaMode="ADD">
+      <Color a="0.25" r="1" g="0" b=".5" />
+    </HighlightTexture>
+  </Button>
+
+  <!-- style for Blizzard UIPanel activators
+    // The template anchor gets overwritten for successive iterations -->
+  <Button name="KTUIPanelButton" virtual="true" parentArray="UIPanels">
+    <Size x="84" y="24" />
+    <Scripts>
+      <OnEnter>
+        if self.tooltip then
+          GameTooltip:SetOwner(self)
+          GameTooltip:SetAnchorType('ANCHOR_TOPRIGHT')
+          GameTooltip:SetText(self.tooltip)
+          GameTooltip:Show()
+        end
+      </OnEnter>
+      <OnLeave>
+        GameTooltip:Hide()
+      </OnLeave>
+    </Scripts>
+    <Layers>
+      <Layer level="OVERLAY">
+        <Texture parentKey="icon" />
+      </Layer>
+    </Layers>
+
+    <ButtonText>
+      <Anchors>
+        <Anchor point="LEFT" x="14" y="0" />
+      </Anchors>
+    </ButtonText>
+
+    <NormalFont style="KTUIPanelFont" />
+    <NormalTexture>
+      <Color a="1" r=".24" g=".24" b=".24" />
+    </NormalTexture>
+    <PushedTexture>
+      <Color a="1" r="0" g="0" b="0" />
+    </PushedTexture>
+    <HighlightTexture alphaMode="ADD">
+      <Size x="32" />
+      <Color a="1" r="1" g="1" b="1" />
+      <Gradient orientation="HORIZONTAL">
+        <MaxColor r=".25" g=".25" b=".25"/>
+        <MinColor  r="0" g="0" b="0" />
+      </Gradient>
+    </HighlightTexture>
+
+  </Button>
+
+  <Button name="KTTabButton" virtual="true" parentArray="tabButtons">
+    <Scripts>
+      <OnEnter>
+
+        if not self.tooltip then
+          return
+        end
+        GameTooltip:SetOwner(self)
+        GameTooltip:SetAnchorType('LEFT')
+        GameTooltip:SetText(self.tooltip)
+        GameTooltip:Show()
+      </OnEnter>
+      <OnLeave>
+
+        if not self.tooltip then
+          return
+        end
+        GameTooltip:Hide()
+      </OnLeave>
+    </Scripts>
+    <Size x="40" y="40" />
+    <Layers>
+      <Layer level="BACKGROUND">
+        </Layer>
+      <Layer level="BORDER">
+        <Texture parentKey="icon">
+          <Color a="1" r="1" g="0" b="0" />
+          <Anchors>
+            <Anchor point="TOPLEFT" x="2" y="-2"/>
+            <Anchor point="BOTTOMRIGHT" x="-2" y="2"/>
+          </Anchors>
+        </Texture>
+      </Layer>
+      <Layer level="ARTWORK">
+      </Layer>
+    </Layers>
+
+    <NormalTexture name="$parentNormalTexture" file="Interface\Buttons\UI-Quickslot2">
+      <Anchors>
+        <Anchor point="TOPLEFT" x="-12" y="12"/>
+        <Anchor point="BOTTOMRIGHT" x="13" y="-13"/>
+      </Anchors>
+    </NormalTexture>
+    <PushedTexture file="Interface\Buttons\UI-Quickslot-Depress"/>
+    <HighlightTexture alphaMode="ADD" file="Interface\Buttons\ButtonHilight-Square"/>
+  </Button>
+
+  <!-- inherited to generate event feedback -->
+  <Frame name="KTDebugTemplate" virtual="true">
+    <Layers>
+      <Layer level="OVERLAY">
+        <FontString inherits="GameFontNormal" parentKey="status" text="text thing here">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="KTLogString" parentKey="logfirst" text="First">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" x="0" y="0" relativeKey="$parent.status" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="KTLogString" parentKey="logdiff" text="Different">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="TOPRIGHT" relativeKey="$parent.logfirst" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="KTLogString" parentKey="log" text="Last">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="TOPRIGHT" relativeKey="$parent.logdiff" />
+          </Anchors>
+        </FontString>
+      </Layer>
+    </Layers>
+  </Frame>
+
+
+
+  <Frame name="KTErrorFrame" parent="UIParent" toplevel="true" movable="true" enableMouse="true" hidden="true" clampedToScreen="true">
+    <Size x="450" y="280" />
+    <Anchors>
+      <Anchor point="CENTER" />
+    </Anchors>
+    <Scripts>
+      <OnLoad>
+        self:RegisterForDrag('LeftButton')
+      </OnLoad>
+      <OnDragStart>
+        self:StartMoving()
+      </OnDragStart>
+      <OnDragStop>
+        self:StopMovingOrSizing()
+      </OnDragStop>
+    </Scripts>
+    <Layers>
+      <Layer level="BACKGROUND">
+        <Texture setAllPoints="true">
+          <Color a="1" r="0" g="0" b="0" />
+        </Texture>
+      </Layer>
+      <Layer level="OVERLAY">
+        <FontString inherits="NumberFont_Outline_Huge" text="KrakTool Error">
+          <Anchors>
+            <Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="NumberFont_Outline_Large" parentKey="errmsg" justifyH="LEFT" spacing="3">
+          <Anchors>
+            <Anchor point="TOP" />
+            <Anchor point="LEFT" />
+            <Anchor point="RIGHT" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="NumberFont_Outline_Large" parentKey="debugstack" justifyH="LEFT" spacing="3">
+          <Anchors>
+            <Anchor point="LEFT" />
+            <Anchor point="RIGHT" />
+            <Anchor point="TOP" relativePoint="BOTTOM" relativeKey="$parent.errmsg" x="0" y="-8" />
+          </Anchors>
+        </FontString>
+      </Layer>
+    </Layers>
+  </Frame>
+</Ui>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LibKraken/LibKraken.iml	Tue Jul 26 19:29:44 2016 -0400
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="LUA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$" isTestSource="false" />
+    </content>
+    <orderEntry type="jdk" jdkName="WoW 7.0.3" jdkType="Lua SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LibKraken/LibKraken.toc	Tue Jul 26 19:29:44 2016 -0400
@@ -0,0 +1,12 @@
+## Interface: 60200
+## Title: !LibKraken
+## Notes: Addon framework for dinosaurs
+## Author: Krakyn
+## Version: 1.0-@project-revision@
+## X-Category: Interface Enhancements
+## DefaultState: Enabled
+## LoadOnDemand: 0
+## OptionalDeps: Ace3, LibStub
+
+LibStub\LibStub.lua
+LibKraken-1.0.xml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LibKraken/LibStub/LibStub.lua	Tue Jul 26 19:29:44 2016 -0400
@@ -0,0 +1,30 @@
+-- LibStub is a simple versioning stub meant for use in Libraries.  http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2  -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+	LibStub = LibStub or {libs = {}, minors = {} }
+	_G[LIBSTUB_MAJOR] = LibStub
+	LibStub.minor = LIBSTUB_MINOR
+	
+	function LibStub:NewLibrary(major, minor)
+		assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+		minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+		
+		local oldminor = self.minors[major]
+		if oldminor and oldminor >= minor then return nil end
+		self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+		return self.libs[major], oldminor
+	end
+	
+	function LibStub:GetLibrary(major, silent)
+		if not self.libs[major] and not silent then
+			error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+		end
+		return self.libs[major], self.minors[major]
+	end
+	
+	function LibStub:IterateLibraries() return pairs(self.libs) end
+	setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
--- 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
--- a/SkeletonKey/KeyBinds.xml	Tue Jun 21 11:56:14 2016 -0400
+++ b/SkeletonKey/KeyBinds.xml	Tue Jul 26 19:29:44 2016 -0400
@@ -1,6 +1,12 @@
 <Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
 ..\FrameXML\UI.xsd">
-  <Button name="KeyBinderMacro" inherits="SecureActionButtonTemplate" />
+  <Button name="KeyBinderMacro" inherits="SecureActionButtonTemplate">
+    <Scripts>
+      <OnLoad>
+        self:SetAttribute('*type*', 'macro')
+      </OnLoad>
+    </Scripts>
+  </Button>
   <CheckButton name="KeyButton" virtual="true">
     <Size x="32" y="32" />
     <Layers>
@@ -21,24 +27,38 @@
       </Layer>
       <Layer level="ARTWORK">
         <Texture setAllPoints="true" parentKey="icon">
+          <Anchors>
+            <Anchor point="TOPLEFT" x="2" y="-2" />
+            <Anchor point="BOTTOMRIGHT" x="-2" y="2" />
+          </Anchors>
 
           <TexCoords left="0.1" right="0.9" top="0.1" bottom="0.9" />
 
-          <Anchors>
-            <Anchor point="TOPLEFT" x="1" y="-1" />
-            <Anchor point="BOTTOMRIGHT" x="-1" y="1" />
-          </Anchors>
         </Texture>
       </Layer>
       <Layer level="OVERLAY">
+        <FontString inherits="NumberFontNormal" parentKey="header" wordwrap="false" justifyH="LEFT">
+
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="2" y="-2" />
+            <Anchor point="RIGHT" x="128" y="0" />
+          </Anchors>
+        </FontString>
         <FontString inherits="NumberFontNormal" parentKey="bind">
           <Anchors>
-            <Anchor point="BOTTOMRIGHT" x="-4" y="4" />
+            <Anchor point="BOTTOMRIGHT" x="-2" y="2" />
           </Anchors>
         </FontString>
-        <FontString inherits="NumberFontNormal" parentKey="macro" hidden="true">
+        <FontString inherits="KTMacroButtonFont" parentKey="macro">
           <Anchors>
-            <Anchor point="TOPLEFT" x="4" y="-4" />
+            <Anchor point="TOPLEFT" x="2" y="-2" />
+            <Anchor point="RIGHT" x="-2" y="0" />
+          </Anchors>
+        </FontString>
+        <FontString inherits="NumberFontNormal" parentKey="details" justifyH="LEFT">
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" x="0" y="-2" relativeKey="$parent.header" />
+            <Anchor point="RIGHT" x="128" y="0" />
           </Anchors>
         </FontString>
       </Layer>
@@ -51,27 +71,32 @@
     </HighlightTexture>
   </CheckButton>
 
-  <Frame name="KeyBinder" parent="UIParent" hidden="true" clampToScreen="true" movable="true" enableMouse="true">
+  <Frame name="KeyBinder" parent="UIParent" hidden="true" clampedToScreen="true" movable="true" enableMouse="true">
+    <Anchors>
+      <Anchor point="TOP" y="-25" x="0" />
+    </Anchors>
+    <Size x="600" y="200" />
     <Scripts>
       <OnLoad>
         self:RegisterForDrag('LeftButton')
       </OnLoad>
+      <OnShow>
+      </OnShow>
       <OnDragStart>
         self:StartMoving()
       </OnDragStart>
       <OnDragStop>
         self:StopMovingOrSizing()
       </OnDragStop>
+      <OnMouseWheel>
+        self:OnMouseWheel(delta)
+      </OnMouseWheel>
+      <OnHide>
+        self:OnHide()
+      </OnHide>
     </Scripts>
-    <Anchors>
-      <Anchor point="TOP" y="-25" x="0" />
-    </Anchors>
-    <Size x="600" y="200" />
     <Layers>
       <Layer level="BACKGROUND">
-        <Texture setAllPoints="true" parentKey="bg">
-          <Color a="0.5" r="0" g="0" b="0" />
-        </Texture>
 
         <Texture parentKey="info">
           <Anchors>
@@ -80,41 +105,147 @@
           </Anchors>
           <Size y="42" />
         </Texture>
-      </Layer>
-      <Layer level="ARTWORK">
-        <Texture parentKey="modebg">
-          <Size x="42" y="42" />
+
+        <Texture parentKey="headerbg" alphaMode="MOD">
+          <Size y="32" />
           <Anchors>
-            <Anchor point="TOPLEFT" relativeKey="$parent.info" />
+            <Anchor point="TOPLEFT" />
+            <Anchor point="RIGHT" />
           </Anchors>
+          <Color a="1" r="1" g="1" b="1" />
+          <Gradient orientation="VERTICAL">
+            <MinColor r="0" g="0" b="0"/>
+            <MaxColor r="1" g="1" b="1"/>
+          </Gradient>
+        </Texture>
+
+        <Texture parentKey="sourcesbg">
+          <Size x="100" />
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.headerbg" />
+            <Anchor point="BOTTOM" />
+          </Anchors>
+          <Color a="1" r="0.2" g="0.2" b="0.2" />
+        </Texture>
+
+
+
+        <Texture parentKey="profilebg">
+          <Size  y="102" />
+          <Anchors>
+            <Anchor point="TOP" relativePoint="BOTTOM" relativeKey="$parent.headerbg" />
+            <Anchor point="LEFT" relativePoint="RIGHT" relativeKey="$parent.sourcesbg" />
+            <Anchor point="RIGHT" />
+          </Anchors>
+          <Color a="1" r="0" g="0" b="0" />
+        </Texture>
+
+        <Texture parentKey="bg">
+          <Anchors>
+            <Anchor point="TOP" relativePoint="BOTTOM" relativeKey="$parent.profilebg" />
+            <Anchor point="LEFT" relativePoint="RIGHT" relativeKey="$parent.sourcesbg" />
+          </Anchors>
+          <Color a="0.5" r="0" g="0" b="0" />
+        </Texture>
+
+        <Texture parentKey="footer">
+          <Size y="52" />
+          <Anchors>
+            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.bg" x="0" y="0" />
+            <Anchor point="RIGHT"  />
+          </Anchors>
+          <Color a="1" r="0" g="0" b="0" />
         </Texture>
       </Layer>
       <Layer level="OVERLAY">
 
-        <FontString parentKey="modetext" inherits="NumberFont_Outline_Large" justifyH="RIGHT">
+        <FontString inherits="KTHeaderFont" text="Bindings" parentKey="header">
           <Anchors>
-            <Anchor point="TOPLEFT" relativeKey="$parent.info" />
+            <Anchor point="TOPLEFT" />
           </Anchors>
         </FontString>
 
-        <FontString parentKey="bindlist" inherits="NumberFont_Outline_Large" justifyH="LEFT" justifyV="TOP">
+        <FontString parentKey="profiletext" inherits="KTHeader2Font" justifyH="LEFT" text="Foobar">
           <Anchors>
-            <Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeKey="$parent.modetext" x="0" y="-4" />
+            <Anchor point="BOTTOMLEFT" relativePoint="BOTTOMRIGHT" relativeKey="$parent.header" x="8" y="5" />
           </Anchors>
+          <Color a="1" r="1" g="0.7" b="0" />
+        </FontString>
+
+        <FontString parentKey="statustext" inherits="NumberFont_Outline_Large" justifyH="LEFT" justifyV="TOP">
+          <Anchors>
+            <Anchor point="BOTTOMLEFT" relativePoint="TOPLEFT" relativeKey="$parent.bg" x="12" y="7" />
+          </Anchors>
+        </FontString>
+
+
+        <FontString parentKey="bindingstext" inherits="NumberFont_Outline_Large" justifyH="RIGHT" justifyV="TOP">
+          <Anchors>
+            <Anchor point="BOTTOMRIGHT" relativePoint="TOPRIGHT" relativeKey="$parent.bg" x="-12" y="7" />
+          </Anchors>
+          <Color a="1" r="0" g="1" b="0" />
         </FontString>
       </Layer>
     </Layers>
     <Frames>
-      <Button inherits="UIPanelCloseButton">
-        <Scripts>
-          <OnClick>
-            self:GetParent():close()
-          </OnClick>
-        </Scripts>
+      <Button inherits="UIPanelCloseButton" parentKey="CloseButton">
         <Anchors>
           <Anchor point="TOPRIGHT" />
         </Anchors>
       </Button>
+
+      <CheckButton inherits="UICheckButtonTemplate" parentKey="DummyCheckButton">
+        <Anchors>
+          <Anchor point="BOTTOMLEFT" x="4" y="4" />
+        </Anchors>
+        <ButtonText text="This is some crap" />
+      </CheckButton>
     </Frames>
   </Frame>
+
+  <ScrollingMessageFrame hidden="true" fade="false"  name="KeyBinderImportLog" parent="KeyBinder" clampedToScreen="true" parentKey="ImportLog" insertMode="BOTTOM" maxLines="500">
+
+    <Scripts>
+      <OnLoad>
+        self:AddMessage('SkeletonKey import tool')
+      </OnLoad>
+      <OnMouseWheel>
+        if delta >= 0 then
+          if IsControlKeyDown() then
+            -- extremely janky but avoids having one line at the bottom
+            for i =1, self:GetMaxLines() do
+              self:ScrollUp()
+            end
+          end
+
+          return self:ScrollUp()
+        else
+
+          if IsControlKeyDown() then
+            return self:ScrollToBottom()
+          end
+
+          self:ScrollDown()
+        end
+      </OnMouseWheel>
+    </Scripts>
+    <Anchors>
+      <Anchor point="TOPLEFT" relativePoint="TOPRIGHT" x="2" y="0" />
+      <Anchor point="BOTTOM" />
+    </Anchors>
+    <Size x="400" />
+    <Layers>
+      <Layer level="BACKGROUND">
+        <Texture setAllPoints="true">
+          <Color a="1" r="0" g="0" b="0" />
+        </Texture>
+      </Layer>
+    </Layers>
+    <FontString inherits="NumberFontNormal" justifyH="LEFT">
+
+      <Anchors>
+        <Anchor point="TOPLEFT" />
+      </Anchors>
+        </FontString>
+  </ScrollingMessageFrame>
 </Ui>
\ No newline at end of file
--- a/SkeletonKey/SkeletonKey.toc	Tue Jun 21 11:56:14 2016 -0400
+++ b/SkeletonKey/SkeletonKey.toc	Tue Jul 26 19:29:44 2016 -0400
@@ -1,4 +1,4 @@
-## Interface: 70000
+## Interface: 60200
 ## Title: SkeletonKey
 ## Notes: Key Bindings for dinosaurs
 ## Author: Krakyn
@@ -7,8 +7,12 @@
 ## X-Category: Interface Enhancements
 ## DefaultState: Enabled
 ## LoadOnDemand: 0
-## OptionalDeps: libKT
+## OptionalDeps: LibStub, libKT
 
+LibStub\LibStub.lua
 libKT-1.0\libKT-1.0.xml
 KeyBinds.xml
-KeyBinds.lua
\ No newline at end of file
+KeyBinds.lua
+Import.lua
+Events.lua
+HotKey.lua
\ No newline at end of file