diff --git a/Broker_Equipment.lua b/Broker_Equipment.lua index 7e32405..16acb42 100644 --- a/Broker_Equipment.lua +++ b/Broker_Equipment.lua @@ -1,220 +1,220 @@ ---[[ - - Copyright (c) 2009 Adrian L Lange <adrianlund@gmail.com> - All rights reserved. - - You're allowed to use this addon, free of monetary charge, - but you are not allowed to modify, alter, or redistribute - this addon without express, written permission of the author. - ---]] - -local L = {} -if(GetLocale() == 'deDE') then -- Katharsis / copystring - L.NOSET = 'Kein set' - L.TOOLTIP = 'Klicke links um dein set zu ändern\nKlicke rechts um den GearManager zu öffnen' - L.HINTS = {' ', '|cff00ff00Shift-klicke um den set zu aktualisieren\nStrg-klicke um den set zu löschen|r'} -elseif(GetLocale() == 'frFR') then -- Soeters / Gnaf - L.NOSET = 'Pas de set' - L.TOOLTIP = 'Clic gauche pour changer d\'équipement\nClic droit pour ouvrir le gestionnaire d\'équipement' - L.HINTS = {' ', '|cff00ff00Maj-clic pour mettre à jour le set\nCtrl-clic pour supprimer le set|r'} -elseif(GetLocale() == 'zhCN') then -- yleaf - L.NOSET = '无套装' - L.TOOLTIP = '左键点击切换套装\n右键打开套装管理器' - L.HINTS = {' ', '|cff00ff00Shift点击覆盖套装\nCtrl点击删除套装|r'} -elseif(GetLocale() == 'zhTW') then -- yleaf - L.NOSET = '無套裝' - L.TOOLTIP = '左鍵點擊切換套裝\n右鍵點擊打開套裝管理器' - L.HINTS = {' ', '|cff00ff00Shift點擊覆蓋套裝\nCtrl點擊刪除套裝|r'} -elseif(GetLocale() == 'koKR') then -- mrgyver - L.NOSET = '세트 없음' - L.TOOLTIP = '좌-클릭 세트 변경\n우-클릭 장비 관리창 열기' - L.HINTS = {' ', '|cff00ff00Shift-클릭 하면 세트 업데이트\nCtrl-클릭 하면 세트 삭제|r'} -else - L.NOSET = 'No set' - L.TOOLTIP = 'Left-click to change your set\nRight-click to open GearManager' - L.HINTS = {' ', '|cff00ff00Shift-click to update set\nCtrl-click to delete set|r'} -end - - -local menu = {} -local pendingUpdate = true -local pendingName = nil - -local addon = CreateFrame('Frame', 'Broker_EquipmentMenu', UIParent, 'UIDropDownMenuTemplate') -local broker = LibStub('LibDataBroker-1.1'):NewDataObject('Broker_Equipment', { - type = 'data source', - text = L.NOSET, - icon = [=[Interface\PaperDollInfoFrame\UI-EquipmentManager-Toggle]=], - iconCoords = {0.065, 0.935, 0.065, 0.935} -}) - --- Borrowed from tekkub's EquipSetUpdater -local function GetTextureIndex(tex) - RefreshEquipmentSetIconInfo() - tex = tex:lower() - local numicons = GetNumMacroIcons() - for i=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do if GetInventoryItemTexture("player", i) then numicons = numicons + 1 end end - for i=1,numicons do - local texture, index = GetEquipmentSetIconInfo(i) - if texture:lower() == tex then return index end - end -end - -local function matchEquipped(name) - for k, v in next, GetEquipmentSetItemIDs(name) do - local link = GetInventoryItemLink('player', k) - if(link) then - local id = tonumber(string.match(link, 'item:(%d+)')) - if(id ~= v) then - return - end - else - if(v ~= 0) then - return - end - end - end - - return true -end - -local function handleClick(name, icon) - if(IsShiftKeyDown()) then - local dialog = StaticPopup_Show('CUSTOM_OVERWRITE_EQUIPMENT_SET', name) -- Custom popup to update the info - dialog.name = name - dialog.icon = icon - elseif(IsControlKeyDown()) then - local dialog = StaticPopup_Show('CONFIRM_DELETE_EQUIPMENT_SET', name) - dialog.data = name - else - EquipmentManager_EquipSet(name) - - if(InCombatLockdown()) then - pendingName = name - addon:RegisterEvent('PLAYER_REGEN_ENABLED') - end - end -end - -local function updateInfo(name, icon) - broker.text = InCombatLockdown() and '|cffff0000'..name or name - broker.icon = icon - - Broker_EquipmentDB.text = name - Broker_EquipmentDB.icon = icon -end - -local function updateMenu() - pendingUpdate = nil - menu = wipe(menu) - - local temp = {text = '|cff0090ffBroker Equipment|r\n', isTitle = true} - table.insert(menu, temp) - - for index = 1, GetNumEquipmentSets() do - local name, icon = GetEquipmentSetInfo(index) - temp = { - notCheckable = true, - text = name, - icon = icon, - func = function() handleClick(name, icon) end - } - table.insert(menu, temp) - end - - for index = 1, 2 do - temp = { - text = L.HINTS[index], - notCheckable = true, - disabled = true - } - table.insert(menu, temp) - end -end - --- Fuck blizzard! -StaticPopupDialogs.CUSTOM_OVERWRITE_EQUIPMENT_SET = { - text = CONFIRM_OVERWRITE_EQUIPMENT_SET, - button1 = YES, - button2 = NO, - OnAccept = function(self) SaveEquipmentSet(self.name, GetTextureIndex(self.icon)); GearManagerDialogPopup:Hide() updateInfo(self.name, self.icon) end, - OnCancel = function() end, - OnHide = function(self) self.name, self.icon = nil, nil end, - hideOnEscape = 1, - timeout = 0, - exclusive = 1, -} - -function broker:OnClick(button) - if(button == 'RightButton') then - if(GearManagerDialog:IsVisible()) then - if(PaperDollFrame:IsVisible()) then - ToggleCharacter('PaperDollFrame') - end - GearManagerDialog:Hide() - else - if(not PaperDollFrame:IsVisible()) then - ToggleCharacter('PaperDollFrame') - end - GearManagerDialog:Show() - end - elseif(GetNumEquipmentSets() > 0) then - if(pendingUpdate) then updateMenu() end - EasyMenu(menu, addon, self, 0, 0, 'MENU') - - if(GameTooltip:GetOwner() == self) then GameTooltip:Hide() end - end -end - -function broker:OnTooltipShow() - self:AddLine('|cff0090ffBroker Equipment|r') - self:AddLine(L.TOOLTIP) -end - -function addon:PLAYER_REGEN_ENABLED(event) - EquipmentManager_EquipSet(pendingName) - pendingName = nil - self:UnregisterEvent(event) -end - -function addon:ADDON_LOADED(event, addon) - if(addon ~= 'Broker_Equipment') then return end - - Broker_EquipmentDB = Broker_EquipmentDB or {text = L.NOSET, icon = broker.icon} - broker.text = Broker_EquipmentDB.text - broker.icon = Broker_EquipmentDB.icon - - self:RegisterEvent('EQUIPMENT_SETS_CHANGED') - self:RegisterEvent('UNIT_INVENTORY_CHANGED') - self:RegisterEvent('VARIABLES_LOADED') - self:UnregisterEvent(event) -end - -function addon:EQUIPMENT_SETS_CHANGED() - pendingUpdate = true -end - -function addon:UNIT_INVENTORY_CHANGED(event, unit) - if(unit ~= 'player') then return end - - for index = 1, GetNumEquipmentSets() do - local name, icon = GetEquipmentSetInfo(index) - if(matchEquipped(name)) then - updateInfo(name, icon) - break - else - updateInfo(UNKNOWN, [=[Interface\Icons\INV_Misc_QuestionMark]=]) - end - end -end - -function addon:VARIABLES_LOADED(event) - SetCVar('equipmentManager', 1) - GearManagerToggleButton:Show() - - self:UnregisterEvent(event) -end - -addon:RegisterEvent('ADDON_LOADED') -addon:SetScript('OnEvent', function(self, event, ...) self[event](self, event, ...) end) \ No newline at end of file +--[[ + + Copyright (c) 2009 Adrian L Lange <adrianlund@gmail.com> + All rights reserved. + + You're allowed to use this addon, free of monetary charge, + but you are not allowed to modify, alter, or redistribute + this addon without express, written permission of the author. + +--]] + +local L = {} +if(GetLocale() == 'deDE') then -- Katharsis / copystring + L.NOSET = 'Kein set' + L.TOOLTIP = 'Klicke links um dein set zu ändern\nKlicke rechts um den GearManager zu öffnen' + L.HINTS = {' ', '|cff00ff00Shift-klicke um den set zu aktualisieren\nStrg-klicke um den set zu löschen|r'} +elseif(GetLocale() == 'frFR') then -- Soeters / Gnaf + L.NOSET = 'Pas de set' + L.TOOLTIP = 'Clic gauche pour changer d\'équipement\nClic droit pour ouvrir le gestionnaire d\'équipement' + L.HINTS = {' ', '|cff00ff00Maj-clic pour mettre à jour le set\nCtrl-clic pour supprimer le set|r'} +elseif(GetLocale() == 'zhCN') then -- yleaf + L.NOSET = '无套装' + L.TOOLTIP = '左键点击切换套装\n右键打开套装管理器' + L.HINTS = {' ', '|cff00ff00Shift点击覆盖套装\nCtrl点击删除套装|r'} +elseif(GetLocale() == 'zhTW') then -- yleaf + L.NOSET = '無套裝' + L.TOOLTIP = '左鍵點擊切換套裝\n右鍵點擊打開套裝管理器' + L.HINTS = {' ', '|cff00ff00Shift點擊覆蓋套裝\nCtrl點擊刪除套裝|r'} +elseif(GetLocale() == 'koKR') then -- mrgyver + L.NOSET = '세트 없음' + L.TOOLTIP = '좌-클릭 세트 변경\n우-클릭 장비 관리창 열기' + L.HINTS = {' ', '|cff00ff00Shift-클릭 하면 세트 업데이트\nCtrl-클릭 하면 세트 삭제|r'} +else + L.NOSET = 'No set' + L.TOOLTIP = 'Left-click to change your set\nRight-click to open GearManager' + L.HINTS = {' ', '|cff00ff00Shift-click to update set\nCtrl-click to delete set|r'} +end + + +local menu = {} +local pendingUpdate = true +local pendingName = nil + +local addon = CreateFrame('Frame', 'Broker_EquipmentMenu', UIParent, 'UIDropDownMenuTemplate') +local broker = LibStub('LibDataBroker-1.1'):NewDataObject('Broker_Equipment', { + type = 'data source', + text = L.NOSET, + icon = [=[Interface\PaperDollInfoFrame\UI-EquipmentManager-Toggle]=], + iconCoords = {0.065, 0.935, 0.065, 0.935} +}) + +-- Borrowed from tekkub's EquipSetUpdater +local function GetTextureIndex(tex) + RefreshEquipmentSetIconInfo() + tex = tex:lower() + local numicons = GetNumMacroIcons() + for i=INVSLOT_FIRST_EQUIPPED,INVSLOT_LAST_EQUIPPED do if GetInventoryItemTexture("player", i) then numicons = numicons + 1 end end + for i=1,numicons do + local texture, index = GetEquipmentSetIconInfo(i) + if texture:lower() == tex then return index end + end +end + +local function matchEquipped(name) + for k, v in next, GetEquipmentSetItemIDs(name) do + local link = GetInventoryItemLink('player', k) + if(link) then + local id = tonumber(string.match(link, 'item:(%d+)')) + if(id ~= v) then + return + end + else + if(v ~= 0) then + return + end + end + end + + return true +end + +local function handleClick(name, icon) + if(IsShiftKeyDown()) then + local dialog = StaticPopup_Show('CUSTOM_OVERWRITE_EQUIPMENT_SET', name) -- Custom popup to update the info + dialog.name = name + dialog.icon = icon + elseif(IsControlKeyDown()) then + local dialog = StaticPopup_Show('CONFIRM_DELETE_EQUIPMENT_SET', name) + dialog.data = name + else + EquipmentManager_EquipSet(name) + + if(InCombatLockdown()) then + pendingName = name + addon:RegisterEvent('PLAYER_REGEN_ENABLED') + end + end +end + +local function updateInfo(name, icon) + broker.text = InCombatLockdown() and '|cffff0000'..name or name + broker.icon = icon + + Broker_EquipmentDB.text = name + Broker_EquipmentDB.icon = icon +end + +local function updateMenu() + pendingUpdate = nil + menu = wipe(menu) + + local temp = {text = '|cff0090ffBroker Equipment|r\n', isTitle = true} + table.insert(menu, temp) + + for index = 1, GetNumEquipmentSets() do + local name, icon = GetEquipmentSetInfo(index) + temp = { + notCheckable = true, + text = name, + icon = icon, + func = function() handleClick(name, icon) end + } + table.insert(menu, temp) + end + + for index = 1, 2 do + temp = { + text = L.HINTS[index], + notCheckable = true, + disabled = true + } + table.insert(menu, temp) + end +end + +-- Fuck blizzard! +StaticPopupDialogs.CUSTOM_OVERWRITE_EQUIPMENT_SET = { + text = CONFIRM_OVERWRITE_EQUIPMENT_SET, + button1 = YES, + button2 = NO, + OnAccept = function(self) SaveEquipmentSet(self.name, GetTextureIndex(self.icon)); GearManagerDialogPopup:Hide() updateInfo(self.name, self.icon) end, + OnCancel = function() end, + OnHide = function(self) self.name, self.icon = nil, nil end, + hideOnEscape = 1, + timeout = 0, + exclusive = 1, +} + +function broker:OnClick(button) + if(button == 'RightButton') then + if(GearManagerDialog:IsVisible()) then + if(PaperDollFrame:IsVisible()) then + ToggleCharacter('PaperDollFrame') + end + GearManagerDialog:Hide() + else + if(not PaperDollFrame:IsVisible()) then + ToggleCharacter('PaperDollFrame') + end + GearManagerDialog:Show() + end + elseif(GetNumEquipmentSets() > 0) then + if(pendingUpdate) then updateMenu() end + EasyMenu(menu, addon, self, 0, 0, 'MENU') + + if(GameTooltip:GetOwner() == self) then GameTooltip:Hide() end + end +end + +function broker:OnTooltipShow() + self:AddLine('|cff0090ffBroker Equipment|r') + self:AddLine(L.TOOLTIP) +end + +function addon:PLAYER_REGEN_ENABLED(event) + EquipmentManager_EquipSet(pendingName) + pendingName = nil + self:UnregisterEvent(event) +end + +function addon:ADDON_LOADED(event, addon) + if(addon ~= 'Broker_Equipment') then return end + + Broker_EquipmentDB = Broker_EquipmentDB or {text = L.NOSET, icon = broker.icon} + broker.text = Broker_EquipmentDB.text + broker.icon = Broker_EquipmentDB.icon + + self:RegisterEvent('EQUIPMENT_SETS_CHANGED') + self:RegisterEvent('UNIT_INVENTORY_CHANGED') + self:RegisterEvent('VARIABLES_LOADED') + self:UnregisterEvent(event) +end + +function addon:EQUIPMENT_SETS_CHANGED() + pendingUpdate = true +end + +function addon:UNIT_INVENTORY_CHANGED(event, unit) + if(unit ~= 'player') then return end + + for index = 1, GetNumEquipmentSets() do + local name, icon = GetEquipmentSetInfo(index) + if(matchEquipped(name)) then + updateInfo(name, icon) + break + else + updateInfo(UNKNOWN, [=[Interface\Icons\INV_Misc_QuestionMark]=]) + end + end +end + +function addon:VARIABLES_LOADED(event) + SetCVar('equipmentManager', 1) + GearManagerToggleButton:Show() + + self:UnregisterEvent(event) +end + +addon:RegisterEvent('ADDON_LOADED') +addon:SetScript('OnEvent', function(self, event, ...) self[event](self, event, ...) end) diff --git a/Broker_Equipment.toc b/Broker_Equipment.toc index 00ed6e4..3dad710 100644 --- a/Broker_Equipment.toc +++ b/Broker_Equipment.toc @@ -1,12 +1,12 @@ -## Interface: 30100 -## Author: p3lim -## Version: 30100.wowi:revision -## Title: Broker Equipment -## Notes: LDB Equipment Manager plug-in -## SavedVariablesPerCharacter: Broker_EquipmentDB - -libs\LibStub.lua -libs\CallbackHandler-1.0.lua -libs\LibDataBroker-1.1.lua - -Broker_Equipment.lua \ No newline at end of file +## Interface: 30100 +## Author: p3lim +## Version: Alpha +## Title: Broker Equipment +## Notes: LDB Equipment Manager plug-in +## SavedVariablesPerCharacter: Broker_EquipmentDB + +libs\LibStub.lua +libs\CallbackHandler-1.0.lua +libs\LibDataBroker-1.1.lua + +Broker_Equipment.lua diff --git a/libs/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0.lua index 5a495f6..9016d1e 100644 --- a/libs/CallbackHandler-1.0.lua +++ b/libs/CallbackHandler-1.0.lua @@ -1,239 +1,239 @@ ---[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]] -local MAJOR, MINOR = "CallbackHandler-1.0", 3 -local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) - -if not CallbackHandler then return end -- No upgrade needed - -local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} - -local type = type -local pcall = pcall -local pairs = pairs -local assert = assert -local concat = table.concat -local loadstring = loadstring -local next = next -local select = select -local type = type -local xpcall = xpcall - -local function errorhandler(err) - return geterrorhandler()(err) -end - -local function CreateDispatcher(argCount) - local code = [[ - local next, xpcall, eh = ... - - local method, ARGS - local function call() method(ARGS) end - - local function dispatch(handlers, ...) - local index - index, method = next(handlers) - if not method then return end - local OLD_ARGS = ARGS - ARGS = ... - repeat - xpcall(call, eh) - index, method = next(handlers, index) - until not method - ARGS = OLD_ARGS - end - - return dispatch - ]] - - local ARGS, OLD_ARGS = {}, {} - for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end - code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) - return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) -end - -local Dispatchers = setmetatable({}, {__index=function(self, argCount) - local dispatcher = CreateDispatcher(argCount) - rawset(self, argCount, dispatcher) - return dispatcher -end}) - --------------------------------------------------------------------------- --- CallbackHandler:New --- --- target - target object to embed public APIs in --- RegisterName - name of the callback registration API, default "RegisterCallback" --- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" --- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. - -function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) - -- TODO: Remove this after beta has gone out - assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") - - RegisterName = RegisterName or "RegisterCallback" - UnregisterName = UnregisterName or "UnregisterCallback" - if UnregisterAllName==nil then -- false is used to indicate "don't want this method" - UnregisterAllName = "UnregisterAllCallbacks" - end - - -- we declare all objects and exported APIs inside this closure to quickly gain access - -- to e.g. function names, the "target" parameter, etc - - - -- Create the registry object - local events = setmetatable({}, meta) - local registry = { recurse=0, events=events } - - -- registry:Fire() - fires the given event/message into the registry - function registry:Fire(eventname, ...) - if not rawget(events, eventname) or not next(events[eventname]) then return end - local oldrecurse = registry.recurse - registry.recurse = oldrecurse + 1 - - Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) - - registry.recurse = oldrecurse - - if registry.insertQueue and oldrecurse==0 then - -- Something in one of our callbacks wanted to register more callbacks; they got queued - for eventname,callbacks in pairs(registry.insertQueue) do - local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. - for self,func in pairs(callbacks) do - events[eventname][self] = func - -- fire OnUsed callback? - if first and registry.OnUsed then - registry.OnUsed(registry, target, eventname) - first = nil - end - end - end - registry.insertQueue = nil - end - end - - -- Registration of a callback, handles: - -- self["method"], leads to self["method"](self, ...) - -- self with function ref, leads to functionref(...) - -- "addonId" (instead of self) with function ref, leads to functionref(...) - -- all with an optional arg, which, if present, gets passed as first argument (after self if present) - target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) - if type(eventname) ~= "string" then - error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) - end - - method = method or eventname - - local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. - - if type(method) ~= "string" and type(method) ~= "function" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) - end - - local regfunc - - if type(method) == "string" then - -- self["method"] calling style - if type(self) ~= "table" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) - elseif self==target then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) - elseif type(self[method]) ~= "function" then - error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) - end - - if select("#",...)>=1 then -- this is not the same as testing for arg==nil! - local arg=select(1,...) - regfunc = function(...) self[method](self,arg,...) end - else - regfunc = function(...) self[method](self,...) end - end - else - -- function ref with self=object or self="addonId" - if type(self)~="table" and type(self)~="string" then - error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) - end - - if select("#",...)>=1 then -- this is not the same as testing for arg==nil! - local arg=select(1,...) - regfunc = function(...) method(arg,...) end - else - regfunc = method - end - end - - - if events[eventname][self] or registry.recurse<1 then - -- if registry.recurse<1 then - -- we're overwriting an existing entry, or not currently recursing. just set it. - events[eventname][self] = regfunc - -- fire OnUsed callback? - if registry.OnUsed and first then - registry.OnUsed(registry, target, eventname) - end - else - -- we're currently processing a callback in this registry, so delay the registration of this new entry! - -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency - registry.insertQueue = registry.insertQueue or setmetatable({},meta) - registry.insertQueue[eventname][self] = regfunc - end - end - - -- Unregister a callback - target[UnregisterName] = function(self, eventname) - if not self or self==target then - error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) - end - if type(eventname) ~= "string" then - error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) - end - if rawget(events, eventname) and events[eventname][self] then - events[eventname][self] = nil - -- Fire OnUnused callback? - if registry.OnUnused and not next(events[eventname]) then - registry.OnUnused(registry, target, eventname) - end - end - if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then - registry.insertQueue[eventname][self] = nil - end - end - - -- OPTIONAL: Unregister all callbacks for given selfs/addonIds - if UnregisterAllName then - target[UnregisterAllName] = function(...) - if select("#",...)<1 then - error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) - end - if select("#",...)==1 and ...==target then - error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) - end - - - for i=1,select("#",...) do - local self = select(i,...) - if registry.insertQueue then - for eventname, callbacks in pairs(registry.insertQueue) do - if callbacks[self] then - callbacks[self] = nil - end - end - end - for eventname, callbacks in pairs(events) do - if callbacks[self] then - callbacks[self] = nil - -- Fire OnUnused callback? - if registry.OnUnused and not next(callbacks) then - registry.OnUnused(registry, target, eventname) - end - end - end - end - end - end - - return registry -end - - --- CallbackHandler purposefully does NOT do explicit embedding. Nor does it --- try to upgrade old implicit embeds since the system is selfcontained and --- relies on closures to work. - +--[[ $Id: CallbackHandler-1.0.lua 3 2008-09-29 16:54:20Z nevcairiel $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 3 +local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) + +if not CallbackHandler then return end -- No upgrade needed + +local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} + +local type = type +local pcall = pcall +local pairs = pairs +local assert = assert +local concat = table.concat +local loadstring = loadstring +local next = next +local select = select +local type = type +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local next, xpcall, eh = ... + + local method, ARGS + local function call() method(ARGS) end + + local function dispatch(handlers, ...) + local index + index, method = next(handlers) + if not method then return end + local OLD_ARGS = ARGS + ARGS = ... + repeat + xpcall(call, eh) + index, method = next(handlers, index) + until not method + ARGS = OLD_ARGS + end + + return dispatch + ]] + + local ARGS, OLD_ARGS = {}, {} + for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end + code = code:gsub("OLD_ARGS", concat(OLD_ARGS, ", ")):gsub("ARGS", concat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) + +-------------------------------------------------------------------------- +-- CallbackHandler:New +-- +-- target - target object to embed public APIs in +-- RegisterName - name of the callback registration API, default "RegisterCallback" +-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" +-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. + +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) + -- TODO: Remove this after beta has gone out + assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") + + RegisterName = RegisterName or "RegisterCallback" + UnregisterName = UnregisterName or "UnregisterCallback" + if UnregisterAllName==nil then -- false is used to indicate "don't want this method" + UnregisterAllName = "UnregisterAllCallbacks" + end + + -- we declare all objects and exported APIs inside this closure to quickly gain access + -- to e.g. function names, the "target" parameter, etc + + + -- Create the registry object + local events = setmetatable({}, meta) + local registry = { recurse=0, events=events } + + -- registry:Fire() - fires the given event/message into the registry + function registry:Fire(eventname, ...) + if not rawget(events, eventname) or not next(events[eventname]) then return end + local oldrecurse = registry.recurse + registry.recurse = oldrecurse + 1 + + Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) + + registry.recurse = oldrecurse + + if registry.insertQueue and oldrecurse==0 then + -- Something in one of our callbacks wanted to register more callbacks; they got queued + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func + -- fire OnUsed callback? + if first and registry.OnUsed then + registry.OnUsed(registry, target, eventname) + first = nil + end + end + end + registry.insertQueue = nil + end + end + + -- Registration of a callback, handles: + -- self["method"], leads to self["method"](self, ...) + -- self with function ref, leads to functionref(...) + -- "addonId" (instead of self) with function ref, leads to functionref(...) + -- all with an optional arg, which, if present, gets passed as first argument (after self if present) + target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) + if type(eventname) ~= "string" then + error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) + end + + method = method or eventname + + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + + if type(method) ~= "string" and type(method) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) + end + + local regfunc + + if type(method) == "string" then + -- self["method"] calling style + if type(self) ~= "table" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) + elseif self==target then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) + elseif type(self[method]) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) self[method](self,arg,...) end + else + regfunc = function(...) self[method](self,...) end + end + else + -- function ref with self=object or self="addonId" + if type(self)~="table" and type(self)~="string" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string expected.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) method(arg,...) end + else + regfunc = method + end + end + + + if events[eventname][self] or registry.recurse<1 then + -- if registry.recurse<1 then + -- we're overwriting an existing entry, or not currently recursing. just set it. + events[eventname][self] = regfunc + -- fire OnUsed callback? + if registry.OnUsed and first then + registry.OnUsed(registry, target, eventname) + end + else + -- we're currently processing a callback in this registry, so delay the registration of this new entry! + -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency + registry.insertQueue = registry.insertQueue or setmetatable({},meta) + registry.insertQueue[eventname][self] = regfunc + end + end + + -- Unregister a callback + target[UnregisterName] = function(self, eventname) + if not self or self==target then + error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) + end + if type(eventname) ~= "string" then + error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) + end + if rawget(events, eventname) and events[eventname][self] then + events[eventname][self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(events[eventname]) then + registry.OnUnused(registry, target, eventname) + end + end + if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then + registry.insertQueue[eventname][self] = nil + end + end + + -- OPTIONAL: Unregister all callbacks for given selfs/addonIds + if UnregisterAllName then + target[UnregisterAllName] = function(...) + if select("#",...)<1 then + error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) + end + if select("#",...)==1 and ...==target then + error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) + end + + + for i=1,select("#",...) do + local self = select(i,...) + if registry.insertQueue then + for eventname, callbacks in pairs(registry.insertQueue) do + if callbacks[self] then + callbacks[self] = nil + end + end + end + for eventname, callbacks in pairs(events) do + if callbacks[self] then + callbacks[self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(callbacks) then + registry.OnUnused(registry, target, eventname) + end + end + end + end + end + end + + return registry +end + + +-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it +-- try to upgrade old implicit embeds since the system is selfcontained and +-- relies on closures to work. + diff --git a/libs/LibStub.lua b/libs/LibStub.lua index cfc97de..0a41ac0 100644 --- a/libs/LibStub.lua +++ b/libs/LibStub.lua @@ -1,30 +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 +-- 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