diff ObjectiveTracker/ObjectiveTracker.lua @ 28:c33c17dd97e7

file renames
author Nenue
date Wed, 13 Apr 2016 20:19:37 -0400
parents
children adcd7c328d07
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ObjectiveTracker/ObjectiveTracker.lua	Wed Apr 13 20:19:37 2016 -0400
@@ -0,0 +1,507 @@
+--- ${PACKAGE_NAME}
+-- @file-author@
+-- @project-revision@ @project-hash@
+-- @file-revision@ @file-hash@
+-- Created: 3/26/2016 1:51 AM
+local B, _G = select(2,...).frame, _G
+local pairs, setmetatable, type, tostring, band, format = _G.pairs, _G.setmetatable, _G.type, _G.tostring, bit.band, string.format
+local ipairs, tinsert, hooksecurefunc = _G.ipairs, _G.tinsert, _G.hooksecurefunc
+local PlaySoundFile, IsQuestTask, SortQuestWatches, GetCurrentMapAreaID, GetZoneText, GetMinimapZoneText = PlaySoundFile, IsQuestTask, SortQuestWatches, GetCurrentMapAreaID, GetZoneText, GetMinimapZoneText
+local AddQuestWatch, SetSuperTrackedQuestID, GetNumQuestWatches, AUTO_QUEST_WATCH, MAX_WATCHABLE_QUESTS = AddQuestWatch, SetSuperTrackedQuestID, GetNumQuestWatches, AUTO_QUEST_WATCH, MAX_WATCHABLE_QUESTS
+local QuestPOIUpdateIcons, GetCVar, IsPlayerInMicroDungeon, WorldMapFrame, GetCVarBool, SetMapToCurrentZone = QuestPOIUpdateIcons, GetCVar, IsPlayerInMicroDungeon, WorldMapFrame, GetCVarBool, SetMapToCurrentZone
+local AddAutoQuestPopUp = AddAutoQuestPopUp
+local T = B:RegisterModule("ObjectiveTracker", _G.VeneerObjectiveWrapper, 'BuffFrame')
+local print = B.print('Objectives')
+local ObjectiveTrackerFrame, VeneerObjectiveScroll, CreateFrame = _G.ObjectiveTrackerFrame, _G.VeneerObjectiveScroll, _G.CreateFrame
+local Wrapper = _G.VeneerObjectiveWrapper
+local Scroller = VeneerObjectiveWrapper.scrollArea
+local Scroll = _G.VeneerObjectiveScroll
+local unitLevel = UnitLevel('player')
+
+--- Performance values
+--[[
+  self:RegisterEvent("QUEST_LOG_UPDATE");
+	self:RegisterEvent("TRACKED_ACHIEVEMENT_LIST_CHANGED");
+	self:RegisterEvent("QUEST_WATCH_LIST_CHANGED");
+	self:RegisterEvent("QUEST_AUTOCOMPLETE");
+	self:RegisterEvent("QUEST_ACCEPTED");
+	self:RegisterEvent("SUPER_TRACKED_QUEST_CHANGED");
+	self:RegisterEvent("SCENARIO_UPDATE");
+	self:RegisterEvent("SCENARIO_CRITERIA_UPDATE");
+	self:RegisterEvent("TRACKED_ACHIEVEMENT_UPDATE");
+	self:RegisterEvent("ZONE_CHANGED_NEW_AREA");
+	self:RegisterEvent("ZONE_CHANGED");
+	self:RegisterEvent("QUEST_POI_UPDATE");
+	self:RegisterEvent("VARIABLES_LOADED");
+	self:RegisterEvent("QUEST_TURNED_IN");
+	self:RegisterEvent("PLAYER_MONEY");
+ ]]
+
+
+--- These are the bitfields used by Blizzard_ObjectiveTracker to determine which segments get parsed.
+--- They are replicated here so that plugins can make use of any securehook args involving this info.
+local OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST                       -- 0x0100
+local OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP = OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP	-- 0x0200
+local OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE = OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE		-- 0x0400
+local OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO = OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO	    		        -- 0x0800
+local OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT	= OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT           -- 0x1000
+
+
+local OBJECTIVE_TRACKER_UPDATE_STATIC = OBJECTIVE_TRACKER_UPDATE_STATIC					                         	-- 0x0000
+local OBJECTIVE_TRACKER_UPDATE_ALL = OBJECTIVE_TRACKER_UPDATE_ALL			                    	          		-- 0xFFFF
+local OBJECTIVE_TRACKER_UPDATE_ID = OBJECTIVE_TRACKER_UPDATE_ID                                           -- 0
+
+local OBJECTIVE_TRACKER_UPDATE_QUEST = OBJECTIVE_TRACKER_UPDATE_QUEST				        	                  	-- 0x0001
+local OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED = OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED		               		    -- 0x0002
+local OBJECTIVE_TRACKER_UPDATE_TASK_ADDED = OBJECTIVE_TRACKER_UPDATE_TASK_ADDED		    	              		-- 0x0004
+local OBJECTIVE_TRACKER_UPDATE_SCENARIO = OBJECTIVE_TRACKER_UPDATE_SCENARIO			      	                 	-- 0x0008
+local OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE = OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE		       	-- 0x0010
+local OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT			                   	-- 0x0020
+local OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED		  	      -- 0x0040
+local OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED = OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED		-- 0x0080
+
+local OBJECTIVE_TRACKER_UPDATE_REASON = OBJECTIVE_TRACKER_UPDATE_ALL -- default
+--- Used to determine which trackers are listening for money events
+
+T.strings = {}
+T.strings.CLICK_TO_ACCCEPT = 'Click to Accept'
+T.strings.CLICK_TO_COMPLETE = 'Click to complete'
+T.colors ={
+  enable = true,
+  default = {
+    titlebg = {'HORIZONTAL', 1, 0, .7, .25,    1, 0, .7, .125},
+    textbg = {'HORIZONTAL', 0, 0, 0, 0.4,      0, 0, 0, 0 },
+    selectionbg = {'HORIZONTAL', 1, 1, 1, 0, 1, 1, 1, 0.225},
+  },
+  daily = {
+    titlebg = {'HORIZONTAL', 0, .7, 1, .25,  0, 1, .7, .125},
+    textbg = {'HORIZONTAL', 0, .7, 1, .1,    0, 1, .7, .075 },
+  },
+  weekly = {
+    titlebg = {'HORIZONTAL', 0, .35, .7, .25,   0, .35, .7, .125},
+    textbg = {'HORIZONTAL', 0, .35, .7, .1,     0, .35, .7, .075 },
+  },
+  account = {
+    titlebg = {'HORIZONTAL', .1, .1, .1, .25, .1, .1, .1, .125},
+    textbg = {'HORIZONTAL', .1, .1, .1, 0.4, .1, .1, .1, .085 },
+  },
+  -- alliance
+  faction_1 = {
+    titlebg = {'HORIZONTAL', .2, .4, 1, 0.4, .2, .4, 1, .085 },
+    textbg = {'HORIZONTAL', .2, .4, 1, 0.4, .2, .4, 1, .085 },
+  },
+  -- horde
+  faction_2 = {
+    titlebg = {'HORIZONTAL', .6, 0, 0.4, 0.4,  .6, 0, 0.4, .085 },
+    textbg = {'HORIZONTAL', .6, 0, 0.4, 0.4,   .6, 0, 0.4, .085 },
+  }
+}
+
+T.watchMoneyReasons = 0
+
+--- Baseline defaults table; values defined in the files that they pertain to
+T.defaults = {}
+
+--- Tracker display order
+T.orderedNames = {'Bonus', 'AutoQuest', 'Quest', 'Cheevs'}
+
+--- ipairs() argument tables
+T.orderedHandlers = setmetatable({}, {__mode = "k"})
+T.orderedTrackers = setmetatable({}, {__mode = "k"})
+T.indexedTrackers = setmetatable({}, {__mode = "k"})
+
+--- pairs() argument tables
+T.namedTrackers = setmetatable({}, {__mode = "k"})
+
+local WRAPPER_ANCHOR_POINT = 'TOPRIGHT'
+local WRAPPER_OFFSET_X = 0
+local WRAPPER_OFFSET_Y = -200
+local WRAPPER_MAX_HEIGHT = 670
+local WRAPPER_WIDTH = 280
+local WRAPPER_HEADER_HEIGHT = 24
+
+T.defaults.Wrapper = {
+  AnchorPoint = WRAPPER_ANCHOR_POINT,
+  OffsetX = WRAPPER_OFFSET_X,
+  OffsetY = WRAPPER_OFFSET_Y,
+  Height = WRAPPER_MAX_HEIGHT,
+  Width = WRAPPER_WIDTH,
+  HeaderHeight = WRAPPER_HEADER_HEIGHT,
+  TextSpacing = 3,
+  TitleSpacing = 3,
+}
+
+
+
+--- Tracker module definitions begin here; innards dealing with data retreival and output are defined further in
+T.DefaultHandler = {
+  previousHeight = 0,
+
+  name = "temp",
+  displayName = "temp",
+  updateReasonModule = 0xFF00,
+  updateReasonEvent  = 0x00FF,
+
+  numWatched = 0,   --- number of entries being handled
+  numBlocks = 0,    --- number of blocks created
+  actualBlocks = 0, --- number of blocks in use
+
+  freeBlocks = {},  --- block heap
+  usedBlocks = {},
+
+  Info = {},        -- find data by ID
+  BlockInfo = {},   -- find data by block ID
+  Watched = {},     -- find watchIndex by data ID
+  WatchInfo = {},   -- find data by watch index
+  WatchBlock = {},  -- find block by watch index
+}
+
+T.AutoQuest = {
+  name = "AutoQuest",
+  displayName = "Notice",
+  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
+  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
+}
+T.Quest = {
+  name = "Quest",
+  displayName = "Quests",
+  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST,
+  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED,
+  itemButtons = {},
+  freeButtons = {},
+}
+T.Cheevs = {
+  name = "Cheevs",
+  displayName = "Achievements",
+  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_ACHIEVEMENT,
+  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT +
+      OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED,
+}
+T.Bonus = {
+  name = "Bonus",
+  displayName = "Bonus Objectives",
+  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE,
+  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_QUEST + OBJECTIVE_TRACKER_UPDATE_TASK_ADDED
+}
+
+T.Scenario = {
+  name = 'Scenario',
+  displayName = 'Scenario Objectives',
+  updateReasonModule = OBJECTIVE_TRACKER_UPDATE_MODULE_SCENARIO,
+  updateReasonEvents = OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE + OBJECTIVE_TRACKER_UPDATE_SCENARIO_BONUS_DELAYED
+}
+
+local Tracker_string = function (self)
+  return self.name
+end
+local Tracker_call = function (self, reason)
+  self:Update(reason)
+end
+local Handler_Initialize = function (self, name, index)
+  local c = T.Conf.Wrapper
+  print('Initializing |cFF00FFFF'..name..'|r module...')
+
+  local handler = setmetatable(T[name] or {}, {
+    __tostring = Tracker_string,
+    __call = Tracker_call
+  })
+  if type(T.orderedHandlers[index]) == 'table' then
+    return T.orderedHandlers[index]
+  end
+
+  print('|cFFFFFF00Acquiring locals')
+  local preset = {}
+  for k, _ in pairs(handler) do
+    preset[k] = true
+  end
+
+
+  print('|cFFFF8800Inheriting')
+  for k, v in pairs(self) do
+    if not handler[k] then
+      if type(v) == 'table' then
+        -- assume all tables to be local data; don't inherit or ref
+        handler[k] = {}
+      else
+        handler[k] = self[k]
+      end
+      print('copying', k)
+    end
+  end
+  print('|cFFFF4400'..tostring(name)..'|r:')
+  for k, v in pairs(handler) do
+    print(format("%32s %8s %s", (preset[k] and '|cFFFFFFFF' or '|cFFFFFF00') .. k .. '|r', type(v), tostring(v)))
+  end
+
+  T[name] = handler
+
+  local trackerName = 'Veneer'..name..'Tracker'
+  local handler = T[name]
+  local frame = CreateFrame('Frame', trackerName, _G.VeneerObjectiveScroll, 'VeneerTrackerTemplate')
+  frame.title:SetText(handler.displayName)
+  frame:SetWidth(c.Width)
+  handler.frame = frame
+  handler.trackerName = trackerName
+  handler.lines = {}
+  T.orderedTrackers[index] = frame
+  T.namedTrackers[name] = frame
+  T.indexedTrackers[handler] = frame
+  print('|cFFFF0088' .. trackerName .. '|r created for |cFF00FFFF' .. handler.displayName .. '|r module')
+
+  T.orderedHandlers[index] = handler
+  return true
+end
+
+local Event = {}
+Event.QUEST_LOG_UPDATE =  function()
+  return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST + OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE
+end
+Event.QUEST_ACCEPTED = function(questLogIndex, questID)
+  if ( IsQuestTask(questID) ) then
+    return OBJECTIVE_TRACKER_UPDATE_TASK_ADDED, questID
+  else
+    if ( AUTO_QUEST_WATCH == "1" and GetNumQuestWatches() < MAX_WATCHABLE_QUESTS ) then
+      AddQuestWatch(questLogIndex);
+      SetSuperTrackedQuestID(questID);
+    end
+    return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST
+  end
+end
+
+Event.QUEST_WATCH_LIST_CHANGED = function(questID, added)
+  if ( added ) then
+    if ( not IsQuestTask(questID) ) then
+      return OBJECTIVE_TRACKER_UPDATE_QUEST_ADDED, questID, added
+    end
+  else
+    return OBJECTIVE_TRACKER_UPDATE_QUEST, questID, added
+  end
+end
+
+Event.QUEST_POI_UPDATE = function()
+  QuestPOIUpdateIcons();
+  if ( GetCVar("trackQuestSorting") == "proximity" ) then
+    SortQuestWatches();
+  end
+  return OBJECTIVE_TRACKER_UPDATE_ALL
+end
+Event.SUPER_TRACKED_QUEST_CHANGED = function()
+  return OBJECTIVE_TRACKER_UPDATE_QUEST
+end
+Event.ZONE_CHANGED = function()
+
+  local inMicroDungeon = IsPlayerInMicroDungeon();
+  if ( inMicroDungeon ~= T.inMicroDungeon ) then
+    if ( not WorldMapFrame:IsShown() and GetCVarBool("questPOI") ) then
+      SetMapToCurrentZone();			-- update the zone to get the right POI numbers for the tracker
+    end
+    --SortQuestWatches();
+    T.inMicroDungeon = inMicroDungeon;
+  end
+end
+Event.QUEST_AUTOCOMPLETE = function(questId)
+  AddAutoQuestPopUp(questId, "COMPLETE");
+  return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST + OBJECTIVE_TRACKER_UPDATE_MODULE_AUTO_QUEST_POPUP
+end
+Event.SCENARIO_CRITERIA_UPDATE =  function()
+  return OBJECTIVE_TRACKER_UPDATE_SCENARIO
+end
+Event.SCENARIO_UPDATE = function(newStage)
+  if ( newStage ) then
+    return OBJECTIVE_TRACKER_UPDATE_SCENARIO_NEW_STAGE
+  else
+    return OBJECTIVE_TRACKER_UPDATE_SCENARIO
+  end
+end
+Event.TRACKED_ACHIEVEMENT_UPDATE = function()
+  return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT
+end
+Event.TRACKED_ACHIEVEMENT_LIST_CHANGED = function(achievementID, added)
+  if ( added ) then
+    return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT_ADDED, achievementID
+  else
+    return OBJECTIVE_TRACKER_UPDATE_ACHIEVEMENT
+  end
+end
+Event.ZONE_CHANGED_NEW_AREA = function ()
+  if ( not WorldMapFrame:IsShown() and GetCVarBool("questPOI") ) then
+    SetMapToCurrentZone();			-- update the zone to get the right POI numbers for the tracker
+  end
+  SortQuestWatches();
+  T.currentZoneArea = GetCurrentMapAreaID()
+  print('Updating zone ID to', T.currentZoneArea, '=', GetZoneText(), GetMinimapZoneText())
+
+
+  return OBJECTIVE_TRACKER_UPDATE_TASK_ADDED
+end
+
+
+Event.PLAYER_MONEY = function()
+  if T.watchMoneyReasons > 0 then
+    return T.watchMoneyReasons
+  end
+end
+Event.CRITERIA_COMPLETE = function()
+  return OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE
+end
+Event.QUEST_TURN_IN = function(questID, xp, money)
+  if ( IsQuestTask(questID) ) then
+    T.Bonus:OnTurnIn(questID, xp, money)
+    print('updating bonus modules (code', OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE, ',', questID, xp, money)
+    return OBJECTIVE_TRACKER_UPDATE_MODULE_BONUS_OBJECTIVE, questID, xp, money
+  else
+    return OBJECTIVE_TRACKER_UPDATE_MODULE_QUEST, questID, xp, money
+  end
+end
+T.Event = Event
+
+--- Done once per ui load
+local BlizzHooks = {
+  ['AddQuestWatch'] = 'AddQuestWatch',
+  ['RemoveQuestWatch'] = 'RemoveQuestWatch',
+  ['AbandonQuest'] = 'AbandonQuest',
+  ['AcknowledgeAutoAcceptQuest'] = 'AcknowledgeAutoAcceptQuest',
+  ['AddAutoQuestPopUp'] = 'AddAutoQuestPopUp',
+  ['RemoveAutoQuestPopUp'] = 'RemoveAutoQuestPopUp',
+  ['AddTrackedAchievement'] = 'AddTrackedAchievement',
+  ['RemoveTrackedAchievement'] = 'RemoveTrackedAchievement',
+  ['SetSuperTrackedQuestID'] = 'SetSuperTrackedQuestID'
+}
+local VeneerData
+
+T.SetWatchMoney = function(watchMoney, reason)
+  if watchMoney then
+    if band(T.watchMoneyReasons, reason) == 0 then
+      T.watchMoneyReasons = T.watchMoneyReasons + reason;
+    end
+  else
+    if band(T.watchMoneyReasons, reason) > 0 then
+      T.watchMoneyReasons = T.watchMoneyReasons - reason;
+    end
+  end
+end
+T.animateReasons = 0
+T.SetAnimate = function(reason)
+  print('comparing', T.animateReasons, reason)
+  if animate then
+    if band(T.animateReasons, reason) == 0 then
+      T.animateReasons = T.animateReasons + reason
+    end
+  else
+    if band(T.animateReasons, reason) > 0 then
+      T.animateReasons = T.animateReasons - reason
+    end
+  end
+end
+
+local Play = function(file) if Devian and Devian.InWorkspace() then PlaySoundFile(file) end end
+
+function T:OnEvent (event, ...)
+  local isHandled
+  print('OnEvent(|cFF00FF00'.. event ..'|r):', ...)
+  local reason, arg1, arg2, arg3
+  if Event[event] then
+    if type(Event[event]) == 'function' then
+      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Info.ogg]])
+      reason, arg1, arg2, arg3 = Event[event](...)
+    elseif type(Event[event]) == 'table' then
+      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Link.ogg]])
+      for i, action in ipairs(Event[event]) do
+        if type(action) == 'function' then
+          reason, arg1, arg2, arg3 = action(event, ...)
+        else
+          reason = action
+        end
+
+        if reason then
+          T:Update(reason, arg1, arg2, arg3)
+        end
+      end
+    else
+      Play([[Interface\Addons\SharedMedia_MyMedia\sound\Heart.ogg]])
+      reason = Event[event]
+    end
+  else
+    Play([[Interface\Addons\SharedMedia_MyMedia\sound\Quack.ogg]])
+  end
+  if reason then
+    T:Update(reason, arg1, arg2, arg3)
+  else
+    print('no reason value returned')
+    Play([[Interface\Addons\SharedMedia_MyMedia\sound\Quack.ogg]])
+  end
+
+end
+
+
+function T:OnInitialize()
+  local c = T.Conf.Wrapper
+  VeneerData = _G.VeneerData
+  VeneerData.CallLog = VeneerData.CallLog or {}
+  if not T.isHooked then
+    T.isHooked = true
+    for blizzFunc, veneerFunc in pairs(BlizzHooks) do
+      if T[veneerFunc] then
+        hooksecurefunc(blizzFunc, T[veneerFunc])
+      else
+        hooksecurefunc(blizzFunc, function(...)
+          print('|cFFFF0088securehook('..tostring(blizzFunc)..')|r args:', ...)
+          tinsert(VeneerData.CallLog, {blizzFunc, ...})
+        end)
+      end
+    end
+  end
+
+  T.Conf.TasksLog = T.Conf.TasksLog or {}
+
+  ObjectiveTrackerFrame:UnregisterAllEvents()
+  ObjectiveTrackerFrame:Hide()
+
+
+  for id, name in ipairs(T.orderedNames) do
+    if not T.orderedHandlers[id] then
+      Handler_Initialize(T.DefaultHandler, name, id)
+    end
+  end
+  self:SetSize(c.Width, 40)
+  T.InitializeWidgets()
+end
+
+--- Done any time the the minimize button is toggled up
+function T:OnEnable()
+
+  print(B.Conf.VeneerObjectiveWrapper.enabled)
+  if not B.Conf.VeneerObjectiveWrapper.enabled then
+    return
+  end
+
+  for event, action in pairs(Event) do
+    print('|cFFFF0088listen to', event, 'for action|r', tostring(action))
+    Wrapper:RegisterEvent(event)
+  end
+
+  local c = T.Conf.Wrapper
+
+  Scroller:SetScrollChild(Scroll)
+  Scroller:SetWidth(c.Width)
+  Scroll:SetWidth(c.Width)
+  Scroll:ClearAllPoints()
+  Scroll:SetPoint('TOP', Scroller, 'TOP')
+  self:SetScript('OnEvent', T.OnEvent)
+
+  Scroller:Show()
+
+  local from, target, to, x, y = Wrapper:GetPoint(1)
+  print(from, target:GetName(), to, x,y)
+
+  T:Update()
+
+  -- run once to prime the data structure
+  T.UpdateActionButtons()
+end
+
+function T:OnDisable()
+  self:UnregisterAllEvents()
+  Scroller:Hide()
+end
+