From e749324ad96ef7de07edc1e4a51c74806e5138fd Mon Sep 17 00:00:00 2001 From: Xruptor Date: Sun, 15 Aug 2010 10:30:47 -0400 Subject: [PATCH] -Added support for LibDataBroker-1.1. BagSync now has a broker icon to launch the search window. -Added support to color names by unit class. -New slash command /bgs unitclass will color names by color. OFF by default. -Changed the way slash commands are handled. Much cleaner code ;) -Fixed an issue where the throttle wasn't always allowing the information to show on certain tooltips. -Fixed an issue where sometimes the throttling wasn't initiated. -Added support for CallbackHandler-1.0.lua. -Fixed an issue in the BagSync Search window where the scroll bar was off the frame sometimes. -Fixed an issue that prevent users from linking items from the BagSync Search window. -Added a few helper functions. --- BagSync.lua | 144 ++++++++++++++++--------- BagSync.toc | 12 ++- BagSync_Search.lua | 10 +- libs/CallbackHandler-1.0.lua | 239 +++++++++++++++++++++++++++++++++++++++++ libs/LibDataBroker-1.1.lua | 90 ++++++++++++++++ localization/localization.lua | 1 + 6 files changed, 437 insertions(+), 59 deletions(-) create mode 100644 libs/CallbackHandler-1.0.lua create mode 100644 libs/LibDataBroker-1.1.lua diff --git a/BagSync.lua b/BagSync.lua index 2758189..66ea8ee 100644 --- a/BagSync.lua +++ b/BagSync.lua @@ -36,6 +36,37 @@ local MOSS = '|cFF80FF00%s|r' local TTL_C = '|cFFF4A460%s|r' local GN_C = '|cFF65B8C0%s|r' +------------------------------ +-- LibDataBroker-1.1 -- +------------------------------ + +local ldb = LibStub:GetLibrary("LibDataBroker-1.1") + +local dataobj = ldb:NewDataObject("BagSyncLDB", { + type = "data source", + icon = "Interface\\Icons\\INV_Misc_Bag_12", + label = "BagSync", + text = "BagSync", + + OnClick = function(self, button) + if getglobal("BagSync_SearchFrame") then + if getglobal("BagSync_SearchFrame"):IsVisible() then + getglobal("BagSync_SearchFrame"):Hide() + else + getglobal("BagSync_SearchFrame"):Show() + end + end + end, + + OnTooltipShow = function(self) + self:Hide() + end +}) + +------------------------------ +-- MAIN OBJ -- +------------------------------ + local BagSync = CreateFrame("frame", "BagSync", UIParent) BagSync:SetScript('OnEvent', function(self, event, ...) @@ -81,6 +112,9 @@ function BagSync:PLAYER_LOGIN() --save the current user money (before bag update) BS_DB["gold:0:0"] = GetMoney() + --save the class information + BS_DB["class:0:0"] = playerClass + --check for player not in guild if IsInGuild() or GetNumGuildMembers(true) > 0 then GuildRoster() @@ -119,6 +153,15 @@ function BagSync:PLAYER_LOGIN() self:RegisterEvent('MAIL_SHOW') self:RegisterEvent('MAIL_INBOX_UPDATE') + local slashChk = { + [L["total"]] = "showTotal", + [L["guildname"]] = "showGuildNames", + [L["throttle"]] = "enableThrottle", + [L["guild"]] = "enableGuild", + [L["mailbox"]] = "enableMailbox", + [L["unitclass"]] = "enableUnitClass", + } + SLASH_BAGSYNC1 = "/bagsync" SLASH_BAGSYNC2 = "/bgs" SlashCmdList["BAGSYNC"] = function(msg) @@ -153,59 +196,14 @@ function BagSync:PLAYER_LOGIN() elseif c and c:lower() == L["fixdb"] then self:FixDB_Data() return true - elseif c and c:lower() == L["total"] then - lastDisplayed = {} - lastItem = nil - if BagSyncOpt.showTotal then - BagSyncOpt.showTotal = false - print("|cFFFF0000BagSync: "..L["Total:"].." "..L["OFF"]) - else - BagSyncOpt.showTotal = true - print("|cFFFF0000BagSync: "..L["Total:"].." "..L["ON"]) - end - return true - elseif c and c:lower() == L["guildname"] then - lastDisplayed = {} - lastItem = nil - if BagSyncOpt.showGuildNames then - BagSyncOpt.showGuildNames = false - print("|cFFFF0000BagSync: "..L["guildname"]..": "..L["OFF"]) - else - BagSyncOpt.showGuildNames = true - print("|cFFFF0000BagSync: "..L["guildname"]..": "..L["ON"]) - end - return true - elseif c and c:lower() == L["throttle"] then - lastDisplayed = {} - lastItem = nil - if BagSyncOpt.enableThrottle then - BagSyncOpt.enableThrottle = false - print("|cFFFF0000BagSync: "..L["throttle"]..": "..L["OFF"]) - else - BagSyncOpt.enableThrottle = true - print("|cFFFF0000BagSync: "..L["throttle"]..": "..L["ON"]) - end - return true - elseif c and c:lower() == L["guild"] then - lastDisplayed = {} - lastItem = nil - if BagSyncOpt.enableGuild then - BagSyncOpt.enableGuild = false - print("|cFFFF0000BagSync: "..L["guild"]..": "..L["OFF"]) - else - BagSyncOpt.enableGuild = true - print("|cFFFF0000BagSync: "..L["guild"]..": "..L["ON"]) - end - return true - elseif c and c:lower() == L["mailbox"] then + elseif c and slashChk[c:lower()] and BagSyncOpt[slashChk[c:lower()]] ~= nil then lastDisplayed = {} lastItem = nil - if BagSyncOpt.enableMailbox then - BagSyncOpt.enableMailbox = false - print("|cFFFF0000BagSync: "..L["mailbox"]..": "..L["OFF"]) + BagSyncOpt[slashChk[c:lower()]] = not BagSyncOpt[slashChk[c:lower()]] + if BagSyncOpt[slashChk[c:lower()]] then + print("|cFFFF0000BagSync: |cFFFFFFFF"..(c:lower()).."|r |cFFFF0000"..L["ON"].."|r") else - BagSyncOpt.enableMailbox = true - print("|cFFFF0000BagSync: "..L["mailbox"]..": "..L["ON"]) + print("|cFFFF0000BagSync:|r |cFFFFFFFF"..(c:lower()).."|r |cFFFF0000"..L["OFF"].."|r") end return true elseif c and c:lower() ~= "" then @@ -336,6 +334,7 @@ function BagSync:StartupDB() if BagSyncOpt.enableThrottle == nil then BagSyncOpt.enableThrottle = true end if BagSyncOpt.enableGuild == nil then BagSyncOpt.enableGuild = true end if BagSyncOpt.enableMailbox == nil then BagSyncOpt.enableMailbox = true end + if BagSyncOpt.enableUnitClass == nil then BagSyncOpt.enableUnitClass = false end BagSyncGUILD_DB = BagSyncGUILD_DB or {} BagSyncGUILD_DB[currentRealm] = BagSyncGUILD_DB[currentRealm] or {} @@ -1029,7 +1028,32 @@ local function pairsByKeys (t, f) return iter end +local function rgbhex(r, g, b) + if type(r) == "table" then + if r.r then + r, g, b = r.r, r.g, r.b + else + r, g, b = unpack(r) + end + end + return string.format("|cff%02x%02x%02x", (r or 1) * 255, (g or 1) * 255, (b or 1) * 255) +end + + +local function getNameColor(sName, sClass) + if not BagSyncOpt.enableUnitClass then + return format(MOSS, sName) + else + if sName ~= "Unknown" and sClass and RAID_CLASS_COLORS[sClass] then + return rgbhex(RAID_CLASS_COLORS[sClass])..sName.."|r" + end + end + return format(MOSS, sName) +end + local function AddOwners(frame, link) + frame.BagSyncShowOnce = nil + local itemLink = ToShortLink(link) if not itemLink then frame:Show() @@ -1111,12 +1135,15 @@ local function AddOwners(frame, link) end end + --get class for the unit if there is one + local pClass = v["class:0:0"] or nil + infoString = CountsToInfoString(invCount or 0, bankCount or 0, equipCount or 0, guildCount or 0, mailboxCount or 0) grandTotal = grandTotal + (invCount or 0) + (bankCount or 0) + (equipCount or 0) + (guildCount or 0) + (mailboxCount or 0) if infoString and infoString ~= '' then - frame:AddDoubleLine(format(MOSS, k), infoString) - table.insert(lastDisplayed, format(MOSS,(k or 'Unknown')).."@"..(infoString or 'unknown')) + frame:AddDoubleLine(getNameColor(k, pClass), infoString) + table.insert(lastDisplayed, getNameColor(k or 'Unknown', pClass).."@"..(infoString or 'unknown')) end end @@ -1150,14 +1177,17 @@ local function HookTip(tooltip) if BagSyncOpt.enableThrottle then if not self.BagSyncThrottle then self.BagSyncThrottle = GetTime() end if not self.BagSyncPrevious then self.BagSyncPrevious = itemName end + if not self.BagSyncShowOnce and self:GetName() == "GameTooltip" then self.BagSyncShowOnce = true end if itemName ~= self.BagSyncPrevious then self.BagSyncPrevious = itemName self.BagSyncThrottle = GetTime() end if self:GetName() == "GameTooltip" and (GetTime() - self.BagSyncThrottle) >= 0.05 then + self.BagSyncShowOnce = nil AddOwners(self, itemLink) elseif self:GetName() ~= "GameTooltip" then + self.BagSyncShowOnce = nil AddOwners(self, itemLink) end else @@ -1165,6 +1195,16 @@ local function HookTip(tooltip) end end end) + + tooltip:HookScript('OnUpdate', function(self, ...) + if self:GetName() == "GameTooltip" and self.BagSyncShowOnce and self.BagSyncThrottle and (GetTime() - self.BagSyncThrottle) >= 0.05 then + local _, itemLink = self:GetItem() + self.BagSyncShowOnce = nil + if itemLink then + AddOwners(self, itemLink) + end + end + end) end HookTip(GameTooltip) diff --git a/BagSync.toc b/BagSync.toc index 7cc1c0f..17b60d7 100644 --- a/BagSync.toc +++ b/BagSync.toc @@ -2,14 +2,16 @@ ## Title: BagSync ## Notes: BagSync tracks your characters items and displays it within tooltips. ## Author: Xruptor -## Version: 4.1 +## Version: 4.2 ## SavedVariables: BagSyncDB, BagSyncOpt, BagSyncGUILD_DB, BagSyncTOKEN_DB localization\localization.lua -Libs\LibStub.lua -Libs\tekKonfigScroll.lua -Libs\tekKonfigDropdown.lua -Libs\LibItemSearch-1.0.lua +libs\LibStub.lua +libs\CallbackHandler-1.0.lua +libs\tekKonfigScroll.lua +libs\tekKonfigDropdown.lua +libs\LibItemSearch-1.0.lua +libs\LibDataBroker-1.1.lua BagSync_Search.lua BagSync_Tokens.lua diff --git a/BagSync_Search.lua b/BagSync_Search.lua index d227167..ef7e02d 100644 --- a/BagSync_Search.lua +++ b/BagSync_Search.lua @@ -101,7 +101,7 @@ function bgSearch:LoadSlider() local EDGEGAP, ROWHEIGHT, ROWGAP, GAP = 16, 20, 2, 4 local FRAME_HEIGHT = bgSearch:GetHeight() - 60 - local SCROLL_TOP_POSITION = -110 + local SCROLL_TOP_POSITION = -90 local totalRows = math.floor((FRAME_HEIGHT-22)/(ROWHEIGHT + ROWGAP)) for i=1, totalRows do @@ -128,7 +128,13 @@ function bgSearch:LoadSlider() row:SetScript("OnClick", function(self) if self.link then if IsShiftKeyDown() then - ChatFrameEditBox:Insert(self.link) + for i = 1, NUM_CHAT_WINDOWS do + local editBox = _G[("ChatFrame%dEditBox"):format(i)] + if editBox:IsVisible() then + editBox:Insert(self.link) + break + end + end elseif IsControlKeyDown() then DressUpItemLink(self.link) end diff --git a/libs/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0.lua new file mode 100644 index 0000000..5ad658f --- /dev/null +++ b/libs/CallbackHandler-1.0.lua @@ -0,0 +1,239 @@ +--[[ $Id: CallbackHandler-1.0.lua 60548 2008-02-07 11:04:06Z 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/LibDataBroker-1.1.lua b/libs/LibDataBroker-1.1.lua new file mode 100644 index 0000000..f47c0cd --- /dev/null +++ b/libs/LibDataBroker-1.1.lua @@ -0,0 +1,90 @@ + +assert(LibStub, "LibDataBroker-1.1 requires LibStub") +assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0") + +local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4) +if not lib then return end +oldminor = oldminor or 0 + + +lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib) +lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {} +local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks + +if oldminor < 2 then + lib.domt = { + __metatable = "access denied", + __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end, + } +end + +if oldminor < 3 then + lib.domt.__newindex = function(self, key, value) + if not attributestorage[self] then attributestorage[self] = {} end + if attributestorage[self][key] == value then return end + attributestorage[self][key] = value + local name = namestorage[self] + if not name then return end + callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self) + callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self) + end +end + +if oldminor < 2 then + function lib:NewDataObject(name, dataobj) + if self.proxystorage[name] then return end + + if dataobj then + assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table") + self.attributestorage[dataobj] = {} + for i,v in pairs(dataobj) do + self.attributestorage[dataobj][i] = v + dataobj[i] = nil + end + end + dataobj = setmetatable(dataobj or {}, self.domt) + self.proxystorage[name], self.namestorage[dataobj] = dataobj, name + self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj) + return dataobj + end +end + +if oldminor < 1 then + function lib:DataObjectIterator() + return pairs(self.proxystorage) + end + + function lib:GetDataObjectByName(dataobjectname) + return self.proxystorage[dataobjectname] + end + + function lib:GetNameByDataObject(dataobject) + return self.namestorage[dataobject] + end +end + +if oldminor < 4 then + local next = pairs(attributestorage) + function lib:pairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return next, attributestorage[dataobj], nil + end + + local ipairs_iter = ipairs(attributestorage) + function lib:ipairs(dataobject_or_name) + local t = type(dataobject_or_name) + assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)") + + local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name + assert(attributestorage[dataobj], "Data object not found") + + return ipairs_iter, attributestorage[dataobj], 0 + end +end diff --git a/localization/localization.lua b/localization/localization.lua index bf1f7be..c6e2126 100644 --- a/localization/localization.lua +++ b/localization/localization.lua @@ -32,6 +32,7 @@ -- ["throttle"] = "", -- ["guild"] = "", -- ["mailbox"] = "", +-- ["unitclass"] = "", -- ["/bgs [itemname] - Does a quick search for an item"] = "", -- ["/bgs search - Opens the search window"] = "", -- ["/bgs gold - Displays a tooltip with the amount of gold on each character."] = "", -- 1.7.9.5