Quantcast

Merge branch 'feature/titan' into develop

Kevin Gilbert [08-29-16 - 18:46]
Merge branch 'feature/titan' into develop
Filename
ArtifactMenu.lua
ArtifactMenu.toc
README.md
libs/CallbackHandler-1.0.lua
libs/LibDataBroker-1.1.lua
libs/LibStub.lua
diff --git a/ArtifactMenu.lua b/ArtifactMenu.lua
index b418bb5..838e7f8 100644
--- a/ArtifactMenu.lua
+++ b/ArtifactMenu.lua
@@ -1,36 +1,107 @@
-SLASH_ARTIFACTMENU1, SLASH_ARTIFACTMENU2 = '/artifact', '/am';
-SLASH_ARTIFACTMENU3 = "/artifactmenu";
-
-local function openWeaponSockets()
-    SocketInventoryItem(16);
-end
-
--- unneeded, because artifacts are "conjoined" items
-local function openOffhandSockets()
-    SocketInventoryItem(17);
-end
-
-function SlashCmdList.ARTIFACTMENU(msg, editBox)
-    -- http://wowprogramming.com/docs/api_types#itemQuality
-    local QUALITY = {}
-    QUALITY.POOR = 0;
-    QUALITY.COMMON = 1;
-    QUALITY.UNCOMMON = 2;
-    QUALITY.RARE = 3;
-    QUALITY.EPIC = 4;
-    QUALITY.LEGENDARY = 5;
-    QUALITY.ARTIFACT = 6;
-    QUALITY.HEIRLOOM = 7;
-    local equippedWeapon = GetInventoryItemID("player", GetInventorySlotInfo("MainHandSlot"));
-    if equippedWeapon == nil then
-        print("You do not have any weapons equipped.");
-        return
-    end
-    name, link, quality, iLvl, reqLvl, class, subclass, maxStack, equipSlot, texture, vendorSellPrice = GetItemInfo(equippedWeapon);
-    if quality == QUALITY["ARTIFACT"] then
-        openWeaponSockets();
-    else
-        print("You do not have an Artifact Weapon equipped yet.");
-    end
-end
-
+SLASH_ARTIFACTMENU1, SLASH_ARTIFACTMENU2 = '/artifact', '/am';
+SLASH_ARTIFACTMENU3 = "/artifactmenu";
+
+local ldb = LibStub:GetLibrary("LibDataBroker-1.1");
+
+local ArtifectMenuFrame = CreateFrame("Button", "ArtifactMenuFrame", UIParent)
+ArtifectMenuFrame:RegisterEvent("ADDON_LOADED")
+ArtifectMenuFrame:SetScript("OnEvent", function(self) self:SetupFrame() end)
+
+function openWeaponSockets()
+    SocketInventoryItem(16);
+end
+
+-- unneeded, because artifacts are "conjoined" items
+local function openOffhandSockets()
+    SocketInventoryItem(17);
+end
+
+function ArtifactMenuFrame:SetupFrame()
+    self:UnregisterEvent("ADDON_LOADED")
+    --Frame
+    self:SetWidth(32)
+    self:SetHeight(32)
+    self:SetPoint("CENTER",32,0)
+    self:EnableMouse(true)
+    self:SetMovable(true)
+    self:RegisterForDrag("LeftButton")
+    self:RegisterForClicks("AnyUp")
+    self:SetScript("OnDragStart", function(s) s:StartMoving() end)
+    self:SetScript("OnDragStop", function(s) s:StopMovingOrSizing() end)
+    --Set Texture
+    self.tex = self:CreateTexture(nil, "BACKGROUND")
+    self.tex:SetAllPoints(self)
+    self.leftGradiant = self:CreateTexture(nil, "BORDER")
+    self.leftGradiant:SetWidth(16)
+    self.leftGradiant:SetHeight(14)
+    self.leftGradiant:SetPoint("LEFT", 0, -5)
+    self.leftGradiant:SetTexture(1,0,0,1)
+    self.leftGradiant:SetGradientAlpha("Horizontal", 0, 0, 0, 0.2, 0, 0, 0, 1)
+    self.rightGradiant = self:CreateTexture(nil, "BORDER")
+    self.rightGradiant:SetWidth(16)
+    self.rightGradiant:SetHeight(14)
+    self.rightGradiant:SetPoint("RIGHT", 0, -5)
+    self.rightGradiant:SetTexture(1,0,0,1)
+    self.rightGradiant:SetGradientAlpha("Horizontal", 0, 0, 0, 1, 0, 0, 0, 0.2)
+    self.text = self:CreateFontString(nil, "ARTWORK", "GameFontWhite")
+    self.text:SetJustifyH("CENTER")
+    self.text:SetText("0")
+    self.text:SetWidth(40)
+    self.text:SetHeight(14)
+    self.text:SetPoint("CENTER", 0, -5)
+    self.text:SetNonSpaceWrap(false)
+    self.menu = CreateFrame("Frame","ArtifactMenuFrameMenu", self, "UIDropDownMenuTemplate", 1)
+    self.menuOnLoad = menuOnLoad
+    UIDropDownMenu_Initialize(self.menu, self.menuOnLoad, "MENU")
+    self:SetScript("OnClick",
+        function(s,button,down)
+            s:OnClick(button,down)
+        end)
+    self.dataBroker = ldb:NewDataObject("ArtifactMenu", {
+        type = "data source",
+        text = "",
+        value = "",
+        label = "ArtifactMenu",
+        OnClick = function (clickedframe, button, down)
+            ArtifactMenuFrame:OnClick(button, down, clickedframe)
+        end
+    })
+end
+
+function ArtifactMenuFrame:OnClick(button, down, anchorFrame)
+    self.menu.button = button
+    anchorFrame = anchorFrame or self
+    self.anchorFrame = anchorFrame
+    if button == "LeftButton" then
+        openArtifactMenu();
+    end
+end
+
+function openArtifactMenu()
+    -- http://wowprogramming.com/docs/api_types#itemQuality
+    local QUALITY = {}
+    QUALITY.POOR = 0;
+    QUALITY.COMMON = 1;
+    QUALITY.UNCOMMON = 2;
+    QUALITY.RARE = 3;
+    QUALITY.EPIC = 4;
+    QUALITY.LEGENDARY = 5;
+    QUALITY.ARTIFACT = 6;
+    QUALITY.HEIRLOOM = 7;
+    local equippedWeapon = GetInventoryItemID("player", GetInventorySlotInfo("MainHandSlot"));
+    if equippedWeapon == nil then
+        print("You do not have any weapons equipped.");
+        return
+    end
+    name, link, quality, iLvl, reqLvl, class, subclass, maxStack, equipSlot, texture, vendorSellPrice = GetItemInfo(equippedWeapon);
+    if quality == QUALITY["ARTIFACT"] then
+        openWeaponSockets();
+    else
+        print("You do not have an Artifact Weapon equipped yet.");
+    end
+end
+
+function SlashCmdList.ARTIFACTMENU(msg, editBox)
+    openArtifactMenu();
+end
+
diff --git a/ArtifactMenu.toc b/ArtifactMenu.toc
index 9b719bd..c4c8e32 100644
--- a/ArtifactMenu.toc
+++ b/ArtifactMenu.toc
@@ -1,6 +1,9 @@
-## Interface: 70000
-## Title: ArtifactMenu
-## Author: Kevin Gilbert (kaygil)
-## Version: 1.0.1
-
-ArtifactMenu.lua
+## Interface: 70000
+## Title: ArtifactMenu
+## Author: Kevin Gilbert (kaygil)
+## Version: 1.0.1
+
+libs/LibStub.lua
+libs/CallbackHandler-1.0.lua
+libs/LibDataBroker-1.1.lua
+ArtifactMenu.lua
diff --git a/README.md b/README.md
index 7fd658e..5f78880 100644
--- a/README.md
+++ b/README.md
@@ -22,3 +22,4 @@ This add-on will simplify all of that to just:
 * Wowpedia [slash commands reference](http://wow.gamepedia.com/Creating_a_slash_command)
 * The nice people of [#wowuidev](http://us.battle.net/forums/en/wow/topic/1127130079) for helping me debug, specifically `kd3`, `Nebula`, and `Semlar`
 * [Farrar-Gilneas](http://us.battle.net/wow/en/character/gilneas/Farrar/simple) for testing this add-on in the *Legion* beta.
+* [/u/IHateMyHandle](https://www.reddit.com/u/IHateMyHandle), whose add-on [Incentive Program](https://mods.curse.com/addons/wow/incentive-program) served as my inspiration (and also my foundation). The entire LDB frame panel in ArtifactMenu is largely sourced from Incentive Program and I can't be thankful enough. [Incentive Program](https://mods.curse.com/addons/wow/incentive-program) is a QoL add-on (much like ArtifactMenu) which makes queuing for Call To Arms satchels as easy as possible. Go check it out!
diff --git a/libs/CallbackHandler-1.0.lua b/libs/CallbackHandler-1.0.lua
new file mode 100644
index 0000000..fd87e02
--- /dev/null
+++ b/libs/CallbackHandler-1.0.lua
@@ -0,0 +1,238 @@
+--[[ $Id: CallbackHandler-1.0.lua 18 2014-10-16 02:52:20Z mikk $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 6
+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}
+
+-- Lua APIs
+local tconcat = table.concat
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local next, select, pairs, type, tostring = next, select, pairs, type, tostring
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: geterrorhandler
+
+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", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(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)
+
+	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" or self=thread
+			if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
+				error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread 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/libs/LibStub.lua b/libs/LibStub.lua
new file mode 100644
index 0000000..cfc97de
--- /dev/null
+++ b/libs/LibStub.lua
@@ -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